554 lines
13 KiB
C
554 lines
13 KiB
C
/* Copyright (c) 2009-2012, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/libra_sdioif.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/mmc/sdio.h>
|
|
#include <linux/mmc/mmc.h>
|
|
#include <linux/mmc/host.h>
|
|
#include <linux/mmc/card.h>
|
|
#include <linux/module.h>
|
|
|
|
/* Libra SDIO function device */
|
|
static struct sdio_func *libra_sdio_func;
|
|
static struct mmc_host *libra_mmc_host;
|
|
static int libra_mmc_host_index;
|
|
|
|
/* SDIO Card ID / Device ID */
|
|
static unsigned short libra_sdio_card_id;
|
|
|
|
/* completion variables */
|
|
struct completion gCard_rem_event_var;
|
|
EXPORT_SYMBOL(gCard_rem_event_var);
|
|
struct completion gShutdown_event_var;
|
|
EXPORT_SYMBOL(gShutdown_event_var);
|
|
|
|
static suspend_handler_t *libra_suspend_hldr;
|
|
static resume_handler_t *libra_resume_hldr;
|
|
static notify_card_removal_t *libra_notify_card_removal_hdlr;
|
|
static shutdown_handler_t *libra_sdio_shutdown_hdlr;
|
|
|
|
int libra_enable_sdio_irq_in_chip(struct sdio_func *func, u8 enable)
|
|
{
|
|
unsigned char reg = 0;
|
|
int err = 0;
|
|
|
|
sdio_claim_host(func);
|
|
|
|
/* Read the value into reg */
|
|
libra_sdiocmd52(func, SDIO_CCCR_IENx, ®, 0, &err);
|
|
if (err)
|
|
printk(KERN_ERR "%s: Could not read SDIO_CCCR_IENx register "
|
|
"err=%d\n", __func__, err);
|
|
|
|
if (libra_mmc_host) {
|
|
if (enable) {
|
|
reg |= 1 << func->num;
|
|
reg |= 1;
|
|
} else {
|
|
reg &= ~(1 << func->num);
|
|
}
|
|
libra_sdiocmd52(func, SDIO_CCCR_IENx, ®, 1, &err);
|
|
if (err)
|
|
printk(KERN_ERR "%s: Could not enable/disable irq "
|
|
"err=%d\n", __func__, err);
|
|
}
|
|
sdio_release_host(func);
|
|
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL(libra_enable_sdio_irq_in_chip);
|
|
|
|
/**
|
|
* libra_sdio_configure() - Function to configure the SDIO device param
|
|
* @libra_sdio_rxhandler Rx handler
|
|
* @func_drv_fn Function driver function for special setup
|
|
* @funcdrv_timeout Function Enable timeout
|
|
* @blksize Block size
|
|
*
|
|
* Configure SDIO device, enable function and set block size
|
|
*/
|
|
int libra_sdio_configure(sdio_irq_handler_t libra_sdio_rxhandler,
|
|
void (*func_drv_fn)(int *status),
|
|
unsigned int funcdrv_timeout, unsigned int blksize)
|
|
{
|
|
int err_ret = 0;
|
|
struct sdio_func *func = libra_sdio_func;
|
|
|
|
if (libra_sdio_func == NULL) {
|
|
printk(KERN_ERR "%s: Error SDIO card not detected\n", __func__);
|
|
goto cfg_error;
|
|
}
|
|
|
|
sdio_claim_host(func);
|
|
|
|
/* Currently block sizes are set here. */
|
|
func->max_blksize = blksize;
|
|
if (sdio_set_block_size(func, blksize)) {
|
|
printk(KERN_ERR "%s: Unable to set the block size.\n",
|
|
__func__);
|
|
sdio_release_host(func);
|
|
goto cfg_error;
|
|
}
|
|
|
|
/* Function driver specific configuration. */
|
|
if (func_drv_fn) {
|
|
(*func_drv_fn)(&err_ret);
|
|
if (err_ret) {
|
|
printk(KERN_ERR "%s: function driver provided configure function error=%d\n",
|
|
__func__, err_ret);
|
|
sdio_release_host(func);
|
|
goto cfg_error;
|
|
}
|
|
}
|
|
|
|
/* We set this based on the function card. */
|
|
func->enable_timeout = funcdrv_timeout;
|
|
err_ret = sdio_enable_func(func);
|
|
if (err_ret != 0) {
|
|
printk(KERN_ERR "%s: Unable to enable function %d\n",
|
|
__func__, err_ret);
|
|
sdio_release_host(func);
|
|
goto cfg_error;
|
|
}
|
|
|
|
if (sdio_claim_irq(func, libra_sdio_rxhandler)) {
|
|
sdio_disable_func(func);
|
|
printk(KERN_ERR "%s: Unable to claim irq.\n", __func__);
|
|
sdio_release_host(func);
|
|
goto cfg_error;
|
|
}
|
|
|
|
libra_enable_sdio_irq_in_chip(func, 0);
|
|
|
|
sdio_release_host(func);
|
|
|
|
return 0;
|
|
|
|
cfg_error:
|
|
return -1;
|
|
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_configure);
|
|
|
|
int libra_sdio_configure_suspend_resume(
|
|
suspend_handler_t *libra_sdio_suspend_hdlr,
|
|
resume_handler_t *libra_sdio_resume_hdlr)
|
|
{
|
|
libra_suspend_hldr = libra_sdio_suspend_hdlr;
|
|
libra_resume_hldr = libra_sdio_resume_hdlr;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_configure_suspend_resume);
|
|
|
|
/*
|
|
* libra_sdio_deconfigure() - Function to reset the SDIO device param
|
|
*/
|
|
void libra_sdio_deconfigure(struct sdio_func *func)
|
|
{
|
|
if (NULL == libra_sdio_func)
|
|
return;
|
|
|
|
sdio_claim_host(func);
|
|
sdio_release_irq(func);
|
|
sdio_disable_func(func);
|
|
sdio_release_host(func);
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_deconfigure);
|
|
|
|
int libra_enable_sdio_irq(struct sdio_func *func, u8 enable)
|
|
{
|
|
if (libra_mmc_host && libra_mmc_host->ops &&
|
|
libra_mmc_host->ops->enable_sdio_irq) {
|
|
libra_mmc_host->ops->enable_sdio_irq(libra_mmc_host, enable);
|
|
return 0;
|
|
}
|
|
|
|
printk(KERN_ERR "%s: Could not enable disable irq\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
EXPORT_SYMBOL(libra_enable_sdio_irq);
|
|
|
|
int libra_disable_sdio_irq_capability(struct sdio_func *func, u8 disable)
|
|
{
|
|
if (libra_mmc_host) {
|
|
if (disable)
|
|
libra_mmc_host->caps &= ~MMC_CAP_SDIO_IRQ;
|
|
else
|
|
libra_mmc_host->caps |= MMC_CAP_SDIO_IRQ;
|
|
return 0;
|
|
}
|
|
printk(KERN_ERR "%s: Could not change sdio capabilities to polling\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
EXPORT_SYMBOL(libra_disable_sdio_irq_capability);
|
|
|
|
/*
|
|
* libra_sdio_release_irq() - Function to release IRQ
|
|
*/
|
|
void libra_sdio_release_irq(struct sdio_func *func)
|
|
{
|
|
if (NULL == libra_sdio_func)
|
|
return;
|
|
|
|
sdio_release_irq(func);
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_release_irq);
|
|
|
|
/*
|
|
* libra_sdio_disable_func() - Function to disable sdio func
|
|
*/
|
|
void libra_sdio_disable_func(struct sdio_func *func)
|
|
{
|
|
if (NULL == libra_sdio_func)
|
|
return;
|
|
|
|
sdio_disable_func(func);
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_disable_func);
|
|
|
|
/*
|
|
* Return the SDIO Function device
|
|
*/
|
|
struct sdio_func *libra_getsdio_funcdev(void)
|
|
{
|
|
return libra_sdio_func;
|
|
}
|
|
EXPORT_SYMBOL(libra_getsdio_funcdev);
|
|
|
|
/*
|
|
* Set function driver as the private data for the function device
|
|
*/
|
|
void libra_sdio_setprivdata(struct sdio_func *sdio_func_dev,
|
|
void *padapter)
|
|
{
|
|
if (NULL == libra_sdio_func)
|
|
return;
|
|
|
|
sdio_set_drvdata(sdio_func_dev, padapter);
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_setprivdata);
|
|
|
|
/*
|
|
* Return private data of the function device.
|
|
*/
|
|
void *libra_sdio_getprivdata(struct sdio_func *sdio_func_dev)
|
|
{
|
|
return sdio_get_drvdata(sdio_func_dev);
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_getprivdata);
|
|
|
|
/*
|
|
* Function driver claims the SDIO device
|
|
*/
|
|
void libra_claim_host(struct sdio_func *sdio_func_dev,
|
|
pid_t *curr_claimed, pid_t current_pid, atomic_t *claim_count)
|
|
{
|
|
if (NULL == libra_sdio_func)
|
|
return;
|
|
|
|
if (*curr_claimed == current_pid) {
|
|
atomic_inc(claim_count);
|
|
return;
|
|
}
|
|
|
|
/* Go ahead and claim the host if not locked by anybody. */
|
|
sdio_claim_host(sdio_func_dev);
|
|
|
|
*curr_claimed = current_pid;
|
|
atomic_inc(claim_count);
|
|
|
|
}
|
|
EXPORT_SYMBOL(libra_claim_host);
|
|
|
|
/*
|
|
* Function driver releases the SDIO device
|
|
*/
|
|
void libra_release_host(struct sdio_func *sdio_func_dev,
|
|
pid_t *curr_claimed, pid_t current_pid, atomic_t *claim_count)
|
|
{
|
|
|
|
if (NULL == libra_sdio_func)
|
|
return;
|
|
|
|
if (*curr_claimed != current_pid) {
|
|
/* Dont release */
|
|
return;
|
|
}
|
|
|
|
atomic_dec(claim_count);
|
|
if (atomic_read(claim_count) == 0) {
|
|
*curr_claimed = 0;
|
|
sdio_release_host(sdio_func_dev);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(libra_release_host);
|
|
|
|
void libra_sdiocmd52(struct sdio_func *sdio_func_dev, unsigned int addr,
|
|
u8 *byte_var, int write, int *err_ret)
|
|
{
|
|
if (write)
|
|
sdio_writeb(sdio_func_dev, byte_var[0], addr, err_ret);
|
|
else
|
|
byte_var[0] = sdio_readb(sdio_func_dev, addr, err_ret);
|
|
}
|
|
EXPORT_SYMBOL(libra_sdiocmd52);
|
|
|
|
u8 libra_sdio_readsb(struct sdio_func *func, void *dst,
|
|
unsigned int addr, int count)
|
|
{
|
|
return sdio_readsb(func, dst, addr, count);
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_readsb);
|
|
|
|
int libra_sdio_memcpy_fromio(struct sdio_func *func,
|
|
void *dst, unsigned int addr, int count)
|
|
{
|
|
return sdio_memcpy_fromio(func, dst, addr, count);
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_memcpy_fromio);
|
|
|
|
int libra_sdio_writesb(struct sdio_func *func,
|
|
unsigned int addr, void *src, int count)
|
|
{
|
|
return sdio_writesb(func, addr, src, count);
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_writesb);
|
|
|
|
int libra_sdio_memcpy_toio(struct sdio_func *func,
|
|
unsigned int addr, void *src, int count)
|
|
{
|
|
return sdio_memcpy_toio(func, addr, src, count);
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_memcpy_toio);
|
|
|
|
int libra_detect_card_change(void)
|
|
{
|
|
if (libra_mmc_host) {
|
|
if (!strcmp(libra_mmc_host->class_dev.class->name, "mmc_host")
|
|
&& (libra_mmc_host_index == libra_mmc_host->index)) {
|
|
mmc_detect_change(libra_mmc_host, 0);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
printk(KERN_ERR "%s: Could not trigger card change\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
EXPORT_SYMBOL(libra_detect_card_change);
|
|
|
|
int libra_sdio_enable_polling(void)
|
|
{
|
|
if (libra_mmc_host) {
|
|
if (!strcmp(libra_mmc_host->class_dev.class->name, "mmc_host")
|
|
&& (libra_mmc_host_index == libra_mmc_host->index)) {
|
|
libra_mmc_host->caps |= MMC_CAP_NEEDS_POLL;
|
|
mmc_detect_change(libra_mmc_host, 0);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
printk(KERN_ERR "%s: Could not trigger SDIO scan\n", __func__);
|
|
return -1;
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_enable_polling);
|
|
|
|
void libra_sdio_set_clock(struct sdio_func *func, unsigned int clk_freq)
|
|
{
|
|
struct mmc_host *host = func->card->host;
|
|
host->ios.clock = clk_freq;
|
|
host->ops->set_ios(host, &host->ios);
|
|
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_set_clock);
|
|
|
|
/*
|
|
* API to get SDIO Device Card ID
|
|
*/
|
|
void libra_sdio_get_card_id(struct sdio_func *func, unsigned short *card_id)
|
|
{
|
|
if (card_id)
|
|
*card_id = libra_sdio_card_id;
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_get_card_id);
|
|
|
|
/*
|
|
* SDIO Probe
|
|
*/
|
|
static int libra_sdio_probe(struct sdio_func *func,
|
|
const struct sdio_device_id *sdio_dev_id)
|
|
{
|
|
libra_mmc_host = func->card->host;
|
|
libra_mmc_host_index = libra_mmc_host->index;
|
|
libra_sdio_func = func;
|
|
libra_sdio_card_id = sdio_dev_id->device;
|
|
|
|
printk(KERN_INFO "%s: success with block size of %d device_id=0x%x\n",
|
|
__func__,
|
|
func->cur_blksize,
|
|
sdio_dev_id->device);
|
|
|
|
/* Turn off SDIO polling from now on */
|
|
libra_mmc_host->caps &= ~MMC_CAP_NEEDS_POLL;
|
|
return 0;
|
|
}
|
|
|
|
static void libra_sdio_remove(struct sdio_func *func)
|
|
{
|
|
if (libra_notify_card_removal_hdlr)
|
|
libra_notify_card_removal_hdlr();
|
|
libra_sdio_func = NULL;
|
|
|
|
printk(KERN_INFO "%s : Module removed.\n", __func__);
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int libra_sdio_suspend(struct device *dev)
|
|
{
|
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
|
int ret = 0;
|
|
|
|
ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
|
|
|
|
if (ret) {
|
|
printk(KERN_ERR "%s: Error Host doesn't support the keep power capability\n" ,
|
|
__func__);
|
|
return ret;
|
|
}
|
|
if (libra_suspend_hldr) {
|
|
/* Disable SDIO IRQ when driver is being suspended */
|
|
libra_enable_sdio_irq(func, 0);
|
|
ret = libra_suspend_hldr(func);
|
|
if (ret) {
|
|
printk(KERN_ERR
|
|
"%s: Libra driver is not able to suspend\n" , __func__);
|
|
/* Error - Restore SDIO IRQ */
|
|
libra_enable_sdio_irq(func, 1);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
return sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ);
|
|
}
|
|
|
|
static int libra_sdio_resume(struct device *dev)
|
|
{
|
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
|
|
|
if (libra_resume_hldr) {
|
|
libra_resume_hldr(func);
|
|
/* Restore SDIO IRQ */
|
|
libra_enable_sdio_irq(func, 1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
#define libra_sdio_suspend 0
|
|
#define libra_sdio_resume 0
|
|
#endif
|
|
|
|
static void libra_sdio_shutdown(struct device *dev)
|
|
{
|
|
if (libra_sdio_shutdown_hdlr) {
|
|
libra_sdio_shutdown_hdlr();
|
|
printk(KERN_INFO "%s : Notified shutdown event to Libra driver.\n",
|
|
__func__);
|
|
}
|
|
}
|
|
|
|
int libra_sdio_register_shutdown_hdlr(
|
|
shutdown_handler_t *libra_shutdown_hdlr)
|
|
{
|
|
libra_sdio_shutdown_hdlr = libra_shutdown_hdlr;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_register_shutdown_hdlr);
|
|
|
|
int libra_sdio_notify_card_removal(
|
|
notify_card_removal_t *libra_sdio_notify_card_removal_hdlr)
|
|
{
|
|
libra_notify_card_removal_hdlr = libra_sdio_notify_card_removal_hdlr;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(libra_sdio_notify_card_removal);
|
|
|
|
static struct sdio_device_id libra_sdioid[] = {
|
|
{.class = 0, .vendor = LIBRA_MAN_ID, .device = LIBRA_REV_1_0_CARD_ID},
|
|
{.class = 0, .vendor = VOLANS_MAN_ID, .device = VOLANS_REV_2_0_CARD_ID},
|
|
{}
|
|
};
|
|
|
|
static const struct dev_pm_ops libra_sdio_pm_ops = {
|
|
.suspend = libra_sdio_suspend,
|
|
.resume = libra_sdio_resume,
|
|
};
|
|
|
|
static struct sdio_driver libra_sdiofn_driver = {
|
|
.name = "libra_sdiofn",
|
|
.id_table = libra_sdioid,
|
|
.probe = libra_sdio_probe,
|
|
.remove = libra_sdio_remove,
|
|
.drv.pm = &libra_sdio_pm_ops,
|
|
.drv.shutdown = libra_sdio_shutdown,
|
|
};
|
|
|
|
static int __init libra_sdioif_init(void)
|
|
{
|
|
libra_sdio_func = NULL;
|
|
libra_mmc_host = NULL;
|
|
libra_mmc_host_index = -1;
|
|
libra_suspend_hldr = NULL;
|
|
libra_resume_hldr = NULL;
|
|
libra_notify_card_removal_hdlr = NULL;
|
|
libra_sdio_shutdown_hdlr = NULL;
|
|
|
|
sdio_register_driver(&libra_sdiofn_driver);
|
|
|
|
printk(KERN_INFO "%s: Loaded Successfully\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit libra_sdioif_exit(void)
|
|
{
|
|
unsigned int attempts = 0;
|
|
|
|
if (!libra_detect_card_change()) {
|
|
do {
|
|
++attempts;
|
|
msleep(500);
|
|
} while (libra_sdio_func != NULL && attempts < 3);
|
|
}
|
|
|
|
if (libra_sdio_func != NULL)
|
|
printk(KERN_ERR "%s: Card removal not detected\n", __func__);
|
|
|
|
sdio_unregister_driver(&libra_sdiofn_driver);
|
|
|
|
libra_sdio_func = NULL;
|
|
libra_mmc_host = NULL;
|
|
libra_mmc_host_index = -1;
|
|
|
|
printk(KERN_INFO "%s: Unloaded Successfully\n", __func__);
|
|
}
|
|
|
|
module_init(libra_sdioif_init);
|
|
module_exit(libra_sdioif_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_VERSION("1.0");
|
|
MODULE_DESCRIPTION("WLAN SDIODriver");
|