Files
bes2600-dkms/bes2600/bes2600_sdio.c
T
test0r b9e340c78c bes2600: Patch G — restore SPDX identifiers + ST-Ericsson attribution
The bes2600 driver is a fork of the upstream cw1200 driver
(drivers/net/wireless/st/cw1200/, ST-Ericsson, Dmitry Tarnyagin
2010-2011).  The fork's file headers have three GPL-compliance issues:

  1. NO SPDX-License-Identifier on any of 48 source files (cw1200
     mainline has them on all 25).  kernel.org-mandated since 2017.

  2. Original "Copyright (c) 2010, ST-Ericsson" lines stripped from
     all files inherited from cw1200, replaced with
     "Copyright (c) 2010, Bestechnic" — factually impossible
     (Bestechnic did not author the 2010 work) and a GPL-2.0 §1
     attribution-preservation violation.

  3. The "GPL version 2 as published by the Free Software Foundation"
     boilerplate paragraph is redundant alongside SPDX and is the
     legacy form modern kernel sources have replaced.

This patch corrects all three for the 48 .c/.h files in bes2600/:

  - Adds `// SPDX-License-Identifier: GPL-2.0-only` (or `/* ... */`
    for headers) as line 1 of every file.
  - Restores `Copyright (c) 2010, ST-Ericsson` + `Author: Dmitry
    Tarnyagin <dmitry.tarnyagin@lockless.no>` as the FIRST copyright
    chain entry on all 22 files derived from cw1200 (bh.{c,h},
    debug.{c,h}, fwio.{c,h}, hwio.{c,h}, main.c, pm.{c,h},
    queue.{c,h}, scan.{c,h}, sta.{c,h}, txrx.{c,h}, wsm.{c,h}).
  - Keeps `Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.` as
    the SECOND chain entry where Bestechnic genuinely contributed.
  - Notes "Derived from cw1200_sdio.c" + ST-Ericsson copyright on
    bes2600_sdio.c (heavy derivation, not a literal rename).
  - Notes "Replaces hwbus.h from cw1200/" + ST-Ericsson copyright
    on sbus.h.
  - Preserves the prism54/islsm authorship chain on main.c and
    bes2600.h (Michael Wu 2006 + Jean-Baptiste Note 2004-2006).
  - Drops the GPL-2.0 boilerplate paragraph in favour of SPDX.

No code changes — only file-header comment blocks.  Module build is
unaffected (verified by header-only diff scope).

This is a prerequisite for any kernel.org submission attempt.  The
existing MODULE_LICENSE("GPL") + MODULE_AUTHOR(Tarnyagin@stericsson.com)
declarations were already present and are unchanged here; the
mismatch between MODULE_AUTHOR and the (since-corrected) per-file
copyrights is now resolved.
2026-05-19 09:06:07 +02:00

2402 lines
63 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* SDIO bus glue for BES2600 mac80211 driver
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Derived from drivers/net/wireless/st/cw1200/cw1200_sdio.c
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
*
*/
#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
/*
* Patch C v3: rx_queue, rx_queue_lock, rx_work removed (no relay).
* The bh thread now reads RX inline; the rx_buffer scratch area
* stays. Counters/timestamps stay for debugfs visibility.
*/
u8 *rx_buffer;
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);
/*
* Patch C v3: no more sdio_rx_work relay. Wake the bh thread
* directly via self->irq_handler (bes2600_irq_handler in bh.c
* which bumps bh_rx atomic + wakes bh_wq). The bh thread will
* then call sbus_ops->bus_rx_batch() to do the SDIO read inline.
* Matches cw1200 mainline IRQ → bh-direct architecture.
*/
if (likely(self->fw_started && self->core && self->irq_handler)) {
spin_lock_irqsave(&self->lock, flags);
self->irq_handler(self->irq_priv);
spin_unlock_irqrestore(&self->lock, flags);
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 v3: deliver the SKB directly into the WSM/mac80211
* stack from the bh thread. No rx_queue, no inter-thread
* handoff, no atomic_t needed on the counters that
* wsm_release_tx_buffer touches — single-writer-from-bh is
* preserved by construction. See bh.c for the contract block.
*/
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;
}
/*
* Patch C v3: bh thread calls this directly via sbus_ops->bus_rx_batch.
* No more sdio_rx_work workqueue. SDIO read sequence (lock →
* read_ctrl → memcpy_fromio → packets_check → extract_packets) runs
* inline in bh-thread context. Each parsed SKB is delivered via
* bes2600_bh_handle_rx_skb() from extract_packets — no rx_queue, no
* second worker, no inter-thread handoff.
*
* Architecture matches cw1200 mainline. Single-writer-from-bh
* invariant on hw_bufs_used preserved by construction.
*
* Returns 0 on success (caller's bh outer loop decides whether to
* continue), negative on bus read error. On error: triggers
* wifi_force_close (same as the old sdio_rx_work).
*/
static int bes2600_sdio_read_rx_batch(struct sbus_priv *self)
{
int ret = 0, again = 0, retry = 0, crc_retry = 0;
u32 ctrl_reg = 0;
int total_len;
u8 *buf = self->rx_buffer;
/* don't read/write sdio when sdio error */
if (bes2600_chrdev_is_bus_error())
return 0;
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;
}
/*
* extract_packets parses the multi-RX buffer and calls
* bes2600_bh_handle_rx_skb() per SKB. No queueing.
*/
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;
} while (again);
bes2600_gpio_allow_mcu_sleep(self, GPIO_WAKE_FLAG_SDIO_RX);
return 0;
failed:
bes2600_gpio_allow_mcu_sleep(self, GPIO_WAKE_FLAG_SDIO_RX);
bes2600_chrdev_wifi_force_close(self->core, false);
WARN_ON(1);
return -1;
}
static void sdio_scan_work(struct work_struct *work)
{
bes_warn("%s: this function does nothing\n", __FUNCTION__);
}
/* Patch C v3: bes2600_sdio_pipe_read deleted. bh thread reads the
* SDIO bus inline via bes2600_sdio_read_rx_batch (sbus_ops->bus_rx_batch).
* No rx_queue, no skb_dequeue, no relay. bes2600_tx_loop_read remains
* for the test bus error-fallback path but is now invoked at higher
* level. */
#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);
/*
* Patch C v3: wake the bh thread to check for any RX
* that piggybacked on this TX window. Bumps bh_rx
* atomic; bh's wait_event will pick it up and call
* sbus_ops->bus_rx_batch().
*/
if (likely(self->irq_handler))
self->irq_handler(self->irq_priv);
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
/* Patch C v3: rx_queue / rx_queue_lock removed (no relay). */
self->rx_buffer = (u8 *)__get_dma_pages(GFP_KERNEL, get_order(1632 * BES_SDIO_RX_MULTIPLE_NUM));
if (!self->rx_buffer)
return -ENOMEM;
/* Patch C v3: sdio_rx_work removed; bh thread does the read. */
#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_TX_MULTIPLE_ENABLE
struct bes_sdio_tx_list_t *tx_buffer, *temp;
#endif
#ifdef BES_SDIO_RX_MULTIPLE_ENABLE
/*
* Patch C v3: rx_work and rx_queue removed. Counters still
* reset for the next attach cycle.
*/
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
/* Patch C v3: .pipe_read removed; bus_rx_batch replaces it. */
.bus_rx_batch = bes2600_sdio_read_rx_batch,
#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;
/*
* Patch C v3: rx_work removed. Wait for IRQ-timestamp activity
* to settle by polling self->last_irq_timestamp via msleep
* (best-effort). The caller already knows the bh thread will
* process pending bh_rx during its next wait_event round.
*/
do {
old_ts = self->last_irq_timestamp;
msleep(2);
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_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_pwr_unregister_en_lp_cb(bus_priv->core, bes2600_sdio_en_lp_cb);
bes2600_core_release(bus_priv->core);
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;
/*
* Patch C v3: work_pending(&self->rx_work) check dropped (no
* relay). bh_rx atomic alone tells us whether the bh thread
* has un-processed RX events queued.
*/
if (self->core && 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);