summaryrefslogtreecommitdiff
path: root/unifi_hostsw_linux_147/unifi-linux/os_linux/driver/sdio_freescale/fs_lx.c
diff options
context:
space:
mode:
Diffstat (limited to 'unifi_hostsw_linux_147/unifi-linux/os_linux/driver/sdio_freescale/fs_lx.c')
-rw-r--r--unifi_hostsw_linux_147/unifi-linux/os_linux/driver/sdio_freescale/fs_lx.c983
1 files changed, 983 insertions, 0 deletions
diff --git a/unifi_hostsw_linux_147/unifi-linux/os_linux/driver/sdio_freescale/fs_lx.c b/unifi_hostsw_linux_147/unifi-linux/os_linux/driver/sdio_freescale/fs_lx.c
new file mode 100644
index 0000000..ea1c5ff
--- /dev/null
+++ b/unifi_hostsw_linux_147/unifi-linux/os_linux/driver/sdio_freescale/fs_lx.c
@@ -0,0 +1,983 @@
+/*
+ * Freescale SDIO glue modules.
+ *
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ *
+ * Refer to LICENSE.txt included with this source code for details on
+ * the license terms.
+ *
+ * Note:
+ * The Freescale MMC/SDIO driver is a project under development so the
+ * code that interfaces their driver is likely to have changed between
+ * releases. This code is tested only with the SDIO/MMC driver released
+ * by Freescale for the imx31ads board using Linux Kernel 2.6.16.
+ *
+ * Also, the code in the probe that sets the pull-ups is platform
+ * specific and should really be part of the controller's initialisation.
+ * In the aforesaid Freescale release this code was missing but may
+ * be in place in the future releases.
+ *
+ * Important:
+ * This module does not support more than one device driver instances.
+ *
+ */
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <linux/scatterlist.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/protocol.h>
+#include <linux/mmc/host.h>
+
+#ifdef CONFIG_MX31_3STACK
+/* The 3STACK board allows control of the power supply and clock to
+ * the APM module
+ */
+#include <linux/clk.h>
+#include <asm/arch/pmic_power.h>
+#include <asm/arch/mmc.h>
+#include <asm/arch/gpio.h>
+#endif
+
+#include "fs_sdio_api.h"
+
+#define CMD_RETRIES 3
+
+#define SDIO_FBR_REG(f, r) (0x100*(f) + (r))
+#define SDIO_FBR_BLK_SIZE(f) SDIO_FBR_REG(f, 0x10)
+
+#define SDIO_CCCR_IO_EN 0x02
+#define SDIO_CCCR_IO_READY 0x03
+#define SDIO_CCCR_INT_EN 0x04
+#define SDIO_CCCR_INT_PENDING 0x05
+#define SDIO_CCCR_IO_ABORT 0x06
+#define SDIO_CCCR_BUS_IFACE_CNTL 0x07
+#define SDIO_CCCR_CIS_PTR 0x09
+#define SDIO_CCCR_FN0_BLK_SZ0 0x10
+#define SDIO_CCCR_FN0_BLK_SZ1 0x11
+
+#define SDIO_FBR_CIS_PTR 0x109
+
+#define CISTPL_MANFID 0x20
+#define CISTPL_MANFID_SIZE 0x04
+#define CISTPL_FUNCE 0x22
+#define CISTPL_FUNCE_01_SIZE 0x2a
+#define CISTPL_END 0xff
+
+static int fs_sdio_probe(struct mmc_card *card);
+static void fs_sdio_remove(struct mmc_card *card);
+static irqreturn_t fs_sdio_irq(int irq, void *devid);
+static int fs_sdio_suspend(struct mmc_card *card, pm_message_t state);
+static int fs_sdio_resume(struct mmc_card *card);
+
+
+EXPORT_SYMBOL(fs_sdio_register_driver);
+EXPORT_SYMBOL(fs_sdio_unregister_driver);
+
+EXPORT_SYMBOL(fs_sdio_readb);
+EXPORT_SYMBOL(fs_sdio_writeb);
+EXPORT_SYMBOL(fs_sdio_block_rw);
+EXPORT_SYMBOL(fs_sdio_set_block_size);
+EXPORT_SYMBOL(fs_sdio_set_max_clock_speed);
+EXPORT_SYMBOL(fs_sdio_enable);
+EXPORT_SYMBOL(fs_sdio_enable_interrupt);
+EXPORT_SYMBOL(fs_sdio_hard_reset);
+
+/* Globals to store the context to this module and the device driver */
+static struct sdio_dev *available_sdio_dev = NULL;
+static struct fs_driver *available_driver = NULL;
+
+
+
+enum sdio_cmd_direction {
+ CMD_READ, CMD_WRITE,
+};
+
+
+static int sdio_cmd52(struct mmc_card *card, int func, uint32_t addr, uint8_t *data,
+ enum sdio_cmd_direction dir)
+{
+ struct mmc_command cmd;
+ int err;
+ int rw, raw;
+
+ if (dir == CMD_READ) {
+ rw = SDIO_RW_READ;
+ raw = 0;
+ } else {
+ rw = SDIO_RW_WRITE;
+ raw = 1;
+ }
+
+ mmc_card_claim_host(card);
+ cmd.opcode = SD_IO_RW_DIRECT;
+ cmd.arg = IO_RW_DIRECT_ARG(rw, raw, func, addr, (rw == SDIO_RW_WRITE ? (*data) : 0));
+ cmd.flags = MMC_RSP_R5 | MMC_KEEP_CLK_RUN;
+ err = mmc_wait_for_cmd(card->host, &cmd, CMD_RETRIES);
+ mmc_card_release_host(card);
+
+ if (err) {
+ return -EINVAL;
+ }
+
+ if (rw == SDIO_RW_READ) {
+ *data = (cmd.resp[0] & 0xff);
+ }
+
+ return 0;
+}
+
+
+static int sdio_cmd53(struct sdio_dev *fdev, int func, uint32_t addr, uint8_t *data,
+ size_t len, enum sdio_cmd_direction dir)
+{
+ struct mmc_card *card = fdev->card;
+ struct mmc_request mmc_req;
+ struct mmc_command cmd;
+ struct mmc_data mdata;
+ struct scatterlist sg;
+
+ int err;
+ int rw;
+ int count;
+
+ /* Read or Write ? */
+ if (dir == CMD_WRITE) {
+ rw = SDIO_RW_WRITE;
+ } else {
+ rw = SDIO_RW_READ;
+ }
+
+ /* Calculate the request length */
+ if (len >= fdev->max_blocksize) {
+ count = len / fdev->max_blocksize;
+ } else {
+ count = len;
+ }
+
+ mmc_card_claim_host(card);
+ cmd.opcode = SD_IO_RW_EXTENDED; //CM53
+ cmd.arg = IO_RW_EXTENDED_ARG(rw,
+ len >= fdev->max_blocksize ? 1 : 0,
+ 0,
+ func, addr, count);
+ cmd.flags = MMC_RSP_R5 | MMC_RSP_BUSY | MMC_KEEP_CLK_RUN;
+
+ cmd.retries = 0;
+
+ mmc_req.cmd = &cmd;
+
+ mdata.timeout_ns = 80000000;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+ mdata.blksz_bits = (len >= fdev->max_blocksize ? fdev->max_blocksize*8 : len*8);
+#endif
+ mdata.blksz = (len >= fdev->max_blocksize ? fdev->max_blocksize : len);
+ mdata.blocks = (len >= fdev->max_blocksize ? count : 1);
+
+ if (rw == SDIO_RW_WRITE) {
+ mdata.flags = MMC_DATA_WRITE;
+ } else {
+ mdata.flags = MMC_DATA_READ;
+ }
+
+ if ((len >= fdev->max_blocksize) && (count > 1)) {
+ mdata.flags |= MMC_DATA_MULTI;
+ }
+
+ /* Make a scatterlist from the buffer. */
+ sg_init_one(&sg, data, len);
+ mdata.sg = &sg;
+ mdata.sg_len = 1;
+
+ mdata.mrq = &mmc_req;
+ mdata.stop = NULL;
+ cmd.data = &mdata;
+ cmd.mrq = &mmc_req;
+
+ mmc_req.data = &mdata;
+ mmc_req.stop = NULL;
+
+ err = mmc_wait_for_req(card->host, &mmc_req);
+ mmc_card_release_host(card);
+
+ if (err || mdata.error) {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+
+int
+fs_sdio_readb(struct sdio_dev *fdev, int funcnum, unsigned long addr,
+ unsigned char *pdata)
+{
+ struct mmc_card *card = fdev->card;
+
+ return sdio_cmd52(card, funcnum, addr, pdata, CMD_READ);
+}
+
+
+int
+fs_sdio_writeb(struct sdio_dev *fdev, int funcnum, unsigned long addr,
+ unsigned char data)
+{
+ struct mmc_card *card = fdev->card;
+
+ return sdio_cmd52(card, funcnum, addr, &data, CMD_WRITE);
+}
+
+
+int
+fs_sdio_block_rw(struct sdio_dev *fdev, int funcnum, unsigned long addr,
+ unsigned char *pdata, unsigned int count, int direction)
+{
+ int ret, remainder = 0;
+
+ if (count > fdev->max_blocksize) {
+ remainder = count % fdev->max_blocksize;
+ count -= remainder;
+ }
+
+ ret = sdio_cmd53(fdev, funcnum, addr, (uint8_t *)pdata, count, direction);
+ if (!ret && remainder) {
+ ret = sdio_cmd53(fdev, funcnum, addr, (uint8_t *)pdata + count, remainder, direction);
+ }
+
+ return ret;
+}
+
+
+int
+fs_sdio_enable_interrupt(struct sdio_dev *fdev, int enable)
+{
+ struct mmc_card *card = fdev->card;
+ unsigned flags;
+
+ spin_lock_irqsave(&fdev->lock, flags);
+ if (enable) {
+ if (!fdev->int_enabled) {
+ fdev->int_enabled = 1;
+ enable_irq(card->host->sdio_irq);
+ }
+ } else {
+ if (fdev->int_enabled) {
+ disable_irq_nosync(card->host->sdio_irq);
+ fdev->int_enabled = 0;
+ }
+ }
+ spin_unlock_irqrestore(&fdev->lock, flags);
+
+ return 0;
+}
+
+
+
+
+
+/**
+ * Read a 24 bit CIS pointer register.
+ */
+static int
+sdio_cis_read_ptr_reg(struct mmc_card *card, uint32_t addr, uint32_t *ptr)
+{
+ uint32_t cis_ptr = 0;
+ int b;
+
+ for (b = 0; b < 3; b++) {
+ uint8_t p;
+ int ret = sdio_cmd52(card, 0, addr + b, &p, CMD_READ);
+ if (ret < 0)
+ return ret;
+ cis_ptr |= p << (b * 8);
+ }
+ *ptr = cis_ptr;
+ return 0;
+}
+
+/**
+ * Read a CIS tuple.
+ */
+static int
+sdio_cis_get_tuple(struct mmc_card *card, uint32_t cis_ptr, uint8_t tuple,
+ void *buf, size_t len)
+{
+ uint8_t *bbuf = buf;
+ uint8_t tpl, lnk;
+
+ /* find tuple */
+ for(;;) {
+ int ret;
+
+ if (cis_ptr >= 0x17000) {
+ /* Valid CIS should have a CISTPL_END so this shouldn't happen. */
+ return -ENXIO;
+ }
+
+ ret = sdio_cmd52(card, 0, cis_ptr++, &tpl, CMD_READ);
+ if (ret < 0) {
+ return ret;
+ }
+ ret = sdio_cmd52(card, 0, cis_ptr++, &lnk, CMD_READ);
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (tpl == CISTPL_END) {
+ return -ENXIO;
+ }
+ if (tpl == tuple) {
+ break;
+ }
+ cis_ptr += lnk;
+ }
+
+ if (lnk > len) {
+ return -EINVAL;
+ }
+
+ /* copy tuple data */
+ for (; lnk > 0; lnk--) {
+ int ret;
+
+ ret = sdio_cmd52(card, 0, cis_ptr++, bbuf++, CMD_READ);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+
+
+static int sdio_card_read_info(struct sdio_dev *fdev)
+{
+ struct mmc_card *card = fdev->card;
+ uint32_t cis_ptr;
+ uint16_t manfid[2];
+ uint8_t funce_dat[CISTPL_FUNCE_01_SIZE];
+ int ret;
+
+ /* read func CIS ptr */
+ if (sdio_cis_read_ptr_reg(card, SDIO_FBR_CIS_PTR, &cis_ptr) < 0) {
+ return -1;
+ }
+ if (cis_ptr < 0x1000 || cis_ptr > 0x17000) {
+ return -1;
+ }
+
+ if (sdio_cis_get_tuple(card, cis_ptr, CISTPL_FUNCE, funce_dat, CISTPL_FUNCE_01_SIZE) < 0) {
+ return -1;
+ }
+ fdev->max_blocksize = (funce_dat[0x0c] & 0xff) | ((funce_dat[0x0d] & 0xff) << 8);
+
+ /* Set the function 1 block size */
+ ret = fs_sdio_writeb(fdev, 0, SDIO_FBR_BLK_SIZE(1), fdev->max_blocksize & 0xFF);
+ if (ret) {
+ return ret;
+ }
+ ret = fs_sdio_writeb(fdev, 0, SDIO_FBR_BLK_SIZE(1)+1, (fdev->max_blocksize >> 8) & 0xFF);
+ if (ret) {
+ return ret;
+ }
+
+ /* Set the block size read from the device to the MMC driver. */
+ card->csd.read_blkbits = fdev->max_blocksize*8;
+ card->csd.write_blkbits = fdev->max_blocksize*8;
+
+
+ /* read common CIS ptr */
+ if (sdio_cis_read_ptr_reg(card, SDIO_CCCR_CIS_PTR, &cis_ptr) < 0) {
+ return -1;
+ }
+ if (cis_ptr < 0x1000 || cis_ptr > 0x17000) {
+ return -1;
+ }
+ /* read manfid from CIS */
+ if (sdio_cis_get_tuple(card, cis_ptr, CISTPL_MANFID, &manfid, CISTPL_MANFID_SIZE) < 0) {
+ return -1;
+ }
+ fdev->vendor_id = le16_to_cpu(manfid[0]);
+
+ /* read common CIS ptr */
+ if (sdio_cis_read_ptr_reg(card, SDIO_CCCR_CIS_PTR, &cis_ptr) < 0) {
+ return -1;
+ }
+ if (cis_ptr < 0x1000 || cis_ptr > 0x17000) {
+ return -1;
+ }
+ /* read manfid from CIS */
+ if (sdio_cis_get_tuple(card, cis_ptr, CISTPL_MANFID, &manfid, CISTPL_MANFID_SIZE) < 0) {
+ return -1;
+ }
+ fdev->device_id = le16_to_cpu(manfid[1]);
+
+ return 0;
+}
+
+static void fs_sdio_cmd0(struct mmc_card *card)
+{
+ struct mmc_command cmd;
+ int err;
+
+ mmc_card_claim_host(card);
+ cmd.opcode = MMC_GO_IDLE_STATE;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_NONE | MMC_CMD_BC;
+ err = mmc_wait_for_cmd(card->host, &cmd, 0);
+ if( err )
+ {
+ printk("%s: error %d\n", __FUNCTION__, err );
+ }
+ mmc_card_release_host(card);
+}
+
+static void fs_sdio_cmd5(struct mmc_card *card, uint32_t arg)
+{
+ struct mmc_command cmd;
+ int i, err = 0;
+
+ mmc_card_claim_host(card);
+
+ cmd.opcode = SD_IO_SEND_OP_COND;
+ cmd.arg = arg;
+ cmd.flags = MMC_RSP_R4 | MMC_CMD_BCR;
+
+ for (i = 100; i; i--) {
+ err = mmc_wait_for_cmd(card->host, &cmd, CMD_RETRIES);
+ if (err != MMC_ERR_NONE)
+ {
+ printk("%s:%d: error %d\n", __FUNCTION__, __LINE__, err );
+ break;
+ }
+
+ if (cmd.resp[0] & MMC_CARD_BUSY || arg == 0)
+ {
+ break;
+ }
+
+ err = MMC_ERR_TIMEOUT;
+
+ mdelay(10);
+ }
+
+ mmc_card_release_host(card);
+}
+
+static void fs_sdio_cmd3(struct mmc_card *card)
+{
+ struct mmc_command cmd;
+ int err;
+
+ mmc_card_claim_host(card);
+ cmd.opcode = MMC_SET_RELATIVE_ADDR;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+ err = mmc_wait_for_cmd(card->host, &cmd, CMD_RETRIES);
+ if( err )
+ {
+ printk("%s:%d: error %d\n", __FUNCTION__, __LINE__, err );
+ }
+
+ mmc_card_release_host(card);
+}
+
+static void fs_sdio_cmd7(struct mmc_card *card)
+{
+ struct mmc_command cmd;
+ int err;
+
+ mmc_card_claim_host(card);
+ cmd.opcode = MMC_SELECT_CARD;
+ cmd.arg = card->rca << 16;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+ err = mmc_wait_for_cmd(card->host, &cmd, CMD_RETRIES);
+ if( err )
+ {
+ printk("%s:%d: error %d\n", __FUNCTION__, __LINE__, err );
+ }
+ mmc_card_release_host(card);
+}
+
+#define IO_EN_TIMEOUT_MS 500
+
+int
+fs_sdio_enable(struct sdio_dev *fdev)
+{
+ uint8_t io_en, io_rdy;
+ int timeout;
+ int ret;
+
+ /* The card may be active if not following a hard reset - abort it first */
+ fs_sdio_writeb(fdev, 0, SDIO_CCCR_IO_ABORT, 0x8);
+
+ /* We must go though complete card init, as we have just reset it */
+ /* cmd0, then 2 x cmd5, cmd3 and finally 7 */
+
+ /* Do a few cmd0 to settle down */
+ for(timeout=0;timeout<2;timeout++)
+ {
+ fs_sdio_cmd0(fdev->card);
+ }
+ timeout = IO_EN_TIMEOUT_MS;
+
+ fs_sdio_cmd5(fdev->card, 0);
+ fs_sdio_cmd5(fdev->card, 0x80000);
+ fs_sdio_cmd3(fdev->card);
+ fs_sdio_cmd7(fdev->card);
+
+ ret = fs_sdio_writeb(fdev, 0, SDIO_CCCR_BUS_IFACE_CNTL, 0x22);
+ if (ret) {
+ printk("%s: err %d line %d\n", __FUNCTION__, ret, __LINE__ );
+ goto err;
+ }
+
+ ret = fs_sdio_writeb(fdev, 0, SDIO_CCCR_INT_EN, 3);
+ if (ret) {
+ printk("%s: err %d line %d\n", __FUNCTION__, ret, __LINE__ );
+ goto err;
+ }
+
+ /* Read-Modify-Write the I/O Enable for function 1. */
+ ret = fs_sdio_readb(fdev, 0, SDIO_CCCR_IO_EN, &io_en);
+ if (ret) {
+ printk("%s: err %d line %d\n", __FUNCTION__, ret, __LINE__ );
+ goto err;
+ }
+
+ io_en |= (1 << 1);
+ ret = fs_sdio_writeb(fdev, 0, SDIO_CCCR_IO_EN, io_en);
+ if (ret) {
+ printk("%s: err %d line %d\n", __FUNCTION__, ret, __LINE__ );
+ goto err;
+ }
+
+ /* Wait until the function is enabled. */
+ while (timeout) {
+ ret = fs_sdio_readb(fdev, 0, SDIO_CCCR_IO_READY, &io_rdy);
+ if (ret) {
+ printk("%s: err %d line %d\n", __FUNCTION__, ret, __LINE__ );
+ goto err;
+ }
+ if (io_rdy & (1 << 1)) {
+ break;
+ }
+
+ udelay(1);
+ timeout--;
+ }
+
+ if(! timeout )
+ {
+ ret = -ETIMEDOUT;
+ printk("%s: err %d line %d\n", __FUNCTION__, ret, __LINE__ );
+ goto err;
+ }
+
+ /* Set the function 1 block size */
+ ret = fs_sdio_writeb(fdev, 0, SDIO_FBR_BLK_SIZE(1), fdev->max_blocksize & 0xFF);
+ if (ret) {
+ return ret;
+ }
+ ret = fs_sdio_writeb(fdev, 0, SDIO_FBR_BLK_SIZE(1)+1, (fdev->max_blocksize >> 8) & 0xFF);
+ if (ret) {
+ return ret;
+ }
+
+err:
+ return ret;
+}
+
+
+int
+fs_sdio_set_max_clock_speed(struct sdio_dev *fdev, int max_khz)
+{
+ struct mmc_card *card = fdev->card;
+
+ /* Respect the host controller's min-max. */
+ max_khz *= 1000;
+ if (max_khz < card->host->f_min) {
+ max_khz = card->host->f_min;
+ }
+ if (max_khz > card->host->f_max) {
+ max_khz = card->host->f_max;
+ }
+
+ card->host->ios.clock = max_khz;
+ card->host->ops->set_ios(card->host, &card->host->ios);
+
+ return max_khz/1000;
+}
+
+int fs_sdio_set_block_size(struct sdio_dev *fdev, int blksz)
+{
+ return 0;
+}
+
+#ifdef CONFIG_MX31_3STACK
+
+extern struct mxcmci_host *mxc_get_mmc_host(int id);
+
+/*
+ * ---------------------------------------------------------------------------
+ *
+ * Turn on the power of WIFI card
+ *
+ * ---------------------------------------------------------------------------
+ */
+static void fs_unifi_power_on( int check_card )
+{
+ struct mxcmci_host * mmc_host = mxc_get_mmc_host(1);
+ t_regulator_voltage voltage;
+
+ /* All comments below for SPF-23250_REV_D and SPF_23251_REV_B */
+
+ /* GPO3 -> enables SW2B 1.8V out - this becomes 1V8 on personality board,
+ * then 1V8_EXT, then BT_VUSB
+ */
+ pmic_power_regulator_on(REGU_GPO3);
+
+ /* GPO4 -> WiFi_PWEN, but this signal is not used on current boards */
+ pmic_power_regulator_on(REGU_GPO4);
+
+ /* VRF1 -> WL_1V5ANA and WL_1V5BB */
+ voltage.vrf1 = VRF1_1_5V;//VRF1_2_775V
+ pmic_power_regulator_set_voltage(REGU_VRF1, voltage);
+ pmic_power_regulator_on(REGU_VRF1);
+
+ /* VMMC2 -> WL_VDD and WL_VPA */
+ voltage.vmmc2 = VMMC2_3V;
+ pmic_power_regulator_set_voltage(REGU_VMMC2, voltage);
+ pmic_power_regulator_on(REGU_VMMC2);
+
+ /* WL_1V5DD should come on last, 10ms after other supplies */
+ mdelay(10);
+ /* VRF2 -> WL_1V5DD */
+ voltage.vrf2 = VRF2_1_5V;//VRF1_2_775V
+ pmic_power_regulator_set_voltage(REGU_VRF2, voltage);
+ pmic_power_regulator_on(REGU_VRF2);
+
+ clk_enable(mmc_host->clk);
+
+ /* MX31_PIN_DCD_DCE1 is connected to both WiFi and BT reset - this will reset BT */
+ /* So don't do it - assume the power up has reset the device */
+ /* mxc_set_gpio_dataout(MX31_PIN_DCD_DCE1, 0); */ //Low
+ /* mdelay(10); */
+ mxc_set_gpio_dataout(MX31_PIN_DCD_DCE1, 1); //high
+ mdelay(10);
+
+ if( check_card && mmc_host ) {
+ mmc_detect_change(mmc_host->mmc, msecs_to_jiffies(100));
+ }
+}
+
+/*
+ * ---------------------------------------------------------------------------
+ *
+ * Turn off the power of WIFI card
+ *
+ * ---------------------------------------------------------------------------
+ */
+static void fs_unifi_power_off( int check_card )
+{
+ struct mxcmci_host * mmc_host = mxc_get_mmc_host(1);
+
+ if( check_card && mmc_host )
+ {
+ mmc_detect_change(mmc_host->mmc, msecs_to_jiffies(50));
+ mdelay(10);
+ clk_disable(mmc_host->clk);
+ }
+
+ /* Don't switch off, will switch off bluetooth! */
+ /* pmic_power_regulator_off(REGU_GPO3); */
+
+ pmic_power_regulator_off(REGU_GPO4);
+ pmic_power_regulator_off(REGU_VRF1);
+
+ pmic_power_regulator_off(REGU_VRF2);
+
+ pmic_power_regulator_off(REGU_VMMC2);
+
+ /* Don't do this, will hold Bluetooth in reset */
+ /* mxc_set_gpio_dataout(MX31_PIN_DCD_DCE1, 0); */ //Low
+}
+
+/* This should be made conditional on being slot 2 too - so we can
+ * use a plug in card in slot 1
+ */
+int fs_sdio_hard_reset(struct sdio_dev *fdev)
+{
+ fs_unifi_power_off( 0 );
+ mdelay(100);
+ fs_unifi_power_on( 0 );
+ mdelay(100);
+ /* We did a hard reset, so return 0 */
+ return 0;
+}
+
+#else /* #ifdef CONFIG_MX31_3STACK */
+
+int fs_sdio_hard_reset(struct sdio_dev *fdev)
+{
+ printk("%s: called\n", __FUNCTION__ );
+
+ /* We did not do a hard reset, so return 1 */
+ return 1;
+}
+
+static void fs_unifi_power_on( int check_card )
+{
+ (void)check_card;
+}
+
+static void fs_unifi_power_off( int check_card )
+{
+ (void)check_card;
+}
+
+#endif /* ! CONFIG_MX31_3STACK */
+
+static struct mmc_driver mmc_driver = {
+ .drv = {
+ .name = "fs_sdio",
+ },
+ .probe = fs_sdio_probe,
+ .remove = fs_sdio_remove,
+ .suspend = fs_sdio_suspend,
+ .resume = fs_sdio_resume,
+};
+
+
+
+int fs_sdio_register_driver(struct fs_driver *driver)
+{
+ int ret;
+
+ printk(KERN_INFO "fs_sdio_register_driver\n");
+
+ /* Switch us on */
+ fs_unifi_power_on(-1);
+
+ /* Store the context to the device driver to the global */
+ available_driver = driver;
+
+ /*
+ * If available_sdio_dev is not NULL, probe has been called,
+ * so pass the probe to the registered driver
+ */
+ if (available_sdio_dev) {
+ /* Store the context to the new device driver */
+ available_sdio_dev->driver = driver;
+ /* Do a bit of initialisation */
+ sdio_card_read_info(available_sdio_dev);
+
+ printk(KERN_INFO "fs_sdio_register_driver: Glue exists, add device driver and register IRQ\n");
+ driver->probe(available_sdio_dev);
+
+ /* Register the IRQ handler to the SDIO IRQ. */
+ ret = request_irq(available_sdio_dev->card->host->sdio_irq,
+ fs_sdio_irq, 0, mmc_driver.drv.name, available_sdio_dev->card);
+ if (ret) {
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+
+void fs_sdio_unregister_driver(struct fs_driver *driver)
+{
+ printk(KERN_INFO "fs_sdio_unregister_driver\n");
+
+ /*
+ * If available_sdio_dev is not NULL, probe has been called,
+ * so pass the remove to the registered driver to clean up.
+ */
+ if (available_sdio_dev) {
+ printk(KERN_INFO "fs_sdio_unregister_driver: Glue exists, unregister IRQ and remove device driver\n");
+
+ /* Unregister the IRQ handler first. */
+ free_irq(available_sdio_dev->card->host->sdio_irq, available_sdio_dev->card);
+
+ driver->remove(available_sdio_dev);
+
+ /* Invalidate the context to the device driver */
+ available_sdio_dev->driver = NULL;
+ }
+
+ /* Power down the UniFi */
+ fs_unifi_power_off( -1 );
+
+ /* invalidate the context to the device driver to the global */
+ available_driver = NULL;
+}
+
+
+static irqreturn_t fs_sdio_irq(int irq, void *devid)
+{
+ struct sdio_dev *fdev = (struct sdio_dev*) mmc_get_drvdata((struct mmc_card*)devid);
+
+ if (fdev->driver) {
+ if (fdev->driver->card_int_handler) {
+ fdev->driver->card_int_handler(fdev);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+
+#ifdef CONFIG_PM
+static int fs_sdio_suspend(struct mmc_card *card, pm_message_t state)
+{
+ struct sdio_dev *fdev = (struct sdio_dev*)mmc_get_drvdata(card);
+
+ /* Pass event to the registered driver. */
+ if (fdev->driver) {
+ if (fdev->driver->suspend) {
+ fdev->driver->suspend(fdev, state);
+ }
+ }
+
+ return 0;
+}
+
+static int fs_sdio_resume(struct mmc_card *card)
+{
+ struct sdio_dev *fdev = (struct sdio_dev*)mmc_get_drvdata(card);
+
+ /* Pass event to the registered driver. */
+ if (fdev->driver) {
+ if (fdev->driver->resume) {
+ fdev->driver->resume(fdev);
+ }
+ }
+
+ return 0;
+}
+#else
+#define fs_sdio_suspend NULL
+#define fs_sdio_resume NULL
+#endif
+
+
+
+static int fs_sdio_probe(struct mmc_card *card)
+{
+ struct sdio_dev *fdev;
+ int ret = 0;
+
+ /*
+ * Set the pull-ups, this code should really be part of the MMC driver.
+ * The IOMUXC_BASE_ADDR and the values set to the registers are platform
+ * specific, so this code may not compile or do the right thing
+ * on a different platform.
+ */
+#ifdef CONFIG_ARCH_MX3
+ writel(0x1a569485, IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x168);
+ writel(0x0a5295a5, IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x16C);
+#endif
+
+ /* Allocate our private context */
+ fdev = kmalloc(sizeof(struct sdio_dev), GFP_KERNEL);
+ memset(fdev, 0, sizeof(struct sdio_dev));
+ fdev->int_enabled = 1;
+ fdev->lock = SPIN_LOCK_UNLOCKED;
+ fdev->card = card;
+ /* Store our context to the global pointer */
+ available_sdio_dev = fdev;
+
+ /* Register the card context to the MMC driver. */
+ card->scr.bus_widths = SD_SCR_BUS_WIDTH_4;
+ card->csd.max_dtr = 1000000;
+ /*
+ * Set a default SDIO block size,
+ * override it when we read the block size from the device.
+ */
+ card->csd.read_blkbits = 64*8;
+ card->csd.write_blkbits = 64*8;
+ card->csd.read_partial = 1;
+ card->csd.write_partial = 1;
+
+ /* Store our context in the MMC driver */
+ printk(KERN_INFO "fs_sdio_probe: Add glue driver\n");
+ mmc_set_drvdata(card, fdev);
+
+ /* TODO: If a device driver is registered, call it's probe here */
+ if (available_driver) {
+ /* Store the context to the device driver */
+ fdev->driver = available_driver;
+ /* Do a bit of initialisation */
+ sdio_card_read_info(fdev);
+
+ printk(KERN_INFO "fs_sdio_probe: Add device driver and register IRQ\n");
+ available_driver->probe(fdev);
+
+ /* Register the IRQ handler to the SDIO IRQ. */
+ ret = request_irq(card->host->sdio_irq, fs_sdio_irq, 0, mmc_driver.drv.name, card);
+ if (ret) {
+ return ret;
+ }
+
+ }
+
+ return 0;
+}
+
+static void fs_sdio_remove(struct mmc_card *card)
+{
+ struct sdio_dev *fdev = (struct sdio_dev*)mmc_get_drvdata(card);
+
+ /* If there is a registered device driver, pass on the remove */
+ if (fdev->driver) {
+ printk(KERN_INFO "fs_sdio_remove: Free IRQ and remove device driver\n");
+ /* Unregister the IRQ handler first. */
+ free_irq(card->host->sdio_irq, card);
+
+ fdev->driver->remove(fdev);
+ }
+
+ printk(KERN_INFO "fs_sdio_remove: Remove glue driver\n");
+ /* Unregister the card context from the MMC driver. */
+ mmc_set_drvdata(card, NULL);
+
+ /* Invalidate the global to our context. */
+ available_sdio_dev = NULL;
+ kfree(fdev);
+
+ return;
+}
+
+
+
+/* Module init and exit, register and unregister to the SDIO/MMC driver */
+static int __init fs_sdio_init(void)
+{
+ printk(KERN_INFO "Freescale: Register to MMC/SDIO driver\n");
+ /* Sleep a bit - otherwise if the mmc subsystem has just started, it will
+ * allow us to register, then immediatly remove us!
+ */
+ msleep(10);
+ return mmc_register_driver(&mmc_driver);
+}
+module_init(fs_sdio_init);
+
+static void __exit fs_sdio_exit(void)
+{
+ printk(KERN_INFO "Freescale: Unregister from MMC/SDIO driver\n");
+ mmc_unregister_driver(&mmc_driver);
+}
+module_exit(fs_sdio_exit);
+
+
+MODULE_DESCRIPTION("Freescale SDIO glue driver");
+MODULE_AUTHOR("Cambridge Silicon Radio Ltd.");
+MODULE_LICENSE("GPL");