/* * Mac80211 SDIO driver for ST-Ericsson CW1200 device * * Copyright (c) 2010, ST-Ericsson * Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no> * * 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 <linux/module.h> #include <linux/gpio.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 <net/mac80211.h> #include "cw1200.h" #include "hwbus.h" #include <linux/platform_data/net-cw1200.h> #include "hwio.h" MODULE_AUTHOR("Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>"); MODULE_DESCRIPTION("mac80211 ST-Ericsson CW1200 SDIO driver"); MODULE_LICENSE("GPL"); #define SDIO_BLOCK_SIZE (512) /* Default platform data for Sagrad modules */ static struct cw1200_platform_data_sdio sagrad_109x_evk_platform_data = { .ref_clk = 38400, .have_5ghz = false, .sdd_file = "sdd_sagrad_1091_1098.bin", }; /* Allow platform data to be overridden */ static struct cw1200_platform_data_sdio *global_plat_data = &sagrad_109x_evk_platform_data; void __init cw1200_sdio_set_platform_data(struct cw1200_platform_data_sdio *pdata) { global_plat_data = pdata; } struct hwbus_priv { struct sdio_func *func; struct cw1200_common *core; const struct cw1200_platform_data_sdio *pdata; }; #ifndef SDIO_VENDOR_ID_STE #define SDIO_VENDOR_ID_STE 0x0020 #endif #ifndef SDIO_DEVICE_ID_STE_CW1200 #define SDIO_DEVICE_ID_STE_CW1200 0x2280 #endif static const struct sdio_device_id cw1200_sdio_ids[] = { { SDIO_DEVICE(SDIO_VENDOR_ID_STE, SDIO_DEVICE_ID_STE_CW1200) }, { /* end: all zeroes */ }, }; /* hwbus_ops implemetation */ static int cw1200_sdio_memcpy_fromio(struct hwbus_priv *self, unsigned int addr, void *dst, int count) { return sdio_memcpy_fromio(self->func, dst, addr, count); } static int cw1200_sdio_memcpy_toio(struct hwbus_priv *self, unsigned int addr, const void *src, int count) { return sdio_memcpy_toio(self->func, addr, (void *)src, count); } static void cw1200_sdio_lock(struct hwbus_priv *self) { sdio_claim_host(self->func); } static void cw1200_sdio_unlock(struct hwbus_priv *self) { sdio_release_host(self->func); } static void cw1200_sdio_irq_handler(struct sdio_func *func) { struct hwbus_priv *self = sdio_get_drvdata(func); /* note: sdio_host already claimed here. */ if (self->core) cw1200_irq_handler(self->core); } static irqreturn_t cw1200_gpio_hardirq(int irq, void *dev_id) { return IRQ_WAKE_THREAD; } static irqreturn_t cw1200_gpio_irq(int irq, void *dev_id) { struct hwbus_priv *self = dev_id; if (self->core) { cw1200_sdio_lock(self); cw1200_irq_handler(self->core); cw1200_sdio_unlock(self); return IRQ_HANDLED; } else { return IRQ_NONE; } } static int cw1200_request_irq(struct hwbus_priv *self) { int ret; u8 cccr; cccr = sdio_f0_readb(self->func, SDIO_CCCR_IENx, &ret); if (WARN_ON(ret)) goto err; /* Master interrupt enable ... */ cccr |= BIT(0); /* ... for our function */ cccr |= BIT(self->func->num); sdio_f0_writeb(self->func, cccr, SDIO_CCCR_IENx, &ret); if (WARN_ON(ret)) goto err; ret = enable_irq_wake(self->pdata->irq); if (WARN_ON(ret)) goto err; /* Request the IRQ */ ret = request_threaded_irq(self->pdata->irq, cw1200_gpio_hardirq, cw1200_gpio_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "cw1200_wlan_irq", self); if (WARN_ON(ret)) goto err; return 0; err: return ret; } static int cw1200_sdio_irq_subscribe(struct hwbus_priv *self) { int ret = 0; pr_debug("SW IRQ subscribe\n"); sdio_claim_host(self->func); if (self->pdata->irq) ret = cw1200_request_irq(self); else ret = sdio_claim_irq(self->func, cw1200_sdio_irq_handler); sdio_release_host(self->func); return ret; } static int cw1200_sdio_irq_unsubscribe(struct hwbus_priv *self) { int ret = 0; pr_debug("SW IRQ unsubscribe\n"); if (self->pdata->irq) { disable_irq_wake(self->pdata->irq); free_irq(self->pdata->irq, self); } else { sdio_claim_host(self->func); ret = sdio_release_irq(self->func); sdio_release_host(self->func); } return ret; } static int cw1200_sdio_off(const struct cw1200_platform_data_sdio *pdata) { if (pdata->reset) { gpio_set_value(pdata->reset, 0); msleep(30); /* Min is 2 * CLK32K cycles */ gpio_free(pdata->reset); } if (pdata->power_ctrl) pdata->power_ctrl(pdata, false); if (pdata->clk_ctrl) pdata->clk_ctrl(pdata, false); return 0; } static int cw1200_sdio_on(const struct cw1200_platform_data_sdio *pdata) { /* Ensure I/Os are pulled low */ if (pdata->reset) { gpio_request(pdata->reset, "cw1200_wlan_reset"); gpio_direction_output(pdata->reset, 0); } if (pdata->powerup) { gpio_request(pdata->powerup, "cw1200_wlan_powerup"); gpio_direction_output(pdata->powerup, 0); } if (pdata->reset || pdata->powerup) msleep(10); /* Settle time? */ /* Enable 3v3 and 1v8 to hardware */ if (pdata->power_ctrl) { if (pdata->power_ctrl(pdata, true)) { pr_err("power_ctrl() failed!\n"); return -1; } } /* Enable CLK32K */ if (pdata->clk_ctrl) { if (pdata->clk_ctrl(pdata, true)) { pr_err("clk_ctrl() failed!\n"); return -1; } msleep(10); /* Delay until clock is stable for 2 cycles */ } /* Enable POWERUP signal */ if (pdata->powerup) { gpio_set_value(pdata->powerup, 1); msleep(250); /* or more..? */ } /* Enable RSTn signal */ if (pdata->reset) { gpio_set_value(pdata->reset, 1); msleep(50); /* Or more..? */ } return 0; } static size_t cw1200_sdio_align_size(struct hwbus_priv *self, size_t size) { if (self->pdata->no_nptb) size = round_up(size, SDIO_BLOCK_SIZE); else size = sdio_align_size(self->func, size); return size; } static int cw1200_sdio_pm(struct hwbus_priv *self, bool suspend) { int ret = 0; if (self->pdata->irq) ret = irq_set_irq_wake(self->pdata->irq, suspend); return ret; } static struct hwbus_ops cw1200_sdio_hwbus_ops = { .hwbus_memcpy_fromio = cw1200_sdio_memcpy_fromio, .hwbus_memcpy_toio = cw1200_sdio_memcpy_toio, .lock = cw1200_sdio_lock, .unlock = cw1200_sdio_unlock, .align_size = cw1200_sdio_align_size, .power_mgmt = cw1200_sdio_pm, }; /* Probe Function to be called by SDIO stack when device is discovered */ static int cw1200_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id) { struct hwbus_priv *self; int status; pr_info("cw1200_wlan_sdio: Probe called\n"); /* We are only able to handle the wlan function */ if (func->num != 0x01) return -ENODEV; self = kzalloc(sizeof(*self), GFP_KERNEL); if (!self) { pr_err("Can't allocate SDIO hwbus_priv.\n"); return -ENOMEM; } func->card->quirks |= MMC_QUIRK_LENIENT_FN0; self->pdata = global_plat_data; /* FIXME */ self->func = func; sdio_set_drvdata(func, self); sdio_claim_host(func); sdio_enable_func(func); sdio_release_host(func); status = cw1200_sdio_irq_subscribe(self); status = cw1200_core_probe(&cw1200_sdio_hwbus_ops, self, &func->dev, &self->core, self->pdata->ref_clk, self->pdata->macaddr, self->pdata->sdd_file, self->pdata->have_5ghz); if (status) { cw1200_sdio_irq_unsubscribe(self); sdio_claim_host(func); sdio_disable_func(func); sdio_release_host(func); sdio_set_drvdata(func, NULL); kfree(self); } return status; } /* Disconnect Function to be called by SDIO stack when * device is disconnected */ static void cw1200_sdio_disconnect(struct sdio_func *func) { struct hwbus_priv *self = sdio_get_drvdata(func); if (self) { cw1200_sdio_irq_unsubscribe(self); if (self->core) { cw1200_core_release(self->core); self->core = NULL; } sdio_claim_host(func); sdio_disable_func(func); sdio_release_host(func); sdio_set_drvdata(func, NULL); kfree(self); } } #ifdef CONFIG_PM static int cw1200_sdio_suspend(struct device *dev) { int ret; struct sdio_func *func = dev_to_sdio_func(dev); struct hwbus_priv *self = sdio_get_drvdata(func); if (!cw1200_can_suspend(self->core)) return -EAGAIN; /* Notify SDIO that CW1200 will remain powered during suspend */ ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); if (ret) pr_err("Error setting SDIO pm flags: %i\n", ret); return ret; } static int cw1200_sdio_resume(struct device *dev) { return 0; } static const struct dev_pm_ops cw1200_pm_ops = { .suspend = cw1200_sdio_suspend, .resume = cw1200_sdio_resume, }; #endif static struct sdio_driver sdio_driver = { .name = "cw1200_wlan_sdio", .id_table = cw1200_sdio_ids, .probe = cw1200_sdio_probe, .remove = cw1200_sdio_disconnect, #ifdef CONFIG_PM .drv = { .pm = &cw1200_pm_ops, } #endif }; /* Init Module function -> Called by insmod */ static int __init cw1200_sdio_init(void) { const struct cw1200_platform_data_sdio *pdata; int ret; /* FIXME -- this won't support multiple devices */ pdata = global_plat_data; if (cw1200_sdio_on(pdata)) { ret = -1; goto err; } ret = sdio_register_driver(&sdio_driver); if (ret) goto err; return 0; err: cw1200_sdio_off(pdata); return ret; } /* Called at Driver Unloading */ static void __exit cw1200_sdio_exit(void) { const struct cw1200_platform_data_sdio *pdata; /* FIXME -- this won't support multiple devices */ pdata = global_plat_data; sdio_unregister_driver(&sdio_driver); cw1200_sdio_off(pdata); } module_init(cw1200_sdio_init); module_exit(cw1200_sdio_exit);