467 lines
12 KiB
C
467 lines
12 KiB
C
|
/*
|
||
|
* SDIO driver for XRadio drivers
|
||
|
*
|
||
|
* Copyright (c) 2013
|
||
|
* Xradio Technology Co., Ltd. <www.xradiotech.com>
|
||
|
*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
#include <generated/uapi/linux/version.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/wait.h>
|
||
|
#include <linux/gpio.h>
|
||
|
#include <linux/regulator/consumer.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/mmc/host.h>
|
||
|
#include <linux/mmc/sdio_func.h>
|
||
|
#include <linux/mmc/card.h>
|
||
|
#include <linux/mmc/sdio.h>
|
||
|
#include <linux/spinlock.h>
|
||
|
#include <net/mac80211.h>
|
||
|
|
||
|
#include "platform.h"
|
||
|
#include "xradio.h"
|
||
|
#include "sbus.h"
|
||
|
|
||
|
/* sdio vendor id and device id*/
|
||
|
#define SDIO_VENDOR_ID_XRADIO 0x0020
|
||
|
#define SDIO_DEVICE_ID_XRADIO 0x2281
|
||
|
#define SDIO_VENDOR_ID_XR829 0x0A9E
|
||
|
#define SDIO_DEVICE_ID_XR829 0x2282
|
||
|
|
||
|
static const struct sdio_device_id xradio_sdio_ids[] = {
|
||
|
{SDIO_DEVICE(SDIO_VENDOR_ID_XRADIO, SDIO_DEVICE_ID_XRADIO)},
|
||
|
{SDIO_DEVICE(SDIO_VENDOR_ID_XR829, SDIO_DEVICE_ID_XR829)},
|
||
|
/*{ SDIO_DEVICE(SDIO_ANY_ID, SDIO_ANY_ID) },*/
|
||
|
{ /* end: all zeroes */ },
|
||
|
};
|
||
|
|
||
|
/* sbus_ops implemetation */
|
||
|
static int sdio_data_read(struct sbus_priv *self, unsigned int addr,
|
||
|
void *dst, int count)
|
||
|
{
|
||
|
return sdio_memcpy_fromio(self->func, dst, addr, count);
|
||
|
}
|
||
|
|
||
|
static int sdio_data_write(struct sbus_priv *self, unsigned int addr,
|
||
|
const void *src, int count)
|
||
|
{
|
||
|
return sdio_memcpy_toio(self->func, addr, (void *)src, count);
|
||
|
}
|
||
|
|
||
|
static void sdio_lock(struct sbus_priv *self)
|
||
|
{
|
||
|
sdio_claim_host(self->func);
|
||
|
}
|
||
|
|
||
|
static void sdio_unlock(struct sbus_priv *self)
|
||
|
{
|
||
|
sdio_release_host(self->func);
|
||
|
}
|
||
|
|
||
|
static size_t sdio_align_len(struct sbus_priv *self, size_t size)
|
||
|
{
|
||
|
return sdio_align_size(self->func, size);
|
||
|
}
|
||
|
|
||
|
static int sdio_set_blk_size(struct sbus_priv *self, size_t size)
|
||
|
{
|
||
|
sbus_printk(XRADIO_DBG_NIY, "set blocksize=%zu\n", size);
|
||
|
return sdio_set_block_size(self->func, size);
|
||
|
}
|
||
|
|
||
|
static size_t sdio_cur_blk_size(struct sbus_priv *self)
|
||
|
{
|
||
|
return (size_t)self->func->cur_blksize;
|
||
|
}
|
||
|
|
||
|
#ifndef CONFIG_XRADIO_USE_GPIO_IRQ
|
||
|
static void sdio_irq_handler(struct sdio_func *func)
|
||
|
{
|
||
|
struct sbus_priv *self = sdio_get_drvdata(func);
|
||
|
unsigned long flags;
|
||
|
sbus_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
|
||
|
SYS_BUG(!self);
|
||
|
spin_lock_irqsave(&self->lock, flags);
|
||
|
if (self->irq_handler)
|
||
|
self->irq_handler(self->irq_priv);
|
||
|
spin_unlock_irqrestore(&self->lock, flags);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static int sdio_irq_subscribe(struct sbus_priv *self,
|
||
|
sbus_irq_handler handler,
|
||
|
void *priv)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
unsigned long flags;
|
||
|
|
||
|
|
||
|
if (!handler)
|
||
|
return -EINVAL;
|
||
|
sbus_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
|
||
|
spin_lock_irqsave(&self->lock, flags);
|
||
|
self->irq_priv = priv;
|
||
|
self->irq_handler = handler;
|
||
|
spin_unlock_irqrestore(&self->lock, flags);
|
||
|
|
||
|
sdio_claim_host(self->func);
|
||
|
#ifndef CONFIG_XRADIO_USE_GPIO_IRQ
|
||
|
ret = sdio_claim_irq(self->func, sdio_irq_handler);
|
||
|
if (ret) {
|
||
|
sbus_printk(XRADIO_DBG_ERROR, "%s:sdio_claim_irq failed(%d).\n",
|
||
|
__func__, ret);
|
||
|
|
||
|
} else {
|
||
|
sbus_printk(XRADIO_DBG_NIY, "%s:sdio_claim_irq success.\n", __func__);
|
||
|
}
|
||
|
#else
|
||
|
ret = xradio_request_gpio_irq(&(self->func->dev), self);
|
||
|
if (!ret) {
|
||
|
/* Hack to access Fuction-0 */
|
||
|
u8 cccr;
|
||
|
int func_num = self->func->num;
|
||
|
sbus_printk(XRADIO_DBG_NIY, "%s:xradio_request_gpio_irq success.\n",
|
||
|
__func__);
|
||
|
|
||
|
self->func->num = 0;
|
||
|
cccr = sdio_readb(self->func, SDIO_CCCR_IENx, &ret);
|
||
|
cccr |= BIT(0); /* Master interrupt enable ... */
|
||
|
cccr |= BIT(func_num); /* ... for our function */
|
||
|
sdio_writeb(self->func, cccr, SDIO_CCCR_IENx, &ret);
|
||
|
if (ret) {
|
||
|
xradio_free_gpio_irq(&(self->func->dev), self);
|
||
|
if (MCI_CHECK_READY(self->func->card->host, 1000) != 0)
|
||
|
sbus_printk(XRADIO_DBG_ERROR,
|
||
|
"%s:MCI_CHECK_READY timeout\n", __func__);
|
||
|
}
|
||
|
/* Restore the WLAN function number */
|
||
|
self->func->num = func_num;
|
||
|
} else {
|
||
|
sbus_printk(XRADIO_DBG_ERROR, "%s:xradio_request_gpio_irq failed(%d).\n",
|
||
|
__func__, ret);
|
||
|
}
|
||
|
#endif
|
||
|
sdio_release_host(self->func);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int sdio_irq_unsubscribe(struct sbus_priv *self)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
unsigned long flags;
|
||
|
|
||
|
sbus_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
|
||
|
if (!self->irq_handler) {
|
||
|
sbus_printk(XRADIO_DBG_ERROR, "%s:irq_handler is NULL!\n", __func__);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#ifndef CONFIG_XRADIO_USE_GPIO_IRQ
|
||
|
sdio_claim_host(self->func);
|
||
|
ret = sdio_release_irq(self->func);
|
||
|
sdio_release_host(self->func);
|
||
|
#else
|
||
|
xradio_free_gpio_irq(&(self->func->dev), self);
|
||
|
#endif /*CONFIG_XRADIO_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 int sdio_pm(struct sbus_priv *self, bool suspend)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
if (suspend) {
|
||
|
/* Notify SDIO that XRADIO will remain powered during suspend */
|
||
|
ret = sdio_set_host_pm_flags(self->func, MMC_PM_KEEP_POWER);
|
||
|
if (ret)
|
||
|
sbus_printk(XRADIO_DBG_ERROR,
|
||
|
"Error setting SDIO pm flags: %i\n", ret);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int sdio_reset(struct sbus_priv *self)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct sbus_ops sdio_sbus_ops = {
|
||
|
.sbus_data_read = sdio_data_read,
|
||
|
.sbus_data_write = sdio_data_write,
|
||
|
.lock = sdio_lock,
|
||
|
.unlock = sdio_unlock,
|
||
|
.align_size = sdio_align_len,
|
||
|
.set_block_size = sdio_set_blk_size,
|
||
|
.get_block_size = sdio_cur_blk_size,
|
||
|
.irq_subscribe = sdio_irq_subscribe,
|
||
|
.irq_unsubscribe = sdio_irq_unsubscribe,
|
||
|
.power_mgmt = sdio_pm,
|
||
|
.reset = sdio_reset,
|
||
|
};
|
||
|
static struct sbus_priv sdio_self;
|
||
|
|
||
|
#if (defined(CONFIG_XRADIO_DEBUGFS))
|
||
|
u32 dbg_sdio_clk;
|
||
|
static int sdio_set_clk(struct sdio_func *func, u32 clk)
|
||
|
{
|
||
|
if (func) {
|
||
|
/* set min to 1M */
|
||
|
if (func->card->host->ops->set_ios && clk >= 1000000) {
|
||
|
sdio_claim_host(func);
|
||
|
func->card->host->ios.clock = (clk < 50000000) ? clk : 50000000;
|
||
|
func->card->host->ops->set_ios(func->card->host,
|
||
|
&func->card->host->ios);
|
||
|
sdio_release_host(func);
|
||
|
sbus_printk(XRADIO_DBG_ALWY, "%s:change mmc clk=%d\n", __func__,
|
||
|
func->card->host->ios.clock);
|
||
|
} else {
|
||
|
sbus_printk(XRADIO_DBG_ALWY, "%s:fail change mmc clk=%d\n",
|
||
|
__func__, clk);
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
sbus_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Probe Function to be called by SDIO stack when device is discovered */
|
||
|
static int sdio_probe(struct sdio_func *func,
|
||
|
const struct sdio_device_id *id)
|
||
|
{
|
||
|
sbus_printk(XRADIO_DBG_ALWY, "XRadio Device:sdio clk=%d\n",
|
||
|
func->card->host->ios.clock);
|
||
|
sbus_printk(XRADIO_DBG_NIY, "sdio func->class=%x\n", func->class);
|
||
|
sbus_printk(XRADIO_DBG_NIY, "sdio_vendor: 0x%04x\n", func->vendor);
|
||
|
sbus_printk(XRADIO_DBG_NIY, "sdio_device: 0x%04x\n", func->device);
|
||
|
sbus_printk(XRADIO_DBG_NIY, "Function#: 0x%04x\n", func->num);
|
||
|
sbus_printk(XRADIO_DBG_NIY, "max_blksize:%u\n", func->max_blksize);
|
||
|
sbus_printk(XRADIO_DBG_NIY, "cur_blksize:%u\n", func->cur_blksize);
|
||
|
|
||
|
#if (defined(CONFIG_XRADIO_DEBUGFS))
|
||
|
if (dbg_sdio_clk)
|
||
|
sdio_set_clk(func, dbg_sdio_clk);
|
||
|
#endif
|
||
|
|
||
|
#if 0 /*for odly and sdly debug.*/
|
||
|
|
||
|
#ifdef __io_address
|
||
|
#undef __io_address
|
||
|
#endif
|
||
|
|
||
|
#define __io_address(_n) (0xf0000000+(_n))
|
||
|
|
||
|
{
|
||
|
u32 sdio_param = 0;
|
||
|
u32 s_dly = 7;
|
||
|
|
||
|
u32 smc_no = 0x1;
|
||
|
|
||
|
sdio_param = readl(__io_address(0x01c20088 + 0x4 * smc_no));
|
||
|
|
||
|
sbus_printk(XRADIO_DBG_ALWY, "%s[read ]: 0x01c20088=0x%08x\n",
|
||
|
__func__, sdio_param);
|
||
|
|
||
|
sdio_param &= ~(0xf << 8);
|
||
|
sdio_param |= 3 << 8;
|
||
|
sdio_param &= ~(0xf << 20);
|
||
|
sdio_param |= s_dly << 20;
|
||
|
|
||
|
writel(sdio_param, __io_address(0x01c20088 + 0x4 * smc_no));
|
||
|
sbus_printk(XRADIO_DBG_ALWY, "%s[write]: 0x01c20088=0x%08x\n",
|
||
|
__func__, sdio_param);
|
||
|
|
||
|
sdio_param = readl(__io_address(0x01c20088 + 0x4 * smc_no));
|
||
|
|
||
|
sbus_printk(XRADIO_DBG_ALWY, "%s[read2]: 0x01c20088=0x%08x\n",
|
||
|
__func__, sdio_param);
|
||
|
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
sdio_self.func = func;
|
||
|
sdio_self.func->card->quirks |= MMC_QUIRK_BROKEN_BYTE_MODE_512;
|
||
|
sdio_set_drvdata(func, &sdio_self);
|
||
|
sdio_claim_host(func);
|
||
|
sdio_enable_func(func);
|
||
|
sdio_release_host(func);
|
||
|
|
||
|
sdio_self.load_state = SDIO_LOAD;
|
||
|
wake_up(&sdio_self.init_wq);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Disconnect Function to be called by SDIO stack when
|
||
|
* device is disconnected */
|
||
|
static void sdio_remove(struct sdio_func *func)
|
||
|
{
|
||
|
struct sbus_priv *self = sdio_get_drvdata(func);
|
||
|
sbus_printk(XRADIO_DBG_NIY, "%s\n", __func__);
|
||
|
sdio_claim_host(func);
|
||
|
sdio_disable_func(func);
|
||
|
sdio_release_host(func);
|
||
|
sdio_set_drvdata(func, NULL);
|
||
|
if (self) {
|
||
|
self->func = NULL;
|
||
|
self->load_state = SDIO_UNLOAD;
|
||
|
wake_up(&sdio_self.init_wq);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
static int sdio_suspend(struct device *dev)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
sbus_printk(XRADIO_DBG_NIY, "%s\n", __func__);
|
||
|
#ifdef CONFIG_XRADIO_ETF
|
||
|
ret = xradio_etf_suspend();
|
||
|
if (ret) {
|
||
|
sbus_printk(XRADIO_DBG_ERROR,
|
||
|
"xradio_etf_suspend failed(%d)\n", ret);
|
||
|
}
|
||
|
#endif
|
||
|
return ret;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* wifi device need to use sdio while working, so sdio should not suspend before wifi device.
|
||
|
* However, sdio may suspend before wifi device, which is controlled by system.so we need to
|
||
|
* keep sdio working.The better way is register sdio device as the father device of wifi device,
|
||
|
* so it will suspend wifi device first
|
||
|
*/
|
||
|
static int sdio_suspend(struct device *dev)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
sbus_printk(XRADIO_DBG_NIY, "%s\n", __func__);
|
||
|
#ifdef CONFIG_XRADIO_ETF
|
||
|
ret = xradio_etf_suspend();
|
||
|
if (!ret) {
|
||
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
||
|
ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
|
||
|
if (ret) {
|
||
|
xradio_etf_resume();
|
||
|
sbus_printk(XRADIO_DBG_ERROR, "set MMC_PM_KEEP_POWER error\n");
|
||
|
}
|
||
|
} else {
|
||
|
sbus_printk(XRADIO_DBG_ERROR, "xradio_etf_suspend failed\n");
|
||
|
}
|
||
|
#else
|
||
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
||
|
ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
|
||
|
if (ret) {
|
||
|
sbus_printk(XRADIO_DBG_ERROR, "set MMC_PM_KEEP_POWER error\n");
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int sdio_resume(struct device *dev)
|
||
|
{
|
||
|
sbus_printk(XRADIO_DBG_NIY, "%s\n", __func__);
|
||
|
#ifdef CONFIG_XRADIO_ETF
|
||
|
xradio_etf_resume();
|
||
|
#endif
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct dev_pm_ops sdio_pm_ops = {
|
||
|
.suspend = sdio_suspend,
|
||
|
.resume = sdio_resume,
|
||
|
};
|
||
|
|
||
|
static struct sdio_driver sdio_driver = {
|
||
|
.name = "xradio_wlan",
|
||
|
.id_table = xradio_sdio_ids,
|
||
|
.probe = sdio_probe,
|
||
|
.remove = sdio_remove,
|
||
|
.drv = {
|
||
|
.pm = &sdio_pm_ops,
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/* Init Module function -> Called by insmod */
|
||
|
struct device *sbus_sdio_init(struct sbus_ops **sdio_ops,
|
||
|
struct sbus_priv **sdio_priv)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct device *sdio_dev = NULL;
|
||
|
sbus_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
|
||
|
/*initialize sbus_priv.*/
|
||
|
if (sdio_self.load_state == SDIO_UNLOAD) {
|
||
|
spin_lock_init(&sdio_self.lock);
|
||
|
init_waitqueue_head(&sdio_self.init_wq);
|
||
|
|
||
|
/*module power up.*/
|
||
|
xradio_wlan_power(1);
|
||
|
/*detect sdio card.*/
|
||
|
xradio_sdio_detect(1);
|
||
|
|
||
|
/*setup sdio driver.*/
|
||
|
ret = sdio_register_driver(&sdio_driver);
|
||
|
if (ret) {
|
||
|
sbus_printk(XRADIO_DBG_ERROR, "sdio_register_driver failed!\n");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (wait_event_timeout(sdio_self.init_wq,
|
||
|
sdio_self.load_state == SDIO_LOAD, 2*HZ) <= 0) {
|
||
|
sdio_unregister_driver(&sdio_driver);
|
||
|
sdio_self.load_state = SDIO_UNLOAD;
|
||
|
|
||
|
xradio_wlan_power(0); /*power down.*/
|
||
|
xradio_sdio_detect(0);
|
||
|
sbus_printk(XRADIO_DBG_ERROR, "sdio probe timeout!\n");
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*register sbus.*/
|
||
|
sdio_dev = &(sdio_self.func->dev);
|
||
|
*sdio_ops = &sdio_sbus_ops;
|
||
|
*sdio_priv = &sdio_self;
|
||
|
|
||
|
return sdio_dev;
|
||
|
}
|
||
|
|
||
|
/* SDIO Driver Unloading */
|
||
|
void sbus_sdio_deinit(void)
|
||
|
{
|
||
|
int retrycnt = 0;
|
||
|
sbus_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
||
|
if (sdio_self.load_state != SDIO_UNLOAD) {
|
||
|
xradio_wlan_power(0); /*power down.*/
|
||
|
|
||
|
retry: xradio_sdio_detect(0);
|
||
|
if (wait_event_interruptible_timeout(sdio_self.init_wq,
|
||
|
sdio_self.load_state == SDIO_UNLOAD, 2*HZ) <= 0) {
|
||
|
sbus_printk(XRADIO_DBG_ERROR, "%s remove timeout!\n", __func__);
|
||
|
|
||
|
if (++retrycnt <= 2)
|
||
|
goto retry;
|
||
|
}
|
||
|
sdio_unregister_driver(&sdio_driver);
|
||
|
memset(&sdio_self, 0, sizeof(sdio_self));
|
||
|
msleep(5);
|
||
|
} else {
|
||
|
sbus_printk(XRADIO_DBG_ERROR, "%s sdio did not init!\n", __func__);
|
||
|
}
|
||
|
}
|