c4797b1dbf
Patch C — collapse the sdio_rx_work → rx_queue → bh_work relay into a
direct call from bes2600_sdio_extract_packets into a new helper
bes2600_bh_handle_rx_skb that performs the per-SKB bookkeeping
previously done inside bes2600_bh_rx_helper after pipe_read.
What this saves per RX frame:
- two spinlock acquires on self->rx_queue->lock
(skb_queue_tail in extract_packets + skb_dequeue in pipe_read)
- one bh wait-queue wake-up per IRQ batch
(sdio_rx_work no longer calls self->irq_handler)
Pre-patch baseline on ohm (4 MB/s sender, ~5 min, srcversion 1B3B3ED0):
- 387,532 RX packets, 578 MB, 1.36 MB/s observed receive
- sdio_rx_work dispatched 34,994 times = 86.4/s = 90.3 per 1000 RX pkts
- sdio_tx_work dispatched 111,770 times = 276.1/s
- bes2600_bh_work redispatches: 0 (single long-lived work item,
refutes earlier review claim of "9 events per frame")
API contract for wsm_handle_rx (cited in bh.c block comment):
- declared wsm.h:2108, defined wsm.c:2211, EXPORT_SYMBOL wsm.c:2463
- process context, sleepable
- caller MUST hold no bes2600 spinlock; SDIO mutex is released at
bes2600_sdio.c before extract_packets is called
- SKB ownership: zeroes *skb_p if consumed, caller frees otherwise.
bes2600_bh_handle_rx_skb frees on every path (success + error)
After patch, bh thread retains responsibility for TX work. TX-confirm
packets that release a TX buffer wake the bh thread via
bes2600_bh_wakeup() inside bes2600_bh_handle_rx_skb (mirrors the in-bh
tx=1 signaling the old bh_rx_helper used to do). Early-boot IRQs
before fw_started are still served via the GPIO IRQ handler's fallback
self->irq_handler path; that branch is unchanged.
Minimum-diff scope:
- struct sbus_priv->rx_queue + ->rx_queue_lock are still defined and
initialised (allocated but never used after this patch). Removed
in a follow-up hygiene patch, not bundled here.
- bes2600_sdio_pipe_read still exists; returns NULL after this patch
(rx_queue always empty). Removed in same follow-up.
- bes2600_bh_rx_helper still exists and still calls pipe_read; it
now always returns 0. Wasted 1 spinlock + skb_dequeue per bh wake;
quantified in Phase 7 to decide whether worth a follow-up.
Awaiting Phase 7 verification on ohm. Per Phase 4 plan §4.5 predicted
delta:
- rx_queue->lock acquire rate: 1914/s -> 0 (high confidence)
- _raw_spin_unlock_irqrestore CPU%: 20% -> 12-15% (medium)
- observed RX KB/s @ 4 MB/s: floor lifts toward >= 1 MB/s sustained
(medium; rep 3's 75 KB/s death has multiple causes)
Phase 7 will N=3 ramp 1 -> 2 -> 4 -> 8 MB/s with identical
instrumentation pre/post. See notes/patch-c-phase4-plan-2026-05-07.md
in marfrit/besser for the plan; this commit is the Phase 6 deliverable.
2397 lines
62 KiB
C
2397 lines
62 KiB
C
/*
|
|
* Mac80211 SDIO driver for BES2600 device
|
|
*
|
|
* Copyright (c) 2010, Bestechnic
|
|
* Author:
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
#define DEBUG 1
|
|
#include <linux/version.h>
|
|
#include <linux/module.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/mmc/host.h>
|
|
#include <linux/mmc/sdio_func.h>
|
|
#include <linux/mmc/card.h>
|
|
#include <linux/mmc/core.h>
|
|
#include <linux/mmc/sdio.h>
|
|
#include <linux/spinlock.h>
|
|
#include <net/mac80211.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/mmc/host.h>
|
|
#include <linux/mmc/card.h>
|
|
#include <linux/mmc/sdio.h>
|
|
#include <linux/mmc/sdio_func.h>
|
|
#include <linux/version.h>
|
|
#include <linux/of_gpio.h>
|
|
|
|
#include "bes2600.h"
|
|
#include "bh.h"
|
|
#include "sbus.h"
|
|
#include "bes2600_plat.h"
|
|
#include "bes2600_factory.h"
|
|
#include "hwio.h"
|
|
#include "bes_chardev.h"
|
|
#include "bes_log.h"
|
|
|
|
static void sdio_scan_work(struct work_struct *work);
|
|
void sdio_work_debug(struct sbus_priv *self);
|
|
static void bes2600_sdio_power_down(struct sbus_priv *self);
|
|
struct bes2600_platform_data_sdio *bes2600_get_platform_data(void);
|
|
int bes2600_register_net_dev(struct sbus_priv *bus_priv);
|
|
int bes2600_unregister_net_dev(struct sbus_priv *bus_priv);
|
|
bool bes2600_is_net_dev_created(struct sbus_priv *bus_priv);
|
|
static void bes2600_gpio_wakeup_mcu(struct sbus_priv *self, int falg);
|
|
static void bes2600_gpio_allow_mcu_sleep(struct sbus_priv *self, int falg);
|
|
|
|
MODULE_AUTHOR("Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com>");
|
|
MODULE_DESCRIPTION("mac80211 BES2600 SDIO driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("bes2600_wlan");
|
|
|
|
struct sbus_priv {
|
|
struct sdio_func *func;
|
|
struct bes2600_common *core;
|
|
const struct bes2600_platform_data_sdio *pdata;
|
|
spinlock_t lock;
|
|
sbus_irq_handler irq_handler;
|
|
void *irq_priv;
|
|
struct work_struct sdio_scan_work;
|
|
struct device *dev;
|
|
struct workqueue_struct *sdio_wq;
|
|
bool fw_started;
|
|
struct mutex io_mutex;
|
|
long unsigned int gpio_wakup_flags;
|
|
struct mutex sbus_mutex;
|
|
bool retune_protected;
|
|
#ifdef BES_SDIO_RXTX_TOGGLE
|
|
u8 next_toggle;
|
|
int tx_data_toggle;
|
|
int rx_data_toggle;
|
|
#endif
|
|
#ifdef BES_SDIO_RX_MULTIPLE_ENABLE
|
|
spinlock_t rx_queue_lock;
|
|
struct sk_buff_head rx_queue;
|
|
u8 *rx_buffer;
|
|
struct work_struct rx_work;
|
|
u32 rx_last_ctrl;
|
|
u32 rx_valid_ctrl;
|
|
u32 rx_total_ctrl_cnt;
|
|
u32 rx_continuous_ctrl_cnt;
|
|
u32 rx_zero_ctrl_cnt;
|
|
u32 rx_remain_ctrl_cnt;
|
|
u32 rx_data_cnt;
|
|
u32 rx_xfer_cnt;
|
|
u32 rx_proc_cnt;
|
|
long unsigned int last_irq_timestamp;
|
|
long unsigned int last_rx_data_timestamp;
|
|
#endif
|
|
#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
|
|
u8 *tx_buffer;
|
|
struct list_head tx_bufferlist;
|
|
struct kmem_cache *tx_bufferlistpool;
|
|
spinlock_t tx_bufferlock;
|
|
struct work_struct tx_work;
|
|
struct scatterlist tx_sg[BES_SDIO_TX_MULTIPLE_NUM + 1];
|
|
struct scatterlist tx_sg_nosignal[BES_SDIO_TX_MULTIPLE_NUM_NOSIGNAL + 1];
|
|
u8 *tx_bounce;
|
|
u32 tx_data_cnt;
|
|
u32 tx_xfer_cnt;
|
|
u32 tx_proc_cnt;
|
|
long unsigned int last_tx_data_timestamp;
|
|
#endif
|
|
#ifndef SDIO_HOST_ADMA_SUPPORT
|
|
u8 *single_gathered_buffer;
|
|
#endif
|
|
bool unregister_in_process;
|
|
};
|
|
|
|
#define IS_DRIVER_VENDOR_CMD(X) ((X & 0x0C00) == 0x0C00)
|
|
struct HI_MSG_HDR {
|
|
uint16_t MsgLen;
|
|
uint16_t MsgId;
|
|
};
|
|
|
|
enum DRIVER_TO_MCU_MSG_ST {
|
|
ST_ENTER,
|
|
ST_EXIT,
|
|
};
|
|
|
|
#define BES_VENDOR_ID 0xbe57
|
|
#define BES_DEVICE_ID_2002 0x2002
|
|
|
|
static const struct sdio_device_id bes2600_sdio_ids[] = {
|
|
{ SDIO_DEVICE(BES_VENDOR_ID, BES_DEVICE_ID_2002) },
|
|
{ /* end: all zeroes */ },
|
|
};
|
|
MODULE_DEVICE_TABLE(sdio, bes2600_sdio_ids);
|
|
|
|
#ifdef BES2600_GPIO_WAKEUP_AP
|
|
static int bes2600_gpio_wakeup_ap_config(struct sbus_priv *priv);
|
|
#endif
|
|
|
|
/* sbus_ops implemetation */
|
|
|
|
#ifdef CONFIG_BES2600_WLAN_BES
|
|
static inline unsigned int sdio_max_byte_size(struct sdio_func *func)
|
|
{
|
|
unsigned mval = min(func->card->host->max_seg_size, func->card->host->max_blk_size);
|
|
|
|
if (func->card->quirks & MMC_QUIRK_BLKSZ_FOR_BYTE_MODE)
|
|
mval = min(mval, func->cur_blksize);
|
|
else
|
|
mval = min(mval, func->max_blksize);
|
|
|
|
return min(mval, 512u);
|
|
}
|
|
|
|
static int bes_sdio_memcpy_io_helper(struct sdio_func *func, int write, void *data_buf, unsigned size)
|
|
{
|
|
int ret = 0;
|
|
unsigned remainder = size;
|
|
unsigned max_blocks, align_blocks, pads;
|
|
|
|
#ifdef BES_SDIO_RXTX_TOGGLE
|
|
struct sbus_priv *self = NULL;
|
|
#endif
|
|
|
|
struct mmc_request mrq;
|
|
struct mmc_command cmd;
|
|
struct mmc_data data;
|
|
struct scatterlist sg[2];
|
|
|
|
#ifdef SDIO_HOST_ADMA_SUPPORT
|
|
struct scatterlist *next;
|
|
#endif
|
|
|
|
#ifdef SDIO_HOST_ADMA_SUPPORT
|
|
u8 *pad_buf = (u8 *)kmalloc(func->cur_blksize, GFP_KERNEL);
|
|
if (!pad_buf)
|
|
return -ENOMEM;
|
|
#endif
|
|
|
|
if (!func || (func->num > 7) || (!data_buf) || (!size)) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
#ifdef BES_SDIO_RXTX_TOGGLE
|
|
self = sdio_get_drvdata(func);
|
|
BUG_ON(!self);
|
|
#endif
|
|
|
|
if (func->card->cccr.multi_block && size > sdio_max_byte_size(func) ) {
|
|
max_blocks = min(func->card->host->max_blk_count, 511u);
|
|
align_blocks = (size + func->cur_blksize - 1) / func->cur_blksize;
|
|
if (align_blocks > max_blocks) {
|
|
/* to be simplified, consider this should not
|
|
* happen, and to be continued;
|
|
*/
|
|
bes_devel("%s warning to be continued, align=%d max=%d", __func__, align_blocks, max_blocks);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
pads = align_blocks * func->cur_blksize - size;
|
|
bes_devel("%s sz=%u blk=%u pad=%u,dir=%d", __func__, size, align_blocks, pads, write);
|
|
|
|
memset(&mrq, 0, sizeof(mrq));
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
memset(&data, 0, sizeof(data));
|
|
|
|
mrq.cmd = &cmd;
|
|
mrq.data = &data;
|
|
|
|
cmd.opcode = SD_IO_RW_EXTENDED;
|
|
cmd.arg = write ? 0x80000000 : 0x00000000;
|
|
cmd.arg |= func->num << 28;
|
|
cmd.arg |= 0x04000000;
|
|
cmd.arg |= size << 9;
|
|
|
|
#ifdef BES_SDIO_RXTX_TOGGLE
|
|
if (likely(self->fw_started == true)) {
|
|
cmd.arg &= ~(1 << 25);
|
|
if (write) {
|
|
cmd.arg |= ((self->tx_data_toggle & 0x1) << 25);
|
|
++self->tx_data_toggle;
|
|
} else {
|
|
cmd.arg |= ((self->rx_data_toggle & 0x1) << 25);
|
|
++self->rx_data_toggle;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
cmd.arg |= (0x08000000 | align_blocks);
|
|
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
|
|
|
|
data.blksz = func->cur_blksize;
|
|
data.blocks = align_blocks;
|
|
data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
|
|
|
|
data.sg = sg;
|
|
data.sg_len = 1;
|
|
#ifdef SDIO_HOST_ADMA_SUPPORT
|
|
sg_init_table(sg, 2);
|
|
sg_set_buf(sg, data_buf, size);
|
|
if (pads) {
|
|
next = sg_next(sg);
|
|
sg_set_buf(next, pad_buf, pads);
|
|
data.sg_len = 2;
|
|
}
|
|
#else
|
|
sg_init_table(sg, 1);
|
|
if (unlikely(pads)) {
|
|
if (unlikely(size > MAX_SDIO_TRANSFER_LEN)) {
|
|
WARN_ON(1);
|
|
return -EINVAL;
|
|
}
|
|
if (write)
|
|
memcpy(self->single_gathered_buffer, data_buf, size);
|
|
sg_set_buf(sg, self->single_gathered_buffer, size + pads);
|
|
} else {
|
|
sg_set_buf(sg, data_buf, size);
|
|
}
|
|
#endif
|
|
|
|
mmc_set_data_timeout(&data, func->card);
|
|
|
|
mmc_wait_for_req(func->card->host, &mrq);
|
|
|
|
if (cmd.error){
|
|
ret = cmd.error;
|
|
goto out;
|
|
}
|
|
if (data.error) {
|
|
ret = data.error;
|
|
goto out;
|
|
}
|
|
if (cmd.resp[0] & R5_ERROR) {
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
if (cmd.resp[0] & R5_FUNCTION_NUMBER) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
if (cmd.resp[0] & R5_OUT_OF_RANGE) {
|
|
ret = -ERANGE;
|
|
goto out;
|
|
}
|
|
|
|
#ifndef SDIO_HOST_ADMA_SUPPORT
|
|
if (pads && (!write)) {
|
|
memcpy(data_buf, self->single_gathered_buffer, size);
|
|
}
|
|
#endif
|
|
|
|
} else {
|
|
while (remainder) {
|
|
size = min(remainder, sdio_max_byte_size(func));
|
|
|
|
bes_devel("%s size=%d dir=%d", __func__, size, write);
|
|
if (write) {
|
|
#ifndef BES_SDIO_RXTX_TOGGLE
|
|
ret = sdio_memcpy_toio(func, size, data_buf, size);
|
|
#else
|
|
if (likely(self->fw_started == true)) {
|
|
ret = sdio_memcpy_toio(func, size | ((self->tx_data_toggle & 0x1) << 16), data_buf, size);
|
|
++self->tx_data_toggle;
|
|
} else {
|
|
ret = sdio_memcpy_toio(func, size, data_buf, size);
|
|
}
|
|
#endif
|
|
} else {
|
|
#ifndef BES_SDIO_RXTX_TOGGLE
|
|
ret = sdio_memcpy_fromio(func, data_buf, size, size);
|
|
#else
|
|
if (likely(self->fw_started == true)) {
|
|
ret = sdio_memcpy_fromio(func, data_buf, size | ((self->rx_data_toggle & 0x1) << 16), size);
|
|
++self->rx_data_toggle;
|
|
} else {
|
|
ret = sdio_memcpy_fromio(func, data_buf, size, size);
|
|
}
|
|
#endif
|
|
}
|
|
if (ret)
|
|
goto out;
|
|
|
|
remainder -= size;
|
|
data_buf += size;
|
|
}
|
|
}
|
|
out:
|
|
#ifdef SDIO_HOST_ADMA_SUPPORT
|
|
kfree(pad_buf);
|
|
#endif
|
|
if (ret) {
|
|
bes_err("%s, err=%d(%d:%p:%d)",
|
|
__func__, ret, func->num, data_buf, size);
|
|
#ifdef BES_SDIO_RXTX_TOGGLE
|
|
if (self && self->fw_started == true) {
|
|
if (write)
|
|
--self->tx_data_toggle;
|
|
else
|
|
--self->rx_data_toggle;
|
|
bes_err("%s,toggle count:%u,%u\n", __func__, self->tx_data_toggle, self->rx_data_toggle);
|
|
}
|
|
#endif
|
|
}
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int bes2600_sdio_memcpy_fromio(struct sbus_priv *self,
|
|
unsigned int addr,
|
|
void *dst, int count)
|
|
{
|
|
return bes_sdio_memcpy_io_helper(self->func, 0, dst, count);
|
|
}
|
|
|
|
static int bes2600_sdio_memcpy_toio(struct sbus_priv *self,
|
|
unsigned int addr,
|
|
const void *src, int count)
|
|
{
|
|
return bes_sdio_memcpy_io_helper(self->func, 1, (void *)src, count);
|
|
}
|
|
|
|
static void bes2600_sdio_lock(struct sbus_priv *self)
|
|
{
|
|
sdio_claim_host(self->func);
|
|
}
|
|
|
|
static void bes2600_sdio_unlock(struct sbus_priv *self)
|
|
{
|
|
sdio_release_host(self->func);
|
|
}
|
|
|
|
/* bes sdio slave regs can only be accessed by command52
|
|
* if a WORD or DWORD reg wants to be accessed,
|
|
* please combine the results of multiple command52
|
|
*/
|
|
static int bes2600_sdio_reg_read(struct sbus_priv *self, u32 reg,
|
|
void *dst, int count)
|
|
{
|
|
int ret = 0;
|
|
if (count <= 0 || !dst)
|
|
return -EINVAL;
|
|
while(count && !ret) {
|
|
*(u8 *)dst = sdio_readb(self->func, reg, &ret);
|
|
dst ++;
|
|
reg ++;
|
|
count--;
|
|
}
|
|
return ret;
|
|
|
|
}
|
|
|
|
static int bes2600_sdio_reg_write(struct sbus_priv *self, u32 reg,
|
|
const void *src, int count)
|
|
{
|
|
int ret = 0;
|
|
if (count <= 0 || !src)
|
|
return -EINVAL;
|
|
while (count && !ret) {
|
|
sdio_writeb(self->func, *(u8 *)src, reg, &ret);
|
|
src ++;
|
|
reg ++;
|
|
count --;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#ifndef CONFIG_BES2600_USE_GPIO_IRQ
|
|
static void bes2600_sdio_irq_handler(struct sdio_func *func)
|
|
{
|
|
struct sbus_priv *self = sdio_get_drvdata(func);
|
|
unsigned long flags;
|
|
|
|
if (WARN_ON(!self)) {
|
|
return;
|
|
}
|
|
|
|
bes_devel("%s called, fw_started:%d \n",
|
|
__func__, self->fw_started);
|
|
if (likely(self->fw_started && self->core)) {
|
|
queue_work(self->sdio_wq, &self->rx_work);
|
|
self->last_irq_timestamp = jiffies;
|
|
} else if(self->irq_handler) {
|
|
spin_lock_irqsave(&self->lock, flags);
|
|
self->irq_handler(self->irq_priv);
|
|
spin_unlock_irqrestore(&self->lock, flags);
|
|
}
|
|
}
|
|
#else /* CONFIG_BES2600_USE_GPIO_IRQ */
|
|
static u32 bes2600_gpio_irq_handler(void *dev_id)
|
|
{
|
|
struct sbus_priv *self = (struct sbus_priv *)dev_id;
|
|
|
|
bes_devel("\n %s called \n", __func__);
|
|
BUG_ON(!self);
|
|
if (self->irq_handler)
|
|
self->irq_handler(self->irq_priv);
|
|
return 0;
|
|
}
|
|
|
|
static int bes2600_request_irq(struct sbus_priv *self,
|
|
u32 handler)
|
|
{
|
|
int ret = 0;
|
|
int func_num;
|
|
const struct resource *irq = self->pdata->irq;
|
|
u8 cccr;
|
|
int ret0 = 0;
|
|
|
|
/* Hack to access Fuction-0 */
|
|
func_num = self->func->num;
|
|
self->func->num = 0;
|
|
|
|
cccr = sdio_readb(self->func, SDIO_CCCR_IENx, &ret);
|
|
if (WARN_ON(ret))
|
|
goto set_func;
|
|
|
|
/* Master interrupt enable ... */
|
|
cccr |= BIT(0);
|
|
|
|
/* ... for our function */
|
|
cccr |= BIT(func_num);
|
|
|
|
sdio_writeb(self->func, cccr, SDIO_CCCR_IENx, &ret);
|
|
if (WARN_ON(ret))
|
|
goto set_func;
|
|
|
|
/* Restore the WLAN function number */
|
|
self->func->num = func_num;
|
|
return 0;
|
|
|
|
set_func:
|
|
//AW judge sdio read write timeout, 1s
|
|
ret0 = sw_mci_check_r1_ready(self->func->card->host, 1000);
|
|
if (ret0 != 0)
|
|
bes_err(("%s data timeout.\n", __FUNCTION__));
|
|
|
|
self->func->num = func_num;
|
|
bes_err("[%s] fail exiting sw_gpio_irq_request.. :%d\n",__func__, ret);
|
|
return ret;
|
|
}
|
|
#endif /* CONFIG_BES2600_USE_GPIO_IRQ */
|
|
|
|
static int bes2600_sdio_irq_subscribe(struct sbus_priv *self,
|
|
sbus_irq_handler handler,
|
|
void *priv)
|
|
{
|
|
int ret;
|
|
unsigned long flags;
|
|
|
|
if (!handler)
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&self->lock, flags);
|
|
self->irq_priv = priv;
|
|
self->irq_handler = handler;
|
|
spin_unlock_irqrestore(&self->lock, flags);
|
|
|
|
bes_devel( "SW IRQ subscribe\n");
|
|
sdio_claim_host(self->func);
|
|
#ifndef CONFIG_BES2600_USE_GPIO_IRQ
|
|
ret = sdio_claim_irq(self->func, bes2600_sdio_irq_handler);
|
|
#else
|
|
mdelay(10);
|
|
ret = bes2600_request_irq(self, bes2600_gpio_irq_handler);
|
|
#endif
|
|
sdio_release_host(self->func);
|
|
return ret;
|
|
}
|
|
|
|
static int bes2600_sdio_irq_unsubscribe(struct sbus_priv *self)
|
|
{
|
|
int ret = 0;
|
|
unsigned long flags;
|
|
#ifdef CONFIG_BES2600_USE_GPIO_IRQ
|
|
const struct resource *irq = self->pdata->irq;
|
|
#endif
|
|
|
|
WARN_ON(!self->irq_handler);
|
|
if (!self->irq_handler)
|
|
return 0;
|
|
|
|
bes_devel("SW IRQ unsubscribe\n");
|
|
|
|
/*
|
|
#ifndef CONFIG_BES2600_USE_GPIO_IRQ
|
|
sdio_claim_host(self->func);
|
|
ret = sdio_release_irq(self->func);
|
|
sdio_release_host(self->func);
|
|
#else
|
|
free_irq(irq->start, self);
|
|
#endif //CONFIG_BES2600_USE_GPIO_IRQ
|
|
*/
|
|
|
|
spin_lock_irqsave(&self->lock, flags);
|
|
self->irq_priv = NULL;
|
|
self->irq_handler = NULL;
|
|
spin_unlock_irqrestore(&self->lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void bes2600_sdio_off(const struct bes2600_platform_data_sdio *pdata)
|
|
{
|
|
bes_devel("%s\n", __func__);
|
|
gpiod_direction_output(pdata->powerup, GPIOD_OUT_LOW);
|
|
gpiod_direction_output(pdata->reset, GPIOD_OUT_LOW);
|
|
}
|
|
|
|
static void bes2600_sdio_on(const struct bes2600_platform_data_sdio *pdata)
|
|
{
|
|
bes_devel("%s\n", __func__);
|
|
gpiod_direction_output(pdata->powerup, GPIOD_OUT_HIGH);
|
|
}
|
|
|
|
static size_t bes2600_sdio_align_size(struct sbus_priv *self, size_t size)
|
|
{
|
|
size_t aligned = size;
|
|
if (self->func->cur_blksize > size)
|
|
aligned = sdio_align_size(self->func, size);
|
|
else
|
|
aligned = (aligned + 3) & (~3);
|
|
|
|
return aligned;
|
|
}
|
|
|
|
static int bes2600_sdio_set_block_size(struct sbus_priv *self, size_t size)
|
|
{
|
|
return sdio_set_block_size(self->func, size);
|
|
}
|
|
|
|
void sdio_work_debug(struct sbus_priv *self)
|
|
{
|
|
u8 cfg;
|
|
int ret;
|
|
bes_err("%s now=%u last irq timestamp=%u\n", __func__,
|
|
(u32)jiffies_to_msecs(jiffies), jiffies_to_msecs(self->last_irq_timestamp));
|
|
bes_err("%s rx ctrl: total=%u continuous=%u xfer=%u remain=%u zero=%u last=%x(%x) next=%d\n", __func__,
|
|
self->rx_total_ctrl_cnt, self->rx_continuous_ctrl_cnt, self->rx_xfer_cnt, self->rx_remain_ctrl_cnt, self->rx_zero_ctrl_cnt,
|
|
self->rx_last_ctrl, self->rx_valid_ctrl, self->next_toggle);
|
|
bes_err("%s rx: last timestamp=%u, total=%u(%u), proc=%u\n", __func__,
|
|
(u32)jiffies_to_msecs(self->last_rx_data_timestamp),
|
|
self->rx_data_cnt, self->rx_xfer_cnt, self->rx_proc_cnt);
|
|
bes_err("%s tx: last timestamp=%u, total=%u,%u, proc=%u\n", __func__,
|
|
(u32)jiffies_to_msecs(self->last_tx_data_timestamp),
|
|
self->tx_data_cnt, self->tx_xfer_cnt, self->tx_proc_cnt);
|
|
mutex_lock(&self->sbus_mutex);
|
|
sdio_claim_host(self->func);
|
|
bes2600_sdio_reg_read(self, BES_TX_CTRL_REG_ID + 1, &cfg, 1);
|
|
bes_err("realtime ctrl=%x\n", cfg);
|
|
cfg = BES_HOST_INT | BES_SUBSYSTEM_WIFI_DEBUG;
|
|
sdio_writeb(self->func, 0, BES_HOST_INT_REG_ID + 1, &ret);
|
|
sdio_writeb(self->func, cfg, BES_HOST_INT_REG_ID, &ret);
|
|
sdio_release_host(self->func);
|
|
mutex_unlock(&self->sbus_mutex);
|
|
}
|
|
|
|
#ifndef BES_SDIO_OPTIMIZED_LEN
|
|
static u8 const crc8_table[256] = {
|
|
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
|
|
0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
|
|
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
|
|
0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
|
|
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
|
|
0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
|
|
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
|
|
0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
|
|
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
|
|
0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
|
|
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
|
|
0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
|
|
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
|
|
0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
|
|
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
|
|
0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
|
|
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
|
|
0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
|
|
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
|
|
0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
|
|
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
|
|
0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
|
|
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
|
|
0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
|
|
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
|
|
0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
|
|
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
|
|
0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
|
|
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
|
|
0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
|
|
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
|
|
0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
|
|
};
|
|
|
|
static u8 bes_crc8(const u8 *data, unsigned len)
|
|
{
|
|
u8 crc = 0;
|
|
|
|
while(len--)
|
|
crc = crc8_table[crc ^ *data++];
|
|
|
|
return crc;
|
|
}
|
|
#endif
|
|
|
|
static int bes2600_sdio_read_ctrl(struct sbus_priv *self, u32 *ctrl_reg)
|
|
{
|
|
u8 data[4];
|
|
#ifndef BES_SDIO_OPTIMIZED_LEN
|
|
u8 check;
|
|
u16 pkts, len;
|
|
#endif
|
|
int ret = 0, again = 0;
|
|
*ctrl_reg = 0;
|
|
|
|
/* clear sdio slave gen interrupt */
|
|
ret = bes2600_sdio_reg_read(self, BES_TX_CTRL_REG_ID + 1, data, 1);
|
|
#ifndef BES_SDIO_OPTIMIZED_LEN
|
|
ret = bes2600_sdio_reg_read(self, BES_TX_NEXT_LEN_REG_ID, data, 4);
|
|
#endif
|
|
if (unlikely(ret)) {
|
|
bes_err("[SBUS] Failed(%d) to read control register.\n", ret);
|
|
return ret;
|
|
}
|
|
self->rx_total_ctrl_cnt++;
|
|
|
|
#ifndef BES_SDIO_OPTIMIZED_LEN
|
|
check = bes_crc8((const u8 *)data, 3);
|
|
if (data[3] == check) {
|
|
/* length field crc8 pass */
|
|
*ctrl_reg = *(u32 *)data;
|
|
if (((data[2] >> 7) & 0x1) == self->next_toggle) {
|
|
/* toggle valid */
|
|
*ctrl_reg &= (~0xff800000);
|
|
} else {
|
|
/* last toggle */
|
|
*ctrl_reg = 0;
|
|
again = 1;
|
|
}
|
|
|
|
if (*ctrl_reg) {
|
|
/* length field valid */
|
|
again = 1;
|
|
pkts = ((*ctrl_reg) >> 16) & 0x7f;
|
|
len = (*ctrl_reg) & 0xffff;
|
|
if (pkts && len) {
|
|
self->next_toggle_debug = *(u32 *)data;
|
|
self->next_toggle ^= 1;
|
|
} else {
|
|
*ctrl_reg = 0;
|
|
}
|
|
}
|
|
} else {
|
|
/* length field crc fail */
|
|
//bes_err("%s, crc err:%x,%x,%x,%x,%x\n", __func__, data[0], data[1], data[2], data[3], check);
|
|
//msleep(1);
|
|
*ctrl_reg = 0;
|
|
again = 1;
|
|
}
|
|
#else
|
|
if (data[0] & 0x7f) {
|
|
/* length field valid */
|
|
again = 1;
|
|
if (((data[0] >> 7) & 0x1) == self->next_toggle) {
|
|
*ctrl_reg = (data[0] & 0x7f) << 9;
|
|
self->rx_valid_ctrl = data[0];
|
|
if (self->rx_last_ctrl && (((self->rx_last_ctrl >> 7) & 0x1) != self->next_toggle))
|
|
self->rx_continuous_ctrl_cnt++;
|
|
self->next_toggle ^= 1;
|
|
} else {
|
|
self->rx_remain_ctrl_cnt++;
|
|
}
|
|
} else {
|
|
/* distinguish zero true or false */
|
|
self->rx_zero_ctrl_cnt++;
|
|
ret = bes2600_sdio_reg_read(self, BES_TX_CTRL_REG_ID, &data[1], 1);
|
|
if (!ret && (data[1] & 0x01))
|
|
again = 0;
|
|
else
|
|
again = 1;
|
|
}
|
|
self->rx_last_ctrl = data[0];
|
|
#endif
|
|
return again;
|
|
}
|
|
|
|
#ifdef BES_SDIO_RX_MULTIPLE_ENABLE
|
|
|
|
static int bes2600_sdio_packets_check(u32 ctrl_reg, u8 *packets)
|
|
{
|
|
int i;
|
|
u16 single, total_cal = 0;
|
|
u16 packets_length;
|
|
struct HI_MSG_HDR *pMsg;
|
|
|
|
/* bit 23-16 indicate count of packets */
|
|
u32 packets_cnt;
|
|
#ifndef BES_SDIO_OPTIMIZED_LEN
|
|
packets_cnt = PACKET_COUNT(ctrl_reg);
|
|
if (WARN_ON(packets_cnt > BES_SDIO_RX_MULTIPLE_NUM))
|
|
return -200;
|
|
#else
|
|
packets_cnt = BES_SDIO_RX_MULTIPLE_NUM;
|
|
#endif
|
|
|
|
/* bit 15-0 indicate totoal length of packets */
|
|
packets_length = PACKET_TOTAL_LEN(ctrl_reg);
|
|
|
|
/* first 32-bit: addr in mcu;
|
|
* second 32-bit: packet length;
|
|
* next: data
|
|
*/
|
|
for (i = 0; i < packets_cnt; i++) {
|
|
pMsg = (struct HI_MSG_HDR *)(packets + total_cal);
|
|
bes_devel("%s, %x,%x\n", __func__, pMsg->MsgId, pMsg->MsgLen);
|
|
single = pMsg->MsgLen;
|
|
single = (single + 3) & (~0x3);
|
|
if (unlikely(single > 1632)) {
|
|
bes_warn("%s %d,len=%u,%dth,total=%u,%u\n", __func__, __LINE__, single, i,
|
|
packets_length, total_cal);
|
|
if (i >= 1) {
|
|
return -201;
|
|
}
|
|
}
|
|
total_cal += single;
|
|
#ifdef BES_SDIO_OPTIMIZED_LEN
|
|
if ((!pMsg->MsgLen) || (total_cal == packets_length)) {
|
|
//bes_devel("%s, contain %d packets\n", __func__, i);
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
bes_devel("%s, %d,%u,%u\n", __func__, packets_cnt, packets_length, total_cal);
|
|
|
|
#ifndef BES_SDIO_OPTIMIZED_LEN
|
|
if (WARN_ON(packets_length != total_cal)) {
|
|
return -202;
|
|
}
|
|
#else
|
|
if (packets_length < total_cal) {
|
|
bes_err("%s,%d pkt len=%u, total len=%u", __func__, __LINE__, packets_length, total_cal);
|
|
return -202;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bes2600_sdio_extract_packets(struct sbus_priv *self, u32 ctrl_reg, u8 *data)
|
|
{
|
|
int i, alloc_retry = 0;
|
|
#ifndef BES_SDIO_OPTIMIZED_LEN
|
|
u8 packets_cnt = PACKET_COUNT(ctrl_reg);
|
|
#else
|
|
u8 packets_cnt = BES_SDIO_RX_MULTIPLE_NUM;
|
|
#endif
|
|
u16 packet_len, pos = 0;
|
|
struct sk_buff *skb;
|
|
|
|
for (i = 0; i < packets_cnt; i++) {
|
|
packet_len = ((struct HI_MSG_HDR *)&(data[pos]))->MsgLen;
|
|
#ifdef BES_SDIO_OPTIMIZED_LEN
|
|
if (!packet_len)
|
|
break;
|
|
#endif
|
|
do {
|
|
skb = dev_alloc_skb(packet_len);
|
|
if (likely(skb))
|
|
break;
|
|
bes_warn("%s,%d no memory and sleep\n", __func__, __LINE__);
|
|
msleep(100);
|
|
++alloc_retry;
|
|
} while(alloc_retry < 10);
|
|
if (WARN_ON(!skb)) {
|
|
return -ENOMEM;
|
|
}
|
|
skb_trim(skb, 0);
|
|
skb_put(skb, packet_len);
|
|
memcpy(skb->data, &data[pos], packet_len);
|
|
bes_devel("%s, %d,%d\n", __func__, packet_len, pos);
|
|
self->rx_data_cnt++;
|
|
/*
|
|
* Patch C: deliver SKB directly into the WSM/mac80211 stack
|
|
* instead of skb_queue_tail-ing onto self->rx_queue for later
|
|
* pickup by the bh thread. Removes two spinlock acquires
|
|
* (rx_queue->lock at queue-tail + at dequeue) per RX frame
|
|
* and one bh wait-queue wake-up per IRQ batch.
|
|
*
|
|
* bes2600_bh_handle_rx_skb owns the SKB on every path.
|
|
* Contract: process context, sleepable, caller holds no
|
|
* bes2600 spinlock. See bh.c for the contract block.
|
|
*
|
|
* Pre-condition satisfied here: bes2600_sdio_unlock(self)
|
|
* was called at the bottom of the SDIO read sequence in
|
|
* sdio_rx_work, so we hold no bes2600 mutex either.
|
|
*/
|
|
bes2600_bh_handle_rx_skb(self->core, skb);
|
|
packet_len = (packet_len + 3) & (~0x3);
|
|
pos += packet_len;
|
|
#ifdef BES_SDIO_OPTIMIZED_LEN
|
|
if (pos == PACKET_TOTAL_LEN(ctrl_reg))
|
|
break;
|
|
#endif
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void sdio_rx_work(struct work_struct *work)
|
|
{
|
|
int ret, again = 0, retry = 0, crc_retry = 0;
|
|
u32 ctrl_reg = 0;
|
|
int total_len;
|
|
struct sbus_priv *self = container_of(work, struct sbus_priv, rx_work);
|
|
u8 *buf = self->rx_buffer;
|
|
|
|
/* don't read/write sdio when sdio error */
|
|
if (bes2600_chrdev_is_bus_error())
|
|
return;
|
|
|
|
bes2600_gpio_wakeup_mcu(self, GPIO_WAKE_FLAG_SDIO_RX);
|
|
|
|
do {
|
|
bes2600_sdio_lock(self);
|
|
again = bes2600_sdio_read_ctrl(self, &ctrl_reg);
|
|
|
|
if(again == -EBUSY || again == -ETIMEDOUT) {
|
|
bes_err("%s sdio read error\n", __func__);
|
|
bes2600_sdio_unlock(self);
|
|
goto failed;
|
|
}
|
|
|
|
total_len = PACKET_TOTAL_LEN(ctrl_reg);
|
|
if (!total_len) {
|
|
bes2600_sdio_unlock(self);
|
|
if ((again == 1) && retry <= 5) {
|
|
retry++;
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
do {
|
|
ret = bes2600_sdio_memcpy_fromio(self, 0, buf, total_len);
|
|
if (likely(ret != -84)) {
|
|
crc_retry = 0;
|
|
break;
|
|
} else {
|
|
crc_retry++;
|
|
bes_err("%s sdio read crc error(%d)\n", __func__, crc_retry);
|
|
}
|
|
} while (crc_retry <= 10);
|
|
if (self->retune_protected == true) {
|
|
sdio_retune_release(self->func);
|
|
self->retune_protected = false;
|
|
}
|
|
bes2600_sdio_unlock(self);
|
|
if (ret) {
|
|
bes_err("%s,%d error=%d\n", __func__, __LINE__, ret);
|
|
sdio_work_debug(self);
|
|
goto failed;
|
|
}
|
|
retry = 0;
|
|
self->rx_xfer_cnt++;
|
|
self->last_rx_data_timestamp = jiffies;
|
|
|
|
if ((ret = bes2600_sdio_packets_check(ctrl_reg, buf))) {
|
|
bes_err("%s,%d error=%d\n", __func__, __LINE__, ret);
|
|
sdio_work_debug(self);
|
|
goto failed;
|
|
}
|
|
|
|
if ((ret = bes2600_sdio_extract_packets(self, ctrl_reg, buf))) {
|
|
bes_err("%s,%d error=%d\n", __func__, __LINE__, ret);
|
|
goto failed;
|
|
}
|
|
|
|
ctrl_reg = 0;
|
|
|
|
/*
|
|
* Patch C: with direct delivery in extract_packets, the bh
|
|
* thread no longer drives RX consumption — there is no
|
|
* rx_queue to drain. Calling self->irq_handler() here would
|
|
* wake the bh thread for nothing on every IRQ batch. TX
|
|
* wakes still flow through bes2600_bh_wakeup() from TX
|
|
* submitters and from bes2600_bh_handle_rx_skb when a
|
|
* confirm releases a TX buffer; early-boot IRQs (before
|
|
* fw_started) still go through self->irq_handler from the
|
|
* GPIO IRQ handler's fallback branch.
|
|
*/
|
|
|
|
} while (again);
|
|
|
|
bes2600_gpio_allow_mcu_sleep(self, GPIO_WAKE_FLAG_SDIO_RX);
|
|
return;
|
|
|
|
failed:
|
|
bes2600_gpio_allow_mcu_sleep(self, GPIO_WAKE_FLAG_SDIO_RX);
|
|
bes2600_chrdev_wifi_force_close(self->core, false);
|
|
WARN_ON(1);
|
|
}
|
|
|
|
static void sdio_scan_work(struct work_struct *work)
|
|
{
|
|
bes_warn("%s: this function does nothing\n", __FUNCTION__);
|
|
}
|
|
|
|
static void *bes2600_sdio_pipe_read(struct sbus_priv *self)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
if (bes2600_chrdev_is_bus_error()) {
|
|
return bes2600_tx_loop_read(self->core);
|
|
}
|
|
|
|
spin_lock(&self->rx_queue_lock);
|
|
skb = skb_dequeue(&self->rx_queue);
|
|
if (skb)
|
|
self->rx_proc_cnt++;
|
|
spin_unlock(&self->rx_queue_lock);
|
|
if (likely(self->fw_started == true &&
|
|
!bes2600_pwr_device_is_idle(self->core) &&
|
|
self->core->hw_bufs_used > 0))
|
|
if (!skb)
|
|
queue_work(self->sdio_wq, &self->rx_work);
|
|
return skb;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
|
|
|
|
struct bes_sdio_tx_list_t {
|
|
struct list_head node;
|
|
u8 *buf;
|
|
u32 len;
|
|
};
|
|
|
|
static int bes_sdio_memcpy_to_io_helper(struct sdio_func *func, unsigned origin_size, struct scatterlist *sg, u32 sg_num)
|
|
{
|
|
int ret = 0;
|
|
u32 align_blocks;
|
|
u32 compensate;
|
|
unsigned size = origin_size & (~0x3);
|
|
|
|
#ifdef BES_SDIO_RXTX_TOGGLE
|
|
struct sbus_priv *self = NULL;
|
|
#endif
|
|
|
|
#ifdef SDIO_HOST_ADMA_SUPPORT
|
|
u32 sg_compensate_num = sg_num;
|
|
#else
|
|
int i, already, separate;
|
|
struct scatterlist sg_copy, *element;
|
|
#endif
|
|
|
|
struct mmc_request mrq;
|
|
struct mmc_command cmd;
|
|
struct mmc_data data;
|
|
|
|
#ifdef BES_SDIO_RXTX_TOGGLE
|
|
self = sdio_get_drvdata(func);
|
|
#endif
|
|
|
|
if (size && func->card->cccr.multi_block) {
|
|
|
|
align_blocks = (size + func->cur_blksize - 1) / func->cur_blksize;
|
|
bes_devel("%s sz=%u blk=%u", __func__, size, align_blocks);
|
|
compensate = align_blocks * func->cur_blksize - size;
|
|
#ifdef SDIO_HOST_ADMA_SUPPORT
|
|
if (compensate) {
|
|
sg_set_buf(&sg[sg_num], self->tx_buffer, compensate);
|
|
sg_compensate_num = sg_num + 1;
|
|
}
|
|
sg_mark_end(&sg[sg_compensate_num - 1]);
|
|
#else
|
|
if (WARN_ON(size + compensate > MAX_SDIO_TRANSFER_LEN))
|
|
return -EINVAL;
|
|
already = 0;
|
|
separate = origin_size & 3;
|
|
if (separate == 3)
|
|
separate = 1632;
|
|
else
|
|
separate = (separate + 1) * 512;
|
|
for (i = 0; i < sg_num; i++) {
|
|
element = &sg[i];
|
|
memcpy(&self->single_gathered_buffer[already], page_address(sg_page(element)) + element->offset,
|
|
element->length);
|
|
already += separate;
|
|
}
|
|
sg_set_buf(&sg_copy, self->single_gathered_buffer, size + compensate);
|
|
#endif
|
|
|
|
memset(&mrq, 0, sizeof(mrq));
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
memset(&data, 0, sizeof(data));
|
|
|
|
mrq.cmd = &cmd;
|
|
mrq.data = &data;
|
|
|
|
cmd.opcode = SD_IO_RW_EXTENDED;
|
|
cmd.arg = 0x80000000;
|
|
cmd.arg |= func->num << 28;
|
|
cmd.arg |= 0x04000000;
|
|
cmd.arg |= (origin_size) << 9;
|
|
|
|
#ifdef BES_SDIO_RXTX_TOGGLE
|
|
if (likely(self->fw_started == true)) {
|
|
cmd.arg &= ~(1 << 25);
|
|
cmd.arg |= ((self->tx_data_toggle & 0x1) << 25);
|
|
++self->tx_data_toggle;
|
|
}
|
|
#endif
|
|
|
|
cmd.arg |= 0x08000000 | align_blocks;
|
|
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
|
|
|
|
data.blksz = func->cur_blksize;
|
|
data.blocks = align_blocks;
|
|
data.flags = MMC_DATA_WRITE;
|
|
|
|
#ifdef SDIO_HOST_ADMA_SUPPORT
|
|
data.sg = sg;
|
|
data.sg_len = sg_compensate_num;
|
|
#else
|
|
data.sg = &sg_copy;
|
|
data.sg_len = 1;
|
|
#endif
|
|
|
|
mmc_set_data_timeout(&data, func->card);
|
|
|
|
mmc_wait_for_req(func->card->host, &mrq);
|
|
|
|
if (cmd.error){
|
|
ret = cmd.error;
|
|
goto out;
|
|
}
|
|
if (data.error) {
|
|
ret = data.error;
|
|
goto out;
|
|
}
|
|
if (cmd.resp[0] & R5_ERROR) {
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
if (cmd.resp[0] & R5_FUNCTION_NUMBER) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
if (cmd.resp[0] & R5_OUT_OF_RANGE) {
|
|
ret = -ERANGE;
|
|
goto out;
|
|
}
|
|
} else {
|
|
bes_err("%s,%d (%u)\n", __func__, __LINE__, size);
|
|
ret = -EINVAL;
|
|
}
|
|
out:
|
|
#ifdef BES_SDIO_RXTX_TOGGLE
|
|
if (unlikely(ret))
|
|
self->tx_data_toggle--;
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
static void sdio_tx_work(struct work_struct *work)
|
|
{
|
|
int ret, crc_retry = 0;
|
|
u32 blks, cur_blk = 0, align, total_len = 0, scatters = 0;
|
|
struct list_head proc_list;
|
|
struct bes_sdio_tx_list_t *tx_buffer, *temp;
|
|
struct sbus_priv *self = container_of(work, struct sbus_priv, tx_work);
|
|
struct scatterlist *sg = NULL;
|
|
int bes_sdio_tx_multiple_num;
|
|
struct HI_MSG_HDR *pMsg;
|
|
enum DRIVER_TO_MCU_MSG_ST driver_to_mcu = ST_EXIT;
|
|
|
|
/* don't read/write sdio when sdio error */
|
|
if (bes2600_chrdev_is_bus_error())
|
|
return;
|
|
|
|
if (bes2600_chrdev_is_signal_mode()) {
|
|
sg = self->tx_sg;
|
|
bes_sdio_tx_multiple_num = BES_SDIO_TX_MULTIPLE_NUM;
|
|
} else {
|
|
sg = self->tx_sg_nosignal;
|
|
bes_sdio_tx_multiple_num = BES_SDIO_TX_MULTIPLE_NUM_NOSIGNAL;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&proc_list);
|
|
|
|
for (;;) {
|
|
spin_lock(&self->tx_bufferlock);
|
|
list_splice_tail_init(&self->tx_bufferlist, &proc_list);
|
|
spin_unlock(&self->tx_bufferlock);
|
|
if (list_empty(&proc_list))
|
|
break;
|
|
sg_init_table(sg, bes_sdio_tx_multiple_num + 1);
|
|
list_for_each_entry_safe(tx_buffer, temp, &proc_list, node) {
|
|
blks = (tx_buffer->len + self->func->cur_blksize - 1) / self->func->cur_blksize;
|
|
align = blks * self->func->cur_blksize;
|
|
if (blks >= 4) {
|
|
align = 1632;
|
|
}
|
|
if (unlikely(blks >= 5)) {
|
|
bes_err("%s,%d skip error-len packet:%u,%d\n", __func__, __LINE__, tx_buffer->len, blks);
|
|
list_del_init(&tx_buffer->node);
|
|
kmem_cache_free(self->tx_bufferlistpool, tx_buffer);
|
|
continue;
|
|
}
|
|
bes_devel("%s,%p,%u->%u\n", __func__, tx_buffer->buf, tx_buffer->len, align);
|
|
if (!cur_blk)
|
|
cur_blk = blks;
|
|
else if (cur_blk != blks)
|
|
goto flush_previous;
|
|
|
|
pMsg = (struct HI_MSG_HDR *)tx_buffer->buf;
|
|
if (unlikely(IS_DRIVER_VENDOR_CMD(pMsg->MsgId))) {
|
|
if (driver_to_mcu == ST_EXIT) {
|
|
driver_to_mcu = ST_ENTER;
|
|
goto flush_previous;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The transfer length is rounded up to the SDIO block
|
|
* size, but tx_buffer->buf is only tx_buffer->len bytes
|
|
* long (it usually aliases into an skb linear head).
|
|
* Copy into a driver-owned bounce buffer and zero-pad
|
|
* to the aligned size; otherwise DMA reads past the
|
|
* skb and leaks adjacent kernel memory on the wire --
|
|
* observed as KFENCE OOB reads from
|
|
* bes_sdio_memcpy_to_io_helper via dma_map_sg.
|
|
*/
|
|
if (WARN_ON_ONCE(total_len + align > MAX_SDIO_TRANSFER_LEN))
|
|
goto flush_previous;
|
|
memcpy(self->tx_bounce + total_len,
|
|
tx_buffer->buf, tx_buffer->len);
|
|
if (align > tx_buffer->len)
|
|
memset(self->tx_bounce + total_len +
|
|
tx_buffer->len, 0,
|
|
align - tx_buffer->len);
|
|
sg_set_buf(&sg[scatters],
|
|
self->tx_bounce + total_len, align);
|
|
total_len += align;
|
|
++scatters;
|
|
/*del_node:*/
|
|
list_del_init(&tx_buffer->node);
|
|
kmem_cache_free(self->tx_bufferlistpool, tx_buffer);
|
|
self->tx_proc_cnt++;
|
|
if (unlikely(IS_DRIVER_VENDOR_CMD(pMsg->MsgId))) {
|
|
if (driver_to_mcu == ST_ENTER) {
|
|
driver_to_mcu = ST_EXIT;
|
|
break;
|
|
}
|
|
}
|
|
if (scatters >= bes_sdio_tx_multiple_num) {
|
|
break;
|
|
}
|
|
}
|
|
flush_previous:
|
|
if (likely(scatters)) {
|
|
if (WARN_ON(total_len & 0x3))
|
|
break;
|
|
else
|
|
total_len |= (cur_blk - 1);
|
|
sdio_claim_host(self->func);
|
|
if (self->retune_protected == false) {
|
|
sdio_retune_hold_now(self->func);
|
|
self->retune_protected = true;
|
|
}
|
|
do {
|
|
ret = bes_sdio_memcpy_to_io_helper(self->func, total_len, sg, scatters);
|
|
if (likely(ret != -84)) {
|
|
crc_retry = 0;
|
|
break;
|
|
} else {
|
|
crc_retry++;
|
|
bes_err("%s sdio write crc error(%d)\n", __func__, crc_retry);
|
|
}
|
|
} while (crc_retry <= 10);
|
|
sdio_release_host(self->func);
|
|
queue_work(self->sdio_wq, &self->rx_work);
|
|
if (ret) {
|
|
bes_err("%s,%d err=%d,%d,%d\n", __func__, __LINE__, ret, scatters, cur_blk);
|
|
sdio_work_debug(self);
|
|
bes2600_chrdev_wifi_force_close(self->core, false);
|
|
}
|
|
scatters = 0;
|
|
total_len = 0;
|
|
cur_blk = 0;
|
|
self->tx_xfer_cnt++;
|
|
self->last_tx_data_timestamp = jiffies;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int bes2600_sdio_pipe_send(struct sbus_priv *self, u8 pipe, u32 len, u8 *buf)
|
|
{
|
|
struct bes_sdio_tx_list_t * desc = NULL;
|
|
|
|
if (bes2600_chrdev_is_bus_error()) {
|
|
bes2600_tx_loop_pipe_send(self->core, buf, len);
|
|
return 0;
|
|
}
|
|
|
|
desc = kmem_cache_alloc(self->tx_bufferlistpool, GFP_KERNEL);
|
|
if (!desc)
|
|
return -ENOMEM;
|
|
INIT_LIST_HEAD(&desc->node);
|
|
desc->buf = buf;
|
|
desc->len = len;
|
|
if (!buf || !len)
|
|
return -EINVAL;
|
|
|
|
spin_lock(&self->tx_bufferlock);
|
|
list_add_tail(&desc->node, &self->tx_bufferlist);
|
|
self->tx_data_cnt++;
|
|
spin_unlock(&self->tx_bufferlock);
|
|
queue_work(self->sdio_wq, &self->tx_work);
|
|
return 0;
|
|
|
|
}
|
|
#endif
|
|
|
|
static int bes2600_sdio_misc_init(struct sbus_priv *self, struct bes2600_common *core)
|
|
{
|
|
#ifdef BES_SDIO_RXTX_TOGGLE
|
|
self->rx_data_toggle = 0;
|
|
self->tx_data_toggle = 0;
|
|
self->next_toggle = 0;
|
|
#endif
|
|
#ifdef BES_SDIO_RX_MULTIPLE_ENABLE
|
|
spin_lock_init(&self->rx_queue_lock);
|
|
skb_queue_head_init(&self->rx_queue);
|
|
self->rx_buffer = (u8 *)__get_dma_pages(GFP_KERNEL, get_order(1632 * BES_SDIO_RX_MULTIPLE_NUM));
|
|
if (!self->rx_buffer)
|
|
return -ENOMEM;
|
|
INIT_WORK(&self->rx_work, sdio_rx_work);
|
|
#endif
|
|
#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
|
|
INIT_LIST_HEAD(&self->tx_bufferlist);
|
|
spin_lock_init(&self->tx_bufferlock);
|
|
self->tx_buffer = (u8 *)kmalloc(512, GFP_KERNEL);
|
|
if (!self->tx_buffer) {
|
|
goto err2;
|
|
}
|
|
self->tx_bufferlistpool = kmem_cache_create("sdio_tx_bufferlistpool", sizeof(struct bes_sdio_tx_list_t), 0, SLAB_HWCACHE_ALIGN, NULL);
|
|
if (!self->tx_bufferlistpool)
|
|
goto err1;
|
|
self->sdio_wq = alloc_workqueue("bes_sdio", WQ_MEM_RECLAIM | WQ_HIGHPRI | WQ_CPU_INTENSIVE, 2);
|
|
if (!self->sdio_wq)
|
|
goto err0;
|
|
INIT_WORK(&self->tx_work, sdio_tx_work);
|
|
return 0;
|
|
err0:
|
|
kmem_cache_destroy(self->tx_bufferlistpool);
|
|
err1:
|
|
kfree(self->tx_buffer);
|
|
err2:
|
|
free_pages((unsigned long)self->rx_buffer, get_order(1632 * BES_SDIO_RX_MULTIPLE_NUM));
|
|
return -ENOMEM;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static struct bes2600_platform_data_sdio bes_sdio_plat_data = {
|
|
.inited = false
|
|
};
|
|
|
|
struct bes2600_platform_data_sdio *bes2600_get_platform_data(void)
|
|
{
|
|
return &bes_sdio_plat_data;
|
|
}
|
|
|
|
static int bes2600_platform_data_init(struct device *dev)
|
|
{
|
|
struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data();
|
|
struct device_node *np;
|
|
|
|
// skip reinit if already inited
|
|
if (pdata->inited)
|
|
return 0;
|
|
|
|
np = of_find_compatible_node(NULL, NULL, "bestechnic,bes2600-sdio");
|
|
if (!np) {
|
|
bes_err("bes2600-sdio device node not found!\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* Ensure I/Os are pulled low */
|
|
pdata->reset = devm_fwnode_gpiod_get_index(dev, &np->fwnode, "reset", 0, GPIOD_OUT_LOW, "bes2600_wlan_reset");
|
|
if (IS_ERR(pdata->reset)) {
|
|
bes_err("can't request reset_gpio (%ld)\n", PTR_ERR(pdata->reset));
|
|
pdata->reset = NULL;
|
|
}
|
|
|
|
pdata->powerup = devm_fwnode_gpiod_get_index(dev, &np->fwnode, "powerup", 0, GPIOD_OUT_LOW, "bes2600_wlan_powerup");
|
|
if (IS_ERR(pdata->powerup)) {
|
|
bes_err("can't request powerup_gpio (%ld)\n", PTR_ERR(pdata->powerup));
|
|
pdata->powerup = NULL;
|
|
}
|
|
|
|
pdata->wakeup = devm_fwnode_gpiod_get_index(dev, &np->fwnode, "wakeup", 0, GPIOD_OUT_LOW, "bes2600_wakeup");
|
|
if (IS_ERR(pdata->wakeup)) {
|
|
bes_err("can't request wakeup_gpio (%ld)\n", PTR_ERR(pdata->wakeup));
|
|
pdata->wakeup = NULL;
|
|
}
|
|
|
|
pdata->host_wakeup = devm_fwnode_gpiod_get_index(dev, &np->fwnode, "host-wakeup", 0, GPIOD_IN, "bes2600_host_irq");
|
|
if (IS_ERR(pdata->host_wakeup)) {
|
|
bes_err("can't request host_wake_gpio (%ld)\n", PTR_ERR(pdata->host_wakeup));
|
|
pdata->host_wakeup = NULL;
|
|
}
|
|
|
|
pdata->wlan_bt_hostwake_registered = false;
|
|
pdata->inited = true;
|
|
return 0;
|
|
}
|
|
|
|
static int bes2600_sdio_reset(struct sbus_priv *self)
|
|
{
|
|
const struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data();
|
|
|
|
bes_devel("%s\n", __func__);
|
|
|
|
gpiod_direction_output(pdata->reset, GPIOD_OUT_HIGH);
|
|
mdelay(50);
|
|
gpiod_direction_output(pdata->reset, GPIOD_OUT_LOW);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bes2600_sdio_readb_safe(struct sdio_func *func, unsigned int addr)
|
|
{
|
|
int ret = 0;
|
|
u8 val = 0;
|
|
u8 retry = 0;
|
|
|
|
do {
|
|
val = sdio_readb(func, addr, &ret);
|
|
} while((ret < 0) && ++retry < 30);
|
|
|
|
if (ret)
|
|
bes_err("%s failed, ret:%d\n", __func__, ret);
|
|
|
|
return (ret < 0) ? ret : val;
|
|
}
|
|
|
|
static int bes2600_sdio_writeb_safe(struct sdio_func *func, unsigned int addr, u8 val)
|
|
{
|
|
int ret;
|
|
u8 retry = 0;
|
|
|
|
do {
|
|
sdio_writeb(func, val, addr, &ret);
|
|
} while((ret < 0) && ++retry < 30);
|
|
|
|
if (ret)
|
|
bes_err("%s failed, ret:%d\n", __func__, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void bes2600_gpio_wakeup_mcu(struct sbus_priv *self, int flag)
|
|
{
|
|
bool gpio_wakeup = false;
|
|
const struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data();
|
|
|
|
bes_devel("%s with %d\n", __func__, flag);
|
|
|
|
mutex_lock(&self->io_mutex);
|
|
|
|
/* error check */
|
|
if((self->gpio_wakup_flags & BIT(flag)) != 0) {
|
|
/*
|
|
* Multiple subsystems holding wake is the steady-state case
|
|
* (e.g. WIFI + BT both want MCU awake). Demoted from bes_err
|
|
* to bes_devel since it isn't an error - the GPIO is already
|
|
* asserted high and the subsystem is now also tracked.
|
|
*/
|
|
bes_devel("repeat set gpio_wake_flag, sub_sys:%d\n", flag);
|
|
self->gpio_wakup_flags |= BIT(flag);
|
|
mutex_unlock(&self->io_mutex);
|
|
return;
|
|
}
|
|
|
|
/* check if this is the first subsystem that need mcu to keep awake */
|
|
gpio_wakeup = (self->gpio_wakup_flags == 0);
|
|
|
|
/* do wakeup mcu operation */
|
|
if(gpio_wakeup) {
|
|
bes_devel("pull high gpio by flag:%d\n", flag);
|
|
gpiod_direction_output(pdata->wakeup, GPIOD_OUT_HIGH);
|
|
msleep(10);
|
|
}
|
|
|
|
/* set flag of gpio_wakeup_flags */
|
|
self->gpio_wakup_flags |= BIT(flag);
|
|
|
|
mutex_unlock(&self->io_mutex);
|
|
}
|
|
|
|
static void bes2600_gpio_allow_mcu_sleep(struct sbus_priv *self, int flag)
|
|
{
|
|
bool gpio_sleep = false;
|
|
const struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data();
|
|
|
|
bes_devel("%s with %d\n", __func__, flag);
|
|
|
|
mutex_lock(&self->io_mutex);
|
|
|
|
/* error check */
|
|
if((self->gpio_wakup_flags & BIT(flag)) == 0) {
|
|
/*
|
|
* Mirror of the wake path: a clear when the bit is already
|
|
* clear is racy bookkeeping, not a hardware error.
|
|
*/
|
|
bes_devel("repeat clear gpio_wake_flag, sub_sys:%d\n", flag);
|
|
mutex_unlock(&self->io_mutex);
|
|
return;
|
|
}
|
|
|
|
/* clear flag of gpio_wakeup_flags */
|
|
self->gpio_wakup_flags &= ~BIT(flag);
|
|
|
|
/* check if this is the last subsystem that need mcu to keep awake */
|
|
gpio_sleep = (self->gpio_wakup_flags == 0);
|
|
|
|
/* do wakeup mcu operation */
|
|
if(gpio_sleep) {
|
|
bes_devel("pull low gpio by flag:%d\n", flag);
|
|
gpiod_direction_output(pdata->wakeup, GPIOD_OUT_LOW);
|
|
}
|
|
|
|
mutex_unlock(&self->io_mutex);
|
|
}
|
|
|
|
static int bes2600_sdio_active(struct sbus_priv *self, int sub_system)
|
|
{
|
|
u16 cfg;
|
|
u8 cfm = 0;
|
|
int ret = 0, retries = 0;
|
|
u8 tmp_val = 0;
|
|
u32 cnt = 0;
|
|
u32 delay_cnt = 2;
|
|
|
|
/* nosignal mode only allow SUBSYSTEM_WIFI */
|
|
if (!bes2600_chrdev_is_signal_mode() && sub_system != SUBSYSTEM_WIFI)
|
|
return -EINVAL;
|
|
|
|
/* don't read/write sdio when sdio error */
|
|
if (bes2600_chrdev_is_bus_error())
|
|
return 0;
|
|
|
|
/* prevent concurrent access */
|
|
mutex_lock(&self->sbus_mutex);
|
|
|
|
/* set config and confirm value */
|
|
if (sub_system == SUBSYSTEM_MCU) {
|
|
cfg = BES_HOST_INT | BES_SUBSYSTEM_MCU_ACTIVE;
|
|
cfm = BES_SLAVE_STATUS_MCU_WAKEUP_READY;
|
|
delay_cnt = 2;
|
|
} else if (sub_system == SUBSYSTEM_WIFI) {
|
|
cfg = BES_HOST_INT | BES_SUBSYSTEM_WIFI_ACTIVE;
|
|
cfm = BES_SLAVE_STATUS_WIFI_READY;
|
|
delay_cnt = 25;
|
|
} else if(sub_system == SUBSYSTEM_BT) {
|
|
cfg = BES_HOST_INT | BES_SUBSYSTEM_BT_ACTIVE;
|
|
cfm = BES_SLAVE_STATUS_BT_READY;
|
|
delay_cnt = 25;
|
|
} else if(sub_system == SUBSYSTEM_BT_LP) {
|
|
cfg = BES_HOST_INT | BES_SUBSYSTEM_BT_WAKEUP;
|
|
cfm = BES_SLAVE_STATUS_BT_WAKE_READY;
|
|
delay_cnt = 2;
|
|
} else {
|
|
mutex_unlock(&self->sbus_mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* set fw_started flag in advance */
|
|
if(sub_system == SUBSYSTEM_WIFI) {
|
|
self->fw_started = true;
|
|
}
|
|
|
|
/* wait until device ready */
|
|
do {
|
|
sdio_claim_host(self->func);
|
|
ret = bes2600_sdio_readb_safe(self->func, BES_SLAVE_STATUS_REG_ID);
|
|
sdio_release_host(self->func);
|
|
bes_devel("active wait mcu ready cnt:%d, reg:%d\n", cnt++, ret);
|
|
if(ret < 0) {
|
|
goto err;
|
|
}
|
|
} while((ret & BES_SLAVE_STATUS_MCU_READY) == 0);
|
|
|
|
do {
|
|
/* claim sdio host */
|
|
sdio_claim_host(self->func);
|
|
|
|
/* write first segment */
|
|
tmp_val = (cfg >> 8) & 0xff;
|
|
ret = bes2600_sdio_writeb_safe(self->func, BES_HOST_INT_REG_ID + 1, tmp_val);
|
|
if(ret < 0) {
|
|
sdio_release_host(self->func);
|
|
bes_err("active write 1st seg failed\n");
|
|
goto err;
|
|
}
|
|
|
|
/* write second segment */
|
|
tmp_val = cfg & 0xff;
|
|
ret = bes2600_sdio_writeb_safe(self->func, BES_HOST_INT_REG_ID, tmp_val);
|
|
if(ret < 0) {
|
|
sdio_release_host(self->func);
|
|
bes_err("active write 2nd seg failed\n");
|
|
goto err;
|
|
}
|
|
|
|
/* release sdio host */
|
|
sdio_release_host(self->func);
|
|
|
|
/* wait device to response */
|
|
msleep(delay_cnt);
|
|
|
|
/* read device response result */
|
|
sdio_claim_host(self->func);
|
|
ret = bes2600_sdio_readb_safe(self->func, BES_SLAVE_STATUS_REG_ID);
|
|
sdio_release_host(self->func);
|
|
if(ret < 0) {
|
|
bes_err("active read response failed\n");
|
|
goto err;
|
|
}
|
|
bes_devel("active resp cnt:%d, reg:%d, sub_sys:%d\n", retries, ret, sub_system);
|
|
} while ((cfm != 0) && (ret & cfm) == 0 && ++retries <= 200); // check if cfm bit is set
|
|
|
|
if (retries > 200) {
|
|
bes_err("bes2600_sdio_active failed, subsys:%d\n", sub_system);
|
|
/* open wifi failed, restore fw_started flag */
|
|
if(sub_system == SUBSYSTEM_WIFI) {
|
|
self->fw_started = false;
|
|
}
|
|
|
|
mutex_unlock(&self->sbus_mutex);
|
|
return -EFAULT;
|
|
} else {
|
|
ret = 0;
|
|
}
|
|
|
|
#ifdef BES2600_GPIO_WAKEUP_AP
|
|
if (sub_system == SUBSYSTEM_WIFI ||
|
|
sub_system == SUBSYSTEM_BT)
|
|
ret = bes2600_gpio_wakeup_ap_config(self);
|
|
#else
|
|
ret = 0;
|
|
#endif
|
|
/* prevent concurrent access */
|
|
mutex_unlock(&self->sbus_mutex);
|
|
|
|
return ret;
|
|
err:
|
|
mutex_unlock(&self->sbus_mutex);
|
|
bes2600_chrdev_wifi_force_close(self->core, false);
|
|
return -ENODEV;
|
|
}
|
|
|
|
static void bes2600_sdio_empty_work(struct sbus_priv *self)
|
|
{
|
|
#ifdef BES_SDIO_RX_MULTIPLE_ENABLE
|
|
struct sk_buff *skb;
|
|
#endif
|
|
#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
|
|
struct bes_sdio_tx_list_t *tx_buffer, *temp;
|
|
#endif
|
|
|
|
#ifdef BES_SDIO_RX_MULTIPLE_ENABLE
|
|
cancel_work_sync(&self->rx_work);
|
|
while (1) {
|
|
skb = skb_dequeue(&self->rx_queue);
|
|
if (skb)
|
|
dev_kfree_skb(skb);
|
|
else
|
|
break;
|
|
}
|
|
self->rx_last_ctrl = 0;
|
|
self->rx_total_ctrl_cnt = 0;
|
|
self->rx_continuous_ctrl_cnt = 0;
|
|
self->rx_remain_ctrl_cnt = 0;
|
|
self->rx_zero_ctrl_cnt = 0;
|
|
self->rx_data_cnt = 0;
|
|
self->rx_xfer_cnt = 0;
|
|
self->rx_proc_cnt = 0;
|
|
#endif
|
|
|
|
#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
|
|
cancel_work_sync(&self->tx_work);
|
|
list_for_each_entry_safe(tx_buffer, temp, &self->tx_bufferlist, node) {
|
|
list_del_init(&tx_buffer->node);
|
|
kmem_cache_free(self->tx_bufferlistpool, tx_buffer);
|
|
}
|
|
self->tx_data_cnt = 0;
|
|
self->tx_xfer_cnt = 0;
|
|
self->tx_proc_cnt = 0;
|
|
#endif
|
|
|
|
#ifdef BES_SDIO_RXTX_TOGGLE
|
|
self->rx_data_toggle = 0;
|
|
self->tx_data_toggle = 0;
|
|
self->next_toggle = 0;
|
|
#endif
|
|
}
|
|
|
|
#ifdef BES2600_GPIO_WAKEUP_AP
|
|
static void bes2600_wlan_bt_hostwake_unregister(void);
|
|
#endif
|
|
|
|
static int bes2600_sdio_deactive(struct sbus_priv *self, int sub_system)
|
|
{
|
|
u16 cfg = 0;
|
|
u8 cfm = 0;
|
|
u8 tmp_val = 0;
|
|
u16 retries = 0;
|
|
u32 cnt = 0;
|
|
u32 delay_cnt = 2;
|
|
int ret;
|
|
|
|
/* don't read/write sdio when sdio error */
|
|
if (bes2600_chrdev_is_bus_error())
|
|
return 0;
|
|
|
|
/* notify device deactive event */
|
|
if (bes2600_chrdev_is_signal_mode()) {
|
|
/* prevent concurrent access */
|
|
mutex_lock(&self->sbus_mutex);
|
|
|
|
/* set config and confirm value */
|
|
if (sub_system == SUBSYSTEM_MCU) {
|
|
cfg = BES_HOST_INT | BES_SUBSYSTEM_MCU_DEACTIVE;
|
|
cfm = BES_SLAVE_STATUS_MCU_WAKEUP_READY;
|
|
} else if (sub_system == SUBSYSTEM_WIFI) {
|
|
cfg = BES_HOST_INT | BES_SUBSYSTEM_WIFI_DEACTIVE;
|
|
cfm = BES_SLAVE_STATUS_WIFI_READY;
|
|
} else if(sub_system == SUBSYSTEM_BT) {
|
|
cfg = BES_HOST_INT | BES_SUBSYSTEM_BT_DEACTIVE;
|
|
cfm = BES_SLAVE_STATUS_BT_READY;
|
|
} else if(sub_system == SUBSYSTEM_BT_LP) {
|
|
cfg = BES_HOST_INT | BES_SUBSYSTEM_BT_SLEEP;
|
|
cfm = BES_SLAVE_STATUS_BT_WAKE_READY;
|
|
} else {
|
|
mutex_unlock(&self->sbus_mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* wait until device ready */
|
|
do {
|
|
sdio_claim_host(self->func);
|
|
ret = bes2600_sdio_readb_safe(self->func, BES_SLAVE_STATUS_REG_ID);
|
|
sdio_release_host(self->func);
|
|
bes_devel("deactive wait mcu ready cnt:%d, reg:%d\n", cnt++, ret);
|
|
|
|
if(ret < 0) {
|
|
goto err;
|
|
}
|
|
} while((ret & BES_SLAVE_STATUS_MCU_READY) == 0);
|
|
|
|
do {
|
|
/* claim sdio host */
|
|
sdio_claim_host(self->func);
|
|
|
|
/* write first segment */
|
|
tmp_val = (cfg >> 8) & 0xff;
|
|
ret = bes2600_sdio_writeb_safe(self->func, BES_HOST_INT_REG_ID + 1, tmp_val);
|
|
if(ret < 0) {
|
|
sdio_release_host(self->func);
|
|
bes_err("deactive write 1st seg failed\n");
|
|
goto err;
|
|
}
|
|
|
|
/* write second segment */
|
|
tmp_val = cfg & 0xff;
|
|
ret = bes2600_sdio_writeb_safe(self->func, BES_HOST_INT_REG_ID, tmp_val);
|
|
if(ret < 0) {
|
|
sdio_release_host(self->func);
|
|
bes_err("deactive write 2nd seg failed\n");
|
|
goto err;
|
|
}
|
|
|
|
/* release sdio host */
|
|
sdio_release_host(self->func);
|
|
|
|
/* wait device to response */
|
|
msleep(delay_cnt);
|
|
|
|
/* read device response result */
|
|
sdio_claim_host(self->func);
|
|
ret = bes2600_sdio_readb_safe(self->func, BES_SLAVE_STATUS_REG_ID);
|
|
sdio_release_host(self->func);
|
|
if(ret < 0) {
|
|
bes_err("deactive read response failed\n");
|
|
if (sub_system == SUBSYSTEM_MCU) {
|
|
/* cmd52 may return error when 2600 is sleeping */
|
|
ret = 0;
|
|
break;
|
|
} else {
|
|
goto err;
|
|
}
|
|
}
|
|
bes_devel("deactive resp cnt:%d, reg:%d, sub_sys:%d\n", retries, ret, sub_system);
|
|
} while((cfm != 0) && (ret & cfm) != 0 && ++retries < 200);
|
|
|
|
/* set fw_started flag to false */
|
|
if(bes2600_chrdev_is_signal_mode()
|
|
&& sub_system == SUBSYSTEM_WIFI)
|
|
self->fw_started = false;
|
|
|
|
/* reset sdio send and receive control variable */
|
|
if(sub_system == SUBSYSTEM_WIFI) {
|
|
bes2600_sdio_empty_work(self);
|
|
}
|
|
|
|
/* prevent concurrent access */
|
|
mutex_unlock(&self->sbus_mutex);
|
|
|
|
return (ret < 0) ? ret : 0;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
err:
|
|
mutex_unlock(&self->sbus_mutex);
|
|
bes2600_chrdev_wifi_force_close(self->core, false);
|
|
return -ENODEV;
|
|
}
|
|
|
|
static void bes2600_sdio_power_down(struct sbus_priv *self)
|
|
{
|
|
#ifdef POWER_DOWN_BY_MSG
|
|
u32 cfg = BES_HOST_INT | BES_SUBSYSTEM_SYSTEM_CLOSE;
|
|
u8 tmp_val = 0;
|
|
int ret = 0;
|
|
|
|
sdio_claim_host(self->func);
|
|
tmp_val = (cfg >> 8) & 0xff;
|
|
sdio_writeb(self->func, tmp_val, BES_HOST_INT_REG_ID + 1, &ret);
|
|
tmp_val = cfg & 0xff;
|
|
sdio_writeb(self->func, tmp_val, BES_HOST_INT_REG_ID, &ret);
|
|
sdio_release_host(self->func);
|
|
#else
|
|
struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data();
|
|
gpiod_direction_output(pdata->powerup, GPIOD_OUT_LOW);
|
|
#endif
|
|
|
|
msleep(10);
|
|
|
|
self->func->card->host->caps &= ~MMC_CAP_NONREMOVABLE;
|
|
schedule_work(&self->sdio_scan_work);
|
|
|
|
}
|
|
|
|
static int bes2600_sdio_power_switch(struct sbus_priv *self, int on)
|
|
{
|
|
// TODO: return something meaningful, perhaps
|
|
struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data();
|
|
if (on) {
|
|
bes2600_sdio_on(pdata);
|
|
return 0;
|
|
}
|
|
bes2600_sdio_power_down(self);
|
|
return 0;
|
|
}
|
|
|
|
static void bes2600_sdio_halt_device(struct sbus_priv *self)
|
|
{
|
|
sdio_work_debug(self);
|
|
}
|
|
|
|
/*
|
|
* Trigger an SDIO bus reset via mmc_hw_reset().
|
|
*
|
|
* With multiple SDIO functions probed (PineTab2 binds func 1 for WLAN and
|
|
* func 2 for the BT-companion path) mmc_sdio_hw_reset() takes the
|
|
* remove-and-rescan path: it marks the card removed and schedules
|
|
* mmc_rescan, which tears down the bound function drivers and re-detects
|
|
* the card on the next sweep, in turn reinvoking bes2600_sdio_probe().
|
|
*
|
|
* With a single function probed it instead invokes mmc_power_cycle()
|
|
* directly, which on PineTab2 toggles the wifi-reset GPIO via sdio_pwrseq.
|
|
*
|
|
* In both cases the chip ends up in a freshly reset state, which is the
|
|
* goal of the recovery path.
|
|
*
|
|
* mmc_hw_reset() must be called without holding the SDIO host claim --
|
|
* the multi-func remove-and-rescan path acquires the host claim via the
|
|
* mmc workqueue.
|
|
*/
|
|
static int bes2600_sdio_bus_reset(struct sbus_priv *self)
|
|
{
|
|
struct mmc_host *host;
|
|
int ret;
|
|
|
|
if (!self || !self->func || !self->func->card)
|
|
return -EINVAL;
|
|
|
|
host = self->func->card->host;
|
|
ret = mmc_hw_reset(self->func->card);
|
|
|
|
/*
|
|
* On multi-function SDIO cards (BES2600 has WLAN func 1 + BT
|
|
* companion func 2), mmc_sdio_hw_reset() removes the card and
|
|
* returns 1 to signal "remove happened, caller must trigger
|
|
* rescan". The kernel does NOT auto-rescan in this case;
|
|
* single-function cards take the rescan path inline and return 0.
|
|
* Treat any non-negative return as success and force a rescan if
|
|
* mmc_hw_reset signalled the multi-function path - otherwise the
|
|
* card stays removed indefinitely after a wedge recovery,
|
|
* leaving wifi (and the BT companion) silent until reboot.
|
|
*/
|
|
if (ret > 0) {
|
|
bes_info("multi-func mmc_hw_reset removed card; scheduling rescan\n");
|
|
mmc_detect_change(host, 0);
|
|
ret = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static bool bes2600_sdio_wakeup_source(struct sbus_priv *self)
|
|
{
|
|
struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data();
|
|
|
|
if(pdata->wakeup_source) {
|
|
pdata->wakeup_source = false;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static struct sbus_ops bes2600_sdio_sbus_ops = {
|
|
.sbus_memcpy_fromio = bes2600_sdio_memcpy_fromio,
|
|
.sbus_memcpy_toio = bes2600_sdio_memcpy_toio,
|
|
.lock = bes2600_sdio_lock,
|
|
.unlock = bes2600_sdio_unlock,
|
|
.irq_subscribe = bes2600_sdio_irq_subscribe,
|
|
.irq_unsubscribe = bes2600_sdio_irq_unsubscribe,
|
|
.reset = bes2600_sdio_reset,
|
|
.align_size = bes2600_sdio_align_size,
|
|
.set_block_size = bes2600_sdio_set_block_size,
|
|
.sbus_reg_read = bes2600_sdio_reg_read,
|
|
.sbus_reg_write = bes2600_sdio_reg_write,
|
|
.init = bes2600_sdio_misc_init,
|
|
#ifdef BES_SDIO_RX_MULTIPLE_ENABLE
|
|
.pipe_read = bes2600_sdio_pipe_read,
|
|
#endif
|
|
#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
|
|
.pipe_send = bes2600_sdio_pipe_send,
|
|
#endif
|
|
.sbus_active = bes2600_sdio_active,
|
|
.sbus_deactive = bes2600_sdio_deactive,
|
|
.power_switch = bes2600_sdio_power_switch,
|
|
.gpio_wake = bes2600_gpio_wakeup_mcu,
|
|
.gpio_sleep = bes2600_gpio_allow_mcu_sleep,
|
|
.halt_device = bes2600_sdio_halt_device,
|
|
.wakeup_source = bes2600_sdio_wakeup_source,
|
|
.bus_reset = bes2600_sdio_bus_reset,
|
|
};
|
|
|
|
static void bes2600_sdio_en_lp_cb(struct bes2600_common *hw_priv)
|
|
{
|
|
long unsigned int old_ts, new_ts;
|
|
struct sbus_priv *self = hw_priv->sbus_priv;
|
|
|
|
do {
|
|
old_ts = self->last_irq_timestamp;
|
|
flush_work(&self->rx_work);
|
|
new_ts = self->last_irq_timestamp;
|
|
} while(old_ts != new_ts);
|
|
}
|
|
|
|
/* Probe Function to be called by SDIO stack when device is discovered */
|
|
static int bes2600_sdio_probe(struct sdio_func *func,
|
|
const struct sdio_device_id *id)
|
|
{
|
|
struct sbus_priv *self;
|
|
int status;
|
|
|
|
bes_devel("Probe called:%p,%d\n", func, func->num);
|
|
if (func->num > 1)
|
|
return 0;
|
|
|
|
func->card->host->caps |= MMC_CAP_NONREMOVABLE;
|
|
bes2600_chrdev_bus_probe_notify();
|
|
|
|
self = kzalloc(sizeof(*self), GFP_KERNEL);
|
|
if (!self) {
|
|
bes_devel("Can't allocate SDIO sbus_priv.");
|
|
return -ENOMEM;
|
|
}
|
|
spin_lock_init(&self->lock);
|
|
|
|
struct device *dev = &func->dev;
|
|
int ret = bes2600_platform_data_init(dev);
|
|
if (ret)
|
|
goto err;
|
|
|
|
/* wire struct device into factory.c for request_firmware() context */
|
|
bes2600_factory_set_dev(dev);
|
|
|
|
self->pdata = bes2600_get_platform_data();
|
|
self->func = func;
|
|
self->dev = &func->dev;
|
|
self->gpio_wakup_flags = 0;
|
|
self->retune_protected = false;
|
|
self->unregister_in_process = false;
|
|
mutex_init(&self->io_mutex);
|
|
mutex_init(&self->sbus_mutex);
|
|
INIT_WORK(&self->sdio_scan_work, sdio_scan_work);
|
|
#ifndef SDIO_HOST_ADMA_SUPPORT
|
|
if ((MAX_SDIO_TRANSFER_LEN < 1632 * BES_SDIO_RX_MULTIPLE_NUM) ||
|
|
(MAX_SDIO_TRANSFER_LEN < 1632 * BES_SDIO_TX_MULTIPLE_NUM)) {
|
|
bes_err("gathered buffer is too small.\n");
|
|
return -EINVAL;
|
|
}
|
|
self->single_gathered_buffer = (u8 *)__get_dma_pages(GFP_KERNEL, get_order(MAX_SDIO_TRANSFER_LEN));
|
|
if (!self->single_gathered_buffer)
|
|
return -ENOMEM;
|
|
#endif
|
|
#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
|
|
self->tx_bounce = (u8 *)__get_dma_pages(GFP_KERNEL,
|
|
get_order(MAX_SDIO_TRANSFER_LEN));
|
|
if (!self->tx_bounce) {
|
|
#ifndef SDIO_HOST_ADMA_SUPPORT
|
|
free_pages((unsigned long)self->single_gathered_buffer,
|
|
get_order(MAX_SDIO_TRANSFER_LEN));
|
|
#endif
|
|
return -ENOMEM;
|
|
}
|
|
#endif
|
|
#ifdef BES_SDIO_RXTX_TOGGLE
|
|
self->fw_started = false;
|
|
#endif
|
|
bes2600_gpio_wakeup_mcu(self, GPIO_WAKE_FLAG_SDIO_PROBE);
|
|
|
|
sdio_set_drvdata(func, self);
|
|
sdio_claim_host(func);
|
|
sdio_enable_func(func);
|
|
sdio_release_host(func);
|
|
|
|
bes2600_reg_set_object(&bes2600_sdio_sbus_ops, self);
|
|
status = bes2600_load_firmware(&bes2600_sdio_sbus_ops, self);
|
|
if(status > 0) { // for wifi closed case
|
|
bes_devel("interrupt init process beacuse device be closed.\n");
|
|
goto out;
|
|
} else if(status < 0) { // for download fail case
|
|
goto err;
|
|
}
|
|
|
|
status = bes2600_register_net_dev(self);
|
|
if (status) {
|
|
goto err;
|
|
}
|
|
|
|
out:
|
|
bes2600_chrdev_set_sbus_priv_data(self, false);
|
|
bes2600_switch_bt(true);
|
|
bes2600_gpio_allow_mcu_sleep(self, GPIO_WAKE_FLAG_SDIO_PROBE);
|
|
return 0;
|
|
|
|
err:
|
|
bes_err("%s failed, func:%d\n", __func__, func->num);
|
|
func->card->host->caps &= ~MMC_CAP_NONREMOVABLE;
|
|
sdio_claim_host(func);
|
|
sdio_disable_func(func);
|
|
sdio_release_host(func);
|
|
bes2600_gpio_allow_mcu_sleep(self, GPIO_WAKE_FLAG_SDIO_PROBE);
|
|
sdio_set_drvdata(func, NULL);
|
|
bes2600_reg_set_object(NULL, NULL);
|
|
bes2600_chrdev_set_sbus_priv_data(NULL, true);
|
|
kfree(self);
|
|
return 0;
|
|
}
|
|
|
|
int bes2600_register_net_dev(struct sbus_priv *bus_priv)
|
|
{
|
|
int status = 0;
|
|
BUG_ON(!bus_priv);
|
|
status = bes2600_core_probe(&bes2600_sdio_sbus_ops,
|
|
bus_priv, bus_priv->dev, &bus_priv->core);
|
|
if(!status)
|
|
bes2600_pwr_register_en_lp_cb(bus_priv->core, bes2600_sdio_en_lp_cb);
|
|
|
|
return status;
|
|
}
|
|
|
|
int bes2600_unregister_net_dev(struct sbus_priv *bus_priv)
|
|
{
|
|
BUG_ON(!bus_priv);
|
|
if (bus_priv->core && !bus_priv->unregister_in_process) {
|
|
bus_priv->unregister_in_process = true;
|
|
bes2600_core_release(bus_priv->core);
|
|
bes2600_pwr_unregister_en_lp_cb(bus_priv->core, bes2600_sdio_en_lp_cb);
|
|
bus_priv->core = NULL;
|
|
|
|
if (bus_priv->sdio_wq) {
|
|
flush_workqueue(bus_priv->sdio_wq);
|
|
destroy_workqueue(bus_priv->sdio_wq);
|
|
bus_priv->sdio_wq = NULL;
|
|
}
|
|
|
|
if (bus_priv->rx_buffer) {
|
|
free_pages((unsigned long)bus_priv->rx_buffer, get_order(1632 * BES_SDIO_RX_MULTIPLE_NUM));
|
|
bus_priv->rx_buffer = NULL;
|
|
}
|
|
|
|
#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
|
|
if (bus_priv->tx_buffer) {
|
|
kfree(bus_priv->tx_buffer);
|
|
bus_priv->tx_buffer = NULL;
|
|
}
|
|
|
|
if (bus_priv->tx_bufferlistpool) {
|
|
kmem_cache_destroy(bus_priv->tx_bufferlistpool);
|
|
bus_priv->tx_bufferlistpool = NULL;
|
|
}
|
|
#endif
|
|
bus_priv->unregister_in_process = false;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool bes2600_is_net_dev_created(struct sbus_priv *bus_priv)
|
|
{
|
|
BUG_ON(!bus_priv);
|
|
return (bus_priv->core != NULL);
|
|
}
|
|
|
|
/* Disconnect Function to be called by SDIO stack when
|
|
* device is disconnected */
|
|
static void bes2600_sdio_remove(struct sdio_func *func)
|
|
{
|
|
struct sbus_priv *self = sdio_get_drvdata(func);
|
|
|
|
func->card->host->caps &= ~MMC_CAP_NONREMOVABLE;
|
|
bes_devel("%s called:%p,%d\n", __func__, func, func->num);
|
|
|
|
if (self) {
|
|
#ifndef CONFIG_BES2600_USE_GPIO_IRQ
|
|
sdio_claim_host(func);
|
|
sdio_release_irq(func);
|
|
sdio_release_host(func);
|
|
#else
|
|
free_irq(irq->start, self);
|
|
#endif //CONFIG_BES2600_USE_GPIO_IRQ
|
|
sdio_claim_host(func);
|
|
sdio_disable_func(func);
|
|
sdio_release_host(func);
|
|
bes2600_reg_set_object(NULL, NULL);
|
|
bes2600_chrdev_set_sbus_priv_data(NULL, false);
|
|
sdio_set_drvdata(func, NULL);
|
|
if (self->retune_protected == true) {
|
|
sdio_retune_release(func);
|
|
}
|
|
#ifndef SDIO_HOST_ADMA_SUPPORT
|
|
if (self->single_gathered_buffer) {
|
|
free_pages((unsigned long)self->single_gathered_buffer, get_order(MAX_SDIO_TRANSFER_LEN));
|
|
}
|
|
#endif
|
|
#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
|
|
if (self->tx_bounce) {
|
|
free_pages((unsigned long)self->tx_bounce,
|
|
get_order(MAX_SDIO_TRANSFER_LEN));
|
|
}
|
|
#endif
|
|
kfree(self);
|
|
}
|
|
}
|
|
|
|
#ifdef BES2600_GPIO_WAKEUP_AP
|
|
|
|
static irqreturn_t bes2600_wlan_bt_hostwake_thread(int irq, void *dev_id)
|
|
{
|
|
struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data();
|
|
|
|
bes_devel("bes2600_wlan_hostwake:%d\n", dev_id == (void *)pdata);
|
|
|
|
if (dev_id == (void *)pdata) {
|
|
bes2600_chrdev_wakeup_by_event_set(WAKEUP_EVENT_SETTING);
|
|
pdata->wakeup_source = true;
|
|
disable_irq_nosync(irq);
|
|
return IRQ_HANDLED;
|
|
} else {
|
|
return IRQ_NONE;
|
|
}
|
|
}
|
|
|
|
static int bes2600_wlan_bt_hostwake_register(void)
|
|
{
|
|
int ret = 0;
|
|
struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data();
|
|
|
|
// flipping internal struct registers considered as nothing
|
|
bes_warn("%s: this function does nothing\n", __FUNCTION__);
|
|
|
|
if (pdata->wlan_bt_hostwake_registered == true) {
|
|
bes_err("wlan hostwake register repeatedly.\n");
|
|
return -1;
|
|
}
|
|
|
|
pdata->wlan_bt_hostwake_registered = true;
|
|
pdata->wakeup_source = false;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void bes2600_wlan_bt_hostwake_unregister(void)
|
|
{
|
|
int ret = 0;
|
|
struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data();
|
|
|
|
// flipping internal struct registers considered as nothing
|
|
bes_warn("%s: this function does nothing\n", __FUNCTION__);
|
|
|
|
if (pdata->wlan_bt_hostwake_registered == false)
|
|
return;
|
|
|
|
pdata->wlan_bt_hostwake_registered = false;
|
|
}
|
|
|
|
static int bes2600_gpio_wakeup_ap_config(struct sbus_priv *self)
|
|
{
|
|
u8 wakeup_cfg = 0;
|
|
int ret = 0, irq_flags = 0, irq = 0;
|
|
|
|
if (!bes2600_chrdev_is_signal_mode())
|
|
return 0;
|
|
|
|
if (irq_flags & IRQF_TRIGGER_HIGH) {
|
|
wakeup_cfg = BES_AP_WAKEUP_GPIO_HIGH | BES_AP_WAKEUP_CFG_VALID;
|
|
} else if (irq_flags & IRQF_TRIGGER_LOW) {
|
|
wakeup_cfg = BES_AP_WAKEUP_GPIO_LOW | BES_AP_WAKEUP_CFG_VALID;
|
|
} else if (irq_flags & IRQF_TRIGGER_RISING) {
|
|
wakeup_cfg = BES_AP_WAKEUP_GPIO_RISE | BES_AP_WAKEUP_CFG_VALID;
|
|
} else if (irq_flags & IRQF_TRIGGER_FALLING) {
|
|
wakeup_cfg = BES_AP_WAKEUP_GPIO_FALL | BES_AP_WAKEUP_CFG_VALID;
|
|
}
|
|
|
|
if (wakeup_cfg & BES_AP_WAKEUP_CFG_VALID)
|
|
wakeup_cfg |= (BES_AP_WAKEUP_TYPE_GPIO << BES_AP_WAKEUP_TYPE_SHIFT);
|
|
|
|
bes_devel("%s config:%x\n", __func__, wakeup_cfg);
|
|
|
|
sdio_claim_host(self->func);
|
|
sdio_writeb(self->func, wakeup_cfg, BES_AP_WAKEUP_REG_ID, &ret);
|
|
if (!ret) {
|
|
sdio_writeb(self->func, 0, BES_HOST_INT_REG_ID + 1, &ret);
|
|
}
|
|
if (!ret) {
|
|
sdio_writeb(self->func, (BES_HOST_INT | BES_AP_WAKEUP_CFG), BES_HOST_INT_REG_ID, &ret);
|
|
}
|
|
sdio_release_host(self->func);
|
|
if (ret) {
|
|
bes_err("%s failed:%d\n", __func__, ret);
|
|
free_irq(irq, &bes_sdio_plat_data);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int bes2600_sdio_prepare(struct device *dev)
|
|
{
|
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
|
struct sbus_priv *self = sdio_get_drvdata(func);
|
|
|
|
bes_devel("%s (%p,%d)enter\n", __func__, func, func->num);
|
|
|
|
if (func->num > 1)
|
|
return 0;
|
|
|
|
if(bes2600_sdio_sbus_ops.gpio_wake)
|
|
bes2600_sdio_sbus_ops.gpio_wake(self, GPIO_WAKE_FLAG_HOST_SUSPEND);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bes2600_sdio_suspend(struct device *dev)
|
|
{
|
|
int ret;
|
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
|
struct sbus_priv *self = sdio_get_drvdata(func);
|
|
|
|
bes_devel("%s (%p,%d)enter\n", __func__, func, func->num);
|
|
if (func->num > 1)
|
|
return 0;
|
|
|
|
#ifndef CONFIG_BES2600_WOWLAN
|
|
if(bes2600_chrdev_check_system_close() == false)
|
|
return -EBUSY;
|
|
#endif
|
|
|
|
/* Notify SDIO that BES2600 will remain powered during suspend */
|
|
ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
|
|
if (ret) {
|
|
bes_err("Error setting SDIO pm flags: %i\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (bes2600_chrdev_is_bt_opened() == true) {
|
|
if ((ret = bes2600_sdio_deactive(self, SUBSYSTEM_BT_LP))) {
|
|
bes_err("bt sleep in suspend failed:%d.\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
#ifdef BES2600_GPIO_WAKEUP_AP
|
|
return bes2600_wlan_bt_hostwake_register();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bes2600_sdio_suspend_noirq(struct device *dev)
|
|
{
|
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
|
struct sbus_priv *self = sdio_get_drvdata(func);
|
|
|
|
bes_devel("%s (%p,%d)enter\n", __func__, func, func->num);
|
|
|
|
if (func->num > 1)
|
|
return 0;
|
|
|
|
if(self->core &&
|
|
(work_pending(&self->rx_work) || atomic_read(&self->core->bh_rx))) {
|
|
bes_devel("%s: Suspend interrupted.\n", __func__);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if(bes2600_sdio_sbus_ops.gpio_sleep)
|
|
bes2600_sdio_sbus_ops.gpio_sleep(self, GPIO_WAKE_FLAG_HOST_SUSPEND);
|
|
|
|
if (self->retune_protected == true)
|
|
bes_warn("retune is closed while ap sleep.\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bes2600_sdio_resume_noirq(struct device *dev)
|
|
{
|
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
|
struct sbus_priv *self = sdio_get_drvdata(func);
|
|
|
|
bes_devel("%s (%p,%d)enter\n", __func__, func, func->num);
|
|
|
|
if (func->num > 1)
|
|
return 0;
|
|
|
|
if(bes2600_sdio_sbus_ops.gpio_wake)
|
|
bes2600_sdio_sbus_ops.gpio_wake(self, GPIO_WAKE_FLAG_HOST_RESUME);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bes2600_sdio_resume(struct device *dev)
|
|
{
|
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
|
|
|
bes_devel("%s (%p,%d)enter\n", __func__, func, func->num);
|
|
|
|
if (func->num > 1)
|
|
return 0;
|
|
|
|
#ifdef BES2600_GPIO_WAKEUP_AP
|
|
bes2600_wlan_bt_hostwake_unregister();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void bes2600_sdio_complete(struct device *dev)
|
|
{
|
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
|
struct sbus_priv *self = sdio_get_drvdata(func);
|
|
|
|
bes_devel("%s (%p,%d)enter\n", __func__, func, func->num);
|
|
|
|
if (func->num > 1)
|
|
return;
|
|
|
|
/* wakeup bt if bt is on */
|
|
bes2600_chrdev_wakeup_bt();
|
|
|
|
/* clear resume gpio wake flag */
|
|
if(bes2600_sdio_sbus_ops.gpio_sleep)
|
|
bes2600_sdio_sbus_ops.gpio_sleep(self, GPIO_WAKE_FLAG_HOST_RESUME);
|
|
}
|
|
|
|
static const struct dev_pm_ops bes2600_pm_ops = {
|
|
.prepare = bes2600_sdio_prepare,
|
|
.suspend = bes2600_sdio_suspend,
|
|
.suspend_noirq = bes2600_sdio_suspend_noirq,
|
|
.resume_noirq = bes2600_sdio_resume_noirq,
|
|
.resume = bes2600_sdio_resume,
|
|
.complete = bes2600_sdio_complete,
|
|
};
|
|
|
|
static struct sdio_driver sdio_driver = {
|
|
.name = "bes2600_wlan",
|
|
.id_table = bes2600_sdio_ids,
|
|
.probe = bes2600_sdio_probe,
|
|
.remove = bes2600_sdio_remove,
|
|
.drv = {
|
|
.pm = &bes2600_pm_ops,
|
|
}
|
|
};
|
|
|
|
/* Init Module function -> Called by insmod */
|
|
static int __init bes2600_sdio_init(void)
|
|
{
|
|
const struct bes2600_platform_data_sdio *pdata = NULL;
|
|
|
|
bes_devel("------Driver: bes2600.ko version :%s\n", BES2600_DRV_VERSION);
|
|
|
|
bes2600_chrdev_update_signal_mode();
|
|
int ret = bes2600_chrdev_init(&bes2600_sdio_sbus_ops);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = sdio_register_driver(&sdio_driver);
|
|
if (ret) {
|
|
bes2600_chrdev_free();
|
|
return ret;
|
|
}
|
|
|
|
pdata = bes2600_get_platform_data();
|
|
if (!pdata->inited) {
|
|
bes_err("pdata->inited = %d, platform data must be inited at this point\n", pdata->inited);
|
|
sdio_unregister_driver(&sdio_driver);
|
|
bes2600_chrdev_free();
|
|
// probably does nothing, but still
|
|
if (pdata)
|
|
bes2600_sdio_off(pdata);
|
|
return -ECANCELED;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Called at Driver Unloading */
|
|
static void __exit bes2600_sdio_exit(void)
|
|
{
|
|
const struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data();
|
|
struct sbus_priv *priv = bes2600_chrdev_get_sbus_priv_data();
|
|
bes_devel("%s called\n", __func__);
|
|
|
|
bes2600_unregister_net_dev(priv);
|
|
sdio_unregister_driver(&sdio_driver);
|
|
bes2600_chrdev_free();
|
|
bes2600_sdio_off(pdata);
|
|
}
|
|
|
|
module_init(bes2600_sdio_init);
|
|
module_exit(bes2600_sdio_exit);
|