Return-path: Received: from cdptpa-omtalb.mail.rr.com ([75.180.132.120]:55316 "EHLO cdptpa-omtalb.mail.rr.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751757Ab2LVPpv (ORCPT ); Sat, 22 Dec 2012 10:45:51 -0500 From: Solomon Peachy To: linux-wireless@vger.kernel.org Cc: Solomon Peachy Subject: [PATCH 15/17] cw1200: SDIO and SPI glue code, plus platform data Date: Sat, 22 Dec 2012 10:45:18 -0500 Message-Id: <1356191120-5280-16-git-send-email-pizza@shaftnet.org> (sfid-20121222_164609_234806_456F290D) In-Reply-To: <1356191120-5280-1-git-send-email-pizza@shaftnet.org> References: <1356191120-5280-1-git-send-email-pizza@shaftnet.org> Sender: linux-wireless-owner@vger.kernel.org List-ID: The SPI driver in particular is uglier than I'd like (with the unfortunately necessary byte swapping). I'm open to suggestons on how to clean this up. The SPI driver wasn't present in ST-E's original submission, and the SDIO driver has been rewritten quite a bit for stability and simplicity. Signed-off-by: Solomon Peachy --- drivers/staging/cw1200/cw1200_plat.h | 42 +++ drivers/staging/cw1200/cw1200_sdio.c | 542 +++++++++++++++++++++++++++++++++++ drivers/staging/cw1200/cw1200_spi.c | 487 +++++++++++++++++++++++++++++++ 3 files changed, 1071 insertions(+) create mode 100644 drivers/staging/cw1200/cw1200_plat.h create mode 100644 drivers/staging/cw1200/cw1200_sdio.c create mode 100644 drivers/staging/cw1200/cw1200_spi.c diff --git a/drivers/staging/cw1200/cw1200_plat.h b/drivers/staging/cw1200/cw1200_plat.h new file mode 100644 index 0000000..5775f38 --- /dev/null +++ b/drivers/staging/cw1200/cw1200_plat.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Dmitry Tarnyagin + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef CW1200_PLAT_H_INCLUDED +#define CW1200_PLAT_H_INCLUDED + +#include + +#include "hwio.h" /* For DPLL init values */ + +struct cw1200_platform_data_spi { + u8 spi_bits_per_word; /* REQUIRED */ + u16 ref_clk; /* REQUIRED (in KHz) */ + const struct resource *reset; /* Can be NULL w/ HW reset circuit */ + int (*power_ctrl)(const struct cw1200_platform_data_spi *pdata, + bool enable); /* Can be NULL w/ HW power circuit */ + int (*clk_ctrl)(const struct cw1200_platform_data_spi *pdata, + bool enable); /* Can be NULL */ + const u8 *macaddr; /* if NULL, use cw1200_mac_template module parameter */ + const char *sdd_file; /* if NULL, will use default for detected hw type */ +}; + +struct cw1200_platform_data_sdio { + const char *mmc_id; /* REQUIRED */ + u16 ref_clk; /* REQUIRED (in KHz) */ +#ifdef CONFIG_CW1200_USE_GPIO_IRQ + const struct resource *irq; /* REQUIRED */ +#endif + const struct resource *reset; /* Can be NULL w/ HW reset circuit */ + int (*power_ctrl)(const struct cw1200_platform_data_sdio *pdata, + bool enable); /* Can be NULL w/ HW power circuit */ + int (*clk_ctrl)(const struct cw1200_platform_data_sdio *pdata, + bool enable); /* Can be NULL */ + const u8 *macaddr; /* if NULL, use cw1200_mac_template module parameter */ + const char *sdd_file; /* if NULL, will use "default" */ +}; + +#endif /* CW1200_PLAT_H_INCLUDED */ diff --git a/drivers/staging/cw1200/cw1200_sdio.c b/drivers/staging/cw1200/cw1200_sdio.c new file mode 100644 index 0000000..d6c8268 --- /dev/null +++ b/drivers/staging/cw1200/cw1200_sdio.c @@ -0,0 +1,542 @@ +/* + * Mac80211 SDIO driver for ST-Ericsson CW1200 device + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cw1200.h" +#include "sbus.h" +#include "cw1200_plat.h" +#include "hwio.h" + +MODULE_AUTHOR("Dmitry Tarnyagin "); +MODULE_DESCRIPTION("mac80211 ST-Ericsson CW1200 SDIO driver"); +MODULE_LICENSE("GPL"); + +#define SDIO_BLOCK_SIZE (512) + +#define USE_INTERNAL_RESOURCE_LIST +// XXX The intent is that this info is part of the board platform data in arch/mach-xxxx/mach-yyyy.c + +/* Declaration only. Should be implemented in arch/xxx/mach-yyy */ +const struct cw1200_platform_data_sdio *cw1200_get_platform_data(void); + +#ifdef USE_INTERNAL_RESOURCE_LIST +#if 0 +static struct resource cw1200_href_resources[] = { + { + .start = 215, // fix me as appropriate + .end = 215, // ditto. + .flags = IORESOURCE_IO, + .name = "cw1200_wlan_reset", + }, +#ifdef CONFIG_CW1200_USE_GPIO_IRQ + { + .start = NOMADIK_GPIO_TO_IRQ(216), // fix me as appropriate + .end = NOMADIK_GPIO_TO_IRQ(216), // ditto + .flags = IORESOURCE_IRQ, + .name = "cw1200_wlan_irq", + }, +#endif /* CONFIG_CW1200_USE_GPIO_IRQ */ +}; +#endif + +static int cw1200_power_ctrl(const struct cw1200_platform_data_sdio *pdata, + bool enable) +{ + /* Turn PWR_EN off and on as appropriate. */ + /* Note this is not needed when there's a hardware reset circuit */ + + return 0; +} + +static int cw1200_clk_ctrl(const struct cw1200_platform_data_sdio *pdata, + bool enable) +{ + /* Turn CLK_32K off and on as appropriate. */ + /* Note this is not needed if it's always on */ + return 0; +} + +struct cw1200_platform_data_sdio cw1200_platform_data = { + .mmc_id = "mmc1", + .ref_clk = 38400, +#if 0 + .reset = &cw1200_href_resources[0], +#ifdef CONFIG_CW1200_USE_GPIO_IRQ + .irq = &cw1200_href_resources[1], +#endif +#endif + .power_ctrl = cw1200_power_ctrl, + .clk_ctrl = cw1200_clk_ctrl, + .sdd_file = "sdd_sagrad_1091_1098.bin", +}; + +const struct cw1200_platform_data_sdio *cw1200_get_platform_data(void) +{ + return &cw1200_platform_data; +} +EXPORT_SYMBOL_GPL(cw1200_get_platform_data); +#endif + +struct sbus_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 */ }, +}; + +/* sbus_ops implemetation */ + +static int cw1200_sdio_memcpy_fromio(struct sbus_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 sbus_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 sbus_priv *self) +{ + sdio_claim_host(self->func); +} + +static void cw1200_sdio_unlock(struct sbus_priv *self) +{ + sdio_release_host(self->func); +} + +#ifndef CONFIG_CW1200_POLL_IRQ + +#ifndef CONFIG_CW1200_USE_GPIO_IRQ +static void cw1200_sdio_irq_handler(struct sdio_func *func) +{ + struct sbus_priv *self = sdio_get_drvdata(func); + + BUG_ON(!self); + + /* note: sdio_host already claimed here. */ + cw1200_irq_handler(self->core); +} +static void cw1200_sdio_irq_enable(struct sbus_priv *self, int enable) +{ +#if 0 + self->func->card->host->ops->enable_sdio_irq(self->func->card->host, enable); +#else + __cw1200_irq_enable(self->core, enable); +#endif +} + +#else /* CONFIG_CW1200_USE_GPIO_IRQ */ +static irqreturn_t cw1200_gpio_irq_handler(int irq, void *dev_id) +{ + struct sbus_priv *self = dev_id; + + BUG_ON(!self); + cw1200_irq_handler(self->core); + return IRQ_HANDLED; +} + +static int cw1200_request_irq(struct sbus_priv *self, + irq_handler_t handler) +{ + int ret; + int func_num; + const struct resource *irq = self->pdata->irq; + u8 cccr; + + ret = request_any_context_irq(irq->start, handler, + IRQF_TRIGGER_FALLING, irq->name, self); + if (WARN_ON(ret < 0)) + goto exit; + + /* Hack to access Fuction-0 */ + func_num = self->func->num; + self->func->num = 0; + + cccr = sdio_readb(self->func, SDIO_CCCR_IENx, &ret); + if (WARN_ON(ret)) + goto set_func; + + /* Master interrupt enable ... */ + cccr |= BIT(0); + + /* ... for our function */ + cccr |= BIT(func_num); + + sdio_writeb(self->func, cccr, SDIO_CCCR_IENx, &ret); + if (WARN_ON(ret)) + goto set_func; + +#ifdef CONFIG_CW1200_PM + ret = enable_irq_wake(self->pdata->irq->start); + if (WARN_ON(ret)) + goto set_func; +#endif + + /* Restore the WLAN function number */ + self->func->num = func_num; + return 0; + +set_func: + self->func->num = func_num; + free_irq(irq->start, self); +exit: + return ret; +} +#endif /* CONFIG_CW1200_USE_GPIO_IRQ */ + +static int cw1200_sdio_irq_subscribe(struct sbus_priv *self) +{ + int ret = 0; + + printk(KERN_DEBUG "SW IRQ subscribe\n"); + sdio_claim_host(self->func); +#ifndef CONFIG_CW1200_USE_GPIO_IRQ + ret = sdio_claim_irq(self->func, cw1200_sdio_irq_handler); +#else + ret = cw1200_request_irq(self, cw1200_gpio_irq_handler); +#endif + sdio_release_host(self->func); + return ret; +} + +static int cw1200_sdio_irq_unsubscribe(struct sbus_priv *self) +{ + int ret = 0; +#ifdef CONFIG_CW1200_USE_GPIO_IRQ + const struct resource *irq = self->pdata->irq; +#endif + + printk(KERN_DEBUG "SW IRQ unsubscribe\n"); +#ifndef CONFIG_CW1200_USE_GPIO_IRQ + sdio_claim_host(self->func); + ret = sdio_release_irq(self->func); + sdio_release_host(self->func); +#else +#ifdef CONFIG_CW1200_PM + disable_irq_wake(self->pdata->irq->start); +#endif + free_irq(irq->start, self); +#endif + + return ret; +} +#endif + +static int cw1200_detect_card(const struct cw1200_platform_data_sdio *pdata) +{ + /* HACK!!! + * Rely on mmc->class_dev.class set in mmc_alloc_host + * Tricky part: a new mmc hook is being (temporary) created + * to discover mmc_host class. + * Do you know more elegant way how to enumerate mmc_hosts? + */ + + struct mmc_host *mmc = NULL; + struct class_dev_iter iter; + struct device *dev; + + mmc = mmc_alloc_host(0, NULL); + if (!mmc) + return -ENOMEM; + + BUG_ON(!mmc->class_dev.class); + class_dev_iter_init(&iter, mmc->class_dev.class, NULL, NULL); + for (;;) { + dev = class_dev_iter_next(&iter); + if (!dev) { + printk(KERN_ERR "cw1200: %s is not found.\n", + pdata->mmc_id); + break; + } else { + struct mmc_host *host = container_of(dev, + struct mmc_host, class_dev); + + if (dev_name(&host->class_dev) && + strcmp(dev_name(&host->class_dev), + pdata->mmc_id)) + continue; + + mmc_detect_change(host, 10); + break; + } + } + mmc_free_host(mmc); + return 0; +} + +static int cw1200_sdio_off(const struct cw1200_platform_data_sdio *pdata) +{ + const struct resource *reset = pdata->reset; + + if (reset) { + gpio_set_value(reset->start, 0); + gpio_free(reset->start); + cw1200_detect_card(pdata); + } + + 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) +{ + const struct resource *reset = pdata->reset; + + if (pdata->clk_ctrl) { + if (pdata->clk_ctrl(pdata, true)) { + printk(KERN_ERR "clk_ctrl() failed!\n"); + return -1; + } + msleep(20); // XXX may be lowered? + } + + if (pdata->power_ctrl) { + if (pdata->power_ctrl(pdata, true)) { + printk(KERN_ERR "power_ctrl() failed!\n"); + return -1; + } + msleep(250); // XXX can be lowered probably + } + + if (!reset) + return 0; + gpio_request(reset->start, reset->name); + gpio_direction_output(reset->start, 0); + /* A valid reset shall be obtained by maintaining WRESETN + * active (low) for at least two cycles of LP_CLK (32768Hz) + * after VDDIO is stable within it operating range. */ + msleep(30); + gpio_set_value(reset->start, 1); + /* The host should wait 30 ms after the WRESETN release + * for the on-chip LDO to stabilize */ + msleep(60); + cw1200_detect_card(pdata); + return 0; +} + +static int cw1200_sdio_reset(struct sbus_priv *self) +{ + cw1200_sdio_off(self->pdata); + msleep(1000); + cw1200_sdio_on(self->pdata); + return 0; +} + +static size_t cw1200_sdio_align_size(struct sbus_priv *self, size_t size) +{ +#if defined(CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES) + size = sdio_align_size(self->func, size); +#else /* CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES */ + size = round_up(size, SDIO_BLOCK_SIZE); +#endif /* CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES */ + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0)) +#ifdef CONFIG_CW1200_SDIO_CMD53_WORKAROUND + if (size == SDIO_BLOCK_SIZE) + size += SDIO_BLOCK_SIZE; /* HW bug; force use of block mode */ +#endif +#endif + + return size; +} + +#ifdef CONFIG_CW1200_PM +static int cw1200_sdio_pm(struct sbus_priv *self, bool suspend) +{ + int ret = 0; + +#ifdef CONFIG_CW1200_USE_GPIO_IRQ + ret = irq_set_irq_wake(self->pdata->irq->start, suspend); +#endif + return ret; +} +#endif + +static struct sbus_ops cw1200_sdio_sbus_ops = { + .sbus_memcpy_fromio = cw1200_sdio_memcpy_fromio, + .sbus_memcpy_toio = cw1200_sdio_memcpy_toio, + .lock = cw1200_sdio_lock, + .unlock = cw1200_sdio_unlock, +#ifndef CONFIG_CW1200_POLL_IRQ + .irq_subscribe = cw1200_sdio_irq_subscribe, + .irq_unsubscribe = cw1200_sdio_irq_unsubscribe, +#ifndef CONFIG_CW1200_USE_GPIO_IRQ + .irq_enable = cw1200_sdio_irq_enable, +#endif +#endif + .reset = cw1200_sdio_reset, + .align_size = cw1200_sdio_align_size, +#ifdef CONFIG_CW1200_PM + .power_mgmt = cw1200_sdio_pm, +#endif +}; + +/* Probe Function to be called by SDIO stack when device is discovered */ +static int __devinit cw1200_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct sbus_priv *self; + int status; + + printk(KERN_INFO "cw1200_wlan_sdio Probe called\n"); + + self = kzalloc(sizeof(*self), GFP_KERNEL); + if (!self) { + printk(KERN_ERR "Can't allocate SDIO sbus_priv."); + return -ENOMEM; + } + + self->pdata = cw1200_get_platform_data(); + self->func = func; + sdio_set_drvdata(func, self); + sdio_claim_host(func); + sdio_enable_func(func); + sdio_release_host(func); + + status = cw1200_core_probe(&cw1200_sdio_sbus_ops, + self, &func->dev, &self->core, + self->pdata->ref_clk, + self->pdata->macaddr, + self->pdata->sdd_file); + if (status) { + 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 __devexit cw1200_sdio_disconnect(struct sdio_func *func) +{ + struct sbus_priv *self = sdio_get_drvdata(func); + + if (self) { +#ifndef CONFIG_CW1200_POLL_IRQ + cw1200_sdio_irq_unsubscribe(self); +#endif + 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); + } +} + +static int cw1200_suspend(struct device *dev) +{ + int ret; + struct sdio_func *func = dev_to_sdio_func(dev); + + /* Notify SDIO that CW1200 will remain powered during suspend */ + ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + if (ret) + printk(KERN_ERR + "Error setting SDIO pm flags: %i\n", ret); + + return ret; +} + +static int cw1200_resume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops cw1200_pm_ops = { + .suspend = cw1200_suspend, + .resume = cw1200_resume, +}; + +static struct sdio_driver sdio_driver = { + .name = "cw1200_wlan_sdio", + .id_table = cw1200_sdio_ids, + .probe = cw1200_sdio_probe, + .remove = __devexit_p(cw1200_sdio_disconnect), + .drv = { + .pm = &cw1200_pm_ops, + } +}; + +/* Init Module function -> Called by insmod */ +static int __init cw1200_sdio_init(void) +{ + const struct cw1200_platform_data_sdio *pdata; + int ret; + + pdata = cw1200_get_platform_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; + pdata = cw1200_get_platform_data(); + sdio_unregister_driver(&sdio_driver); + cw1200_sdio_off(pdata); +} + + +module_init(cw1200_sdio_init); +module_exit(cw1200_sdio_exit); diff --git a/drivers/staging/cw1200/cw1200_spi.c b/drivers/staging/cw1200/cw1200_spi.c new file mode 100644 index 0000000..ff90b9a --- /dev/null +++ b/drivers/staging/cw1200/cw1200_spi.c @@ -0,0 +1,487 @@ +/* + * Mac80211 SPI driver for ST-Ericsson CW1200 device + * + * Copyright (c) 2011, Sagrad Inc. + * Author: Solomon Peachy + * + * Based on cw1200_gpio.c + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 +#include +#include +#include +#include + +#include +#include + +#include "cw1200.h" +#include "sbus.h" +#include "cw1200_plat.h" +#include "hwio.h" + +MODULE_AUTHOR("Solomon Peachy "); +MODULE_DESCRIPTION("mac80211 ST-Ericsson CW1200 SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:cw1200_wlan_spi"); + +#if 0 +/* Note that this is an example of integrating into your board support file */ +static struct resource cw1200_href_resources[] = { + { + .start = GPIO_RF_RESET, + .end = GPIO_RF_RESET, + .flags = IORESOURCE_IO, + .name = "cw1200_wlan_reset", + }, +}; + +static int cw1200_power_ctrl(const struct cw1200_platform_data_spi *pdata, + bool enable) +{ + gpio_set_value(GPIO_RF_POWER, enable); + + /* Turn PWR_EN off and on as appropriate. */ + /* Note this is not needed when there's a hardware reset circuit */ + return 0; +} +static int cw1200_clk_ctrl(const struct cw1200_platform_data_spi *pdata, + bool enable) +{ + /* Turn CLK_32K off and on as appropriate. */ + /* Note this is not needed if it's always on */ + return 0; +} + +static struct cw1200_platform_data_spi cw1200_platform_data = { + .ref_clk = 38400, + .spi_bits_per_word = 16, + .reset = &cw1200_href_resources[0], + .power_ctrl = cw1200_power_ctrl, + .clk_ctrl = cw1200_clk_ctrl, +// .macaddr = ??? + .sdd_file = "sdd_sagrad_1091_1098.bin", +}; +static struct spi_board_info myboard_spi_devices[] __initdata = { + { + .modalias = "cw1200_wlan_spi", + .max_speed_hz = 10000000, // 52MHz Max + .bus_num = 0, + .irq = WIFI_IRQ, + .platform_data = &cw1200_platform_data, + .chip_select = 0, + }, +}; +#endif + +struct sbus_priv { + struct spi_device *func; + struct cw1200_common *core; + const struct cw1200_platform_data_spi *pdata; +}; + +/* sbus_ops implemetation */ + +#define SDIO_TO_SPI_ADDR(addr) ((addr & 0x1f)>>2) +#define SET_WRITE 0x7FFF //usage: and operation +#define SET_READ 0x8000 //usage: or operation + +/* Notes on byte ordering: + LE: B0 B1 B2 B3 + BE: B3 B2 B1 B0 + + Hardware expects 32-bit data in this order: + + B1 B0 B3 B2 +*/ + +static int cw1200_spi_memcpy_fromio(struct sbus_priv *self, + unsigned int addr, + void *dst, int count) +{ + int ret, i; + uint16_t regaddr; + struct spi_message m; + + struct spi_transfer t_addr = { + .tx_buf = ®addr, + .len = sizeof(regaddr), + }; + struct spi_transfer t_msg = { + .rx_buf = dst, + .len = count, + }; + + regaddr = (SDIO_TO_SPI_ADDR(addr))<<12; + regaddr |= SET_READ; + regaddr |= (count>>1); + regaddr = cpu_to_le16(regaddr); + + // printk(KERN_ERR "READ : %04d from 0x%02x (%04x)\n", count, addr, le16_to_cpu(regaddr)); + +#if defined(__LITTLE_ENDIAN) + /* We have to byteswap if the SPI bus is limited to 8b operation */ + if (self->func->bits_per_word == 8) +#endif + regaddr = swab16(regaddr); + + spi_message_init(&m); + spi_message_add_tail(&t_addr, &m); + spi_message_add_tail(&t_msg, &m); + ret = spi_sync(self->func, &m); + +#if 0 + printk(KERN_INFO "READ : "); + for (i = 0 ; i < t_addr.len ; i++) { + printk("%02x ", ((u8 *)t_addr.tx_buf)[i]); + } + printk(" : "); + for (i = 0 ; i < t_msg.len ; i++) { + printk("%02x ", ((u8 *)t_msg.rx_buf)[i]); + } + printk("\n"); +#endif + +#if defined(__LITTLE_ENDIAN) + /* We have to byteswap if the SPI bus is limited to 8b operation */ + if (self->func->bits_per_word == 8) +#endif + { + uint16_t *buf = (uint16_t*)dst; + for (i = 0 ; i < (count + 1) >> 1 ; i++) { + buf[i] = swab16(buf[i]); + } + } + + return ret; +} + +static int cw1200_spi_memcpy_toio(struct sbus_priv *self, + unsigned int addr, + const void *src, int count) +{ + int rval, i; + uint16_t regaddr; + struct spi_transfer t_addr = { + .tx_buf = ®addr, + .len = sizeof(regaddr), + }; + struct spi_transfer t_msg = { + .tx_buf = src, + .len = count, + }; + struct spi_message m; + + regaddr = (SDIO_TO_SPI_ADDR(addr))<<12; + regaddr &= SET_WRITE; + regaddr |= (count>>1); + regaddr = cpu_to_le16(regaddr); + + // printk(KERN_ERR "WRITE: %04d to 0x%02x (%04x)\n", count, addr, le16_to_cpu(regaddr)); + +#if defined(__LITTLE_ENDIAN) + /* We have to byteswap if the SPI bus is limited to 8b operation */ + if (self->func->bits_per_word == 8) +#endif + { + uint16_t *buf = (uint16_t*)src; + regaddr = swab16(regaddr); + for (i = 0 ; i < (count + 1) >> 1 ; i++) { + buf[i] = swab16(buf[i]); + } + } + +#if 0 + printk(KERN_INFO "WRITE: "); + for (i = 0 ; i < t_addr.len ; i++) { + printk("%02x ", ((u8 *)t_addr.tx_buf)[i]); + } + printk(" : "); + for (i = 0 ; i < t_msg.len ; i++) { + printk("%02x ", ((u8 *)t_msg.tx_buf)[i]); + } + printk("\n"); +#endif + + spi_message_init(&m); + spi_message_add_tail(&t_addr, &m); + spi_message_add_tail(&t_msg, &m); + rval = spi_sync(self->func, &m); + +#if 0 + printk(KERN_INFO "WROTE: %d\n", m.actual_length); +#endif + +#if defined(__LITTLE_ENDIAN) + /* We have to byteswap if the SPI bus is limited to 8b operation */ + if (self->func->bits_per_word == 8) +#endif + { + uint16_t *buf = (uint16_t *)src; + for (i = 0 ; i < (count + 1) >> 1 ; i++) { + buf[i] = swab16(buf[i]); + } + } + + return rval; +} + +static void cw1200_spi_lock(struct sbus_priv *self) +{ + return; +} + +static void cw1200_spi_unlock(struct sbus_priv *self) +{ + return; +} + +#ifndef CONFIG_CW1200_POLL_IRQ +static irqreturn_t cw1200_spi_irq_handler(int irq, void *dev_id) +{ + struct sbus_priv *self = dev_id; + + BUG_ON(!self); + cw1200_irq_handler(self->core); + return IRQ_HANDLED; +} + +static int cw1200_spi_irq_subscribe(struct sbus_priv *self) +{ + int ret; + + printk(KERN_DEBUG "SW IRQ subscribe\n"); + + ret = request_any_context_irq(self->func->irq, cw1200_spi_irq_handler, + IRQF_TRIGGER_RISING, "cw1200_wlan_irq", self); + if (WARN_ON(ret < 0)) + goto exit; + +#ifdef CONFIG_CW1200_PM + ret = enable_irq_wake(self->func->irq); + if (WARN_ON(ret)) + goto free_irq; +#endif + + return 0; + +#ifdef CONFIG_CW1200_PM +free_irq: + free_irq(self->func->irq, self); +#endif +exit: + return ret; +} + +static int cw1200_spi_irq_unsubscribe(struct sbus_priv *self) +{ + int ret = 0; + + printk(KERN_DEBUG "SW IRQ unsubscribe\n"); +#ifdef CONFIG_CW1200_PM + disable_irq_wake(self->func->irq); +#endif + free_irq(self->func->irq, self); + + return ret; +} +#endif + +static int cw1200_spi_off(const struct cw1200_platform_data_spi *pdata) +{ + const struct resource *reset = pdata->reset; + + if (reset) { + gpio_set_value(reset->start, 0); + gpio_free(reset->start); + } + + if (pdata->power_ctrl) + pdata->power_ctrl(pdata, false); + if (pdata->clk_ctrl) + pdata->clk_ctrl(pdata, false); + + return 0; +} + +static int cw1200_spi_on(const struct cw1200_platform_data_spi *pdata) +{ + const struct resource *reset = pdata->reset; + + if (pdata->clk_ctrl) { + if (pdata->clk_ctrl(pdata, true)) { + printk(KERN_ERR "clk_ctrl() failed!\n"); + return -1; + } + msleep(20); // XXX may be lowered? + } + + if (pdata->power_ctrl) { + if (pdata->power_ctrl(pdata, true)) { + printk(KERN_ERR "power_ctrl() failed!\n"); + return -1; + } + msleep(250); // XXX can be lowered probably + } + + if (!reset) + return 0; + gpio_request(reset->start, reset->name); + gpio_direction_output(reset->start, 0); + /* A valid reset shall be obtained by maintaining WRESETN + * active (low) for at least two cycles of LP_CLK (32768Hz) + * after VDDIO is stable within it operating range. */ + msleep(30); + gpio_set_value(reset->start, 1); + /* The host should wait 30 ms after the WRESETN release + * for the on-chip LDO to stabilize */ + msleep(60); + return 0; +} + +static int cw1200_spi_reset(struct sbus_priv *self) +{ + cw1200_spi_off(self->pdata); + msleep(1000); + cw1200_spi_on(self->pdata); + return 0; +} + +static size_t cw1200_spi_align_size(struct sbus_priv *self, size_t size) +{ + return size & 1 ? size + 1 : size; +} + +#ifdef CONFIG_CW1200_PM +static int cw1200_spi_pm(struct sbus_priv *self, bool suspend) +{ + return irq_set_irq_wake(self->func->irq, suspend); +} +#endif + +static struct sbus_ops cw1200_spi_sbus_ops = { + .sbus_memcpy_fromio = cw1200_spi_memcpy_fromio, + .sbus_memcpy_toio = cw1200_spi_memcpy_toio, + .lock = cw1200_spi_lock, + .unlock = cw1200_spi_unlock, +#ifndef CONFIG_CW1200_POLL_IRQ + .irq_subscribe = cw1200_spi_irq_subscribe, + .irq_unsubscribe = cw1200_spi_irq_unsubscribe, +#endif + .reset = cw1200_spi_reset, + .align_size = cw1200_spi_align_size, +#ifdef CONFIG_CW1200_PM + .power_mgmt = cw1200_spi_pm, +#endif +}; + +/* Probe Function to be called by SPI stack when device is discovered */ +static int __devinit cw1200_spi_probe(struct spi_device *func) +{ + const struct cw1200_platform_data_spi *plat_data = func->dev.platform_data; + struct sbus_priv *self; + int status; + + /* Sanity check speed */ + if (func->max_speed_hz > 52000000) + func->max_speed_hz = 52000000; + if (func->max_speed_hz < 1000000) + func->max_speed_hz = 1000000; + + /* Fix up transfer size */ + if (plat_data->spi_bits_per_word) + func->bits_per_word = plat_data->spi_bits_per_word; + if (!func->bits_per_word) + func->bits_per_word = 16; + + /* And finally.. */ + func->mode = SPI_MODE_0; + + printk(KERN_INFO "cw1200_wlan_spi Probe called (CS %d M %d BPW %d CLK %d)\n", + func->chip_select, func->mode, func->bits_per_word, func->max_speed_hz); + + if (cw1200_spi_on(plat_data)) { + printk(KERN_ERR "spi_on() failed!\n"); + return -1; + } + + if (spi_setup(func)) { + printk(KERN_ERR "spi_setup() failed!\n"); + return -1; + } + + self = kzalloc(sizeof(*self), GFP_KERNEL); + if (!self) { + printk(KERN_ERR "Can't allocate SPI sbus_priv."); + return -ENOMEM; + } + + self->pdata = plat_data; + self->func = func; + spi_set_drvdata(func, self); + + status = cw1200_core_probe(&cw1200_spi_sbus_ops, + self, &func->dev, &self->core, + self->pdata->ref_clk, + self->pdata->macaddr, + self->pdata->sdd_file); + + if (status) { + cw1200_spi_off(plat_data); + kfree(self); + } + + return status; +} + +/* Disconnect Function to be called by SPI stack when device is disconnected */ +static int __devexit cw1200_spi_disconnect(struct spi_device *func) +{ + struct sbus_priv *self = spi_get_drvdata(func); + + if (self) { +#ifndef CONFIG_CW1200_POLL_IRQ + cw1200_spi_irq_unsubscribe(self); +#endif + if (self->core) { + cw1200_core_release(self->core); + self->core = NULL; + } + kfree(self); + } + cw1200_spi_off(func->dev.platform_data); + + return 0; +} + +static struct spi_driver spi_driver = { + .probe = cw1200_spi_probe, + .remove = __devexit_p(cw1200_spi_disconnect), + .driver = { + .name = "cw1200_wlan_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, +}; + +/* Init Module function -> Called by insmod */ +static int __init cw1200_spi_init(void) +{ + return spi_register_driver(&spi_driver); +} + +/* Called at Driver Unloading */ +static void __exit cw1200_spi_exit(void) +{ + spi_unregister_driver(&spi_driver); +} + +module_init(cw1200_spi_init); +module_exit(cw1200_spi_exit); -- 1.7.11.7