blob: 99385596a65f491c40954d8208cd4a1f622b6fac [file] [log] [blame]
/* 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, &reg, 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, &reg, 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");