summaryrefslogtreecommitdiff
path: root/drivers/mmc
diff options
context:
space:
mode:
authorPavan Kunapuli <pkunapuli@nvidia.com>2011-01-10 20:43:32 +0530
committerDan Willemsen <dwillemsen@nvidia.com>2011-04-26 15:49:23 -0700
commit320395ccc6b474be0855c7323126fa62fb4ef143 (patch)
tree2d68f85ac753597a9f07fca291e7143ecce9d94c /drivers/mmc
parentff77b31c173918111e98cf7f9d704d94c5deb3c2 (diff)
sd:Adding support for sd3.0
Adding support for sd3.0 cards. Added support for SDR50, SDR104 and DDR50 modes. Bug 661035 Original-Change-Id: Iecd6634b8e25d5fcbc05f79f34aea40a8460b527 Reviewed-on: http://git-master/r/15003 Tested-by: Pavan Kunapuli <pkunapuli@nvidia.com> Reviewed-by: Venkata Nageswara Penumarty <vpenumarty@nvidia.com> Reviewed-by: Scott Williams <scwilliams@nvidia.com> Change-Id: Ic2119387d55b2e4263f50a606e0f957ab518f0cb
Diffstat (limited to 'drivers/mmc')
-rw-r--r--drivers/mmc/core/core.c42
-rw-r--r--drivers/mmc/core/core.h4
-rw-r--r--drivers/mmc/core/sd.c121
-rw-r--r--drivers/mmc/core/sd_ops.c59
-rw-r--r--drivers/mmc/core/sd_ops.h2
-rw-r--r--drivers/mmc/host/sdhci.c81
-rw-r--r--drivers/mmc/host/sdhci.h10
7 files changed, 310 insertions, 9 deletions
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 175c1a039f74..0d0c0e1d4738 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -673,6 +673,48 @@ void mmc_set_bus_width(struct mmc_host *host, unsigned int width)
mmc_set_ios(host);
}
+/*
+ * Switch the signalling voltage.
+ */
+void mmc_switch_signalling_voltage(struct mmc_host * host, unsigned int signalling_voltage)
+{
+ host->ios.signalling_voltage = signalling_voltage;
+ mmc_set_ios(host);
+}
+
+/*
+ * Start the frequency tuning process.
+ */
+void mmc_start_tuning(struct mmc_host *host)
+{
+ host->ios.tuning_arg = MMC_EXECUTE_TUNING;
+ host->tuning_status = 0;
+ mmc_set_ios(host);
+ host->ios.tuning_arg = 0;
+}
+
+/*
+ * Get the frequency tuning status.
+ */
+void mmc_get_tuning_status(struct mmc_host *host, int tuning_arg)
+{
+ host->ios.tuning_arg = tuning_arg;
+ host->tuning_status = 0;
+ mmc_set_ios(host);
+ host->ios.tuning_arg = 0;
+}
+
+/*
+ * Reset the tuning circuit.
+ */
+void mmc_reset_tuning_circuit(struct mmc_host *host)
+{
+ host->ios.tuning_arg = MMC_RESET_TUNING_CIRCUIT;
+ host->tuning_status = 0;
+ mmc_set_ios(host);
+ host->ios.tuning_arg = 0;
+}
+
/**
* mmc_vdd_to_ocrbitnum - Convert a voltage to the OCR bit number
* @vdd: voltage (mV)
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
index 1df918567d4b..d341dc51b196 100644
--- a/drivers/mmc/core/core.h
+++ b/drivers/mmc/core/core.h
@@ -38,6 +38,10 @@ void mmc_set_bus_width(struct mmc_host *host, unsigned int width);
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr);
void mmc_set_timing(struct mmc_host *host, unsigned int timing);
void mmc_set_bus_speed(struct mmc_host *host, unsigned char bus_speed_mode);
+void mmc_switch_signalling_voltage(struct mmc_host *host, unsigned int signalling_voltage);
+void mmc_start_tuning(struct mmc_host *host);
+void mmc_get_tuning_status(struct mmc_host *host, int tuning_arg);
+void mmc_reset_tuning_circuit(struct mmc_host *host);
static inline void mmc_delay(unsigned int ms)
{
diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c
index 7ab8fdc61c77..206b21f9797a 100644
--- a/drivers/mmc/core/sd.c
+++ b/drivers/mmc/core/sd.c
@@ -250,6 +250,59 @@ out:
return err;
}
+static int mmc_execute_tuning(struct mmc_card *card)
+{
+ int err;
+ u8 resp;
+
+ mmc_start_tuning(card->host);
+
+ err = mmc_send_tuning_pattern(card, &resp);
+ if (err)
+ goto out;
+
+ mmc_get_tuning_status(card->host, MMC_QUERY_TUNING_STATUS);
+ if (card->host->tuning_status != MMC_SD_TUNING_COMPLETED)
+ err = -EAGAIN;
+out:
+ card->host->tuning_status = 0;
+ return err;
+}
+
+static int mmc_frequency_tuning(struct mmc_card *card)
+{
+ int err;
+ unsigned int count = 0;
+ unsigned char tuning_done = 0;
+
+ while (!tuning_done) {
+ err = mmc_execute_tuning(card);
+ if (err) {
+ count++;
+ continue;
+ } else {
+ if (count >= 40) {
+ count = 0;
+ mmc_reset_tuning_circuit(card->host);
+ continue;
+ } else
+ tuning_done = 1;
+ }
+ }
+
+ /* If the sampling clock select is set, tuning is successful */
+ mmc_get_tuning_status(card->host, MMC_SAMPLING_CLOCK_SELECT);
+ if (card->host->tuning_status != MMC_SD_SAMPLING_CLOCK_SELECT_SET) {
+ printk(KERN_ERR "%s: frequency tuning failed.Reset tuning circuit\n",
+ mmc_hostname(card->host));
+
+ mmc_reset_tuning_circuit(card->host);
+ return -EIO;
+ }
+
+ return 0;
+}
+
/*
* Fetches and decodes switch information
*/
@@ -294,7 +347,11 @@ static int mmc_read_switch(struct mmc_card *card)
goto out;
}
- if (status[13] & 0x02)
+ if (status[13] & 0x08) /* UHS104 mode */
+ card->sw_caps.hs_max_dtr = 208000000;
+ else if (status[13] & 0x04) /* UHS50 mode */
+ card->sw_caps.hs_max_dtr = 104000000;
+ else if (status[13] & 0x02) /* high speed mode */
card->sw_caps.hs_max_dtr = 50000000;
out:
@@ -310,6 +367,7 @@ int mmc_sd_switch_hs(struct mmc_card *card)
{
int err;
u8 *status;
+ u8 bus_speed;
if (card->scr.sda_vsn < SCR_SPEC_VER_1)
return 0;
@@ -332,11 +390,16 @@ int mmc_sd_switch_hs(struct mmc_card *card)
return -ENOMEM;
}
- err = mmc_sd_switch(card, 1, 0, 1, status);
+ if (card->bus_speed)
+ bus_speed = card->bus_speed; /* Set the supported UHS mode */
+ else
+ bus_speed = 1; /* Set high speed mode */
+
+ err = mmc_sd_switch(card, 1, 0, bus_speed, status);
if (err)
goto out;
- if ((status[16] & 0xF) != 1) {
+ if ((status[16] & 0xF) != bus_speed) {
printk(KERN_WARNING "%s: Problem switching card "
"into high-speed mode!\n",
mmc_hostname(card->host));
@@ -402,6 +465,7 @@ struct device_type sd_type = {
int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid)
{
int err;
+ u32 rocr;
/*
* Since we're changing the OCR value, we seem to
@@ -421,10 +485,30 @@ int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid)
if (!err)
ocr |= 1 << 30;
- err = mmc_send_app_op_cond(host, ocr, NULL);
+ /*
+ * Check the voltage switching capability if the host supports SDR
+ * or DDR modes.
+ */
+ if (((host->caps & MMC_CAP_SDR50) || (host->caps & MMC_CAP_SDR104) ||
+ (host->caps & MMC_CAP_DDR50))&& (!mmc_host_is_spi(host)))
+ ocr |= (1 << 24);
+
+ err = mmc_send_app_op_cond(host, ocr, &rocr);
if (err)
return err;
+ /* Check if switch to 1.8V is accepted by the card */
+ if ((rocr >> 24) & 1) {
+ /* Issue voltage switch command */
+ err = mmc_send_voltage_switch(host);
+ if (err)
+ return err;
+
+ /* Switch to 1.8 V */
+ mmc_switch_signalling_voltage(host, MMC_1_8_VOLT_SIGNALLING);
+ host->is_voltage_switched = 1;
+ }
+
if (mmc_host_is_spi(host))
err = mmc_send_cid(host, cid);
else
@@ -560,6 +644,8 @@ void mmc_sd_go_highspeed(struct mmc_card *card)
{
mmc_card_set_highspeed(card);
mmc_set_timing(card->host, MMC_TIMING_SD_HS);
+ if (card->bus_speed)
+ mmc_set_bus_speed(card->host, card->bus_speed);
}
/*
@@ -632,6 +718,23 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
goto free_card;
/*
+ * If voltage switching is supported, attempt to switch to a supported
+ * UHS mode. If voltage switching is not supported, attempt to switch
+ * to high speed mode.
+ */
+ if (host->is_voltage_switched) {
+ if ((host->caps & MMC_CAP_SDR104) &&
+ (card->sw_caps.hs_max_dtr == 208000000)) {
+ card->bus_speed = MMC_BUS_SPEED_MODE_SDR104;
+ } else if (host->caps & MMC_CAP_SDR50) {
+ card->bus_speed = MMC_BUS_SPEED_MODE_SDR50;
+ } else if (host->caps & MMC_CAP_DDR50) {
+ card->bus_speed = MMC_BUS_SPEED_MODE_DDR50;
+ } else
+ card->bus_speed = 0;
+ }
+
+ /*
* Attempt to change to high-speed (if supported)
*/
err = mmc_sd_switch_hs(card);
@@ -646,6 +749,16 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
mmc_set_clock(host, mmc_sd_get_max_clock(card));
/*
+ * If SDR104 or SDR50 modes are set, do the frequency tuning.
+ */
+ if ((card->bus_speed == MMC_BUS_SPEED_MODE_SDR104) ||
+ (card->bus_speed == MMC_BUS_SPEED_MODE_SDR50)){
+ err = mmc_frequency_tuning(card);
+ if (err)
+ goto free_card;
+ }
+
+ /*
* Switch to wider bus (if supported).
*/
if ((host->caps & MMC_CAP_4_BIT_DATA) &&
diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c
index 797cdb5887fd..c450af8276d8 100644
--- a/drivers/mmc/core/sd_ops.c
+++ b/drivers/mmc/core/sd_ops.c
@@ -394,3 +394,62 @@ int mmc_app_sd_status(struct mmc_card *card, void *ssr)
return 0;
}
+
+int mmc_send_voltage_switch(struct mmc_host *host)
+{
+ int err = 0;
+ struct mmc_command cmd = {
+ .opcode = SD_VOLTAGE_SWITCH,
+ .arg = 0,
+ .flags = MMC_RSP_R1 | MMC_CMD_AC,
+ };
+
+ BUG_ON(!host);
+ err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
+
+ return err;
+}
+
+int mmc_send_tuning_pattern(struct mmc_card *card, u8 *resp)
+{
+ struct mmc_request mrq;
+ struct mmc_command cmd;
+ struct mmc_data data;
+ struct scatterlist sg;
+
+ BUG_ON(!card);
+ BUG_ON(!card->host);
+
+ /* NOTE: caller guarantees resp is heap-allocated */
+
+ memset(&mrq, 0, sizeof(struct mmc_request));
+ memset(&cmd, 0, sizeof(struct mmc_command));
+ memset(&data, 0, sizeof(struct mmc_data));
+
+ mrq.cmd = &cmd;
+ mrq.data = &data;
+
+ cmd.opcode = SD_SEND_TUNING_PATTERN;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ data.blksz = 64;
+ data.blocks = 1;
+ data.flags = MMC_DATA_READ;
+ data.sg = &sg;
+ data.sg_len = 1;
+
+ sg_init_one(&sg, resp, 64);
+
+ mmc_set_data_timeout(&data, card);
+
+ mmc_wait_for_req(card->host, &mrq);
+
+ if (cmd.error)
+ return cmd.error;
+ if (data.error)
+ return data.error;
+
+ return 0;
+}
+
diff --git a/drivers/mmc/core/sd_ops.h b/drivers/mmc/core/sd_ops.h
index ffc2305d905f..17daf7842485 100644
--- a/drivers/mmc/core/sd_ops.h
+++ b/drivers/mmc/core/sd_ops.h
@@ -20,6 +20,8 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr);
int mmc_sd_switch(struct mmc_card *card, int mode, int group,
u8 value, u8 *resp);
int mmc_app_sd_status(struct mmc_card *card, void *ssr);
+int mmc_send_voltage_switch(struct mmc_host *host);
+int mmc_send_tuning_pattern(struct mmc_card *card, u8 *resp);
#endif
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index c7bc61fd13ca..4ea522ba5044 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -179,6 +179,7 @@ static void sdhci_reset(struct sdhci_host *host, u8 mask)
if (mask & SDHCI_RESET_ALL)
host->ops->configure_capabilities(host);
+ host->uhs_mode = 0;
}
static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
@@ -673,8 +674,13 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data)
count = sdhci_calc_timeout(host, data);
sdhci_writeb(host, count, SDHCI_TIMEOUT_CONTROL);
- if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA))
- host->flags |= SDHCI_REQ_USE_DMA;
+ /* Frequency tuning uses only PIO mode */
+ if (host->mmc->tuning_status == MMC_SD_TUNING_IN_PROGRESS) {
+ host->flags &= ~SDHCI_REQ_USE_DMA;
+ } else {
+ if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA))
+ host->flags |= SDHCI_REQ_USE_DMA;
+ }
/*
* FIXME: This doesn't account for merging when mapping the
@@ -1217,6 +1223,14 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
ctrl |= SDHCI_UHS_MODE_SEL_DDR50;
host->uhs_mode = SDHCI_UHS_MODE_SEL_DDR50;
break;
+ case MMC_BUS_SPEED_MODE_SDR50:
+ ctrl |= SDHCI_UHS_MODE_SEL_SDR50;
+ host->uhs_mode = SDHCI_UHS_MODE_SEL_SDR50;
+ break;
+ case MMC_BUS_SPEED_MODE_SDR104:
+ ctrl |= SDHCI_UHS_MODE_SEL_SDR104;
+ host->uhs_mode = SDHCI_UHS_MODE_SEL_SDR104;
+ break;
default:
goto out;
}
@@ -1242,6 +1256,54 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
}
+ if ((ios->signalling_voltage == MMC_1_8_VOLT_SIGNALLING) &&
+ (ios->signalling_voltage != host->signalling_voltage)) {
+ /* Switch OFF SD clock */
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+ clk &= (~SDHCI_CLOCK_CARD_EN);
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL_2);
+ ctrl |= SDHCI_CTRL_2_VOLT_18_EN;
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL_2);
+
+ /* Wait for 5msec for the output to be stable */
+ mdelay(5);
+
+ /* Switch ON sd clock */
+ clk |= SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+ host->signalling_voltage = ios->signalling_voltage;
+ }
+
+ if (ios->tuning_arg) {
+ switch(ios->tuning_arg) {
+ case MMC_EXECUTE_TUNING:
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL_2);
+ if (!(ctrl & SDHCI_CTRL_2_EXECUTE_TUNING)) {
+ ctrl |= SDHCI_CTRL_2_EXECUTE_TUNING;
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL_2);
+ }
+ mmc->tuning_status = MMC_SD_TUNING_IN_PROGRESS;
+ break;
+ case MMC_QUERY_TUNING_STATUS:
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL_2);
+ if (!(ctrl & SDHCI_CTRL_2_EXECUTE_TUNING))
+ mmc->tuning_status = MMC_SD_TUNING_COMPLETED;
+ break;
+ case MMC_SAMPLING_CLOCK_SELECT:
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL_2);
+ if (ctrl & SDHCI_CTRL_2_SAMPLING_CLOCK_SELECT)
+ mmc->tuning_status = MMC_SD_SAMPLING_CLOCK_SELECT_SET;
+ break;
+ default:
+ printk(KERN_ERR "%s: Undefined tuning operation\n",
+ mmc_hostname(host->mmc));
+ break;
+ }
+ }
+
out:
mmiowb();
spin_unlock_irqrestore(&host->lock, flags);
@@ -1591,8 +1653,12 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
if (host->data->error)
sdhci_finish_data(host);
else {
- if (intmask & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL))
- sdhci_transfer_pio(host);
+ if (intmask & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL)) {
+ if (host->mmc->tuning_status == MMC_SD_TUNING_IN_PROGRESS)
+ tasklet_schedule(&host->finish_tasklet);
+ else
+ sdhci_transfer_pio(host);
+ }
/*
* We currently don't do anything fancy with DMA
@@ -2026,7 +2092,12 @@ int sdhci_add_host(struct sdhci_host *host)
caps = sdhci_readb(host, SDHCI_HIGHER_CAPABILITIES);
if (caps & SDHCI_CAN_SUPPORT_DDR50)
mmc->caps |= MMC_CAP_DDR50;
-
+ if (caps & SDHCI_CAN_SUPPORT_SDR50)
+ mmc->caps |= MMC_CAP_SDR50;
+ if (caps & SDHCI_CAN_SUPPORT_SDR104)
+ mmc->caps |= MMC_CAP_SDR104;
+ if (caps & SDHCI_CAN_SDR50_TUNING)
+ mmc->caps |= MMC_CAP_SDR50_TUNING;
/*
* Init tasklets.
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index dc346f14664c..69576e84fa3d 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -135,7 +135,13 @@
#define SDHCI_ACMD12_ERR 0x3C
#define SDHCI_HOST_CONTROL_2 0x3E
+#define SDHCI_UHS_MODE_SEL_SDR25 0x01
+#define SDHCI_UHS_MODE_SEL_SDR50 0x02
+#define SDHCI_UHS_MODE_SEL_SDR104 0x03
#define SDHCI_UHS_MODE_SEL_DDR50 0x04
+#define SDHCI_CTRL_2_VOLT_18_EN 0x08
+#define SDHCI_CTRL_2_EXECUTE_TUNING 0x40
+#define SDHCI_CTRL_2_SAMPLING_CLOCK_SELECT 0x80
#define SDHCI_CAPABILITIES 0x40
#define SDHCI_TIMEOUT_CLK_MASK 0x0000003F
@@ -155,7 +161,10 @@
#define SDHCI_CAN_64BIT 0x10000000
#define SDHCI_HIGHER_CAPABILITIES 0x44
+#define SDHCI_CAN_SUPPORT_SDR50 0x00000001
+#define SDHCI_CAN_SUPPORT_SDR104 0x00000002
#define SDHCI_CAN_SUPPORT_DDR50 0x00000004
+#define SDHCI_CAN_SDR50_TUNING 0x00002000
#define SDHCI_MAX_CURRENT 0x48
@@ -301,6 +310,7 @@ struct sdhci_host {
unsigned int clock; /* Current clock (MHz) */
u8 pwr; /* Current voltage */
u8 uhs_mode; /* Current uhs mode */
+ u8 signalling_voltage; /* Signalling voltage */
struct mmc_request *mrq; /* Current request */
struct mmc_command *cmd; /* Current command */