/* * BCMSDH Function Driver for the native SDIO/MMC driver in the Linux Kernel * * Copyright (C) 1999-2013, Broadcom Corporation * * Unless you and Broadcom execute a separate written software license * agreement governing use of this software, this software is licensed to you * under the terms of the GNU General Public License version 2 (the "GPL"), * available at http://www.broadcom.com/licenses/GPLv2.php, with the * following added to such license: * * As a special exception, the copyright holders of this software give you * permission to link this software with independent modules, and to copy and * distribute the resulting executable under terms of your choice, provided that * you also meet, for each linked independent module, the terms and conditions of * the license of that module. An independent module is a module which is not * derived from this software. The special exception does not apply to any * modifications of the software. * * Notwithstanding the above, under no circumstances may you combine this * software in any way with any other Broadcom software provided under a license * other than the GPL, without Broadcom's express prior written consent. * * $Id: bcmsdh_sdmmc_linux.c 425711 2013-09-25 06:40:41Z $ */ #include #include #include /* SDIO Device and Protocol Specs */ #include /* bcmsdh to/from specific controller APIs */ #include /* to get msglevel bit values */ #include /* request_irq() */ #include #include #include #include #include #include #if !defined(SDIO_VENDOR_ID_BROADCOM) #define SDIO_VENDOR_ID_BROADCOM 0x02d0 #endif /* !defined(SDIO_VENDOR_ID_BROADCOM) */ #define SDIO_DEVICE_ID_BROADCOM_DEFAULT 0x0000 #if !defined(SDIO_DEVICE_ID_BROADCOM_4325_SDGWB) #define SDIO_DEVICE_ID_BROADCOM_4325_SDGWB 0x0492 /* BCM94325SDGWB */ #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4325_SDGWB) */ #if !defined(SDIO_DEVICE_ID_BROADCOM_4325) #define SDIO_DEVICE_ID_BROADCOM_4325 0x0493 #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4325) */ #if !defined(SDIO_DEVICE_ID_BROADCOM_4329) #define SDIO_DEVICE_ID_BROADCOM_4329 0x4329 #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4329) */ #if !defined(SDIO_DEVICE_ID_BROADCOM_4319) #define SDIO_DEVICE_ID_BROADCOM_4319 0x4319 #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4319) */ #if !defined(SDIO_DEVICE_ID_BROADCOM_4330) #define SDIO_DEVICE_ID_BROADCOM_4330 0x4330 #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4330) */ #if !defined(SDIO_DEVICE_ID_BROADCOM_4334) #define SDIO_DEVICE_ID_BROADCOM_4334 0x4334 #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4334) */ #if !defined(SDIO_DEVICE_ID_BROADCOM_43340) #define SDIO_DEVICE_ID_BROADCOM_43340 0xa94d #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_43340) */ #if !defined(SDIO_DEVICE_ID_BROADCOM_4335) #define SDIO_DEVICE_ID_BROADCOM_4335 0x4335 #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4335) */ #if !defined(SDIO_DEVICE_ID_BROADCOM_4324) #define SDIO_DEVICE_ID_BROADCOM_4324 0x4324 #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4324) */ #if !defined(SDIO_DEVICE_ID_BROADCOM_43239) #define SDIO_DEVICE_ID_BROADCOM_43239 43239 #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_43239) */ #if !defined(SDIO_DEVICE_ID_BROADCOM_4354) #define SDIO_DEVICE_ID_BROADCOM_4354 0x4354 #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4354) */ #include #include #ifdef WL_CFG80211 extern void wl_cfg80211_set_parent_dev(void *dev); #endif extern void sdioh_sdmmc_devintr_off(sdioh_info_t *sd); extern void sdioh_sdmmc_devintr_on(sdioh_info_t *sd); extern int dhd_os_check_wakelock(void *dhdp); extern int dhd_os_check_if_up(void *dhdp); extern void dhd_os_shutdown(void *dhdp); extern void *bcmsdh_get_drvdata(void); int sdio_function_init(void); void sdio_function_cleanup(void); #define DESCRIPTION "bcmsdh_sdmmc Driver" #define AUTHOR "Broadcom Corporation" /* module param defaults */ static int clockoverride = 0; module_param(clockoverride, int, 0644); MODULE_PARM_DESC(clockoverride, "SDIO card clock override"); PBCMSDH_SDMMC_INSTANCE gInstance; /* Maximum number of bcmsdh_sdmmc devices supported by driver */ #define BCMSDH_SDMMC_MAX_DEVICES 1 extern void bcmsdh_set_irq(int flag); extern int bcmsdh_probe(struct device *dev); extern int bcmsdh_remove(struct device *dev); extern volatile bool dhd_mmc_suspend; extern int sdhci_pci_request_regulators(void); int bcmsdh_sdmmc_set_power(int on) { static struct sdio_func *sdio_func; struct sdhci_host *host; sdio_func = gInstance->func[2]; host = (struct sdhci_host *)sdio_func->card->host; if (on) mmc_power_restore_host(sdio_func->card->host); else mmc_power_save_host(sdio_func->card->host); return 0; } void bcmsdh_sdmmc_get_regulator() { sdhci_pci_request_regulators(); } static int bcmsdh_sdmmc_probe(struct sdio_func *func, const struct sdio_device_id *id) { int ret = 0; static struct sdio_func sdio_func_0; if (!gInstance) return -EINVAL; if (func) { sd_trace(("bcmsdh_sdmmc: %s Enter\n", __FUNCTION__)); sd_trace(("sdio_bcmsdh: func->class=%x\n", func->class)); sd_trace(("sdio_vendor: 0x%04x\n", func->vendor)); sd_trace(("sdio_device: 0x%04x\n", func->device)); sd_trace(("Function#: 0x%04x\n", func->num)); if (func->num == 1) { func->card->host->bus_resume_flags = 0; sdio_func_0.num = 0; sdio_func_0.card = func->card; gInstance->func[0] = &sdio_func_0; if(func->device == 0x4) { /* 4318 */ gInstance->func[2] = NULL; sd_trace(("NIC found, calling bcmsdh_probe...\n")); ret = bcmsdh_probe(&func->dev); } } gInstance->func[func->num] = func; if (func->num == 2) { #ifdef WL_CFG80211 wl_cfg80211_set_parent_dev(&func->dev); #endif sd_trace(("F2 found, calling bcmsdh_probe...\n")); ret = bcmsdh_probe(&func->dev); if (ret < 0) gInstance->func[2] = NULL; } } else { ret = -ENODEV; } return ret; } static void bcmsdh_sdmmc_remove(struct sdio_func *func) { if (func) { sd_trace(("bcmsdh_sdmmc: %s Enter\n", __FUNCTION__)); sd_info(("sdio_bcmsdh: func->class=%x\n", func->class)); sd_info(("sdio_vendor: 0x%04x\n", func->vendor)); sd_info(("sdio_device: 0x%04x\n", func->device)); sd_info(("Function#: 0x%04x\n", func->num)); if (func->num == 2) { sd_trace(("F2 found, calling bcmsdh_remove...\n")); bcmsdh_remove(&func->dev); gInstance->func[2] = NULL; } if (func->num == 1) { func->card->host->bus_resume_flags |= MMC_BUSRESUME_MANUAL_RESUME; sdio_claim_host(func); sdio_disable_func(func); sdio_release_host(func); gInstance->func[1] = NULL; } } } static void bcmsdh_sdmmc_shutdown(struct device *dev) { struct sdio_func *func = dev_to_sdio_func(dev); sd_trace(("%s Enter\n", __FUNCTION__)); if (func && func->num == 2) bcmsdh_set_irq(0); } /* devices we support, null terminated */ static const struct sdio_device_id bcmsdh_sdmmc_ids[] = { { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4334) }, { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_43340) }, { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4324) }, { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4335) }, { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4354) }, { /* end: all zeroes */ }, }; MODULE_DEVICE_TABLE(sdio, bcmsdh_sdmmc_ids); #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM) static int bcmsdh_sdmmc_suspend(struct device *pdev) { struct sdio_func *func = dev_to_sdio_func(pdev); mmc_pm_flag_t sdio_flags; int ret; if (func->num != 2) return 0; sd_trace(("%s Enter\n", __FUNCTION__)); if (dhd_os_check_wakelock(bcmsdh_get_drvdata())) return -EBUSY; sdio_flags = sdio_get_host_pm_caps(func); if (!(sdio_flags & MMC_PM_KEEP_POWER)) { sd_err(("%s: can't keep power while host is suspended\n", __FUNCTION__)); return -EINVAL; } /* keep power while host suspended */ ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); if (ret) { sd_err(("%s: error while trying to keep power\n", __FUNCTION__)); return ret; } #if defined(OOB_INTR_ONLY) bcmsdh_oob_intr_set(0); #endif dhd_mmc_suspend = TRUE; smp_mb(); return 0; } static int bcmsdh_sdmmc_resume(struct device *pdev) { #if defined(OOB_INTR_ONLY) struct sdio_func *func = dev_to_sdio_func(pdev); #endif sd_trace(("%s Enter\n", __FUNCTION__)); dhd_mmc_suspend = FALSE; #if defined(OOB_INTR_ONLY) if ((func->num == 2) && dhd_os_check_if_up(bcmsdh_get_drvdata())) bcmsdh_oob_intr_set(1); #endif if (func->card && func->card->host) func->card->host->pm_flags &= ~MMC_PM_KEEP_POWER; smp_mb(); return 0; } static const struct dev_pm_ops bcmsdh_sdmmc_pm_ops = { .suspend = bcmsdh_sdmmc_suspend, .resume = bcmsdh_sdmmc_resume, }; #endif /* (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM) */ #if defined(BCMLXSDMMC) static struct semaphore *notify_semaphore = NULL; static int dummy_probe(struct sdio_func *func, const struct sdio_device_id *id) { if (func && (func->num != 2)) { return 0; } if (notify_semaphore) up(notify_semaphore); return 0; } static void dummy_remove(struct sdio_func *func) { } static struct sdio_driver dummy_sdmmc_driver = { .probe = dummy_probe, .remove = dummy_remove, .name = "dummy_sdmmc", .id_table = bcmsdh_sdmmc_ids, }; int sdio_func_reg_notify(void* semaphore) { notify_semaphore = semaphore; return sdio_register_driver(&dummy_sdmmc_driver); } void sdio_func_unreg_notify(void) { OSL_SLEEP(15); sdio_unregister_driver(&dummy_sdmmc_driver); } #endif /* defined(BCMLXSDMMC) */ static struct sdio_driver bcmsdh_sdmmc_driver = { .probe = bcmsdh_sdmmc_probe, .remove = bcmsdh_sdmmc_remove, .name = "bcmsdh_sdmmc", .id_table = bcmsdh_sdmmc_ids, .drv = { #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM) .pm = &bcmsdh_sdmmc_pm_ops, #endif /* (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM) */ .shutdown = bcmsdh_sdmmc_shutdown, }, }; struct sdos_info { sdioh_info_t *sd; spinlock_t lock; }; int sdioh_sdmmc_osinit(sdioh_info_t *sd) { struct sdos_info *sdos; if (!sd) return BCME_BADARG; sdos = (struct sdos_info*)MALLOC(sd->osh, sizeof(struct sdos_info)); sd->sdos_info = (void*)sdos; if (sdos == NULL) return BCME_NOMEM; sdos->sd = sd; spin_lock_init(&sdos->lock); return BCME_OK; } void sdioh_sdmmc_osfree(sdioh_info_t *sd) { struct sdos_info *sdos; ASSERT(sd && sd->sdos_info); sdos = (struct sdos_info *)sd->sdos_info; MFREE(sd->osh, sdos, sizeof(struct sdos_info)); } /* Interrupt enable/disable */ SDIOH_API_RC sdioh_interrupt_set(sdioh_info_t *sd, bool enable) { ulong flags; struct sdos_info *sdos; if (!sd) return BCME_BADARG; sd_trace(("%s: %s\n", __FUNCTION__, enable ? "Enabling" : "Disabling")); sdos = (struct sdos_info *)sd->sdos_info; ASSERT(sdos); #if !defined(OOB_INTR_ONLY) if (enable && !(sd->intr_handler && sd->intr_handler_arg)) { sd_err(("%s: no handler registered, will not enable\n", __FUNCTION__)); return SDIOH_API_RC_FAIL; } #endif /* !defined(OOB_INTR_ONLY) */ /* Ensure atomicity for enable/disable calls */ spin_lock_irqsave(&sdos->lock, flags); sd->client_intr_enabled = enable; if (enable) { sdioh_sdmmc_devintr_on(sd); } else { sdioh_sdmmc_devintr_off(sd); } spin_unlock_irqrestore(&sdos->lock, flags); return SDIOH_API_RC_SUCCESS; } #ifdef BCMSDH_MODULE static int __init bcmsdh_module_init(void) { int error = 0; error = sdio_function_init(); return error; } static void __exit bcmsdh_module_cleanup(void) { sdio_function_cleanup(); } module_init(bcmsdh_module_init); module_exit(bcmsdh_module_cleanup); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION(DESCRIPTION); MODULE_AUTHOR(AUTHOR); #endif /* BCMSDH_MODULE */ /* * module init */ int sdio_function_init(void) { int error = 0; sd_trace(("bcmsdh_sdmmc: %s Enter\n", __FUNCTION__)); gInstance = kzalloc(sizeof(BCMSDH_SDMMC_INSTANCE), GFP_KERNEL); if (!gInstance) return -ENOMEM; error = sdio_register_driver(&bcmsdh_sdmmc_driver); if (error) { kfree(gInstance); gInstance = NULL; } return error; } /* * module cleanup */ extern int bcmsdh_remove(struct device *dev); void sdio_function_cleanup(void) { sd_trace(("%s Enter\n", __FUNCTION__)); sdio_unregister_driver(&bcmsdh_sdmmc_driver); if (gInstance) { kfree(gInstance); gInstance = NULL; } }