summaryrefslogtreecommitdiffstats
path: root/drivers/mmc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mmc')
-rw-r--r--drivers/mmc/host/Kconfig14
-rw-r--r--drivers/mmc/host/Makefile2
-rw-r--r--drivers/mmc/host/mmci.c324
-rw-r--r--drivers/mmc/host/mmci.h14
-rw-r--r--drivers/mmc/host/rtsx_usb_sdmmc.c1455
-rw-r--r--drivers/mmc/host/sunxi-mmc.c1049
6 files changed, 2614 insertions, 244 deletions
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 8aaf8c1f3f63..779368b683d0 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -694,3 +694,17 @@ config MMC_REALTEK_PCI
help
Say Y here to include driver code to support SD/MMC card interface
of Realtek PCI-E card reader
+
+config MMC_REALTEK_USB
+ tristate "Realtek USB SD/MMC Card Interface Driver"
+ depends on MFD_RTSX_USB
+ help
+ Say Y here to include driver code to support SD/MMC card interface
+ of Realtek RTS5129/39 series card reader
+
+config MMC_SUNXI
+ tristate "Allwinner sunxi SD/MMC Host Controller support"
+ depends on ARCH_SUNXI
+ help
+ This selects support for the SD/MMC Host Controller on
+ Allwinner sunxi SoCs.
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 0c8aa5e1e304..61cbc241935b 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -50,8 +50,10 @@ obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o
obj-$(CONFIG_MMC_VUB300) += vub300.o
obj-$(CONFIG_MMC_USHC) += ushc.o
obj-$(CONFIG_MMC_WMT) += wmt-sdmmc.o
+obj-$(CONFIG_MMC_SUNXI) += sunxi-mmc.o
obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o
+obj-$(CONFIG_MMC_REALTEK_USB) += rtsx_usb_sdmmc.o
obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o
obj-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o
diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c
index 771c60ab4a32..a084edd37af5 100644
--- a/drivers/mmc/host/mmci.c
+++ b/drivers/mmc/host/mmci.c
@@ -13,6 +13,7 @@
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/device.h>
+#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -23,6 +24,7 @@
#include <linux/mmc/pm.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
+#include <linux/mmc/slot-gpio.h>
#include <linux/amba/bus.h>
#include <linux/clk.h>
#include <linux/scatterlist.h>
@@ -364,7 +366,6 @@ static void mmci_init_sg(struct mmci_host *host, struct mmc_data *data)
#ifdef CONFIG_DMA_ENGINE
static void mmci_dma_setup(struct mmci_host *host)
{
- struct mmci_platform_data *plat = host->plat;
const char *rxname, *txname;
dma_cap_mask_t mask;
@@ -378,25 +379,6 @@ static void mmci_dma_setup(struct mmci_host *host)
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
- if (plat && plat->dma_filter) {
- if (!host->dma_rx_channel && plat->dma_rx_param) {
- host->dma_rx_channel = dma_request_channel(mask,
- plat->dma_filter,
- plat->dma_rx_param);
- /* E.g if no DMA hardware is present */
- if (!host->dma_rx_channel)
- dev_err(mmc_dev(host->mmc), "no RX DMA channel\n");
- }
-
- if (!host->dma_tx_channel && plat->dma_tx_param) {
- host->dma_tx_channel = dma_request_channel(mask,
- plat->dma_filter,
- plat->dma_tx_param);
- if (!host->dma_tx_channel)
- dev_warn(mmc_dev(host->mmc), "no TX DMA channel\n");
- }
- }
-
/*
* If only an RX channel is specified, the driver will
* attempt to use it bidirectionally, however if it is
@@ -444,11 +426,9 @@ static void mmci_dma_setup(struct mmci_host *host)
*/
static inline void mmci_dma_release(struct mmci_host *host)
{
- struct mmci_platform_data *plat = host->plat;
-
if (host->dma_rx_channel)
dma_release_channel(host->dma_rx_channel);
- if (host->dma_tx_channel && plat->dma_tx_param)
+ if (host->dma_tx_channel)
dma_release_channel(host->dma_tx_channel);
host->dma_rx_channel = host->dma_tx_channel = NULL;
}
@@ -1285,7 +1265,7 @@ static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
* indicating signal direction for the signals in
* the SD/MMC bus and feedback-clock usage.
*/
- pwr |= host->plat->sigdir;
+ pwr |= host->pwr_reg_add;
if (ios->bus_width == MMC_BUS_WIDTH_4)
pwr &= ~MCI_ST_DATA74DIREN;
@@ -1326,35 +1306,18 @@ static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
pm_runtime_put_autosuspend(mmc_dev(mmc));
}
-static int mmci_get_ro(struct mmc_host *mmc)
-{
- struct mmci_host *host = mmc_priv(mmc);
-
- if (host->gpio_wp == -ENOSYS)
- return -ENOSYS;
-
- return gpio_get_value_cansleep(host->gpio_wp);
-}
-
static int mmci_get_cd(struct mmc_host *mmc)
{
struct mmci_host *host = mmc_priv(mmc);
struct mmci_platform_data *plat = host->plat;
- unsigned int status;
+ unsigned int status = mmc_gpio_get_cd(mmc);
- if (host->gpio_cd == -ENOSYS) {
+ if (status == -ENOSYS) {
if (!plat->status)
return 1; /* Assume always present */
status = plat->status(mmc_dev(host->mmc));
- } else
- status = !!gpio_get_value_cansleep(host->gpio_cd)
- ^ plat->cd_invert;
-
- /*
- * Use positive logic throughout - status is zero for no card,
- * non-zero for card inserted.
- */
+ }
return status;
}
@@ -1391,70 +1354,44 @@ static int mmci_sig_volt_switch(struct mmc_host *mmc, struct mmc_ios *ios)
return ret;
}
-static irqreturn_t mmci_cd_irq(int irq, void *dev_id)
-{
- struct mmci_host *host = dev_id;
-
- mmc_detect_change(host->mmc, msecs_to_jiffies(500));
-
- return IRQ_HANDLED;
-}
-
static struct mmc_host_ops mmci_ops = {
.request = mmci_request,
.pre_req = mmci_pre_request,
.post_req = mmci_post_request,
.set_ios = mmci_set_ios,
- .get_ro = mmci_get_ro,
+ .get_ro = mmc_gpio_get_ro,
.get_cd = mmci_get_cd,
.start_signal_voltage_switch = mmci_sig_volt_switch,
};
-#ifdef CONFIG_OF
-static void mmci_dt_populate_generic_pdata(struct device_node *np,
- struct mmci_platform_data *pdata)
+static int mmci_of_parse(struct device_node *np, struct mmc_host *mmc)
{
- int bus_width = 0;
-
- pdata->gpio_wp = of_get_named_gpio(np, "wp-gpios", 0);
- pdata->gpio_cd = of_get_named_gpio(np, "cd-gpios", 0);
+ struct mmci_host *host = mmc_priv(mmc);
+ int ret = mmc_of_parse(mmc);
- if (of_get_property(np, "cd-inverted", NULL))
- pdata->cd_invert = true;
- else
- pdata->cd_invert = false;
+ if (ret)
+ return ret;
- of_property_read_u32(np, "max-frequency", &pdata->f_max);
- if (!pdata->f_max)
- pr_warn("%s has no 'max-frequency' property\n", np->full_name);
+ if (of_get_property(np, "st,sig-dir-dat0", NULL))
+ host->pwr_reg_add |= MCI_ST_DATA0DIREN;
+ if (of_get_property(np, "st,sig-dir-dat2", NULL))
+ host->pwr_reg_add |= MCI_ST_DATA2DIREN;
+ if (of_get_property(np, "st,sig-dir-dat31", NULL))
+ host->pwr_reg_add |= MCI_ST_DATA31DIREN;
+ if (of_get_property(np, "st,sig-dir-dat74", NULL))
+ host->pwr_reg_add |= MCI_ST_DATA74DIREN;
+ if (of_get_property(np, "st,sig-dir-cmd", NULL))
+ host->pwr_reg_add |= MCI_ST_CMDDIREN;
+ if (of_get_property(np, "st,sig-pin-fbclk", NULL))
+ host->pwr_reg_add |= MCI_ST_FBCLKEN;
if (of_get_property(np, "mmc-cap-mmc-highspeed", NULL))
- pdata->capabilities |= MMC_CAP_MMC_HIGHSPEED;
+ mmc->caps |= MMC_CAP_MMC_HIGHSPEED;
if (of_get_property(np, "mmc-cap-sd-highspeed", NULL))
- pdata->capabilities |= MMC_CAP_SD_HIGHSPEED;
+ mmc->caps |= MMC_CAP_SD_HIGHSPEED;
- of_property_read_u32(np, "bus-width", &bus_width);
- switch (bus_width) {
- case 0 :
- /* No bus-width supplied. */
- break;
- case 4 :
- pdata->capabilities |= MMC_CAP_4_BIT_DATA;
- break;
- case 8 :
- pdata->capabilities |= MMC_CAP_8_BIT_DATA;
- break;
- default :
- pr_warn("%s: Unsupported bus width\n", np->full_name);
- }
-}
-#else
-static void mmci_dt_populate_generic_pdata(struct device_node *np,
- struct mmci_platform_data *pdata)
-{
- return;
+ return 0;
}
-#endif
static int mmci_probe(struct amba_device *dev,
const struct amba_id *id)
@@ -1478,26 +1415,17 @@ static int mmci_probe(struct amba_device *dev,
return -ENOMEM;
}
- if (np)
- mmci_dt_populate_generic_pdata(np, plat);
+ mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);
+ if (!mmc)
+ return -ENOMEM;
- ret = amba_request_regions(dev, DRIVER_NAME);
+ ret = mmci_of_parse(np, mmc);
if (ret)
- goto out;
-
- mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);
- if (!mmc) {
- ret = -ENOMEM;
- goto rel_regions;
- }
+ goto host_free;
host = mmc_priv(mmc);
host->mmc = mmc;
- host->gpio_wp = -ENOSYS;
- host->gpio_cd = -ENOSYS;
- host->gpio_cd_irq = -1;
-
host->hw_designer = amba_manf(dev);
host->hw_revision = amba_rev(dev);
dev_dbg(mmc_dev(mmc), "designer ID = 0x%02x\n", host->hw_designer);
@@ -1529,10 +1457,11 @@ static int mmci_probe(struct amba_device *dev,
dev_dbg(mmc_dev(mmc), "eventual mclk rate: %u Hz\n",
host->mclk);
}
+
host->phybase = dev->res.start;
- host->base = ioremap(dev->res.start, resource_size(&dev->res));
- if (!host->base) {
- ret = -ENOMEM;
+ host->base = devm_ioremap_resource(&dev->dev, &dev->res);
+ if (IS_ERR(host->base)) {
+ ret = PTR_ERR(host->base);
goto clk_disable;
}
@@ -1546,15 +1475,13 @@ static int mmci_probe(struct amba_device *dev,
else
mmc->f_min = DIV_ROUND_UP(host->mclk, 512);
/*
- * If the platform data supplies a maximum operating
- * frequency, this takes precedence. Else, we fall back
- * to using the module parameter, which has a (low)
- * default value in case it is not specified. Either
- * value must not exceed the clock rate into the block,
- * of course.
+ * If no maximum operating frequency is supplied, fall back to use
+ * the module parameter, which has a (low) default value in case it
+ * is not specified. Either value must not exceed the clock rate into
+ * the block, of course.
*/
- if (plat->f_max)
- mmc->f_max = min(host->mclk, plat->f_max);
+ if (mmc->f_max)
+ mmc->f_max = min(host->mclk, mmc->f_max);
else
mmc->f_max = min(host->mclk, fmax);
dev_dbg(mmc_dev(mmc), "clocking block at %u Hz\n", mmc->f_max);
@@ -1566,8 +1493,15 @@ static int mmci_probe(struct amba_device *dev,
else if (plat->ocr_mask)
dev_warn(mmc_dev(mmc), "Platform OCR mask is ignored\n");
- mmc->caps = plat->capabilities;
- mmc->caps2 = plat->capabilities2;
+ /* DT takes precedence over platform data. */
+ if (!np) {
+ if (!plat->cd_invert)
+ mmc->caps2 |= MMC_CAP2_CD_ACTIVE_HIGH;
+ mmc->caps2 |= MMC_CAP2_RO_ACTIVE_HIGH;
+ }
+
+ /* We support these capabilities. */
+ mmc->caps |= MMC_CAP_CMD23;
if (variant->busy_detect) {
mmci_ops.card_busy = mmci_card_busy;
@@ -1579,7 +1513,7 @@ static int mmci_probe(struct amba_device *dev,
mmc->ops = &mmci_ops;
/* We support these PM capabilities. */
- mmc->pm_caps = MMC_PM_KEEP_POWER;
+ mmc->pm_caps |= MMC_PM_KEEP_POWER;
/*
* We can do SGIO
@@ -1616,62 +1550,30 @@ static int mmci_probe(struct amba_device *dev,
writel(0, host->base + MMCIMASK1);
writel(0xfff, host->base + MMCICLEAR);
- if (plat->gpio_cd == -EPROBE_DEFER) {
- ret = -EPROBE_DEFER;
- goto err_gpio_cd;
- }
- if (gpio_is_valid(plat->gpio_cd)) {
- ret = gpio_request(plat->gpio_cd, DRIVER_NAME " (cd)");
- if (ret == 0)
- ret = gpio_direction_input(plat->gpio_cd);
- if (ret == 0)
- host->gpio_cd = plat->gpio_cd;
- else if (ret != -ENOSYS)
- goto err_gpio_cd;
-
- /*
- * A gpio pin that will detect cards when inserted and removed
- * will most likely want to trigger on the edges if it is
- * 0 when ejected and 1 when inserted (or mutatis mutandis
- * for the inverted case) so we request triggers on both
- * edges.
- */
- ret = request_any_context_irq(gpio_to_irq(plat->gpio_cd),
- mmci_cd_irq,
- IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
- DRIVER_NAME " (cd)", host);
- if (ret >= 0)
- host->gpio_cd_irq = gpio_to_irq(plat->gpio_cd);
- }
- if (plat->gpio_wp == -EPROBE_DEFER) {
- ret = -EPROBE_DEFER;
- goto err_gpio_wp;
+ /* If DT, cd/wp gpios must be supplied through it. */
+ if (!np && gpio_is_valid(plat->gpio_cd)) {
+ ret = mmc_gpio_request_cd(mmc, plat->gpio_cd, 0);
+ if (ret)
+ goto clk_disable;
}
- if (gpio_is_valid(plat->gpio_wp)) {
- ret = gpio_request(plat->gpio_wp, DRIVER_NAME " (wp)");
- if (ret == 0)
- ret = gpio_direction_input(plat->gpio_wp);
- if (ret == 0)
- host->gpio_wp = plat->gpio_wp;
- else if (ret != -ENOSYS)
- goto err_gpio_wp;
+ if (!np && gpio_is_valid(plat->gpio_wp)) {
+ ret = mmc_gpio_request_ro(mmc, plat->gpio_wp);
+ if (ret)
+ goto clk_disable;
}
- if ((host->plat->status || host->gpio_cd != -ENOSYS)
- && host->gpio_cd_irq < 0)
- mmc->caps |= MMC_CAP_NEEDS_POLL;
-
- ret = request_irq(dev->irq[0], mmci_irq, IRQF_SHARED, DRIVER_NAME " (cmd)", host);
+ ret = devm_request_irq(&dev->dev, dev->irq[0], mmci_irq, IRQF_SHARED,
+ DRIVER_NAME " (cmd)", host);
if (ret)
- goto unmap;
+ goto clk_disable;
if (!dev->irq[1])
host->singleirq = true;
else {
- ret = request_irq(dev->irq[1], mmci_pio_irq, IRQF_SHARED,
- DRIVER_NAME " (pio)", host);
+ ret = devm_request_irq(&dev->dev, dev->irq[1], mmci_pio_irq,
+ IRQF_SHARED, DRIVER_NAME " (pio)", host);
if (ret)
- goto irq0_free;
+ goto clk_disable;
}
writel(MCI_IRQENABLE, host->base + MMCIMASK0);
@@ -1693,25 +1595,10 @@ static int mmci_probe(struct amba_device *dev,
return 0;
- irq0_free:
- free_irq(dev->irq[0], host);
- unmap:
- if (host->gpio_wp != -ENOSYS)
- gpio_free(host->gpio_wp);
- err_gpio_wp:
- if (host->gpio_cd_irq >= 0)
- free_irq(host->gpio_cd_irq, host);
- if (host->gpio_cd != -ENOSYS)
- gpio_free(host->gpio_cd);
- err_gpio_cd:
- iounmap(host->base);
clk_disable:
clk_disable_unprepare(host->clk);
host_free:
mmc_free_host(mmc);
- rel_regions:
- amba_release_regions(dev);
- out:
return ret;
}
@@ -1737,92 +1624,46 @@ static int mmci_remove(struct amba_device *dev)
writel(0, host->base + MMCIDATACTRL);
mmci_dma_release(host);
- free_irq(dev->irq[0], host);
- if (!host->singleirq)
- free_irq(dev->irq[1], host);
-
- if (host->gpio_wp != -ENOSYS)
- gpio_free(host->gpio_wp);
- if (host->gpio_cd_irq >= 0)
- free_irq(host->gpio_cd_irq, host);
- if (host->gpio_cd != -ENOSYS)
- gpio_free(host->gpio_cd);
-
- iounmap(host->base);
clk_disable_unprepare(host->clk);
-
mmc_free_host(mmc);
-
- amba_release_regions(dev);
- }
-
- return 0;
-}
-
-#ifdef CONFIG_SUSPEND
-static int mmci_suspend(struct device *dev)
-{
- struct amba_device *adev = to_amba_device(dev);
- struct mmc_host *mmc = amba_get_drvdata(adev);
-
- if (mmc) {
- struct mmci_host *host = mmc_priv(mmc);
- pm_runtime_get_sync(dev);
- writel(0, host->base + MMCIMASK0);
}
return 0;
}
-static int mmci_resume(struct device *dev)
-{
- struct amba_device *adev = to_amba_device(dev);
- struct mmc_host *mmc = amba_get_drvdata(adev);
-
- if (mmc) {
- struct mmci_host *host = mmc_priv(mmc);
- writel(MCI_IRQENABLE, host->base + MMCIMASK0);
- pm_runtime_put(dev);
- }
-
- return 0;
-}
-#endif
-
-#ifdef CONFIG_PM_RUNTIME
+#ifdef CONFIG_PM
static void mmci_save(struct mmci_host *host)
{
unsigned long flags;
- if (host->variant->pwrreg_nopower) {
- spin_lock_irqsave(&host->lock, flags);
+ spin_lock_irqsave(&host->lock, flags);
- writel(0, host->base + MMCIMASK0);
+ writel(0, host->base + MMCIMASK0);
+ if (host->variant->pwrreg_nopower) {
writel(0, host->base + MMCIDATACTRL);
writel(0, host->base + MMCIPOWER);
writel(0, host->base + MMCICLOCK);
- mmci_reg_delay(host);
-
- spin_unlock_irqrestore(&host->lock, flags);
}
+ mmci_reg_delay(host);
+ spin_unlock_irqrestore(&host->lock, flags);
}
static void mmci_restore(struct mmci_host *host)
{
unsigned long flags;
- if (host->variant->pwrreg_nopower) {
- spin_lock_irqsave(&host->lock, flags);
+ spin_lock_irqsave(&host->lock, flags);
+ if (host->variant->pwrreg_nopower) {
writel(host->clk_reg, host->base + MMCICLOCK);
writel(host->datactrl_reg, host->base + MMCIDATACTRL);
writel(host->pwr_reg, host->base + MMCIPOWER);
- writel(MCI_IRQENABLE, host->base + MMCIMASK0);
- mmci_reg_delay(host);
-
- spin_unlock_irqrestore(&host->lock, flags);
}
+ writel(MCI_IRQENABLE, host->base + MMCIMASK0);
+ mmci_reg_delay(host);
+
+ spin_unlock_irqrestore(&host->lock, flags);
}
static int mmci_runtime_suspend(struct device *dev)
@@ -1857,8 +1698,9 @@ static int mmci_runtime_resume(struct device *dev)
#endif
static const struct dev_pm_ops mmci_dev_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(mmci_suspend, mmci_resume)
- SET_RUNTIME_PM_OPS(mmci_runtime_suspend, mmci_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_PM_RUNTIME_PM_OPS(mmci_runtime_suspend, mmci_runtime_resume, NULL)
};
static struct amba_id mmci_ids[] = {
diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h
index 58b1b8896bf2..347d942d740b 100644
--- a/drivers/mmc/host/mmci.h
+++ b/drivers/mmc/host/mmci.h
@@ -13,6 +13,16 @@
#define MCI_PWR_ON 0x03
#define MCI_OD (1 << 6)
#define MCI_ROD (1 << 7)
+/*
+ * The ST Micro version does not have ROD and reuse the voltage registers for
+ * direction settings.
+ */
+#define MCI_ST_DATA2DIREN (1 << 2)
+#define MCI_ST_CMDDIREN (1 << 3)
+#define MCI_ST_DATA0DIREN (1 << 4)
+#define MCI_ST_DATA31DIREN (1 << 5)
+#define MCI_ST_FBCLKEN (1 << 7)
+#define MCI_ST_DATA74DIREN (1 << 8)
#define MMCICLOCK 0x004
#define MCI_CLK_ENABLE (1 << 8)
@@ -176,9 +186,6 @@ struct mmci_host {
struct mmc_data *data;
struct mmc_host *mmc;
struct clk *clk;
- int gpio_cd;
- int gpio_wp;
- int gpio_cd_irq;
bool singleirq;
spinlock_t lock;
@@ -186,6 +193,7 @@ struct mmci_host {
unsigned int mclk;
unsigned int cclk;
u32 pwr_reg;
+ u32 pwr_reg_add;
u32 clk_reg;
u32 datactrl_reg;
u32 busy_status;
diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c
new file mode 100644
index 000000000000..e11fafa6fc6b
--- /dev/null
+++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
@@ -0,0 +1,1455 @@
+/* Realtek USB SD/MMC Card Interface driver
+ *
+ * Copyright(c) 2009-2013 Realtek Semiconductor Corp. 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 version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ * Roger Tseng <rogerable@realtek.com>
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/usb.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/card.h>
+#include <linux/scatterlist.h>
+#include <linux/pm_runtime.h>
+
+#include <linux/mfd/rtsx_usb.h>
+#include <asm/unaligned.h>
+
+#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
+#include <linux/leds.h>
+#include <linux/workqueue.h>
+#define RTSX_USB_USE_LEDS_CLASS
+#endif
+
+struct rtsx_usb_sdmmc {
+ struct platform_device *pdev;
+ struct rtsx_ucr *ucr;
+ struct mmc_host *mmc;
+ struct mmc_request *mrq;
+
+ struct mutex host_mutex;
+
+ u8 ssc_depth;
+ unsigned int clock;
+ bool vpclk;
+ bool double_clk;
+ bool host_removal;
+ bool card_exist;
+ bool initial_mode;
+ bool ddr_mode;
+
+ unsigned char power_mode;
+
+#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
+ struct led_classdev led;
+ char led_name[32];
+ struct work_struct led_work;
+#endif
+};
+
+static inline struct device *sdmmc_dev(struct rtsx_usb_sdmmc *host)
+{
+ return &(host->pdev->dev);
+}
+
+static inline void sd_clear_error(struct rtsx_usb_sdmmc *host)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ rtsx_usb_ep0_write_register(ucr, CARD_STOP,
+ SD_STOP | SD_CLR_ERR,
+ SD_STOP | SD_CLR_ERR);
+
+ rtsx_usb_clear_dma_err(ucr);
+ rtsx_usb_clear_fsm_err(ucr);
+}
+
+#ifdef DEBUG
+static void sd_print_debug_regs(struct rtsx_usb_sdmmc *host)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ u8 val = 0;
+
+ rtsx_usb_ep0_read_register(ucr, SD_STAT1, &val);
+ dev_dbg(sdmmc_dev(host), "SD_STAT1: 0x%x\n", val);
+ rtsx_usb_ep0_read_register(ucr, SD_STAT2, &val);
+ dev_dbg(sdmmc_dev(host), "SD_STAT2: 0x%x\n", val);
+ rtsx_usb_ep0_read_register(ucr, SD_BUS_STAT, &val);
+ dev_dbg(sdmmc_dev(host), "SD_BUS_STAT: 0x%x\n", val);
+}
+#else
+#define sd_print_debug_regs(host)
+#endif /* DEBUG */
+
+static int sd_read_data(struct rtsx_usb_sdmmc *host, struct mmc_command *cmd,
+ u16 byte_cnt, u8 *buf, int buf_len, int timeout)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ int err;
+ u8 trans_mode;
+
+ if (!buf)
+ buf_len = 0;
+
+ rtsx_usb_init_cmd(ucr);
+ if (cmd != NULL) {
+ dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD%d\n", __func__
+ , cmd->opcode);
+ if (cmd->opcode == MMC_SEND_TUNING_BLOCK)
+ trans_mode = SD_TM_AUTO_TUNING;
+ else
+ trans_mode = SD_TM_NORMAL_READ;
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ SD_CMD0, 0xFF, (u8)(cmd->opcode) | 0x40);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ SD_CMD1, 0xFF, (u8)(cmd->arg >> 24));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ SD_CMD2, 0xFF, (u8)(cmd->arg >> 16));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ SD_CMD3, 0xFF, (u8)(cmd->arg >> 8));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ SD_CMD4, 0xFF, (u8)cmd->arg);
+ } else {
+ trans_mode = SD_TM_AUTO_READ_3;
+ }
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BYTE_CNT_L, 0xFF, (u8)byte_cnt);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BYTE_CNT_H,
+ 0xFF, (u8)(byte_cnt >> 8));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BLOCK_CNT_L, 0xFF, 1);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BLOCK_CNT_H, 0xFF, 0);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG2, 0xFF,
+ SD_CALCULATE_CRC7 | SD_CHECK_CRC16 |
+ SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_6);
+ if (trans_mode != SD_TM_AUTO_TUNING)
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ CARD_DATA_SOURCE, 0x01, PINGPONG_BUFFER);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_TRANSFER,
+ 0xFF, trans_mode | SD_TRANSFER_START);
+ rtsx_usb_add_cmd(ucr, CHECK_REG_CMD, SD_TRANSFER,
+ SD_TRANSFER_END, SD_TRANSFER_END);
+
+ if (cmd != NULL) {
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD1, 0, 0);
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD2, 0, 0);
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD3, 0, 0);
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD4, 0, 0);
+ }
+
+ err = rtsx_usb_send_cmd(ucr, MODE_CR, timeout);
+ if (err) {
+ dev_dbg(sdmmc_dev(host),
+ "rtsx_usb_send_cmd failed (err = %d)\n", err);
+ return err;
+ }
+
+ err = rtsx_usb_get_rsp(ucr, !cmd ? 1 : 5, timeout);
+ if (err || (ucr->rsp_buf[0] & SD_TRANSFER_ERR)) {
+ sd_print_debug_regs(host);
+
+ if (!err) {
+ dev_dbg(sdmmc_dev(host),
+ "Transfer failed (SD_TRANSFER = %02x)\n",
+ ucr->rsp_buf[0]);
+ err = -EIO;
+ } else {
+ dev_dbg(sdmmc_dev(host),
+ "rtsx_usb_get_rsp failed (err = %d)\n", err);
+ }
+
+ return err;
+ }
+
+ if (cmd != NULL) {
+ cmd->resp[0] = get_unaligned_be32(ucr->rsp_buf + 1);
+ dev_dbg(sdmmc_dev(host), "cmd->resp[0] = 0x%08x\n",
+ cmd->resp[0]);
+ }
+
+ if (buf && buf_len) {
+ /* 2-byte aligned part */
+ err = rtsx_usb_read_ppbuf(ucr, buf, byte_cnt - (byte_cnt % 2));
+ if (err) {
+ dev_dbg(sdmmc_dev(host),
+ "rtsx_usb_read_ppbuf failed (err = %d)\n", err);
+ return err;
+ }
+
+ /* unaligned byte */
+ if (byte_cnt % 2)
+ return rtsx_usb_read_register(ucr,
+ PPBUF_BASE2 + byte_cnt,
+ buf + byte_cnt - 1);
+ }
+
+ return 0;
+}
+
+static int sd_write_data(struct rtsx_usb_sdmmc *host, struct mmc_command *cmd,
+ u16 byte_cnt, u8 *buf, int buf_len, int timeout)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ int err;
+ u8 trans_mode;
+
+ if (!buf)
+ buf_len = 0;
+
+ if (buf && buf_len) {
+ err = rtsx_usb_write_ppbuf(ucr, buf, buf_len);
+ if (err) {
+ dev_dbg(sdmmc_dev(host),
+ "rtsx_usb_write_ppbuf failed (err = %d)\n",
+ err);
+ return err;
+ }
+ }
+
+ trans_mode = (cmd != NULL) ? SD_TM_AUTO_WRITE_2 : SD_TM_AUTO_WRITE_3;
+ rtsx_usb_init_cmd(ucr);
+
+ if (cmd != NULL) {
+ dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD%d\n", __func__,
+ cmd->opcode);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ SD_CMD0, 0xFF, (u8)(cmd->opcode) | 0x40);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ SD_CMD1, 0xFF, (u8)(cmd->arg >> 24));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ SD_CMD2, 0xFF, (u8)(cmd->arg >> 16));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ SD_CMD3, 0xFF, (u8)(cmd->arg >> 8));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ SD_CMD4, 0xFF, (u8)cmd->arg);
+ }
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BYTE_CNT_L, 0xFF, (u8)byte_cnt);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BYTE_CNT_H,
+ 0xFF, (u8)(byte_cnt >> 8));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BLOCK_CNT_L, 0xFF, 1);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BLOCK_CNT_H, 0xFF, 0);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG2, 0xFF,
+ SD_CALCULATE_CRC7 | SD_CHECK_CRC16 |
+ SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_6);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ CARD_DATA_SOURCE, 0x01, PINGPONG_BUFFER);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_TRANSFER, 0xFF,
+ trans_mode | SD_TRANSFER_START);
+ rtsx_usb_add_cmd(ucr, CHECK_REG_CMD, SD_TRANSFER,
+ SD_TRANSFER_END, SD_TRANSFER_END);
+
+ if (cmd != NULL) {
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD1, 0, 0);
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD2, 0, 0);
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD3, 0, 0);
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD4, 0, 0);
+ }
+
+ err = rtsx_usb_send_cmd(ucr, MODE_CR, timeout);
+ if (err) {
+ dev_dbg(sdmmc_dev(host),
+ "rtsx_usb_send_cmd failed (err = %d)\n", err);
+ return err;
+ }
+
+ err = rtsx_usb_get_rsp(ucr, !cmd ? 1 : 5, timeout);
+ if (err) {
+ sd_print_debug_regs(host);
+ dev_dbg(sdmmc_dev(host),
+ "rtsx_usb_get_rsp failed (err = %d)\n", err);
+ return err;
+ }
+
+ if (cmd != NULL) {
+ cmd->resp[0] = get_unaligned_be32(ucr->rsp_buf + 1);
+ dev_dbg(sdmmc_dev(host), "cmd->resp[0] = 0x%08x\n",
+ cmd->resp[0]);
+ }
+
+ return 0;
+}
+
+static void sd_send_cmd_get_rsp(struct rtsx_usb_sdmmc *host,
+ struct mmc_command *cmd)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ u8 cmd_idx = (u8)cmd->opcode;
+ u32 arg = cmd->arg;
+ int err = 0;
+ int timeout = 100;
+ int i;
+ u8 *ptr;
+ int stat_idx = 0;
+ int len = 2;
+ u8 rsp_type;
+
+ dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD %d, arg = 0x%08x\n",
+ __func__, cmd_idx, arg);
+
+ /* Response type:
+ * R0
+ * R1, R5, R6, R7
+ * R1b
+ * R2
+ * R3, R4
+ */
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_NONE:
+ rsp_type = SD_RSP_TYPE_R0;
+ break;
+ case MMC_RSP_R1:
+ rsp_type = SD_RSP_TYPE_R1;
+ break;
+ case MMC_RSP_R1 & ~MMC_RSP_CRC:
+ rsp_type = SD_RSP_TYPE_R1 | SD_NO_CHECK_CRC7;
+ break;
+ case MMC_RSP_R1B:
+ rsp_type = SD_RSP_TYPE_R1b;
+ break;
+ case MMC_RSP_R2:
+ rsp_type = SD_RSP_TYPE_R2;
+ break;
+ case MMC_RSP_R3:
+ rsp_type = SD_RSP_TYPE_R3;
+ break;
+ default:
+ dev_dbg(sdmmc_dev(host), "cmd->flag is not valid\n");
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (rsp_type == SD_RSP_TYPE_R1b)
+ timeout = 3000;
+
+ if (cmd->opcode == SD_SWITCH_VOLTAGE) {
+ err = rtsx_usb_write_register(ucr, SD_BUS_STAT,
+ SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP,
+ SD_CLK_TOGGLE_EN);
+ if (err)
+ goto out;
+ }
+
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CMD0, 0xFF, 0x40 | cmd_idx);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CMD1, 0xFF, (u8)(arg >> 24));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CMD2, 0xFF, (u8)(arg >> 16));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CMD3, 0xFF, (u8)(arg >> 8));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CMD4, 0xFF, (u8)arg);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG2, 0xFF, rsp_type);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_DATA_SOURCE,
+ 0x01, PINGPONG_BUFFER);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_TRANSFER,
+ 0xFF, SD_TM_CMD_RSP | SD_TRANSFER_START);
+ rtsx_usb_add_cmd(ucr, CHECK_REG_CMD, SD_TRANSFER,
+ SD_TRANSFER_END | SD_STAT_IDLE,
+ SD_TRANSFER_END | SD_STAT_IDLE);
+
+ if (rsp_type == SD_RSP_TYPE_R2) {
+ /* Read data from ping-pong buffer */
+ for (i = PPBUF_BASE2; i < PPBUF_BASE2 + 16; i++)
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, (u16)i, 0, 0);
+ stat_idx = 16;
+ } else if (rsp_type != SD_RSP_TYPE_R0) {
+ /* Read data from SD_CMDx registers */
+ for (i = SD_CMD0; i <= SD_CMD4; i++)
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, (u16)i, 0, 0);
+ stat_idx = 5;
+ }
+ len += stat_idx;
+
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_STAT1, 0, 0);
+
+ err = rtsx_usb_send_cmd(ucr, MODE_CR, 100);
+ if (err) {
+ dev_dbg(sdmmc_dev(host),
+ "rtsx_usb_send_cmd error (err = %d)\n", err);
+ goto out;
+ }
+
+ err = rtsx_usb_get_rsp(ucr, len, timeout);
+ if (err || (ucr->rsp_buf[0] & SD_TRANSFER_ERR)) {
+ sd_print_debug_regs(host);
+ sd_clear_error(host);
+
+ if (!err) {
+ dev_dbg(sdmmc_dev(host),
+ "Transfer failed (SD_TRANSFER = %02x)\n",
+ ucr->rsp_buf[0]);
+ err = -EIO;
+ } else {
+ dev_dbg(sdmmc_dev(host),
+ "rtsx_usb_get_rsp failed (err = %d)\n", err);
+ }
+
+ goto out;
+ }
+
+ if (rsp_type == SD_RSP_TYPE_R0) {
+ err = 0;
+ goto out;
+ }
+
+ /* Skip result of CHECK_REG_CMD */
+ ptr = ucr->rsp_buf + 1;
+
+ /* Check (Start,Transmission) bit of Response */
+ if ((ptr[0] & 0xC0) != 0) {
+ err = -EILSEQ;
+ dev_dbg(sdmmc_dev(host), "Invalid response bit\n");
+ goto out;
+ }
+
+ /* Check CRC7 */
+ if (!(rsp_type & SD_NO_CHECK_CRC7)) {
+ if (ptr[stat_idx] & SD_CRC7_ERR) {
+ err = -EILSEQ;
+ dev_dbg(sdmmc_dev(host), "CRC7 error\n");
+ goto out;
+ }
+ }
+
+ if (rsp_type == SD_RSP_TYPE_R2) {
+ for (i = 0; i < 4; i++) {
+ cmd->resp[i] = get_unaligned_be32(ptr + 1 + i * 4);
+ dev_dbg(sdmmc_dev(host), "cmd->resp[%d] = 0x%08x\n",
+ i, cmd->resp[i]);
+ }
+ } else {
+ cmd->resp[0] = get_unaligned_be32(ptr + 1);
+ dev_dbg(sdmmc_dev(host), "cmd->resp[0] = 0x%08x\n",
+ cmd->resp[0]);
+ }
+
+out:
+ cmd->error = err;
+}
+
+static int sd_rw_multi(struct rtsx_usb_sdmmc *host, struct mmc_request *mrq)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ struct mmc_data *data = mrq->data;
+ int read = (data->flags & MMC_DATA_READ) ? 1 : 0;
+ u8 cfg2, trans_mode;
+ int err;
+ u8 flag;
+ size_t data_len = data->blksz * data->blocks;
+ unsigned int pipe;
+
+ if (read) {
+ dev_dbg(sdmmc_dev(host), "%s: read %zu bytes\n",
+ __func__, data_len);
+ cfg2 = SD_CALCULATE_CRC7 | SD_CHECK_CRC16 |
+ SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_0;
+ trans_mode = SD_TM_AUTO_READ_3;
+ } else {
+ dev_dbg(sdmmc_dev(host), "%s: write %zu bytes\n",
+ __func__, data_len);
+ cfg2 = SD_NO_CALCULATE_CRC7 | SD_CHECK_CRC16 |
+ SD_NO_WAIT_BUSY_END | SD_NO_CHECK_CRC7 | SD_RSP_LEN_0;
+ trans_mode = SD_TM_AUTO_WRITE_3;
+ }
+
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BYTE_CNT_L, 0xFF, 0x00);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BYTE_CNT_H, 0xFF, 0x02);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BLOCK_CNT_L,
+ 0xFF, (u8)data->blocks);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BLOCK_CNT_H,
+ 0xFF, (u8)(data->blocks >> 8));
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_DATA_SOURCE,
+ 0x01, RING_BUFFER);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC3,
+ 0xFF, (u8)(data_len >> 24));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC2,
+ 0xFF, (u8)(data_len >> 16));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC1,
+ 0xFF, (u8)(data_len >> 8));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC0,
+ 0xFF, (u8)data_len);
+ if (read) {
+ flag = MODE_CDIR;
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_CTL,
+ 0x03 | DMA_PACK_SIZE_MASK,
+ DMA_DIR_FROM_CARD | DMA_EN | DMA_512);
+ } else {
+ flag = MODE_CDOR;
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_CTL,
+ 0x03 | DMA_PACK_SIZE_MASK,
+ DMA_DIR_TO_CARD | DMA_EN | DMA_512);
+ }
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG2, 0xFF, cfg2);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_TRANSFER, 0xFF,
+ trans_mode | SD_TRANSFER_START);
+ rtsx_usb_add_cmd(ucr, CHECK_REG_CMD, SD_TRANSFER,
+ SD_TRANSFER_END, SD_TRANSFER_END);
+
+ err = rtsx_usb_send_cmd(ucr, flag, 100);
+ if (err)
+ return err;
+
+ if (read)
+ pipe = usb_rcvbulkpipe(ucr->pusb_dev, EP_BULK_IN);
+ else
+ pipe = usb_sndbulkpipe(ucr->pusb_dev, EP_BULK_OUT);
+
+ err = rtsx_usb_transfer_data(ucr, pipe, data->sg, data_len,
+ data->sg_len, NULL, 10000);
+ if (err) {
+ dev_dbg(sdmmc_dev(host), "rtsx_usb_transfer_data error %d\n"
+ , err);
+ sd_clear_error(host);
+ return err;
+ }
+
+ return rtsx_usb_get_rsp(ucr, 1, 2000);
+}
+
+static inline void sd_enable_initial_mode(struct rtsx_usb_sdmmc *host)
+{
+ rtsx_usb_write_register(host->ucr, SD_CFG1,
+ SD_CLK_DIVIDE_MASK, SD_CLK_DIVIDE_128);
+}
+
+static inline void sd_disable_initial_mode(struct rtsx_usb_sdmmc *host)
+{
+ rtsx_usb_write_register(host->ucr, SD_CFG1,
+ SD_CLK_DIVIDE_MASK, SD_CLK_DIVIDE_0);
+}
+
+static void sd_normal_rw(struct rtsx_usb_sdmmc *host,
+ struct mmc_request *mrq)
+{
+ struct mmc_command *cmd = mrq->cmd;
+ struct mmc_data *data = mrq->data;
+ u8 *buf;
+
+ buf = kzalloc(data->blksz, GFP_NOIO);
+ if (!buf) {
+ cmd->error = -ENOMEM;
+ return;
+ }
+
+ if (data->flags & MMC_DATA_READ) {
+ if (host->initial_mode)
+ sd_disable_initial_mode(host);
+
+ cmd->error = sd_read_data(host, cmd, (u16)data->blksz, buf,
+ data->blksz, 200);
+
+ if (host->initial_mode)
+ sd_enable_initial_mode(host);
+
+ sg_copy_from_buffer(data->sg, data->sg_len, buf, data->blksz);
+ } else {
+ sg_copy_to_buffer(data->sg, data->sg_len, buf, data->blksz);
+
+ cmd->error = sd_write_data(host, cmd, (u16)data->blksz, buf,
+ data->blksz, 200);
+ }
+
+ kfree(buf);
+}
+
+static int sd_change_phase(struct rtsx_usb_sdmmc *host, u8 sample_point, int tx)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ int err;
+
+ dev_dbg(sdmmc_dev(host), "%s: %s sample_point = %d\n",
+ __func__, tx ? "TX" : "RX", sample_point);
+
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CLK_DIV, CLK_CHANGE, CLK_CHANGE);
+
+ if (tx)
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_VPCLK0_CTL,
+ 0x0F, sample_point);
+ else
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_VPCLK1_CTL,
+ 0x0F, sample_point);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_VPCLK0_CTL, PHASE_NOT_RESET, 0);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_VPCLK0_CTL,
+ PHASE_NOT_RESET, PHASE_NOT_RESET);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CLK_DIV, CLK_CHANGE, 0);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG1, SD_ASYNC_FIFO_RST, 0);
+
+ err = rtsx_usb_send_cmd(ucr, MODE_C, 100);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static inline u32 get_phase_point(u32 phase_map, unsigned int idx)
+{
+ idx &= MAX_PHASE;
+ return phase_map & (1 << idx);
+}
+
+static int get_phase_len(u32 phase_map, unsigned int idx)
+{
+ int i;
+
+ for (i = 0; i < MAX_PHASE + 1; i++) {
+ if (get_phase_point(phase_map, idx + i) == 0)
+ return i;
+ }
+ return MAX_PHASE + 1;
+}
+
+static u8 sd_search_final_phase(struct rtsx_usb_sdmmc *host, u32 phase_map)
+{
+ int start = 0, len = 0;
+ int start_final = 0, len_final = 0;
+ u8 final_phase = 0xFF;
+
+ if (phase_map == 0) {
+ dev_dbg(sdmmc_dev(host), "Phase: [map:%x]\n", phase_map);
+ return final_phase;
+ }
+
+ while (start < MAX_PHASE + 1) {
+ len = get_phase_len(phase_map, start);
+ if (len_final < len) {
+ start_final = start;
+ len_final = len;
+ }
+ start += len ? len : 1;
+ }
+
+ final_phase = (start_final + len_final / 2) & MAX_PHASE;
+ dev_dbg(sdmmc_dev(host), "Phase: [map:%x] [maxlen:%d] [final:%d]\n",
+ phase_map, len_final, final_phase);
+
+ return final_phase;
+}
+
+static void sd_wait_data_idle(struct rtsx_usb_sdmmc *host)
+{
+ int err, i;
+ u8 val = 0;
+
+ for (i = 0; i < 100; i++) {
+ err = rtsx_usb_ep0_read_register(host->ucr,
+ SD_DATA_STATE, &val);
+ if (val & SD_DATA_IDLE)
+ return;
+
+ usleep_range(100, 1000);
+ }
+}
+
+static int sd_tuning_rx_cmd(struct rtsx_usb_sdmmc *host,
+ u8 opcode, u8 sample_point)
+{
+ int err;
+ struct mmc_command cmd = {0};
+
+ err = sd_change_phase(host, sample_point, 0);
+ if (err)
+ return err;
+
+ cmd.opcode = MMC_SEND_TUNING_BLOCK;
+ err = sd_read_data(host, &cmd, 0x40, NULL, 0, 100);
+ if (err) {
+ /* Wait till SD DATA IDLE */
+ sd_wait_data_idle(host);
+ sd_clear_error(host);
+ return err;
+ }
+
+ return 0;
+}
+
+static void sd_tuning_phase(struct rtsx_usb_sdmmc *host,
+ u8 opcode, u16 *phase_map)
+{
+ int err, i;
+ u16 raw_phase_map = 0;
+
+ for (i = MAX_PHASE; i >= 0; i--) {
+ err = sd_tuning_rx_cmd(host, opcode, (u8)i);
+ if (!err)
+ raw_phase_map |= 1 << i;
+ }
+
+ if (phase_map)
+ *phase_map = raw_phase_map;
+}
+
+static int sd_tuning_rx(struct rtsx_usb_sdmmc *host, u8 opcode)
+{
+ int err, i;
+ u16 raw_phase_map[RX_TUNING_CNT] = {0}, phase_map;
+ u8 final_phase;
+
+ /* setting fixed default TX phase */
+ err = sd_change_phase(host, 0x01, 1);
+ if (err) {
+ dev_dbg(sdmmc_dev(host), "TX phase setting failed\n");
+ return err;
+ }
+
+ /* tuning RX phase */
+ for (i = 0; i < RX_TUNING_CNT; i++) {
+ sd_tuning_phase(host, opcode, &(raw_phase_map[i]));
+
+ if (raw_phase_map[i] == 0)
+ break;
+ }
+
+ phase_map = 0xFFFF;
+ for (i = 0; i < RX_TUNING_CNT; i++) {
+ dev_dbg(sdmmc_dev(host), "RX raw_phase_map[%d] = 0x%04x\n",
+ i, raw_phase_map[i]);
+ phase_map &= raw_phase_map[i];
+ }
+ dev_dbg(sdmmc_dev(host), "RX phase_map = 0x%04x\n", phase_map);
+
+ if (phase_map) {
+ final_phase = sd_search_final_phase(host, phase_map);
+ if (final_phase == 0xFF)
+ return -EINVAL;
+
+ err = sd_change_phase(host, final_phase, 0);
+ if (err)
+ return err;
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sdmmc_get_ro(struct mmc_host *mmc)
+{
+ struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
+ struct rtsx_ucr *ucr = host->ucr;
+ int err;
+ u16 val;
+
+ if (host->host_removal)
+ return -ENOMEDIUM;
+
+ mutex_lock(&ucr->dev_mutex);
+
+ /* Check SD card detect */
+ err = rtsx_usb_get_card_status(ucr, &val);
+
+ mutex_unlock(&ucr->dev_mutex);
+
+
+ /* Treat failed detection as non-ro */
+ if (err)
+ return 0;
+
+ if (val & SD_WP)
+ return 1;
+
+ return 0;
+}
+
+static int sdmmc_get_cd(struct mmc_host *mmc)
+{
+ struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
+ struct rtsx_ucr *ucr = host->ucr;
+ int err;
+ u16 val;
+
+ if (host->host_removal)
+ return -ENOMEDIUM;
+
+ mutex_lock(&ucr->dev_mutex);
+
+ /* Check SD card detect */
+ err = rtsx_usb_get_card_status(ucr, &val);
+
+ mutex_unlock(&ucr->dev_mutex);
+
+ /* Treat failed detection as non-exist */
+ if (err)
+ goto no_card;
+
+ if (val & SD_CD) {
+ host->card_exist = true;
+ return 1;
+ }
+
+no_card:
+ host->card_exist = false;
+ return 0;
+}
+
+static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
+ struct rtsx_ucr *ucr = host->ucr;
+ struct mmc_command *cmd = mrq->cmd;
+ struct mmc_data *data = mrq->data;
+ unsigned int data_size = 0;
+
+ dev_dbg(sdmmc_dev(host), "%s\n", __func__);
+
+ if (host->host_removal) {
+ cmd->error = -ENOMEDIUM;
+ goto finish;
+ }
+
+ if ((!host->card_exist)) {
+ cmd->error = -ENOMEDIUM;
+ goto finish_detect_card;
+ }
+
+ /*
+ * Reject SDIO CMDs to speed up card identification
+ * since unsupported
+ */
+ if (cmd->opcode == SD_IO_SEND_OP_COND ||
+ cmd->opcode == SD_IO_RW_DIRECT ||
+ cmd->opcode == SD_IO_RW_EXTENDED) {
+ cmd->error = -EINVAL;
+ goto finish;
+ }
+
+ mutex_lock(&ucr->dev_mutex);
+
+ mutex_lock(&host->host_mutex);
+ host->mrq = mrq;
+ mutex_unlock(&host->host_mutex);
+
+ if (mrq->data)
+ data_size = data->blocks * data->blksz;
+
+ if (!data_size) {
+ sd_send_cmd_get_rsp(host, cmd);
+ } else if ((!(data_size % 512) && cmd->opcode != MMC_SEND_EXT_CSD) ||
+ mmc_op_multi(cmd->opcode)) {
+ sd_send_cmd_get_rsp(host, cmd);
+
+ if (!cmd->error) {
+ sd_rw_multi(host, mrq);
+
+ if (mmc_op_multi(cmd->opcode) && mrq->stop) {
+ sd_send_cmd_get_rsp(host, mrq->stop);
+ rtsx_usb_write_register(ucr, MC_FIFO_CTL,
+ FIFO_FLUSH, FIFO_FLUSH);
+ }
+ }
+ } else {
+ sd_normal_rw(host, mrq);
+ }
+
+ if (mrq->data) {
+ if (cmd->error || data->error)
+ data->bytes_xfered = 0;
+ else
+ data->bytes_xfered = data->blocks * data->blksz;
+ }
+
+ mutex_unlock(&ucr->dev_mutex);
+
+finish_detect_card:
+ if (cmd->error) {
+ /*
+ * detect card when fail to update card existence state and
+ * speed up card removal when retry
+ */
+ sdmmc_get_cd(mmc);
+ dev_dbg(sdmmc_dev(host), "cmd->error = %d\n", cmd->error);
+ }
+
+finish:
+ mutex_lock(&host->host_mutex);
+ host->mrq = NULL;
+ mutex_unlock(&host->host_mutex);
+
+ mmc_request_done(mmc, mrq);
+}
+
+static int sd_set_bus_width(struct rtsx_usb_sdmmc *host,
+ unsigned char bus_width)
+{
+ int err = 0;
+ u8 width[] = {
+ [MMC_BUS_WIDTH_1] = SD_BUS_WIDTH_1BIT,
+ [MMC_BUS_WIDTH_4] = SD_BUS_WIDTH_4BIT,
+ [MMC_BUS_WIDTH_8] = SD_BUS_WIDTH_8BIT,
+ };
+
+ if (bus_width <= MMC_BUS_WIDTH_8)
+ err = rtsx_usb_write_register(host->ucr, SD_CFG1,
+ 0x03, width[bus_width]);
+
+ return err;
+}
+
+static int sd_pull_ctl_disable_lqfp48(struct rtsx_ucr *ucr)
+{
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0x95);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0xA5);
+
+ return rtsx_usb_send_cmd(ucr, MODE_C, 100);
+}
+
+static int sd_pull_ctl_disable_qfn24(struct rtsx_ucr *ucr)
+{
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0x65);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0x95);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x56);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0x59);
+
+ return rtsx_usb_send_cmd(ucr, MODE_C, 100);
+}
+
+static int sd_pull_ctl_enable_lqfp48(struct rtsx_ucr *ucr)
+{
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0xAA);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0xAA);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0xA9);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0xA5);
+
+ return rtsx_usb_send_cmd(ucr, MODE_C, 100);
+}
+
+static int sd_pull_ctl_enable_qfn24(struct rtsx_ucr *ucr)
+{
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0xA5);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0x9A);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0xA5);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x9A);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x65);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0x5A);
+
+ return rtsx_usb_send_cmd(ucr, MODE_C, 100);
+}
+
+static int sd_power_on(struct rtsx_usb_sdmmc *host)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ int err;
+
+ dev_dbg(sdmmc_dev(host), "%s\n", __func__);
+ rtsx_usb_init_cmd(ucr);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SELECT, 0x07, SD_MOD_SEL);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SHARE_MODE,
+ CARD_SHARE_MASK, CARD_SHARE_SD);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_EN,
+ SD_CLK_EN, SD_CLK_EN);
+ err = rtsx_usb_send_cmd(ucr, MODE_C, 100);
+ if (err)
+ return err;
+
+ if (CHECK_PKG(ucr, LQFP48))
+ err = sd_pull_ctl_enable_lqfp48(ucr);
+ else
+ err = sd_pull_ctl_enable_qfn24(ucr);
+ if (err)
+ return err;
+
+ err = rtsx_usb_write_register(ucr, CARD_PWR_CTL,
+ POWER_MASK, PARTIAL_POWER_ON);
+ if (err)
+ return err;
+
+ usleep_range(800, 1000);
+
+ rtsx_usb_init_cmd(ucr);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PWR_CTL,
+ POWER_MASK|LDO3318_PWR_MASK, POWER_ON|LDO_ON);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_OE,
+ SD_OUTPUT_EN, SD_OUTPUT_EN);
+
+ return rtsx_usb_send_cmd(ucr, MODE_C, 100);
+}
+
+static int sd_power_off(struct rtsx_usb_sdmmc *host)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ int err;
+
+ dev_dbg(sdmmc_dev(host), "%s\n", __func__);
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_EN, SD_CLK_EN, 0);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_OE, SD_OUTPUT_EN, 0);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PWR_CTL,
+ POWER_MASK, POWER_OFF);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PWR_CTL,
+ POWER_MASK|LDO3318_PWR_MASK, POWER_OFF|LDO_SUSPEND);
+
+ err = rtsx_usb_send_cmd(ucr, MODE_C, 100);
+ if (err)
+ return err;
+
+ if (CHECK_PKG(ucr, LQFP48))
+ return sd_pull_ctl_disable_lqfp48(ucr);
+ return sd_pull_ctl_disable_qfn24(ucr);
+}
+
+static int sd_set_power_mode(struct rtsx_usb_sdmmc *host,
+ unsigned char power_mode)
+{
+ int err;
+
+ if (power_mode != MMC_POWER_OFF)
+ power_mode = MMC_POWER_ON;
+
+ if (power_mode == host->power_mode)
+ return 0;
+
+ if (power_mode == MMC_POWER_OFF) {
+ err = sd_power_off(host);
+ pm_runtime_put(sdmmc_dev(host));
+ } else {
+ pm_runtime_get_sync(sdmmc_dev(host));
+ err = sd_power_on(host);
+ }
+
+ if (!err)
+ host->power_mode = power_mode;
+
+ return err;
+}
+
+static int sd_set_timing(struct rtsx_usb_sdmmc *host,
+ unsigned char timing, bool *ddr_mode)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ int err;
+
+ *ddr_mode = false;
+
+ rtsx_usb_init_cmd(ucr);
+
+ switch (timing) {
+ case MMC_TIMING_UHS_SDR104:
+ case MMC_TIMING_UHS_SDR50:
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG1,
+ 0x0C | SD_ASYNC_FIFO_RST,
+ SD_30_MODE | SD_ASYNC_FIFO_RST);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF,
+ CRC_VAR_CLK0 | SD30_FIX_CLK | SAMPLE_VAR_CLK1);
+ break;
+
+ case MMC_TIMING_UHS_DDR50:
+ *ddr_mode = true;
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG1,
+ 0x0C | SD_ASYNC_FIFO_RST,
+ SD_DDR_MODE | SD_ASYNC_FIFO_RST);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF,
+ CRC_VAR_CLK0 | SD30_FIX_CLK | SAMPLE_VAR_CLK1);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_PUSH_POINT_CTL,
+ DDR_VAR_TX_CMD_DAT, DDR_VAR_TX_CMD_DAT);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_SAMPLE_POINT_CTL,
+ DDR_VAR_RX_DAT | DDR_VAR_RX_CMD,
+ DDR_VAR_RX_DAT | DDR_VAR_RX_CMD);
+ break;
+
+ case MMC_TIMING_MMC_HS:
+ case MMC_TIMING_SD_HS:
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG1,
+ 0x0C, SD_20_MODE);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF,
+ CRC_FIX_CLK | SD30_VAR_CLK0 | SAMPLE_VAR_CLK1);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_PUSH_POINT_CTL,
+ SD20_TX_SEL_MASK, SD20_TX_14_AHEAD);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_SAMPLE_POINT_CTL,
+ SD20_RX_SEL_MASK, SD20_RX_14_DELAY);
+ break;
+
+ default:
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ SD_CFG1, 0x0C, SD_20_MODE);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF,
+ CRC_FIX_CLK | SD30_VAR_CLK0 | SAMPLE_VAR_CLK1);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ SD_PUSH_POINT_CTL, 0xFF, 0);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_SAMPLE_POINT_CTL,
+ SD20_RX_SEL_MASK, SD20_RX_POS_EDGE);
+ break;
+ }
+
+ err = rtsx_usb_send_cmd(ucr, MODE_C, 100);
+
+ return err;
+}
+
+static void sdmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
+ struct rtsx_ucr *ucr = host->ucr;
+
+ dev_dbg(sdmmc_dev(host), "%s\n", __func__);
+ mutex_lock(&ucr->dev_mutex);
+
+ if (rtsx_usb_card_exclusive_check(ucr, RTSX_USB_SD_CARD)) {
+ mutex_unlock(&ucr->dev_mutex);
+ return;
+ }
+
+ sd_set_power_mode(host, ios->power_mode);
+ sd_set_bus_width(host, ios->bus_width);
+ sd_set_timing(host, ios->timing, &host->ddr_mode);
+
+ host->vpclk = false;
+ host->double_clk = true;
+
+ switch (ios->timing) {
+ case MMC_TIMING_UHS_SDR104:
+ case MMC_TIMING_UHS_SDR50:
+ host->ssc_depth = SSC_DEPTH_2M;
+ host->vpclk = true;
+ host->double_clk = false;
+ break;
+ case MMC_TIMING_UHS_DDR50:
+ case MMC_TIMING_UHS_SDR25:
+ host->ssc_depth = SSC_DEPTH_1M;
+ break;
+ default:
+ host->ssc_depth = SSC_DEPTH_512K;
+ break;
+ }
+
+ host->initial_mode = (ios->clock <= 1000000) ? true : false;
+ host->clock = ios->clock;
+
+ rtsx_usb_switch_clock(host->ucr, host->clock, host->ssc_depth,
+ host->initial_mode, host->double_clk, host->vpclk);
+
+ mutex_unlock(&ucr->dev_mutex);
+ dev_dbg(sdmmc_dev(host), "%s end\n", __func__);
+}
+
+static int sdmmc_switch_voltage(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
+ struct rtsx_ucr *ucr = host->ucr;
+ int err = 0;
+
+ dev_dbg(sdmmc_dev(host), "%s: signal_voltage = %d\n",
+ __func__, ios->signal_voltage);
+
+ if (host->host_removal)
+ return -ENOMEDIUM;
+
+ if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_120)
+ return -EPERM;
+
+ mutex_lock(&ucr->dev_mutex);
+
+ err = rtsx_usb_card_exclusive_check(ucr, RTSX_USB_SD_CARD);
+ if (err) {
+ mutex_unlock(&ucr->dev_mutex);
+ return err;
+ }
+
+ /* Let mmc core do the busy checking, simply stop the forced-toggle
+ * clock(while issuing CMD11) and switch voltage.
+ */
+ rtsx_usb_init_cmd(ucr);
+
+ if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330) {
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_PAD_CTL,
+ SD_IO_USING_1V8, SD_IO_USING_3V3);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, LDO_POWER_CFG,
+ TUNE_SD18_MASK, TUNE_SD18_3V3);
+ } else {
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BUS_STAT,
+ SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP,
+ SD_CLK_FORCE_STOP);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_PAD_CTL,
+ SD_IO_USING_1V8, SD_IO_USING_1V8);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, LDO_POWER_CFG,
+ TUNE_SD18_MASK, TUNE_SD18_1V8);
+ }
+
+ err = rtsx_usb_send_cmd(ucr, MODE_C, 100);
+ mutex_unlock(&ucr->dev_mutex);
+
+ return err;
+}
+
+static int sdmmc_card_busy(struct mmc_host *mmc)
+{
+ struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
+ struct rtsx_ucr *ucr = host->ucr;
+ int err;
+ u8 stat;
+ u8 mask = SD_DAT3_STATUS | SD_DAT2_STATUS | SD_DAT1_STATUS
+ | SD_DAT0_STATUS;
+
+ dev_dbg(sdmmc_dev(host), "%s\n", __func__);
+
+ mutex_lock(&ucr->dev_mutex);
+
+ err = rtsx_usb_write_register(ucr, SD_BUS_STAT,
+ SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP,
+ SD_CLK_TOGGLE_EN);
+ if (err)
+ goto out;
+
+ mdelay(1);
+
+ err = rtsx_usb_read_register(ucr, SD_BUS_STAT, &stat);
+ if (err)
+ goto out;
+
+ err = rtsx_usb_write_register(ucr, SD_BUS_STAT,
+ SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP, 0);
+out:
+ mutex_unlock(&ucr->dev_mutex);
+
+ if (err)
+ return err;
+
+ /* check if any pin between dat[0:3] is low */
+ if ((stat & mask) != mask)
+ return 1;
+ else
+ return 0;
+}
+
+static int sdmmc_execute_tuning(struct mmc_host *mmc, u32 opcode)
+{
+ struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
+ struct rtsx_ucr *ucr = host->ucr;
+ int err = 0;
+
+ if (host->host_removal)
+ return -ENOMEDIUM;
+
+ mutex_lock(&ucr->dev_mutex);
+
+ if (!host->ddr_mode)
+ err = sd_tuning_rx(host, MMC_SEND_TUNING_BLOCK);
+
+ mutex_unlock(&ucr->dev_mutex);
+
+ return err;
+}
+
+static const struct mmc_host_ops rtsx_usb_sdmmc_ops = {
+ .request = sdmmc_request,
+ .set_ios = sdmmc_set_ios,
+ .get_ro = sdmmc_get_ro,
+ .get_cd = sdmmc_get_cd,
+ .start_signal_voltage_switch = sdmmc_switch_voltage,
+ .card_busy = sdmmc_card_busy,
+ .execute_tuning = sdmmc_execute_tuning,
+};
+
+#ifdef RTSX_USB_USE_LEDS_CLASS
+static void rtsx_usb_led_control(struct led_classdev *led,
+ enum led_brightness brightness)
+{
+ struct rtsx_usb_sdmmc *host = container_of(led,
+ struct rtsx_usb_sdmmc, led);
+
+ if (host->host_removal)
+ return;
+
+ host->led.brightness = brightness;
+ schedule_work(&host->led_work);
+}
+
+static void rtsx_usb_update_led(struct work_struct *work)
+{
+ struct rtsx_usb_sdmmc *host =
+ container_of(work, struct rtsx_usb_sdmmc, led_work);
+ struct rtsx_ucr *ucr = host->ucr;
+
+ mutex_lock(&ucr->dev_mutex);
+
+ if (host->led.brightness == LED_OFF)
+ rtsx_usb_turn_off_led(ucr);
+ else
+ rtsx_usb_turn_on_led(ucr);
+
+ mutex_unlock(&ucr->dev_mutex);
+}
+#endif
+
+static void rtsx_usb_init_host(struct rtsx_usb_sdmmc *host)
+{
+ struct mmc_host *mmc = host->mmc;
+
+ mmc->f_min = 250000;
+ mmc->f_max = 208000000;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
+ mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED |
+ MMC_CAP_MMC_HIGHSPEED | MMC_CAP_BUS_WIDTH_TEST |
+ MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | MMC_CAP_UHS_SDR50 |
+ MMC_CAP_NEEDS_POLL;
+
+ mmc->max_current_330 = 400;
+ mmc->max_current_180 = 800;
+ mmc->ops = &rtsx_usb_sdmmc_ops;
+ mmc->max_segs = 256;
+ mmc->max_seg_size = 65536;
+ mmc->max_blk_size = 512;
+ mmc->max_blk_count = 65535;
+ mmc->max_req_size = 524288;
+
+ host->power_mode = MMC_POWER_OFF;
+}
+
+static int rtsx_usb_sdmmc_drv_probe(struct platform_device *pdev)
+{
+ struct mmc_host *mmc;
+ struct rtsx_usb_sdmmc *host;
+ struct rtsx_ucr *ucr;
+#ifdef RTSX_USB_USE_LEDS_CLASS
+ int err;
+#endif
+
+ ucr = usb_get_intfdata(to_usb_interface(pdev->dev.parent));
+ if (!ucr)
+ return -ENXIO;
+
+ dev_dbg(&(pdev->dev), ": Realtek USB SD/MMC controller found\n");
+
+ mmc = mmc_alloc_host(sizeof(*host), &pdev->dev);
+ if (!mmc)
+ return -ENOMEM;
+
+ host = mmc_priv(mmc);
+ host->ucr = ucr;
+ host->mmc = mmc;
+ host->pdev = pdev;
+ platform_set_drvdata(pdev, host);
+
+ mutex_init(&host->host_mutex);
+ rtsx_usb_init_host(host);
+ pm_runtime_enable(&pdev->dev);
+
+#ifdef RTSX_USB_USE_LEDS_CLASS
+ snprintf(host->led_name, sizeof(host->led_name),
+ "%s::", mmc_hostname(mmc));
+ host->led.name = host->led_name;
+ host->led.brightness = LED_OFF;
+ host->led.default_trigger = mmc_hostname(mmc);
+ host->led.brightness_set = rtsx_usb_led_control;
+
+ err = led_classdev_register(mmc_dev(mmc), &host->led);
+ if (err)
+ dev_err(&(pdev->dev),
+ "Failed to register LED device: %d\n", err);
+ INIT_WORK(&host->led_work, rtsx_usb_update_led);
+
+#endif
+ mmc_add_host(mmc);
+
+ return 0;
+}
+
+static int rtsx_usb_sdmmc_drv_remove(struct platform_device *pdev)
+{
+ struct rtsx_usb_sdmmc *host = platform_get_drvdata(pdev);
+ struct mmc_host *mmc;
+
+ if (!host)
+ return 0;
+
+ mmc = host->mmc;
+ host->host_removal = true;
+
+ mutex_lock(&host->host_mutex);
+ if (host->mrq) {
+ dev_dbg(&(pdev->dev),
+ "%s: Controller removed during transfer\n",
+ mmc_hostname(mmc));
+ host->mrq->cmd->error = -ENOMEDIUM;
+ if (host->mrq->stop)
+ host->mrq->stop->error = -ENOMEDIUM;
+ mmc_request_done(mmc, host->mrq);
+ }
+ mutex_unlock(&host->host_mutex);
+
+ mmc_remove_host(mmc);
+
+#ifdef RTSX_USB_USE_LEDS_CLASS
+ cancel_work_sync(&host->led_work);
+ led_classdev_unregister(&host->led);
+#endif
+
+ mmc_free_host(mmc);
+ pm_runtime_disable(&pdev->dev);
+ platform_set_drvdata(pdev, NULL);
+
+ dev_dbg(&(pdev->dev),
+ ": Realtek USB SD/MMC module has been removed\n");
+
+ return 0;
+}
+
+static struct platform_device_id rtsx_usb_sdmmc_ids[] = {
+ {
+ .name = "rtsx_usb_sdmmc",
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(platform, rtsx_usb_sdmmc_ids);
+
+static struct platform_driver rtsx_usb_sdmmc_driver = {
+ .probe = rtsx_usb_sdmmc_drv_probe,
+ .remove = rtsx_usb_sdmmc_drv_remove,
+ .id_table = rtsx_usb_sdmmc_ids,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "rtsx_usb_sdmmc",
+ },
+};
+module_platform_driver(rtsx_usb_sdmmc_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Roger Tseng <rogerable@realtek.com>");
+MODULE_DESCRIPTION("Realtek USB SD/MMC Card Host Driver");
diff --git a/drivers/mmc/host/sunxi-mmc.c b/drivers/mmc/host/sunxi-mmc.c
new file mode 100644
index 000000000000..024f67c98cdc
--- /dev/null
+++ b/drivers/mmc/host/sunxi-mmc.c
@@ -0,0 +1,1049 @@
+/*
+ * Driver for sunxi SD/MMC host controllers
+ * (C) Copyright 2007-2011 Reuuimlla Technology Co., Ltd.
+ * (C) Copyright 2007-2011 Aaron Maoye <leafy.myeh@reuuimllatech.com>
+ * (C) Copyright 2013-2014 O2S GmbH <www.o2s.ch>
+ * (C) Copyright 2013-2014 David Lanzend�rfer <david.lanzendoerfer@o2s.ch>
+ * (C) Copyright 2013-2014 Hans de Goede <hdegoede@redhat.com>
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+
+#include <linux/clk.h>
+#include <linux/clk-private.h>
+#include <linux/clk/sunxi.h>
+
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/scatterlist.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/reset.h>
+
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/core.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/slot-gpio.h>
+
+/* register offset definitions */
+#define SDXC_REG_GCTRL (0x00) /* SMC Global Control Register */
+#define SDXC_REG_CLKCR (0x04) /* SMC Clock Control Register */
+#define SDXC_REG_TMOUT (0x08) /* SMC Time Out Register */
+#define SDXC_REG_WIDTH (0x0C) /* SMC Bus Width Register */
+#define SDXC_REG_BLKSZ (0x10) /* SMC Block Size Register */
+#define SDXC_REG_BCNTR (0x14) /* SMC Byte Count Register */
+#define SDXC_REG_CMDR (0x18) /* SMC Command Register */
+#define SDXC_REG_CARG (0x1C) /* SMC Argument Register */
+#define SDXC_REG_RESP0 (0x20) /* SMC Response Register 0 */
+#define SDXC_REG_RESP1 (0x24) /* SMC Response Register 1 */
+#define SDXC_REG_RESP2 (0x28) /* SMC Response Register 2 */
+#define SDXC_REG_RESP3 (0x2C) /* SMC Response Register 3 */
+#define SDXC_REG_IMASK (0x30) /* SMC Interrupt Mask Register */
+#define SDXC_REG_MISTA (0x34) /* SMC Masked Interrupt Status Register */
+#define SDXC_REG_RINTR (0x38) /* SMC Raw Interrupt Status Register */
+#define SDXC_REG_STAS (0x3C) /* SMC Status Register */
+#define SDXC_REG_FTRGL (0x40) /* SMC FIFO Threshold Watermark Registe */
+#define SDXC_REG_FUNS (0x44) /* SMC Function Select Register */
+#define SDXC_REG_CBCR (0x48) /* SMC CIU Byte Count Register */
+#define SDXC_REG_BBCR (0x4C) /* SMC BIU Byte Count Register */
+#define SDXC_REG_DBGC (0x50) /* SMC Debug Enable Register */
+#define SDXC_REG_HWRST (0x78) /* SMC Card Hardware Reset for Register */
+#define SDXC_REG_DMAC (0x80) /* SMC IDMAC Control Register */
+#define SDXC_REG_DLBA (0x84) /* SMC IDMAC Descriptor List Base Addre */
+#define SDXC_REG_IDST (0x88) /* SMC IDMAC Status Register */
+#define SDXC_REG_IDIE (0x8C) /* SMC IDMAC Interrupt Enable Register */
+#define SDXC_REG_CHDA (0x90)
+#define SDXC_REG_CBDA (0x94)
+
+#define mmc_readl(host, reg) \
+ readl((host)->reg_base + SDXC_##reg)
+#define mmc_writel(host, reg, value) \
+ writel((value), (host)->reg_base + SDXC_##reg)
+
+/* global control register bits */
+#define SDXC_SOFT_RESET BIT(0)
+#define SDXC_FIFO_RESET BIT(1)
+#define SDXC_DMA_RESET BIT(2)
+#define SDXC_INTERRUPT_ENABLE_BIT BIT(4)
+#define SDXC_DMA_ENABLE_BIT BIT(5)
+#define SDXC_DEBOUNCE_ENABLE_BIT BIT(8)
+#define SDXC_POSEDGE_LATCH_DATA BIT(9)
+#define SDXC_DDR_MODE BIT(10)
+#define SDXC_MEMORY_ACCESS_DONE BIT(29)
+#define SDXC_ACCESS_DONE_DIRECT BIT(30)
+#define SDXC_ACCESS_BY_AHB BIT(31)
+#define SDXC_ACCESS_BY_DMA (0 << 31)
+#define SDXC_HARDWARE_RESET \
+ (SDXC_SOFT_RESET | SDXC_FIFO_RESET | SDXC_DMA_RESET)
+
+/* clock control bits */
+#define SDXC_CARD_CLOCK_ON BIT(16)
+#define SDXC_LOW_POWER_ON BIT(17)
+
+/* bus width */
+#define SDXC_WIDTH1 0
+#define SDXC_WIDTH4 1
+#define SDXC_WIDTH8 2
+
+/* smc command bits */
+#define SDXC_RESP_EXPIRE BIT(6)
+#define SDXC_LONG_RESPONSE BIT(7)
+#define SDXC_CHECK_RESPONSE_CRC BIT(8)
+#define SDXC_DATA_EXPIRE BIT(9)
+#define SDXC_WRITE BIT(10)
+#define SDXC_SEQUENCE_MODE BIT(11)
+#define SDXC_SEND_AUTO_STOP BIT(12)
+#define SDXC_WAIT_PRE_OVER BIT(13)
+#define SDXC_STOP_ABORT_CMD BIT(14)
+#define SDXC_SEND_INIT_SEQUENCE BIT(15)
+#define SDXC_UPCLK_ONLY BIT(21)
+#define SDXC_READ_CEATA_DEV BIT(22)
+#define SDXC_CCS_EXPIRE BIT(23)
+#define SDXC_ENABLE_BIT_BOOT BIT(24)
+#define SDXC_ALT_BOOT_OPTIONS BIT(25)
+#define SDXC_BOOT_ACK_EXPIRE BIT(26)
+#define SDXC_BOOT_ABORT BIT(27)
+#define SDXC_VOLTAGE_SWITCH BIT(28)
+#define SDXC_USE_HOLD_REGISTER BIT(29)
+#define SDXC_START BIT(31)
+
+/* interrupt bits */
+#define SDXC_RESP_ERROR BIT(1)
+#define SDXC_COMMAND_DONE BIT(2)
+#define SDXC_DATA_OVER BIT(3)
+#define SDXC_TX_DATA_REQUEST BIT(4)
+#define SDXC_RX_DATA_REQUEST BIT(5)
+#define SDXC_RESP_CRC_ERROR BIT(6)
+#define SDXC_DATA_CRC_ERROR BIT(7)
+#define SDXC_RESP_TIMEOUT BIT(8)
+#define SDXC_DATA_TIMEOUT BIT(9)
+#define SDXC_VOLTAGE_CHANGE_DONE BIT(10)
+#define SDXC_FIFO_RUN_ERROR BIT(11)
+#define SDXC_HARD_WARE_LOCKED BIT(12)
+#define SDXC_START_BIT_ERROR BIT(13)
+#define SDXC_AUTO_COMMAND_DONE BIT(14)
+#define SDXC_END_BIT_ERROR BIT(15)
+#define SDXC_SDIO_INTERRUPT BIT(16)
+#define SDXC_CARD_INSERT BIT(30)
+#define SDXC_CARD_REMOVE BIT(31)
+#define SDXC_INTERRUPT_ERROR_BIT \
+ (SDXC_RESP_ERROR | SDXC_RESP_CRC_ERROR | SDXC_DATA_CRC_ERROR | \
+ SDXC_RESP_TIMEOUT | SDXC_DATA_TIMEOUT | SDXC_FIFO_RUN_ERROR | \
+ SDXC_HARD_WARE_LOCKED | SDXC_START_BIT_ERROR | SDXC_END_BIT_ERROR)
+#define SDXC_INTERRUPT_DONE_BIT \
+ (SDXC_AUTO_COMMAND_DONE | SDXC_DATA_OVER | \
+ SDXC_COMMAND_DONE | SDXC_VOLTAGE_CHANGE_DONE)
+
+/* status */
+#define SDXC_RXWL_FLAG BIT(0)
+#define SDXC_TXWL_FLAG BIT(1)
+#define SDXC_FIFO_EMPTY BIT(2)
+#define SDXC_FIFO_FULL BIT(3)
+#define SDXC_CARD_PRESENT BIT(8)
+#define SDXC_CARD_DATA_BUSY BIT(9)
+#define SDXC_DATA_FSM_BUSY BIT(10)
+#define SDXC_DMA_REQUEST BIT(31)
+#define SDXC_FIFO_SIZE 16
+
+/* Function select */
+#define SDXC_CEATA_ON (0xceaa << 16)
+#define SDXC_SEND_IRQ_RESPONSE BIT(0)
+#define SDXC_SDIO_READ_WAIT BIT(1)
+#define SDXC_ABORT_READ_DATA BIT(2)
+#define SDXC_SEND_CCSD BIT(8)
+#define SDXC_SEND_AUTO_STOPCCSD BIT(9)
+#define SDXC_CEATA_DEV_IRQ_ENABLE BIT(10)
+
+/* IDMA controller bus mod bit field */
+#define SDXC_IDMAC_SOFT_RESET BIT(0)
+#define SDXC_IDMAC_FIX_BURST BIT(1)
+#define SDXC_IDMAC_IDMA_ON BIT(7)
+#define SDXC_IDMAC_REFETCH_DES BIT(31)
+
+/* IDMA status bit field */
+#define SDXC_IDMAC_TRANSMIT_INTERRUPT BIT(0)
+#define SDXC_IDMAC_RECEIVE_INTERRUPT BIT(1)
+#define SDXC_IDMAC_FATAL_BUS_ERROR BIT(2)
+#define SDXC_IDMAC_DESTINATION_INVALID BIT(4)
+#define SDXC_IDMAC_CARD_ERROR_SUM BIT(5)
+#define SDXC_IDMAC_NORMAL_INTERRUPT_SUM BIT(8)
+#define SDXC_IDMAC_ABNORMAL_INTERRUPT_SUM BIT(9)
+#define SDXC_IDMAC_HOST_ABORT_INTERRUPT BIT(10)
+#define SDXC_IDMAC_IDLE (0 << 13)
+#define SDXC_IDMAC_SUSPEND (1 << 13)
+#define SDXC_IDMAC_DESC_READ (2 << 13)
+#define SDXC_IDMAC_DESC_CHECK (3 << 13)
+#define SDXC_IDMAC_READ_REQUEST_WAIT (4 << 13)
+#define SDXC_IDMAC_WRITE_REQUEST_WAIT (5 << 13)
+#define SDXC_IDMAC_READ (6 << 13)
+#define SDXC_IDMAC_WRITE (7 << 13)
+#define SDXC_IDMAC_DESC_CLOSE (8 << 13)
+
+/*
+* If the idma-des-size-bits of property is ie 13, bufsize bits are:
+* Bits 0-12: buf1 size
+* Bits 13-25: buf2 size
+* Bits 26-31: not used
+* Since we only ever set buf1 size, we can simply store it directly.
+*/
+#define SDXC_IDMAC_DES0_DIC BIT(1) /* disable interrupt on completion */
+#define SDXC_IDMAC_DES0_LD BIT(2) /* last descriptor */
+#define SDXC_IDMAC_DES0_FD BIT(3) /* first descriptor */
+#define SDXC_IDMAC_DES0_CH BIT(4) /* chain mode */
+#define SDXC_IDMAC_DES0_ER BIT(5) /* end of ring */
+#define SDXC_IDMAC_DES0_CES BIT(30) /* card error summary */
+#define SDXC_IDMAC_DES0_OWN BIT(31) /* 1-idma owns it, 0-host owns it */
+
+struct sunxi_idma_des {
+ u32 config;
+ u32 buf_size;
+ u32 buf_addr_ptr1;
+ u32 buf_addr_ptr2;
+};
+
+struct sunxi_mmc_host {
+ struct mmc_host *mmc;
+ struct reset_control *reset;
+
+ /* IO mapping base */
+ void __iomem *reg_base;
+
+ /* clock management */
+ struct clk *clk_ahb;
+ struct clk *clk_mmc;
+
+ /* irq */
+ spinlock_t lock;
+ int irq;
+ u32 int_sum;
+ u32 sdio_imask;
+
+ /* dma */
+ u32 idma_des_size_bits;
+ dma_addr_t sg_dma;
+ void *sg_cpu;
+ bool wait_dma;
+
+ struct mmc_request *mrq;
+ struct mmc_request *manual_stop_mrq;
+ int ferror;
+};
+
+static int sunxi_mmc_reset_host(struct sunxi_mmc_host *host)
+{
+ unsigned long expire = jiffies + msecs_to_jiffies(250);
+ u32 rval;
+
+ mmc_writel(host, REG_CMDR, SDXC_HARDWARE_RESET);
+ do {
+ rval = mmc_readl(host, REG_GCTRL);
+ } while (time_before(jiffies, expire) && (rval & SDXC_HARDWARE_RESET));
+
+ if (rval & SDXC_HARDWARE_RESET) {
+ dev_err(mmc_dev(host->mmc), "fatal err reset timeout\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int sunxi_mmc_init_host(struct mmc_host *mmc)
+{
+ u32 rval;
+ struct sunxi_mmc_host *host = mmc_priv(mmc);
+
+ if (sunxi_mmc_reset_host(host))
+ return -EIO;
+
+ mmc_writel(host, REG_FTRGL, 0x20070008);
+ mmc_writel(host, REG_TMOUT, 0xffffffff);
+ mmc_writel(host, REG_IMASK, host->sdio_imask);
+ mmc_writel(host, REG_RINTR, 0xffffffff);
+ mmc_writel(host, REG_DBGC, 0xdeb);
+ mmc_writel(host, REG_FUNS, SDXC_CEATA_ON);
+ mmc_writel(host, REG_DLBA, host->sg_dma);
+
+ rval = mmc_readl(host, REG_GCTRL);
+ rval |= SDXC_INTERRUPT_ENABLE_BIT;
+ rval &= ~SDXC_ACCESS_DONE_DIRECT;
+ mmc_writel(host, REG_GCTRL, rval);
+
+ return 0;
+}
+
+static void sunxi_mmc_init_idma_des(struct sunxi_mmc_host *host,
+ struct mmc_data *data)
+{
+ struct sunxi_idma_des *pdes = (struct sunxi_idma_des *)host->sg_cpu;
+ struct sunxi_idma_des *pdes_pa = (struct sunxi_idma_des *)host->sg_dma;
+ int i, max_len = (1 << host->idma_des_size_bits);
+
+ for (i = 0; i < data->sg_len; i++) {
+ pdes[i].config = SDXC_IDMAC_DES0_CH | SDXC_IDMAC_DES0_OWN |
+ SDXC_IDMAC_DES0_DIC;
+
+ if (data->sg[i].length == max_len)
+ pdes[i].buf_size = 0; /* 0 == max_len */
+ else
+ pdes[i].buf_size = data->sg[i].length;
+
+ pdes[i].buf_addr_ptr1 = sg_dma_address(&data->sg[i]);
+ pdes[i].buf_addr_ptr2 = (u32)&pdes_pa[i + 1];
+ }
+
+ pdes[0].config |= SDXC_IDMAC_DES0_FD;
+ pdes[i - 1].config = SDXC_IDMAC_DES0_OWN | SDXC_IDMAC_DES0_LD;
+
+ /*
+ * Avoid the io-store starting the idmac hitting io-mem before the
+ * descriptors hit the main-mem.
+ */
+ wmb();
+}
+
+static enum dma_data_direction sunxi_mmc_get_dma_dir(struct mmc_data *data)
+{
+ if (data->flags & MMC_DATA_WRITE)
+ return DMA_TO_DEVICE;
+ else
+ return DMA_FROM_DEVICE;
+}
+
+static int sunxi_mmc_map_dma(struct sunxi_mmc_host *host,
+ struct mmc_data *data)
+{
+ u32 i, dma_len;
+ struct scatterlist *sg;
+
+ dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ sunxi_mmc_get_dma_dir(data));
+ if (dma_len == 0) {
+ dev_err(mmc_dev(host->mmc), "dma_map_sg failed\n");
+ return -ENOMEM;
+ }
+
+ for_each_sg(data->sg, sg, data->sg_len, i) {
+ if (sg->offset & 3 || sg->length & 3) {
+ dev_err(mmc_dev(host->mmc),
+ "unaligned scatterlist: os %x length %d\n",
+ sg->offset, sg->length);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static void sunxi_mmc_start_dma(struct sunxi_mmc_host *host,
+ struct mmc_data *data)
+{
+ u32 rval;
+
+ sunxi_mmc_init_idma_des(host, data);
+
+ rval = mmc_readl(host, REG_GCTRL);
+ rval |= SDXC_DMA_ENABLE_BIT;
+ mmc_writel(host, REG_GCTRL, rval);
+ rval |= SDXC_DMA_RESET;
+ mmc_writel(host, REG_GCTRL, rval);
+
+ mmc_writel(host, REG_DMAC, SDXC_IDMAC_SOFT_RESET);
+
+ if (!(data->flags & MMC_DATA_WRITE))
+ mmc_writel(host, REG_IDIE, SDXC_IDMAC_RECEIVE_INTERRUPT);
+
+ mmc_writel(host, REG_DMAC,
+ SDXC_IDMAC_FIX_BURST | SDXC_IDMAC_IDMA_ON);
+}
+
+static void sunxi_mmc_send_manual_stop(struct sunxi_mmc_host *host,
+ struct mmc_request *req)
+{
+ u32 arg, cmd_val, ri;
+ unsigned long expire = jiffies + msecs_to_jiffies(1000);
+
+ cmd_val = SDXC_START | SDXC_RESP_EXPIRE |
+ SDXC_STOP_ABORT_CMD | SDXC_CHECK_RESPONSE_CRC;
+
+ if (req->cmd->opcode == SD_IO_RW_EXTENDED) {
+ cmd_val |= SD_IO_RW_DIRECT;
+ arg = (1 << 31) | (0 << 28) | (SDIO_CCCR_ABORT << 9) |
+ ((req->cmd->arg >> 28) & 0x7);
+ } else {
+ cmd_val |= MMC_STOP_TRANSMISSION;
+ arg = 0;
+ }
+
+ mmc_writel(host, REG_CARG, arg);
+ mmc_writel(host, REG_CMDR, cmd_val);
+
+ do {
+ ri = mmc_readl(host, REG_RINTR);
+ } while (!(ri & (SDXC_COMMAND_DONE | SDXC_INTERRUPT_ERROR_BIT)) &&
+ time_before(jiffies, expire));
+
+ if (!(ri & SDXC_COMMAND_DONE) || (ri & SDXC_INTERRUPT_ERROR_BIT)) {
+ dev_err(mmc_dev(host->mmc), "send stop command failed\n");
+ if (req->stop)
+ req->stop->resp[0] = -ETIMEDOUT;
+ } else {
+ if (req->stop)
+ req->stop->resp[0] = mmc_readl(host, REG_RESP0);
+ }
+
+ mmc_writel(host, REG_RINTR, 0xffff);
+}
+
+static void sunxi_mmc_dump_errinfo(struct sunxi_mmc_host *host)
+{
+ struct mmc_command *cmd = host->mrq->cmd;
+ struct mmc_data *data = host->mrq->data;
+
+ /* For some cmds timeout is normal with sd/mmc cards */
+ if ((host->int_sum & SDXC_INTERRUPT_ERROR_BIT) ==
+ SDXC_RESP_TIMEOUT && (cmd->opcode == SD_IO_SEND_OP_COND ||
+ cmd->opcode == SD_IO_RW_DIRECT))
+ return;
+
+ dev_err(mmc_dev(host->mmc),
+ "smc %d err, cmd %d,%s%s%s%s%s%s%s%s%s%s !!\n",
+ host->mmc->index, cmd->opcode,
+ data ? (data->flags & MMC_DATA_WRITE ? " WR" : " RD") : "",
+ host->int_sum & SDXC_RESP_ERROR ? " RE" : "",
+ host->int_sum & SDXC_RESP_CRC_ERROR ? " RCE" : "",
+ host->int_sum & SDXC_DATA_CRC_ERROR ? " DCE" : "",
+ host->int_sum & SDXC_RESP_TIMEOUT ? " RTO" : "",
+ host->int_sum & SDXC_DATA_TIMEOUT ? " DTO" : "",
+ host->int_sum & SDXC_FIFO_RUN_ERROR ? " FE" : "",
+ host->int_sum & SDXC_HARD_WARE_LOCKED ? " HL" : "",
+ host->int_sum & SDXC_START_BIT_ERROR ? " SBE" : "",
+ host->int_sum & SDXC_END_BIT_ERROR ? " EBE" : ""
+ );
+}
+
+/* Called in interrupt context! */
+static irqreturn_t sunxi_mmc_finalize_request(struct sunxi_mmc_host *host)
+{
+ struct mmc_request *mrq = host->mrq;
+ struct mmc_data *data = mrq->data;
+ u32 rval;
+
+ mmc_writel(host, REG_IMASK, host->sdio_imask);
+ mmc_writel(host, REG_IDIE, 0);
+
+ if (host->int_sum & SDXC_INTERRUPT_ERROR_BIT) {
+ sunxi_mmc_dump_errinfo(host);
+ mrq->cmd->error = -ETIMEDOUT;
+
+ if (data) {
+ data->error = -ETIMEDOUT;
+ host->manual_stop_mrq = mrq;
+ }
+
+ if (mrq->stop)
+ mrq->stop->error = -ETIMEDOUT;
+ } else {
+ if (mrq->cmd->flags & MMC_RSP_136) {
+ mrq->cmd->resp[0] = mmc_readl(host, REG_RESP3);
+ mrq->cmd->resp[1] = mmc_readl(host, REG_RESP2);
+ mrq->cmd->resp[2] = mmc_readl(host, REG_RESP1);
+ mrq->cmd->resp[3] = mmc_readl(host, REG_RESP0);
+ } else {
+ mrq->cmd->resp[0] = mmc_readl(host, REG_RESP0);
+ }
+
+ if (data)
+ data->bytes_xfered = data->blocks * data->blksz;
+ }
+
+ if (data) {
+ mmc_writel(host, REG_IDST, 0x337);
+ mmc_writel(host, REG_DMAC, 0);
+ rval = mmc_readl(host, REG_GCTRL);
+ rval |= SDXC_DMA_RESET;
+ mmc_writel(host, REG_GCTRL, rval);
+ rval &= ~SDXC_DMA_ENABLE_BIT;
+ mmc_writel(host, REG_GCTRL, rval);
+ rval |= SDXC_FIFO_RESET;
+ mmc_writel(host, REG_GCTRL, rval);
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ sunxi_mmc_get_dma_dir(data));
+ }
+
+ mmc_writel(host, REG_RINTR, 0xffff);
+
+ host->mrq = NULL;
+ host->int_sum = 0;
+ host->wait_dma = false;
+
+ return host->manual_stop_mrq ? IRQ_WAKE_THREAD : IRQ_HANDLED;
+}
+
+static irqreturn_t sunxi_mmc_irq(int irq, void *dev_id)
+{
+ struct sunxi_mmc_host *host = dev_id;
+ struct mmc_request *mrq;
+ u32 msk_int, idma_int;
+ bool finalize = false;
+ bool sdio_int = false;
+ irqreturn_t ret = IRQ_HANDLED;
+
+ spin_lock(&host->lock);
+
+ idma_int = mmc_readl(host, REG_IDST);
+ msk_int = mmc_readl(host, REG_MISTA);
+
+ dev_dbg(mmc_dev(host->mmc), "irq: rq %p mi %08x idi %08x\n",
+ host->mrq, msk_int, idma_int);
+
+ mrq = host->mrq;
+ if (mrq) {
+ if (idma_int & SDXC_IDMAC_RECEIVE_INTERRUPT)
+ host->wait_dma = false;
+
+ host->int_sum |= msk_int;
+
+ /* Wait for COMMAND_DONE on RESPONSE_TIMEOUT before finalize */
+ if ((host->int_sum & SDXC_RESP_TIMEOUT) &&
+ !(host->int_sum & SDXC_COMMAND_DONE))
+ mmc_writel(host, REG_IMASK,
+ host->sdio_imask | SDXC_COMMAND_DONE);
+ /* Don't wait for dma on error */
+ else if (host->int_sum & SDXC_INTERRUPT_ERROR_BIT)
+ finalize = true;
+ else if ((host->int_sum & SDXC_INTERRUPT_DONE_BIT) &&
+ !host->wait_dma)
+ finalize = true;
+ }
+
+ if (msk_int & SDXC_SDIO_INTERRUPT)
+ sdio_int = true;
+
+ mmc_writel(host, REG_RINTR, msk_int);
+ mmc_writel(host, REG_IDST, idma_int);
+
+ if (finalize)
+ ret = sunxi_mmc_finalize_request(host);
+
+ spin_unlock(&host->lock);
+
+ if (finalize && ret == IRQ_HANDLED)
+ mmc_request_done(host->mmc, mrq);
+
+ if (sdio_int)
+ mmc_signal_sdio_irq(host->mmc);
+
+ return ret;
+}
+
+static irqreturn_t sunxi_mmc_handle_manual_stop(int irq, void *dev_id)
+{
+ struct sunxi_mmc_host *host = dev_id;
+ struct mmc_request *mrq;
+ unsigned long iflags;
+
+ spin_lock_irqsave(&host->lock, iflags);
+ mrq = host->manual_stop_mrq;
+ spin_unlock_irqrestore(&host->lock, iflags);
+
+ if (!mrq) {
+ dev_err(mmc_dev(host->mmc), "no request for manual stop\n");
+ return IRQ_HANDLED;
+ }
+
+ dev_err(mmc_dev(host->mmc), "data error, sending stop command\n");
+ sunxi_mmc_send_manual_stop(host, mrq);
+
+ spin_lock_irqsave(&host->lock, iflags);
+ host->manual_stop_mrq = NULL;
+ spin_unlock_irqrestore(&host->lock, iflags);
+
+ mmc_request_done(host->mmc, mrq);
+
+ return IRQ_HANDLED;
+}
+
+static int sunxi_mmc_oclk_onoff(struct sunxi_mmc_host *host, u32 oclk_en)
+{
+ unsigned long expire = jiffies + msecs_to_jiffies(250);
+ u32 rval;
+
+ rval = mmc_readl(host, REG_CLKCR);
+ rval &= ~(SDXC_CARD_CLOCK_ON | SDXC_LOW_POWER_ON);
+
+ if (oclk_en)
+ rval |= SDXC_CARD_CLOCK_ON;
+
+ mmc_writel(host, REG_CLKCR, rval);
+
+ rval = SDXC_START | SDXC_UPCLK_ONLY | SDXC_WAIT_PRE_OVER;
+ mmc_writel(host, REG_CMDR, rval);
+
+ do {
+ rval = mmc_readl(host, REG_CMDR);
+ } while (time_before(jiffies, expire) && (rval & SDXC_START));
+
+ /* clear irq status bits set by the command */
+ mmc_writel(host, REG_RINTR,
+ mmc_readl(host, REG_RINTR) & ~SDXC_SDIO_INTERRUPT);
+
+ if (rval & SDXC_START) {
+ dev_err(mmc_dev(host->mmc), "fatal err update clk timeout\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int sunxi_mmc_clk_set_rate(struct sunxi_mmc_host *host,
+ struct mmc_ios *ios)
+{
+ u32 rate, oclk_dly, rval, sclk_dly, src_clk;
+ int ret;
+
+ rate = clk_round_rate(host->clk_mmc, ios->clock);
+ dev_dbg(mmc_dev(host->mmc), "setting clk to %d, rounded %d\n",
+ ios->clock, rate);
+
+ /* setting clock rate */
+ ret = clk_set_rate(host->clk_mmc, rate);
+ if (ret) {
+ dev_err(mmc_dev(host->mmc), "error setting clk to %d: %d\n",
+ rate, ret);
+ return ret;
+ }
+
+ ret = sunxi_mmc_oclk_onoff(host, 0);
+ if (ret)
+ return ret;
+
+ /* clear internal divider */
+ rval = mmc_readl(host, REG_CLKCR);
+ rval &= ~0xff;
+ mmc_writel(host, REG_CLKCR, rval);
+
+ /* determine delays */
+ if (rate <= 400000) {
+ oclk_dly = 0;
+ sclk_dly = 7;
+ } else if (rate <= 25000000) {
+ oclk_dly = 0;
+ sclk_dly = 5;
+ } else if (rate <= 50000000) {
+ if (ios->timing == MMC_TIMING_UHS_DDR50) {
+ oclk_dly = 2;
+ sclk_dly = 4;
+ } else {
+ oclk_dly = 3;
+ sclk_dly = 5;
+ }
+ } else {
+ /* rate > 50000000 */
+ oclk_dly = 2;
+ sclk_dly = 4;
+ }
+
+ src_clk = clk_get_rate(clk_get_parent(host->clk_mmc));
+ if (src_clk >= 300000000 && src_clk <= 400000000) {
+ if (oclk_dly)
+ oclk_dly--;
+ if (sclk_dly)
+ sclk_dly--;
+ }
+
+ clk_sunxi_mmc_phase_control(host->clk_mmc, sclk_dly, oclk_dly);
+
+ return sunxi_mmc_oclk_onoff(host, 1);
+}
+
+static void sunxi_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct sunxi_mmc_host *host = mmc_priv(mmc);
+ u32 rval;
+
+ /* Set the power state */
+ switch (ios->power_mode) {
+ case MMC_POWER_ON:
+ break;
+
+ case MMC_POWER_UP:
+ mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, ios->vdd);
+
+ host->ferror = sunxi_mmc_init_host(mmc);
+ if (host->ferror)
+ return;
+
+ dev_dbg(mmc_dev(mmc), "power on!\n");
+ break;
+
+ case MMC_POWER_OFF:
+ dev_dbg(mmc_dev(mmc), "power off!\n");
+ sunxi_mmc_reset_host(host);
+ mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
+ break;
+ }
+
+ /* set bus width */
+ switch (ios->bus_width) {
+ case MMC_BUS_WIDTH_1:
+ mmc_writel(host, REG_WIDTH, SDXC_WIDTH1);
+ break;
+ case MMC_BUS_WIDTH_4:
+ mmc_writel(host, REG_WIDTH, SDXC_WIDTH4);
+ break;
+ case MMC_BUS_WIDTH_8:
+ mmc_writel(host, REG_WIDTH, SDXC_WIDTH8);
+ break;
+ }
+
+ /* set ddr mode */
+ rval = mmc_readl(host, REG_GCTRL);
+ if (ios->timing == MMC_TIMING_UHS_DDR50)
+ rval |= SDXC_DDR_MODE;
+ else
+ rval &= ~SDXC_DDR_MODE;
+ mmc_writel(host, REG_GCTRL, rval);
+
+ /* set up clock */
+ if (ios->clock && ios->power_mode) {
+ host->ferror = sunxi_mmc_clk_set_rate(host, ios);
+ /* Android code had a usleep_range(50000, 55000); here */
+ }
+}
+
+static void sunxi_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct sunxi_mmc_host *host = mmc_priv(mmc);
+ unsigned long flags;
+ u32 imask;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ imask = mmc_readl(host, REG_IMASK);
+ if (enable) {
+ host->sdio_imask = SDXC_SDIO_INTERRUPT;
+ imask |= SDXC_SDIO_INTERRUPT;
+ } else {
+ host->sdio_imask = 0;
+ imask &= ~SDXC_SDIO_INTERRUPT;
+ }
+ mmc_writel(host, REG_IMASK, imask);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void sunxi_mmc_hw_reset(struct mmc_host *mmc)
+{
+ struct sunxi_mmc_host *host = mmc_priv(mmc);
+ mmc_writel(host, REG_HWRST, 0);
+ udelay(10);
+ mmc_writel(host, REG_HWRST, 1);
+ udelay(300);
+}
+
+static void sunxi_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct sunxi_mmc_host *host = mmc_priv(mmc);
+ struct mmc_command *cmd = mrq->cmd;
+ struct mmc_data *data = mrq->data;
+ unsigned long iflags;
+ u32 imask = SDXC_INTERRUPT_ERROR_BIT;
+ u32 cmd_val = SDXC_START | (cmd->opcode & 0x3f);
+ int ret;
+
+ /* Check for set_ios errors (should never happen) */
+ if (host->ferror) {
+ mrq->cmd->error = host->ferror;
+ mmc_request_done(mmc, mrq);
+ return;
+ }
+
+ if (data) {
+ ret = sunxi_mmc_map_dma(host, data);
+ if (ret < 0) {
+ dev_err(mmc_dev(mmc), "map DMA failed\n");
+ cmd->error = ret;
+ data->error = ret;
+ mmc_request_done(mmc, mrq);
+ return;
+ }
+ }
+
+ if (cmd->opcode == MMC_GO_IDLE_STATE) {
+ cmd_val |= SDXC_SEND_INIT_SEQUENCE;
+ imask |= SDXC_COMMAND_DONE;
+ }
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ cmd_val |= SDXC_RESP_EXPIRE;
+ if (cmd->flags & MMC_RSP_136)
+ cmd_val |= SDXC_LONG_RESPONSE;
+ if (cmd->flags & MMC_RSP_CRC)
+ cmd_val |= SDXC_CHECK_RESPONSE_CRC;
+
+ if ((cmd->flags & MMC_CMD_MASK) == MMC_CMD_ADTC) {
+ cmd_val |= SDXC_DATA_EXPIRE | SDXC_WAIT_PRE_OVER;
+ if (cmd->data->flags & MMC_DATA_STREAM) {
+ imask |= SDXC_AUTO_COMMAND_DONE;
+ cmd_val |= SDXC_SEQUENCE_MODE |
+ SDXC_SEND_AUTO_STOP;
+ }
+
+ if (cmd->data->stop) {
+ imask |= SDXC_AUTO_COMMAND_DONE;
+ cmd_val |= SDXC_SEND_AUTO_STOP;
+ } else {
+ imask |= SDXC_DATA_OVER;
+ }
+
+ if (cmd->data->flags & MMC_DATA_WRITE)
+ cmd_val |= SDXC_WRITE;
+ else
+ host->wait_dma = true;
+ } else {
+ imask |= SDXC_COMMAND_DONE;
+ }
+ } else {
+ imask |= SDXC_COMMAND_DONE;
+ }
+
+ dev_dbg(mmc_dev(mmc), "cmd %d(%08x) arg %x ie 0x%08x len %d\n",
+ cmd_val & 0x3f, cmd_val, cmd->arg, imask,
+ mrq->data ? mrq->data->blksz * mrq->data->blocks : 0);
+
+ spin_lock_irqsave(&host->lock, iflags);
+
+ if (host->mrq || host->manual_stop_mrq) {
+ spin_unlock_irqrestore(&host->lock, iflags);
+
+ if (data)
+ dma_unmap_sg(mmc_dev(mmc), data->sg, data->sg_len,
+ sunxi_mmc_get_dma_dir(data));
+
+ dev_err(mmc_dev(mmc), "request already pending\n");
+ mrq->cmd->error = -EBUSY;
+ mmc_request_done(mmc, mrq);
+ return;
+ }
+
+ if (data) {
+ mmc_writel(host, REG_BLKSZ, data->blksz);
+ mmc_writel(host, REG_BCNTR, data->blksz * data->blocks);
+ sunxi_mmc_start_dma(host, data);
+ }
+
+ host->mrq = mrq;
+ mmc_writel(host, REG_IMASK, host->sdio_imask | imask);
+ mmc_writel(host, REG_CARG, cmd->arg);
+ mmc_writel(host, REG_CMDR, cmd_val);
+
+ spin_unlock_irqrestore(&host->lock, iflags);
+}
+
+static const struct of_device_id sunxi_mmc_of_match[] = {
+ { .compatible = "allwinner,sun4i-a10-mmc", },
+ { .compatible = "allwinner,sun5i-a13-mmc", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sunxi_mmc_of_match);
+
+static struct mmc_host_ops sunxi_mmc_ops = {
+ .request = sunxi_mmc_request,
+ .set_ios = sunxi_mmc_set_ios,
+ .get_ro = mmc_gpio_get_ro,
+ .get_cd = mmc_gpio_get_cd,
+ .enable_sdio_irq = sunxi_mmc_enable_sdio_irq,
+ .hw_reset = sunxi_mmc_hw_reset,
+};
+
+static int sunxi_mmc_resource_request(struct sunxi_mmc_host *host,
+ struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ int ret;
+
+ if (of_device_is_compatible(np, "allwinner,sun4i-a10-mmc"))
+ host->idma_des_size_bits = 13;
+ else
+ host->idma_des_size_bits = 16;
+
+ ret = mmc_regulator_get_supply(host->mmc);
+ if (ret) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "Could not get vmmc supply\n");
+ return ret;
+ }
+
+ host->reg_base = devm_ioremap_resource(&pdev->dev,
+ platform_get_resource(pdev, IORESOURCE_MEM, 0));
+ if (IS_ERR(host->reg_base))
+ return PTR_ERR(host->reg_base);
+
+ host->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
+ if (IS_ERR(host->clk_ahb)) {
+ dev_err(&pdev->dev, "Could not get ahb clock\n");
+ return PTR_ERR(host->clk_ahb);
+ }
+
+ host->clk_mmc = devm_clk_get(&pdev->dev, "mmc");
+ if (IS_ERR(host->clk_mmc)) {
+ dev_err(&pdev->dev, "Could not get mmc clock\n");
+ return PTR_ERR(host->clk_mmc);
+ }
+
+ host->reset = devm_reset_control_get(&pdev->dev, "ahb");
+
+ ret = clk_prepare_enable(host->clk_ahb);
+ if (ret) {
+ dev_err(&pdev->dev, "Enable ahb clk err %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(host->clk_mmc);
+ if (ret) {
+ dev_err(&pdev->dev, "Enable mmc clk err %d\n", ret);
+ goto error_disable_clk_ahb;
+ }
+
+ if (!IS_ERR(host->reset)) {
+ ret = reset_control_deassert(host->reset);
+ if (ret) {
+ dev_err(&pdev->dev, "reset err %d\n", ret);
+ goto error_disable_clk_mmc;
+ }
+ }
+
+ /*
+ * Sometimes the controller asserts the irq on boot for some reason,
+ * make sure the controller is in a sane state before enabling irqs.
+ */
+ ret = sunxi_mmc_reset_host(host);
+ if (ret)
+ goto error_assert_reset;
+
+ host->irq = platform_get_irq(pdev, 0);
+ return devm_request_threaded_irq(&pdev->dev, host->irq, sunxi_mmc_irq,
+ sunxi_mmc_handle_manual_stop, 0, "sunxi-mmc", host);
+
+error_assert_reset:
+ if (!IS_ERR(host->reset))
+ reset_control_assert(host->reset);
+error_disable_clk_mmc:
+ clk_disable_unprepare(host->clk_mmc);
+error_disable_clk_ahb:
+ clk_disable_unprepare(host->clk_ahb);
+ return ret;
+}
+
+static int sunxi_mmc_probe(struct platform_device *pdev)
+{
+ struct sunxi_mmc_host *host;
+ struct mmc_host *mmc;
+ int ret;
+
+ mmc = mmc_alloc_host(sizeof(struct sunxi_mmc_host), &pdev->dev);
+ if (!mmc) {
+ dev_err(&pdev->dev, "mmc alloc host failed\n");
+ return -ENOMEM;
+ }
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ spin_lock_init(&host->lock);
+
+ ret = sunxi_mmc_resource_request(host, pdev);
+ if (ret)
+ goto error_free_host;
+
+ host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE,
+ &host->sg_dma, GFP_KERNEL);
+ if (!host->sg_cpu) {
+ dev_err(&pdev->dev, "Failed to allocate DMA descriptor mem\n");
+ ret = -ENOMEM;
+ goto error_free_host;
+ }
+
+ mmc->ops = &sunxi_mmc_ops;
+ mmc->max_blk_count = 8192;
+ mmc->max_blk_size = 4096;
+ mmc->max_segs = PAGE_SIZE / sizeof(struct sunxi_idma_des);
+ mmc->max_seg_size = (1 << host->idma_des_size_bits);
+ mmc->max_req_size = mmc->max_seg_size * mmc->max_segs;
+ /* 400kHz ~ 50MHz */
+ mmc->f_min = 400000;
+ mmc->f_max = 50000000;
+ mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED;
+
+ ret = mmc_of_parse(mmc);
+ if (ret)
+ goto error_free_dma;
+
+ ret = mmc_add_host(mmc);
+ if (ret)
+ goto error_free_dma;
+
+ dev_info(&pdev->dev, "base:0x%p irq:%u\n", host->reg_base, host->irq);
+ platform_set_drvdata(pdev, mmc);
+ return 0;
+
+error_free_dma:
+ dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
+error_free_host:
+ mmc_free_host(mmc);
+ return ret;
+}
+
+static int sunxi_mmc_remove(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ struct sunxi_mmc_host *host = mmc_priv(mmc);
+
+ mmc_remove_host(mmc);
+ disable_irq(host->irq);
+ sunxi_mmc_reset_host(host);
+
+ if (!IS_ERR(host->reset))
+ reset_control_assert(host->reset);
+
+ clk_disable_unprepare(host->clk_mmc);
+ clk_disable_unprepare(host->clk_ahb);
+
+ dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
+ mmc_free_host(mmc);
+
+ return 0;
+}
+
+static struct platform_driver sunxi_mmc_driver = {
+ .driver = {
+ .name = "sunxi-mmc",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(sunxi_mmc_of_match),
+ },
+ .probe = sunxi_mmc_probe,
+ .remove = sunxi_mmc_remove,
+};
+module_platform_driver(sunxi_mmc_driver);
+
+MODULE_DESCRIPTION("Allwinner's SD/MMC Card Controller Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("David Lanzend�rfer <david.lanzendoerfer@o2s.ch>");
+MODULE_ALIAS("platform:sunxi-mmc");