Files
bes2600-dkms/bes2600/bes2600_sdio.c
T
test0r d48f2ae73c bes2600: handle multi-function SDIO cards in mmc_hw_reset bus_reset
c5.2 (recover-wedged-firmware-via-mmc-hw-reset) wraps mmc_hw_reset()
and treats any non-zero return as a recovery failure. On
single-function SDIO cards mmc_hw_reset returns 0 after doing the
remove + rescan inline. On multi-function cards (BES2600 has WLAN
func 1 + BT companion func 2) the kernel's mmc_sdio_hw_reset() does
NOT do the rescan: it tears the card down and returns 1 to signal
"caller must trigger rescan".

Field observation on PineTab2 (linux-pinetab2 6.19.10-danctnix1):
when a real LMAC wedge fired bes2600_chrdev_wifi_force_close ->
bes2600_chrdev_do_bus_reset, mmc_hw_reset returned 1, c5.2's wrapper
treated that as "bus_reset failed: 1", logged the error, and gave
up. The card was already removed (mmc2: card 0001 removed) but
nothing scheduled a rescan; wifi (and the BT companion which shares
the same SDIO host) stayed silent until the user rebooted four
minutes later.

Fix:

  - Capture the mmc_host pointer before calling mmc_hw_reset (the
    card pointer is invalid after the remove).
  - On positive return (multi-function path), log informationally
    and call mmc_detect_change(host, 0) to schedule a rescan.
    Return 0 so callers see the recovery as successful.
  - Negative return is still treated as failure as before.

The mmc_detect_change side effect is asynchronous; the chrdev's
wait_event_timeout(probe_done_wq, !sbus_priv) still observes the
remove half synchronously, and the rescan + re-probe runs out of
the host detect work afterwards.

Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
2026-05-20 20:17:58 +02:00

2336 lines
60 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 "sbus.h"
#include "bes2600_plat.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];
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);
spin_lock(&self->rx_queue_lock);
skb_queue_tail(&self->rx_queue, skb);
self->rx_data_cnt++;
spin_unlock(&self->rx_queue_lock);
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;
if (likely(self->irq_handler)) {
self->irq_handler(self->irq_priv);
} else {
bes_err("%s,%d\n", __func__, __LINE__);
goto failed;
}
} 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;
}
}
sg_set_buf(&sg[scatters], tx_buffer->buf, 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;
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_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
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);