/* 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 #include #include #include #include #include #include /* 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");