summaryrefslogtreecommitdiff
path: root/drivers/mmc/host/sdhost.c
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/mmc/host/sdhost.c
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'drivers/mmc/host/sdhost.c')
-rwxr-xr-xdrivers/mmc/host/sdhost.c1189
1 files changed, 1189 insertions, 0 deletions
diff --git a/drivers/mmc/host/sdhost.c b/drivers/mmc/host/sdhost.c
new file mode 100755
index 00000000..1c40454e
--- /dev/null
+++ b/drivers/mmc/host/sdhost.c
@@ -0,0 +1,1189 @@
+/*
+ * linux/drivers/mmc/host/sdhost.c - Secure Digital Host Controller Interface driver
+ *
+ * Copyright (C) 2014-2014 Jason.Wu(Jishuang.Wu), 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 as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/highmem.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/scatterlist.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/sci_glb_regs.h>
+
+#include "sdhost.h"
+#ifdef CONFIG_DEBUG_FS
+#include "sdhost_debugfs.h"
+#endif
+//#include "linux/mmc/sdhost.h"
+#ifdef CONFIG_ARCH_SCX20
+
+#include <soc/sprd/arch_lock.h>
+
+#define BIT_12 0x00001000
+#define REG_PIN_SD0_D3 ( 0x0224 )
+#define REG_PIN_CTRL7 ( 0x001c )
+
+extern int pinmap_set(u32 offset, u32 value);
+extern u32 pinmap_get(u32 offset);
+
+static void mmc_pin_ctl_set(int powerup)
+{
+ u32 value = 0;
+
+ value = pinmap_get(REG_PIN_CTRL7);
+
+ if (!powerup) {
+ value |= (1 << 16);
+ } else {
+ value &= ~(1 << 16);
+ }
+ pinmap_set(REG_PIN_CTRL7, value);
+}
+
+static void mmc_pin_set(int powerup)
+{
+ u32 value = 0;
+ int i = 0;
+
+ if (!powerup) {
+ for (i = 0; i < 5; i++){
+ value = pinmap_get(REG_PIN_SD0_D3 + i * 4);
+ value = value & (~ BIT_12);
+ pinmap_set(REG_PIN_SD0_D3 + i * 4, value);
+ }
+ } else {
+ for (i = 0; i < 5; i++){
+ value = pinmap_get(REG_PIN_SD0_D3 + i * 4);
+ value = value | BIT_12;
+ pinmap_set(REG_PIN_SD0_D3 + i * 4, value);
+ }
+ }
+}
+#endif
+#define DRIVER_NAME "sdhost"
+
+#define STATIC_FUNC
+
+extern void mmc_power_cycle(struct mmc_host *host);
+
+STATIC_FUNC void _resetIOS(struct sdhost_host *host)
+{
+ _sdhost_disableall_int(host->ioaddr);
+ //---
+ host->ios.clock = 0;
+ host->ios.vdd = 0;
+ //host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN;
+ //host->ios.chip_select = MMC_CS_DONTCARE;
+ host->ios.power_mode = MMC_POWER_OFF;
+ host->ios.bus_width = MMC_BUS_WIDTH_1;
+ host->ios.timing = MMC_TIMING_LEGACY;
+ host->ios.signal_voltage = MMC_SIGNAL_VOLTAGE_330;
+ //host->ios.drv_type = MMC_SET_DRIVER_TYPE_B;
+ //---
+ _sdhost_reset(host->ioaddr, _RST_ALL);
+ _sdhost_set_delay(host->ioaddr, host->writeDelay, host->readPosDelay, host->readNegDelay);
+}
+
+#if defined(CONFIG_PM_RUNTIME) || defined(CONFIG_PM)
+STATIC_FUNC int __local_pm_suspend(struct sdhost_host *host)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ _sdhost_disableall_int(host->ioaddr);
+ _sdhost_AllClk_off(host->ioaddr);
+ // wake lock
+ spin_unlock_irqrestore(&host->lock, flags);
+ clk_disable(host->clk);
+ clk_unprepare(host->clk);
+ synchronize_irq(host->irq);
+
+ return 0;
+}
+
+STATIC_FUNC int __local_pm_resume(struct sdhost_host *host)
+{
+ unsigned long flags;
+ clk_prepare(host->clk);
+ clk_enable(host->clk);
+ spin_lock_irqsave(&host->lock, flags);
+ if (host->ios.clock) {
+ _sdhost_SDClk_off(host->ioaddr);
+ _sdhost_Clk_set_and_on(host->ioaddr, _sdhost_calcDiv(host->base_clk, host->ios.clock));
+ _sdhost_SDClk_on(host->ioaddr);
+ }
+ _sdhost_set_delay(host->ioaddr, host->writeDelay, host->readPosDelay, host->readNegDelay);
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+STATIC_FUNC void _pm_runtime_setting(struct platform_device *pdev, struct sdhost_host *host)
+{
+ printk("sdhost %s run PM init\n", host->deviceName);
+ pm_runtime_set_active(&pdev->dev);
+ pm_suspend_ignore_children(&pdev->dev, true);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 100);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ return;
+}
+
+STATIC_FUNC int _runtime_get(struct sdhost_host *host)
+{
+ return pm_runtime_get_sync(host->mmc->parent);
+}
+
+STATIC_FUNC int _runtime_put(struct sdhost_host *host)
+{
+ pm_runtime_mark_last_busy(host->mmc->parent);
+ return pm_runtime_put_autosuspend(host->mmc->parent);
+}
+
+STATIC_FUNC int _runtime_suspend(struct device *dev)
+{
+ struct platform_device *pdev = container_of(dev, struct platform_device, dev);
+ struct sdhost_host *host = platform_get_drvdata(pdev);
+
+ return __local_pm_suspend(host);
+}
+
+STATIC_FUNC int _runtime_resume(struct device *dev)
+{
+ struct platform_device *pdev = container_of(dev, struct platform_device, dev);
+ struct sdhost_host *host = platform_get_drvdata(pdev);
+
+ return __local_pm_resume(host);
+}
+
+STATIC_FUNC int _runtime_idle(struct device *dev)
+{
+ return 0;
+}
+
+#else
+STATIC_FUNC void _pm_runtime_setting(struct platform_device *pdev, struct sdhost_host *host)
+{
+ return;
+}
+
+STATIC_FUNC int _runtime_get(struct sdhost_host *host)
+{
+ return 0;
+}
+
+STATIC_FUNC int _runtime_put(struct sdhost_host *host)
+{
+ return 0;
+}
+
+#endif
+
+#ifdef CONFIG_PM
+STATIC_FUNC int _pm_suspend(struct device *dev)
+{
+ int err = 0;
+ struct platform_device *pdev = container_of(dev, struct platform_device, dev);
+ struct sdhost_host *host = platform_get_drvdata(pdev);
+
+ _runtime_get(host);
+
+ host->mmc->pm_flags = host->mmc->pm_caps;
+ err = mmc_suspend_host(host->mmc);
+ if (err) {
+ _runtime_put(host);
+ printk("sdhost %s pm suspend fail %d\n", host->deviceName, err);
+ return err;
+ }
+/*
+ printk("sdhost %s:\n"
+ "sdhost clock = %d\n"
+ "sdhost vdd = %d\n"
+ "sdhost bus_mode = %d\n"
+ "sdhost chip_select = %d\n"
+ "sdhost power_mode = %d\n"
+ "sdhost bus_width = %d\n"
+ "sdhost timing = %d\n"
+ "sdhost signal_voltage = %d\n"
+ "sdhost drv_type = %d\n",
+ host->deviceName,
+ host->ios.clock,
+ host->ios.vdd,
+ host->ios.bus_mode,
+ host->ios.chip_select,
+ host->ios.power_mode,
+ host->ios.bus_width,
+ host->ios.timing,
+ host->ios.signal_voltage,
+ host->ios.drv_type);
+*/
+ return __local_pm_suspend(host);
+}
+
+STATIC_FUNC int _pm_resume(struct device *dev)
+{
+ int err = 0;
+ struct platform_device *pdev = container_of(dev, struct platform_device, dev);
+ struct sdhost_host *host = platform_get_drvdata(pdev);
+ struct mmc_ios ios;
+
+ __local_pm_resume(host);
+
+ ios = host->mmc->ios;
+ _resetIOS(host);
+ host->mmc->ops->set_ios(host->mmc, &ios);
+
+/* printk("sdhost %s:\n"
+ "sdhost clock = %d\n"
+ "sdhost vdd = %d\n"
+ "sdhost bus_mode = %d\n"
+ "sdhost chip_select = %d\n"
+ "sdhost power_mode = %d\n"
+ "sdhost bus_width = %d\n"
+ "sdhost timing = %d\n"
+ "sdhost signal_voltage = %d\n"
+ "sdhost drv_type = %d\n",
+ host->deviceName,
+ host->ios.clock,
+ host->ios.vdd,
+ host->ios.bus_mode,
+ host->ios.chip_select,
+ host->ios.power_mode,
+ host->ios.bus_width,
+ host->ios.timing,
+ host->ios.signal_voltage,
+ host->ios.drv_type);
+*/
+ err = mmc_resume_host(host->mmc);
+ if (err) {
+ printk("sdhost %s pm resume fail %d\n", host->deviceName, err);
+ return err;
+ }
+ _runtime_put(host);
+ return err;
+}
+#endif
+
+STATIC_FUNC void __getRsp(struct sdhost_host *host)
+{
+ u32 i;
+ if (host->cmd->flags & MMC_RSP_PRESENT) {
+ if (host->cmd->flags & MMC_RSP_136) {
+ /* CRC is stripped so we need to do some shifting. */
+ for (i = 0; i < 4; i++) {
+ host->cmd->resp[i] = _sdhost_readl(host->ioaddr, SDHOST_32_RESPONSE + (3 - i) * 4) << 8;
+ if (i != 3)
+ host->cmd->resp[i] |= _sdhost_readb(host->ioaddr, SDHOST_32_RESPONSE + (3 - i) * 4 - 1);
+ }
+ } else {
+ host->cmd->resp[0] = _sdhost_readl(host->ioaddr, SDHOST_32_RESPONSE);
+ }
+ }
+}
+
+STATIC_FUNC void _sendCmd(struct sdhost_host *host, struct mmc_command *cmd)
+{
+ struct mmc_data *data = cmd->data;
+ int sg_cnt;
+ u32 flag = 0;
+ u16 rspType = 0;
+ int ifHasData = 0;
+ int ifMulti = 0;
+ int ifRd = 0;
+ int ifDma = 0;
+ uint16_t autoCmd = __ACMD_DIS;
+
+// printk("sdhost %s cmd %d, arg 0x%x, flag 0x%x\n", host->deviceName, cmd->opcode, cmd->arg, cmd->flags);
+// if (cmd->data) {
+// printk("sdhost %s blkSize %d, cnt %d\n", host->deviceName, cmd->data->blksz, cmd->data->blocks);
+// }
+ _sdhost_disableall_int(host->ioaddr);
+
+ if (38 == cmd->opcode) {
+ // if it is erase command , it's busy time will long, so we set long timeout value here.
+ mod_timer(&host->timer, jiffies + msecs_to_jiffies(host->mmc->max_discard_to+1000));
+ _sdhost_writeb(host->ioaddr, __DATA_TIMEOUT_MAX_VAL, SDHOST_8_TIMEOUT);
+ } else {
+ mod_timer(&host->timer, jiffies + (SDHOST_MAX_TIMEOUT+1) * HZ);
+ _sdhost_writeb(host->ioaddr, host->dataTimeOutVal, SDHOST_8_TIMEOUT);
+ }
+ host->cmd = cmd;
+ if (data) {
+ // set data param
+ BUG_ON(data->blksz * data->blocks > 524288);
+ BUG_ON(data->blksz > host->mmc->max_blk_size);
+ BUG_ON(data->blocks > 65535);
+
+ data->bytes_xfered = 0;
+
+ ifHasData = 1;
+ ifRd = (data->flags & MMC_DATA_READ);
+ ifMulti = (mmc_op_multi(cmd->opcode) || data->blocks > 1);
+ if (ifRd && !ifMulti) {
+ flag = _DATA_FILTER_RD_SIGLE;
+ } else if (ifRd && ifMulti) {
+ flag = _DATA_FILTER_RD_MULTI;
+ } else if (!ifRd && !ifMulti) {
+ flag = _DATA_FILTER_WR_SIGLE;
+ } else {
+ flag = _DATA_FILTER_WR_MULT;
+ }
+ if (!host->autoCmdMode) {
+ flag |= _INT_ERR_ACMD;
+ }
+ ifDma = 1;
+ autoCmd = host->autoCmdMode;
+// _sdhost_set_trans_mode(host->ioaddr, ifMulti, ifRd, host->autoCmdMode, ifMulti, ifDma);
+ _sdhost_set_blk_size(host->ioaddr, data->blksz);
+
+ sg_cnt = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, (data->flags & MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
+ if (1 == sg_cnt) {
+ _sdhost_set_DMA(host->ioaddr, __SDMA_MOD);
+ _sdhost_set_16_blk_cnt(host->ioaddr, data->blocks);
+ _sdhost_writel(host->ioaddr, sg_dma_address(data->sg), SDHOST_32_SYS_ADDR);
+ } else {
+ //WARN_ON(1);
+ BUG_ON(1);
+ flag |= _INT_ERR_ADMA;
+ _sdhost_set_DMA(host->ioaddr, __32ADMA_MOD);
+ _sdhost_set_32_blk_cnt(host->ioaddr, data->blocks);
+ _sdhost_writel(host->ioaddr, sg_dma_address(data->sg), SDHOST_32_SYS_ADDR);
+ }
+ } else {
+// _sdhost_set_trans_mode(host->ioaddr, 0, 0, __ACMD_DIS, 0, 0);
+ }
+ _sdhost_writel(host->ioaddr, cmd->arg, SDHOST_32_ARG);
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_R1B:
+ rspType = _RSP1B_5B;
+ flag |= _CMD_FILTER_R1B;
+ break;
+ case MMC_RSP_NONE:
+ rspType = _RSP0;
+ flag |= _CMD_FILTER_R0;
+ break;
+ case MMC_RSP_R2:
+ rspType = _RSP2;
+ flag |= _CMD_FILTER_R2;
+ break;
+// case MMC_RSP_R3:
+// rspType = _RSP3_4;
+// flag |= _CMD_FILTER_R3;
+// break;
+ case MMC_RSP_R4:
+ rspType = _RSP3_4;
+ flag |= _CMD_FILTER_R1_R4_R5_R6_R7;
+ break;
+ case MMC_RSP_R1:
+// case MMC_RSP_R5:
+// case MMC_RSP_R6:
+// case MMC_RSP_R7:
+ rspType = _RSP1_5_6_7;
+ flag |= _CMD_FILTER_R1_R4_R5_R6_R7;
+ break;
+ default:
+ BUG_ON(1);
+ break;
+ }
+ host->int_filter = flag;
+ _sdhost_enable_int(host->ioaddr, flag);
+// printk("sdhost %s cmd:%d rsp:%d intflag:0x%x ifMulti:0x%x ifRd:0x%x autoCmd:0x%x ifDma:0x%x\n", host->deviceName, cmd->opcode, mmc_resp_type(cmd), flag,ifMulti,ifRd,autoCmd,ifDma);
+
+// _sdhost_set_cmd(host->ioaddr, cmd->opcode, ifHasData, rspType);
+ _sdhost_set_trans_and_cmd(host->ioaddr, ifMulti, ifRd, autoCmd, ifMulti, ifDma, cmd->opcode, ifHasData, rspType);
+}
+
+STATIC_FUNC irqreturn_t _irq(int irq, void *param)
+{
+ u32 intmask;
+ struct sdhost_host *host = (struct sdhost_host *)param;
+ struct mmc_request *mrq = host->mrq;
+ struct mmc_command *cmd = host->cmd;
+ struct mmc_data *data;
+ spin_lock(&host->lock);
+ if ((!mrq) || (!cmd)) { // bug 411922 : maybe _timeout run in one core and _irq run in another core, this will panic if access cmd->data. jason.wu
+ spin_unlock(&host->lock);
+ return IRQ_NONE;
+ }
+ data = cmd->data;
+
+ intmask = _sdhost_readl(host->ioaddr, SDHOST_32_INT_ST);
+ if (!intmask) {
+ spin_unlock(&host->lock);
+ return IRQ_NONE;
+ }
+// printk("sdhost %s int 0x%x\n", host->deviceName, intmask);
+
+ // disable unused interrupt
+ _sdhost_clear_int(host->ioaddr, intmask);
+ // just care about the interrupt that we want
+ intmask &= host->int_filter;
+ while (intmask) {
+ if (_INT_FILTER_ERR & intmask) {
+ // some error happened in command
+ if (_INT_FILTER_ERR_CMD & intmask) {
+ if (_INT_ERR_CMD_TIMEOUT & intmask) {
+ cmd->error = -ETIMEDOUT;
+ } else {
+ cmd->error = -EILSEQ;
+ }
+ }
+ // some error happened in data token or command with R1B
+ if (_INT_FILTER_ERR_DATA & intmask) {
+ if (data) {
+ // current error is happened in data token
+ if (_INT_ERR_DATA_TIMEOUT & intmask) {
+ data->error = -ETIMEDOUT;
+ } else {
+ data->error = -EILSEQ;
+ }
+ } else {
+ // current error is happend in response with busy
+ if (_INT_ERR_DATA_TIMEOUT & intmask) {
+ cmd->error = -ETIMEDOUT;
+ } else {
+ cmd->error = -EILSEQ;
+ }
+ }
+ }
+ if (_INT_ERR_ACMD & intmask) {
+ // Auto cmd12 and cmd23 error is belong to data token error
+ data->error = -EILSEQ;
+ }
+ if (_INT_ERR_ADMA & intmask) {
+ data->error = -EIO;
+ }
+ // for debug
+ printk("sdhost %s int 0x%x\n", host->deviceName, intmask);
+ dumpSDIOReg(host);
+ _sdhost_disableall_int(host->ioaddr);
+ // if current error happened in data token, we send cmd12 to stop it.
+ if ((mrq->cmd == cmd) && (mrq->stop)) {
+ _sdhost_reset(host->ioaddr, _RST_CMD | _RST_DATA);
+ _sendCmd(host, mrq->stop);
+ } else {
+ // request finish with error, so reset it and stop the request
+ _sdhost_reset(host->ioaddr, _RST_CMD | _RST_DATA);
+ tasklet_schedule(&host->finish_tasklet);
+ }
+ goto out;
+ } else {
+ // delete irq that wanted in filter
+ //_sdhost_clear_int(host->ioaddr, _INT_FILTER_NORMAL & intmask);
+ host->int_filter &= ~(_INT_FILTER_NORMAL & intmask);
+ if (_INT_DMA_END & intmask) {
+ _sdhost_writel(host->ioaddr, _sdhost_readl(host->ioaddr, SDHOST_32_SYS_ADDR), SDHOST_32_SYS_ADDR);
+ }
+ if (_INT_CMD_END & intmask) {
+ cmd->error = 0;
+ __getRsp(host);
+ }
+ if (_INT_TRAN_END & intmask) {
+ if (data) {
+ dma_unmap_sg(mmc_dev(host->mmc),
+ data->sg, data->sg_len, (data->flags & MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
+ data->error = 0;
+ data->bytes_xfered = data->blksz * data->blocks;
+ } else {
+ // R1B also can produce transferComplete interrupt
+ cmd->error = 0;
+ }
+ }
+ if (!(_INT_FILTER_NORMAL & host->int_filter)) {
+ // current cmd finished
+ _sdhost_disableall_int(host->ioaddr);
+ if (mrq->sbc == cmd) {
+ _sendCmd(host, mrq->cmd);
+ } else if ((mrq->cmd == host->cmd)
+ && (mrq->stop)) {
+ _sendCmd(host, mrq->stop);
+ } else {
+ // finish with success and stop the request
+ tasklet_schedule(&host->finish_tasklet);
+ goto out;
+ }
+ }
+ }
+
+ intmask = _sdhost_readl(host->ioaddr, SDHOST_32_INT_ST);
+ _sdhost_clear_int(host->ioaddr, intmask);
+ intmask &= host->int_filter;
+ };
+out:
+ spin_unlock(&host->lock);
+ return IRQ_HANDLED;
+}
+
+STATIC_FUNC void _tasklet(unsigned long param)
+{
+ struct sdhost_host *host = (struct sdhost_host *)param;
+ unsigned long flags;
+ struct mmc_request *mrq;
+
+ del_timer(&host->timer);
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (!host->mrq) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ return;
+ }
+ mrq = host->mrq;
+ host->mrq = NULL;
+ host->cmd = NULL;
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+
+// printk("sdhost %s cmd %d data %d\n", host->deviceName, mrq->cmd->error, ((! !mrq->cmd->data) ? mrq->cmd->data->error : 0));
+ mmc_request_done(host->mmc, mrq);
+ _runtime_put(host);
+}
+
+STATIC_FUNC void _timeout(unsigned long data)
+{
+ struct sdhost_host *host = (struct sdhost_host *)data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (host->mrq) {
+ printk("sdhost %s Timeout waiting for hardware interrupt!\n", host->deviceName);
+ dumpSDIOReg(host);
+ if (host->cmd->data) {
+ host->cmd->data->error = -ETIMEDOUT;
+ } else if (host->cmd) {
+ host->cmd->error = -ETIMEDOUT;
+ } else {
+ host->mrq->cmd->error = -ETIMEDOUT;
+ }
+ _sdhost_disableall_int(host->ioaddr);
+ _sdhost_reset(host->ioaddr, _RST_CMD | _RST_DATA);
+ tasklet_schedule(&host->finish_tasklet);
+ }
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+ return;
+}
+
+STATIC_FUNC void sdhost_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct sdhost_host *host = mmc_priv(mmc);
+ unsigned long flags;
+
+ _runtime_get(host);
+ spin_lock_irqsave(&host->lock, flags);
+
+ host->mrq = mrq;
+ // 1 find whether card is still in slot
+ if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE)) {
+ if (!mmc_gpio_get_cd(host->mmc)) {
+ mrq->cmd->error = -ENOMEDIUM;
+ tasklet_schedule(&host->finish_tasklet);
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+ return;
+ }
+ // else asume sdcard is present
+ }
+ // in our control we can not use auto cmd12 and auto cmd23 together
+ // so in following program we use auto cmd23 prior to auto cmd12
+ //printk("sdhost %s request %d %d %d\n", host->deviceName, !!mrq->sbc,!!mrq->cmd,!!mrq->stop);
+ host->autoCmdMode = __ACMD_DIS;
+ if (!mrq->sbc && mrq->stop && SDHOST_FLAG_ENABLE_ACMD12) {
+ host->autoCmdMode = __ACMD12;
+ mrq->data->stop = NULL;
+ mrq->stop = NULL;
+ }
+ // 3 send cmd list
+ if ((mrq->sbc) && SDHOST_FLAG_ENABLE_ACMD23) {
+ host->autoCmdMode = __ACMD23;
+ mrq->data->stop = NULL;
+ mrq->stop = NULL;
+ _sendCmd(host, mrq->cmd);
+ } else if (mrq->sbc) {
+ mrq->data->stop = NULL;
+ mrq->stop = NULL;
+ _sendCmd(host, mrq->sbc);
+ } else {
+ _sendCmd(host, mrq->cmd);
+ }
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+ return;
+}
+
+STATIC_FUNC void _signalVoltageOnOff(struct sdhost_host *host, uint32_t onOff)
+{
+ if (!host->_1_8V_signal) {
+ printk("sdhost %s there is no signal voltage!\n", host->deviceName);
+ return;
+ }
+ if (onOff) {
+ if (!host->_1_8V_signal_enabled) {
+ if (regulator_enable(host->_1_8V_signal)) {
+ printk("sdhost %s signal voltage enable fail!\n", host->deviceName);
+ } else if (regulator_is_enabled(host->_1_8V_signal)) {
+ host->_1_8V_signal_enabled = true;
+ printk("sdhost %s signal voltage enable success!\n", host->deviceName);
+ } else {
+ printk("sdhost %s signal voltage enable hw fail!\n", host->deviceName);
+ }
+ }
+ } else {
+ if (host->_1_8V_signal_enabled) {
+ if (regulator_disable(host->_1_8V_signal)) {
+ printk("sdhost %s signal voltage disable fail\n", host->deviceName);
+ } else if (!regulator_is_enabled(host->_1_8V_signal)) {
+ host->_1_8V_signal_enabled = false;
+ printk("sdhost %s signal voltage disable success!\n", host->deviceName);
+ } else {
+ printk("sdhost %s signal voltage disable hw fail\n", host->deviceName);
+ }
+ }
+ }
+ return;
+}
+
+/*
+ charpter:
+ 1 This votage is always poweron
+ 2 initial votage is 2.7v~3.6v
+ 3 It can be reconfig to 1.7v~1.95v
+*/
+STATIC_FUNC int sdhost_set_vqmmc(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct sdhost_host *host = mmc_priv(mmc);
+ unsigned long flags;
+ int err;
+/*
+ printk("sdhost %s vqmmc:\n"
+ "sdhost clock = %d-->%d\n"
+ "sdhost vdd = %d-->%d\n"
+ "sdhost bus_mode = %d-->%d\n"
+ "sdhost chip_select = %d-->%d\n"
+ "sdhost power_mode = %d-->%d\n"
+ "sdhost bus_width = %d-->%d\n"
+ "sdhost timing = %d-->%d\n"
+ "sdhost signal_voltage = %d-->%d\n"
+ "sdhost drv_type = %d-->%d\n",
+ host->deviceName,
+ host->ios.clock, ios->clock,
+ host->ios.vdd, ios->vdd,
+ host->ios.bus_mode, ios->bus_mode,
+ host->ios.chip_select, ios->chip_select,
+ host->ios.power_mode, ios->power_mode,
+ host->ios.bus_width, ios->bus_width,
+ host->ios.timing, ios->timing, host->ios.signal_voltage, ios->signal_voltage, host->ios.drv_type, ios->drv_type);
+*/
+ _runtime_get(host);
+ spin_lock_irqsave(&host->lock, flags);
+
+ if (!host->_1_8V_signal) {
+ // there are no 1.8v signal votage.
+ spin_unlock_irqrestore(&host->lock, flags);
+ _runtime_put(host);
+ err = -EINVAL;
+ printk("sdhost %s There is no signalling voltage\n", host->deviceName);
+ return err;
+ }
+
+ switch (ios->signal_voltage) {
+ case MMC_SIGNAL_VOLTAGE_330:
+ //err = regulator_set_voltage(host->_1_8V_signal, 2700000, 3600000);
+ err = regulator_set_voltage(host->_1_8V_signal, 3000000, 3000000);
+ break;
+ case MMC_SIGNAL_VOLTAGE_180:
+ //err = regulator_set_voltage(host->_1_8V_signal, 1700000, 1950000);
+ err = regulator_set_voltage(host->_1_8V_signal, 1800000, 1800000);
+ break;
+ case MMC_SIGNAL_VOLTAGE_120:
+ err = regulator_set_voltage(host->_1_8V_signal, 1100000, 1300000);
+ break;
+ default:
+ err = -EIO;
+ WARN_ON(1);
+ break;
+ }
+ if (likely(!err)) {
+ host->ios.signal_voltage = ios->signal_voltage;
+ }
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+ _runtime_put(host);
+ if (err) {
+ printk(KERN_WARNING "sdhost %s: Switching to signalling voltage degree %d" " failed\n", host->deviceName, ios->signal_voltage);
+ }
+ return err;
+}
+
+STATIC_FUNC void sdhost_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct sdhost_host *host = mmc_priv(mmc);
+ unsigned long flags;
+/*
+ printk("sdhost %s ios:\n"
+ "sdhost clock = %d-->%d\n"
+ "sdhost vdd = %d-->%d\n"
+ "sdhost bus_mode = %d-->%d\n"
+ "sdhost chip_select = %d-->%d\n"
+ "sdhost power_mode = %d-->%d\n"
+ "sdhost bus_width = %d-->%d\n"
+ "sdhost timing = %d-->%d\n"
+ "sdhost signal_voltage = %d-->%d\n"
+ "sdhost drv_type = %d-->%d\n",
+ host->deviceName,
+ host->ios.clock, ios->clock,
+ host->ios.vdd, ios->vdd,
+ host->ios.bus_mode, ios->bus_mode,
+ host->ios.chip_select, ios->chip_select,
+ host->ios.power_mode, ios->power_mode,
+ host->ios.bus_width, ios->bus_width,
+ host->ios.timing, ios->timing, host->ios.signal_voltage, ios->signal_voltage, host->ios.drv_type, ios->drv_type);
+*/
+ _runtime_get(host);
+ spin_lock_irqsave(&host->lock, flags);
+
+ do {
+ if (0 == ios->clock) {
+ _sdhost_AllClk_off(host->ioaddr);
+ host->ios.clock = 0;
+ } else if (ios->clock != host->ios.clock) {
+ uint32_t div;
+ div = _sdhost_calcDiv(host->base_clk, ios->clock);
+ _sdhost_SDClk_off(host->ioaddr);
+ _sdhost_Clk_set_and_on(host->ioaddr, div);
+ _sdhost_SDClk_on(host->ioaddr);
+ host->ios.clock = ios->clock;
+ host->dataTimeOutVal = _sdhost_calcTimeout(host->ios.clock, SDHOST_MAX_TIMEOUT);
+ mmc->max_discard_to = (1 << 30) / (ios->clock / 1000);
+ }
+ if (ios->power_mode != host->ios.power_mode) {
+ if (MMC_POWER_OFF == ios->power_mode) {
+ spin_unlock_irqrestore(&host->lock, flags);
+#ifdef CONFIG_ARCH_SCX20
+ if (!strcmp(host->deviceName, "sdio_sd")) {
+ mmc_pin_set(0);
+ }
+#endif
+ _signalVoltageOnOff(host, 0);
+#ifdef CONFIG_ARCH_SCX20
+ mmc_pin_ctl_set(0);
+#endif
+ if (host->SD_pwr) {
+ mmc_regulator_set_ocr(host->mmc, host->SD_pwr, 0);
+ }
+ mdelay(50);
+ spin_lock_irqsave(&host->lock, flags);
+ _resetIOS(host);
+ host->ios.power_mode = ios->power_mode;
+ } else if ((MMC_POWER_ON == ios->power_mode)
+ || (MMC_POWER_UP == ios->power_mode)
+ ) {
+spin_unlock_irqrestore(&host->lock, flags);
+ if (host->SD_pwr) {
+ mmc_regulator_set_ocr(host->mmc, host->SD_pwr, ios->vdd);
+ }
+#ifdef CONFIG_ARCH_SCX20
+ mmc_pin_ctl_set(1);
+#endif
+ _signalVoltageOnOff(host, 1);
+#ifdef CONFIG_ARCH_SCX20
+ if (!strcmp(host->deviceName, "sdio_sd")) {
+ mmc_pin_set(1);
+ }
+#endif
+ mdelay(50);
+ spin_lock_irqsave(&host->lock, flags);
+ host->ios.power_mode = ios->power_mode;
+ host->ios.vdd = ios->vdd;
+ } else {
+ BUG_ON(1);
+ }
+ }
+ // flash power voltage select
+ if (ios->vdd != host->ios.vdd) {
+ if (host->SD_pwr) {
+ printk("sdhost %s 3.0 %d!\n", host->deviceName, ios->vdd);
+ mmc_regulator_set_ocr(host->mmc, host->SD_pwr, ios->vdd);
+ mdelay(50);
+ }
+ host->ios.vdd = ios->vdd;
+ }
+#if 0
+ if (ios->bus_mode != host->ios.bus_mode) {
+ host->ios.bus_mode = ios->bus_mode;
+ }
+ // No use in spreadtrum platform
+ if (ios->chip_select != host->ios.chip_select) {
+ host->ios.chip_select = ios->chip_select;
+ }
+#endif
+ if (ios->bus_width != host->ios.bus_width) {
+ _sdhost_set_buswidth(host->ioaddr, ios->bus_width);
+ host->ios.bus_width = ios->bus_width;
+ }
+ if (ios->timing != host->ios.timing) {
+ // 1 first close SD clock, charpter:
+ _sdhost_SDClk_off(host->ioaddr);
+ // 2 set timing mode
+ switch (ios->timing) { /* timing specification used */
+ case MMC_TIMING_LEGACY:{
+ /*basic clock mode */
+ //_sdhost_disable_HISPD(host->ioaddr);
+ _sdhost_set_UHS_mode(host->ioaddr, __TIMING_MODE_SDR12);
+ }
+ break;
+ case MMC_TIMING_MMC_HS:
+ case MMC_TIMING_SD_HS:{
+ //_sdhost_enable_HISPD(host->ioaddr);
+ _sdhost_set_UHS_mode(host->ioaddr, __TIMING_MODE_SDR12);
+ }
+ break;
+ case MMC_TIMING_UHS_SDR12:
+ case MMC_TIMING_UHS_SDR25:
+ case MMC_TIMING_UHS_SDR50:
+ case MMC_TIMING_UHS_SDR104:
+ case MMC_TIMING_UHS_DDR50:
+ case MMC_TIMING_MMC_HS200:{
+ //_sdhost_enable_HISPD(host->ioaddr);
+ _sdhost_set_UHS_mode(host->ioaddr, ios->timing - MMC_TIMING_UHS_SDR12 + __TIMING_MODE_SDR12);
+ }
+ break;
+ default:{
+ BUG_ON(1);
+ }
+ break;
+ }
+ // 3 open SD clock
+ _sdhost_SDClk_on(host->ioaddr);
+ host->ios.timing = ios->timing;
+ }
+#if 0
+ // No use in spreadtrum platform
+ if (ios->drv_type != host->ios.drv_type) {
+ host->ios.drv_type = ios->drv_type;
+ }
+#endif
+ } while (0);
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+ _runtime_put(host);
+}
+
+STATIC_FUNC int sdhost_get_ro(struct mmc_host *mmc)
+{
+ struct sdhost_host *host = mmc_priv(mmc);
+ unsigned long flags;
+
+ _runtime_get(host);
+ spin_lock_irqsave(&host->lock, flags);
+ /*read & write */
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+ _runtime_put(host);
+ return 0;
+}
+
+STATIC_FUNC int sdhost_get_cd(struct mmc_host *mmc)
+{
+ struct sdhost_host *host = mmc_priv(mmc);
+ unsigned long flags;
+ int gpio_cd;
+
+ //printk("sdhost %s, get cd\n",host->deviceName);
+ _runtime_get(host);
+ spin_lock_irqsave(&host->lock, flags);
+
+ if (host->mmc->caps & MMC_CAP_NONREMOVABLE) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ _runtime_put(host);
+ return 1;
+ }
+ gpio_cd = mmc_gpio_get_cd(host->mmc);
+ if (IS_ERR_VALUE(gpio_cd)) {
+ gpio_cd = 1;
+ }
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+ _runtime_put(host);
+ return ! !gpio_cd;
+}
+
+STATIC_FUNC int sdhost_card_busy(struct mmc_host *mmc)
+{
+ struct sdhost_host *host = mmc_priv(mmc);
+ unsigned long flags;
+ u32 present_state;
+
+ _runtime_get(host);
+ spin_lock_irqsave(&host->lock, flags);
+
+ /* Check whether DAT[3:0] is 0000 */
+ present_state = _sdhost_readl(host->ioaddr, SDHOST_32_PRES_STATE);
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+ _runtime_put(host);
+
+ return !(present_state & _DATA_LVL_MASK);
+}
+
+STATIC_FUNC void sdhost_hw_reset(struct mmc_host *mmc)
+{
+ struct sdhost_host *host = mmc_priv(mmc);
+ unsigned long flags;
+ printk("sdhost %s: sdhost_hw_reset start.\n", host->deviceName);
+ mmc_power_cycle(host->mmc);
+ printk("sdhost %s: sdhost_hw_reset end.\n", host->deviceName);
+ return;
+}
+
+STATIC_FUNC const struct mmc_host_ops sdhost_ops = {
+ .enable = 0,
+ .disable = 0,
+ .post_req = 0,
+ .pre_req = 0,
+
+ .request = sdhost_request,
+ .set_ios = sdhost_set_ios,
+ .get_ro = sdhost_get_ro,
+ .get_cd = sdhost_get_cd,
+
+ .enable_sdio_irq = 0,
+ .init_card = 0,
+
+ .start_signal_voltage_switch = sdhost_set_vqmmc,
+ .card_busy = sdhost_card_busy,
+ .execute_tuning = 0, //sdhost_execute_tuning,
+ .select_drive_strength = 0,
+
+ .hw_reset = sdhost_hw_reset,
+ .card_event = 0 //sdhost_card_event
+};
+
+STATIC_FUNC int _getBasicResource(struct platform_device *pdev, struct sdhost_host *host)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct resource *res;
+
+#if 0
+ host->ioaddr = (void __iomem *)0xf511c000;
+ host->irq = (60 + 32);
+ host->detect_gpio = -1;
+ host->SD_Pwr_Name = "vddemmccore";
+ host->_1_8V_signal_Name = "vddgen1";
+ host->ocr_avail = MMC_VDD_30_31;
+ host->clkName = "clk_emmc";
+ host->clkParentName = "clk_384m";
+ host->base_clk = 384000000;
+ host->caps = 0xC00F8547;
+ host->caps2 = 0x202;
+ host->writeDelay = 0x04;
+ host->readPosDelay = 0x04;
+ host->readNegDelay = 0x04;
+#else
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOENT;
+ host->ioaddr = ioremap(res->start, resource_size(res));
+ host->irq = platform_get_irq(pdev, 0);
+ if (host->irq < 0)
+ return host->irq;
+ of_property_read_string(np, "sprd,name", &host->deviceName);
+ if (of_property_read_u32(np, "detect_gpio", &host->detect_gpio))
+ host->detect_gpio = -1;
+ of_property_read_string(np, "SD_Pwr_Name", &host->SD_Pwr_Name);
+ of_property_read_string(np, "_1_8V_signal_Name", &host->_1_8V_signal_Name);
+
+ of_property_read_u32(np, "ocr_avail", &host->ocr_avail);
+ of_property_read_u32(np, "signal_default_Voltage", &host->signal_default_Voltage);
+
+ host->clk = of_clk_get(np, 0);
+ if (IS_ERR_OR_NULL(host->clk))
+ return PTR_ERR(host->clk);
+ host->clk_parent = of_clk_get(np, 1);
+ if (IS_ERR_OR_NULL(host->clk_parent))
+ return PTR_ERR(host->clk_parent);
+
+ of_property_read_u32(np, "base_clk", &host->base_clk);
+ of_property_read_u32(np, "caps", &host->caps);
+ of_property_read_u32(np, "caps2", &host->caps2);
+ of_property_read_u32(np, "pm_caps", &host->pm_caps);
+ of_property_read_u32(np, "writeDelay", &host->writeDelay);
+ of_property_read_u32(np, "readPosDelay", &host->readPosDelay);
+ of_property_read_u32(np, "readNegDelay", &host->readNegDelay);
+#endif
+
+ return 0;
+}
+
+STATIC_FUNC int _getExtResource(struct sdhost_host *host)
+{
+ int err;
+
+ host->dma_mask = DMA_BIT_MASK(64);
+ host->dataTimeOutVal = 0;
+// 1 LDO
+ host->SD_pwr = regulator_get(NULL, host->SD_Pwr_Name);
+ if (IS_ERR_OR_NULL(host->SD_pwr)) {
+ printk("sdhost %s: no SD_pwr regulator found\n", host->deviceName);
+ host->SD_pwr = NULL;
+ }
+
+ host->_1_8V_signal = regulator_get(NULL, host->_1_8V_signal_Name);
+ if (IS_ERR_OR_NULL(host->_1_8V_signal)) {
+ printk("sdhost %s: no _1_8V_signal regulator found\n", host->deviceName);
+ host->_1_8V_signal = NULL;
+ } else {
+ regulator_is_supported_voltage(host->_1_8V_signal, host->signal_default_Voltage, host->signal_default_Voltage);
+ regulator_set_voltage(host->_1_8V_signal, host->signal_default_Voltage, host->signal_default_Voltage);
+ }
+// 2 clock
+ clk_set_parent(host->clk, host->clk_parent);
+ clk_prepare_enable(host->clk);
+// 3 reset sdio
+ _resetIOS(host);
+ err = request_irq(host->irq, _irq, IRQF_SHARED, mmc_hostname(host->mmc), host);
+ if (err)
+ return err;
+ tasklet_init(&host->finish_tasklet, _tasklet, (unsigned long)host);
+// 4 init timer
+ setup_timer(&host->timer, _timeout, (unsigned long)host);
+ return 0;
+}
+
+STATIC_FUNC int _setMmcStruct(struct sdhost_host *host, struct mmc_host *mmc)
+{
+ mmc_dev(host->mmc)->dma_mask = &host->dma_mask;
+ mmc->ops = &sdhost_ops;
+ mmc->f_max = host->base_clk;
+ mmc->f_min = (unsigned int)(host->base_clk / __CLK_MAX_DIV);
+
+ mmc->caps = host->caps;
+ mmc->caps2 = host->caps2;
+ mmc->pm_caps = host->pm_caps;
+ mmc->pm_flags = host->pm_caps;
+ mmc->ocr_avail = host->ocr_avail;
+ mmc->ocr_avail_sdio = host->ocr_avail;
+ mmc->ocr_avail_sd = host->ocr_avail;
+ mmc->ocr_avail_mmc = host->ocr_avail;
+ mmc->max_current_330 = SDHOST_MAX_CUR;
+ mmc->max_current_300 = SDHOST_MAX_CUR;
+ mmc->max_current_180 = SDHOST_MAX_CUR;
+
+ mmc->max_segs = 1;
+ mmc->max_req_size = 524288; // 512k
+ mmc->max_seg_size = mmc->max_req_size;
+
+ mmc->max_blk_size = 512;
+ mmc->max_blk_count = 65535;
+ return 0;
+}
+
+STATIC_FUNC int sdhost_probe(struct platform_device *pdev)
+{
+ struct mmc_host *mmc;
+ struct sdhost_host *host;
+
+// 1 globe resource
+ mmc = mmc_alloc_host(sizeof(struct sdhost_host), &pdev->dev);
+ if (!mmc)
+ return -ENOMEM;
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ spin_lock_init(&host->lock);
+ platform_set_drvdata(pdev, host);
+// 2 get sdio irq and sdio iomem
+ _getBasicResource(pdev, host);
+ _getExtResource(host);
+ _setMmcStruct(host, mmc);
+ _pm_runtime_setting(pdev, host);
+// 3 add host
+ mmiowb();
+ mmc_add_host(mmc);
+ if (-1 != host->detect_gpio) {
+ mmc->caps &= ~MMC_CAP_NONREMOVABLE;
+ mmc_gpio_request_cd(mmc, host->detect_gpio);
+ }
+#ifdef CONFIG_DEBUG_FS
+ sdhost_add_debugfs(host);
+#endif
+ return 0;
+}
+
+STATIC_FUNC void sdhost_shutdown(struct platform_device *pdev)
+{
+ return;
+}
+
+STATIC_FUNC const struct dev_pm_ops sdhost_dev_pm_ops = {
+#ifdef CONFIG_PM
+ .suspend = _pm_suspend,
+ .resume = _pm_resume,
+#endif
+#ifdef CONFIG_PM_RUNTIME
+ SET_RUNTIME_PM_OPS(_runtime_suspend, _runtime_resume, _runtime_idle)
+#endif
+};
+
+STATIC_FUNC const struct of_device_id sdhost_of_match[] = {
+ {.compatible = "sprd,sdhost-3.0"},
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, sdhost_of_match);
+
+STATIC_FUNC struct platform_driver sdhost_driver = {
+ .probe = sdhost_probe,
+ .shutdown = sdhost_shutdown,
+ .driver = {
+ .owner = THIS_MODULE,
+ .pm = &sdhost_dev_pm_ops,
+ .name = DRIVER_NAME,
+ .of_match_table = of_match_ptr(sdhost_of_match),
+ },
+};
+
+STATIC_FUNC int __init sdhost_drv_init(void)
+{
+ struct device_node *np, *parent;
+ const char *devName;
+ parent = of_find_node_by_name(NULL, "sdios");
+ for_each_child_of_node(parent, np) {
+ of_property_read_string(np, "sprd,name", &devName);
+ of_platform_device_create(np, devName, NULL);
+ printk("%s deviceName:%s\n", __func__, devName);
+ }
+ return platform_driver_register(&sdhost_driver);
+}
+
+STATIC_FUNC void __exit sdhost_drv_exit(void)
+{
+ platform_driver_unregister(&sdhost_driver);
+}
+
+module_init(sdhost_drv_init);
+module_exit(sdhost_drv_exit);
+MODULE_AUTHOR("Jason.Wu(Jishuang.Wu) <jason.wu@spreadtrum.com>");
+MODULE_DESCRIPTION("Spreadtrum sdio host controller driver");
+MODULE_LICENSE("GPL");