From d65af1e0a51bd4cc6b7a55b48df2bc23ccb84f2d Mon Sep 17 00:00:00 2001
From: Zhangfei Gao <zhangfei.gao@marvell.com>
Date: Sun, 9 Jan 2011 18:09:12 -0500
Subject: [PATCH] mmc: sdhci support emmc ddr50 mode
1. spec sdhc 3.0 does not claim support 1.2v ddr mode
2. Call back function set_uhs is added, since some controller count on external pmic to provide io voltage
Signed-off-by: Zhangfei Gao <zhangfei.gao@marvell.com>
---
drivers/mmc/core/core.c | 17 ++++++++++++
drivers/mmc/core/core.h | 1 +
drivers/mmc/core/mmc.c | 5 +++
drivers/mmc/host/sdhci.c | 62 +++++++++++++++++++++++++++++++++++++++++++--
drivers/mmc/host/sdhci.h | 14 +++++++++-
include/linux/mmc/host.h | 2 +
6 files changed, 97 insertions(+), 4 deletions(-)
@@ -623,6 +623,23 @@ static inline void mmc_set_ios(struct mmc_host *host)
host->ops->set_ios(host, ios);
}
+int mmc_set_uhs(struct mmc_host *host, unsigned int uhs_mode)
+{
+ int ret = -EINVAL;
+
+ if (host->ops->set_uhs) {
+ ret = host->ops->set_uhs(host, uhs_mode);
+
+ /*
+ * According to sdhc standard spec v3.0
+ * 1.8v regulator should be stable withing 5ms
+ */
+ mmc_delay(5);
+ }
+
+ return ret;
+}
+
/*
* Control chip select pin on a host.
*/
@@ -41,6 +41,7 @@ void mmc_set_bus_width(struct mmc_host *host, unsigned int width);
void mmc_set_bus_width_ddr(struct mmc_host *host, unsigned int width,
unsigned int ddr);
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr);
+int mmc_set_uhs(struct mmc_host *host, unsigned int uhs_mode);
void mmc_set_timing(struct mmc_host *host, unsigned int timing);
static inline void mmc_delay(unsigned int ms)
@@ -529,6 +529,11 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
ddr = MMC_1_2V_DDR_MODE;
}
+ if (ddr) {
+ if (mmc_set_uhs(host, ddr))
+ ddr = MMC_SDR_MODE;
+ }
+
/*
* Activate wide bus and DDR (if supported).
*/
@@ -986,6 +986,22 @@ static void sdhci_finish_command(struct sdhci_host *host)
host->cmd = NULL;
}
+static void sdhci_set_ddr(struct sdhci_host *host, unsigned int ddr)
+{
+ u16 con;
+
+ if (ddr == MMC_SDR_MODE)
+ return;
+
+ con = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ if (con & SDHCI_CTRL2_1_8V) {
+ con &= ~SDHCI_CTRL2_UHS_MASK;
+ if (ddr & MMC_1_8V_DDR_MODE)
+ con |= SDHCI_CTRL2_DDR50;
+ sdhci_writew(host, con, SDHCI_HOST_CONTROL2);
+ }
+}
+
static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
{
int div;
@@ -1180,6 +1196,7 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
}
sdhci_set_clock(host, ios->clock);
+ sdhci_set_ddr(host, ios->ddr);
if (ios->power_mode == MMC_POWER_OFF)
sdhci_set_power(host, -1);
@@ -1237,6 +1254,35 @@ out:
spin_unlock_irqrestore(&host->lock, flags);
}
+static int sdhci_set_uhs(struct mmc_host *mmc, unsigned int uhs_mode)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+ int ret = 0;
+
+ host = mmc_priv(mmc);
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (host->mmc->caps & MMC_CAP_1_8V_DDR) {
+ u16 con;
+
+ con = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ con |= SDHCI_CTRL2_1_8V;
+ sdhci_writew(host, con, SDHCI_HOST_CONTROL2);
+ } else
+ goto err;
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ if (host->ops->set_uhs)
+ ret = host->ops->set_uhs(host, uhs_mode);
+
+ return ret;
+err:
+ spin_unlock_irqrestore(&host->lock, flags);
+ return -EINVAL;
+}
+
static int sdhci_get_ro(struct mmc_host *mmc)
{
struct sdhci_host *host;
@@ -1287,6 +1333,7 @@ out:
static const struct mmc_host_ops sdhci_ops = {
.request = sdhci_request,
.set_ios = sdhci_set_ios,
+ .set_uhs = sdhci_set_uhs,
.get_ro = sdhci_get_ro,
.enable_sdio_irq = sdhci_enable_sdio_irq,
};
@@ -1744,7 +1791,7 @@ EXPORT_SYMBOL_GPL(sdhci_alloc_host);
int sdhci_add_host(struct sdhci_host *host)
{
struct mmc_host *mmc;
- unsigned int caps, ocr_avail;
+ unsigned int caps, caps_h = 0, ocr_avail;
int ret;
WARN_ON(host == NULL);
@@ -1767,8 +1814,17 @@ int sdhci_add_host(struct sdhci_host *host)
host->version);
}
- caps = (host->quirks & SDHCI_QUIRK_MISSING_CAPS) ? host->caps :
- sdhci_readl(host, SDHCI_CAPABILITIES);
+ if (host->quirks & SDHCI_QUIRK_MISSING_CAPS)
+ caps = host->caps;
+ else {
+ caps = sdhci_readl(host, SDHCI_CAPABILITIES);
+ caps_h = sdhci_readl(host, SDHCI_CAPABILITIES_1);
+ }
+
+ if (caps & SDHCI_CAN_VDD_180) {
+ if (caps_h & SDHCI_CAN_SDR50)
+ mmc->caps |= (MMC_CAP_1_8V_DDR);
+ }
if (host->quirks & SDHCI_QUIRK_FORCE_DMA)
host->flags |= SDHCI_USE_SDMA;
@@ -145,7 +145,14 @@
#define SDHCI_ACMD12_ERR 0x3C
-/* 3E-3F reserved */
+#define SDHCI_HOST_CONTROL2 0x3E
+#define SDHCI_CTRL2_UHS_MASK 0x0007
+#define SDHCI_CTRL2_SDR12 0x0000
+#define SDHCI_CTRL2_SDR25 0x0001
+#define SDHCI_CTRL2_SDR50 0x0002
+#define SDHCI_CTRL2_SDR104 0x0003
+#define SDHCI_CTRL2_DDR50 0x0004
+#define SDHCI_CTRL2_1_8V 0x0008
#define SDHCI_CAPABILITIES 0x40
#define SDHCI_TIMEOUT_CLK_MASK 0x0000003F
@@ -167,6 +174,9 @@
#define SDHCI_CAN_64BIT 0x10000000
#define SDHCI_CAPABILITIES_1 0x44
+#define SDHCI_CAN_SDR50 0x00000001
+#define SDHCI_CAN_SDR104 0x00000002
+#define SDHCI_CAN_DDR50 0x00000004
#define SDHCI_MAX_CURRENT 0x48
@@ -222,6 +232,8 @@ struct sdhci_ops {
void (*platform_send_init_74_clocks)(struct sdhci_host *host,
u8 power_mode);
unsigned int (*get_ro)(struct sdhci_host *host);
+ unsigned int (*set_uhs)(struct sdhci_host *host,
+ unsigned int uhs_mode);
};
#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
@@ -117,6 +117,8 @@ struct mmc_host_ops {
/* optional callback for HC quirks */
void (*init_card)(struct mmc_host *host, struct mmc_card *card);
+
+ int (*set_uhs)(struct mmc_host *host, unsigned int uhs_mode);
};
struct mmc_card;
--
1.7.0.4