mmc: core: Refactor bus speed selection code
The bus speed selection code in mmc_card_init() is convoluted, refactor
the code to simplify this and make it simpler for adding new bus speed
capabilities.
Signed-off-by: Venkat Gopalakrishnan <venkatg@codeaurora.org>
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 90d9826..bc5dd4d 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -766,75 +766,270 @@
}
/*
- * Selects the desired buswidth and switch to the HS200 mode
- * if bus width set without error
+ * Select the correct bus width supported by both host and card
*/
-static int mmc_select_hs200(struct mmc_card *card)
+static int mmc_select_bus_width(struct mmc_card *card, int ddr, u8 *ext_csd)
{
- int idx, err = 0;
struct mmc_host *host;
- static unsigned ext_csd_bits[] = {
- EXT_CSD_BUS_WIDTH_4,
- EXT_CSD_BUS_WIDTH_8,
+ static unsigned ext_csd_bits[][2] = {
+ { EXT_CSD_BUS_WIDTH_8, EXT_CSD_DDR_BUS_WIDTH_8 },
+ { EXT_CSD_BUS_WIDTH_4, EXT_CSD_DDR_BUS_WIDTH_4 },
+ { EXT_CSD_BUS_WIDTH_1, EXT_CSD_BUS_WIDTH_1 },
};
static unsigned bus_widths[] = {
- MMC_BUS_WIDTH_4,
MMC_BUS_WIDTH_8,
+ MMC_BUS_WIDTH_4,
+ MMC_BUS_WIDTH_1
};
-
- BUG_ON(!card);
+ unsigned idx, bus_width = 0;
+ int err = 0;
host = card->host;
+ if ((card->csd.mmca_vsn < CSD_SPEC_VER_4) ||
+ !(host->caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA)))
+ goto out;
+
+ if (host->caps & MMC_CAP_8_BIT_DATA)
+ idx = 0;
+ else
+ idx = 1;
+
+ for (; idx < ARRAY_SIZE(bus_widths); idx++) {
+ bus_width = bus_widths[idx];
+ if (bus_width == MMC_BUS_WIDTH_1)
+ ddr = 0; /* no DDR for 1-bit width */
+ err = mmc_select_powerclass(card, ext_csd_bits[idx][0],
+ ext_csd);
+ if (err)
+ pr_warning("%s: power class selection to " \
+ "bus width %d failed\n",
+ mmc_hostname(host),
+ 1 << bus_width);
+
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_BUS_WIDTH,
+ ext_csd_bits[idx][0],
+ card->ext_csd.generic_cmd6_time);
+ if (!err) {
+ mmc_set_bus_width(host, bus_width);
+
+ /*
+ * If controller can't handle bus width test,
+ * compare ext_csd previously read in 1 bit mode
+ * against ext_csd at new bus width
+ */
+ if (!(host->caps & MMC_CAP_BUS_WIDTH_TEST))
+ err = mmc_compare_ext_csds(card, bus_width);
+ else
+ err = mmc_bus_test(card, bus_width);
+ if (!err)
+ break;
+ }
+ }
+
+ if (!err && ddr) {
+ err = mmc_select_powerclass(card, ext_csd_bits[idx][1],
+ ext_csd);
+ if (err)
+ pr_warning("%s: power class selection to " \
+ "bus width %d ddr %d failed\n",
+ mmc_hostname(host),
+ 1 << bus_width, ddr);
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_BUS_WIDTH,
+ ext_csd_bits[idx][1],
+ card->ext_csd.generic_cmd6_time);
+ }
+
+out:
+ return err;
+}
+
+/*
+ * Switch to HighSpeed mode and select wide bus if supported
+ */
+static int mmc_select_hs(struct mmc_card *card, u8 *ext_csd)
+{
+ int err = 0;
+ struct mmc_host *host;
+
+ host = card->host;
+
+ if (!(host->caps & MMC_CAP_MMC_HIGHSPEED) ||
+ !(card->ext_csd.card_type & EXT_CSD_CARD_TYPE_52)) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_HS_TIMING, 1,
+ card->ext_csd.generic_cmd6_time);
+
+ if (err && err != -EBADMSG)
+ goto out;
+
+ mmc_card_set_highspeed(card);
+ mmc_set_timing(host, MMC_TIMING_MMC_HS);
+ mmc_set_clock(host, card->ext_csd.hs_max_dtr);
+
+ err = mmc_select_bus_width(card, 0, ext_csd);
+
+out:
+ if (err && err != -EOPNOTSUPP)
+ pr_warning("%s: Switch to HighSpeed mode failed (err:%d)\n",
+ mmc_hostname(host), err);
+ return err;
+}
+
+/*
+ * Select the desired buswidth and switch to HighSpeed DDR mode
+ * if bus width set without error
+ */
+static int mmc_select_hsddr(struct mmc_card *card, u8 *ext_csd)
+{
+ int ddr = 0, err = 0;
+ struct mmc_host *host;
+
+ host = card->host;
+
+ if (!(host->caps & MMC_CAP_HSDDR) ||
+ !(card->ext_csd.card_type & EXT_CSD_CARD_TYPE_DDR_52)) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
+ err = mmc_select_hs(card, ext_csd);
+ if (err)
+ goto out;
+ mmc_card_clr_highspeed(card);
+
+ if ((card->ext_csd.card_type & EXT_CSD_CARD_TYPE_DDR_1_8V)
+ && ((host->caps & (MMC_CAP_1_8V_DDR |
+ MMC_CAP_UHS_DDR50))
+ == (MMC_CAP_1_8V_DDR | MMC_CAP_UHS_DDR50)))
+ ddr = MMC_1_8V_DDR_MODE;
+ else if ((card->ext_csd.card_type & EXT_CSD_CARD_TYPE_DDR_1_2V)
+ && ((host->caps & (MMC_CAP_1_2V_DDR |
+ MMC_CAP_UHS_DDR50))
+ == (MMC_CAP_1_2V_DDR | MMC_CAP_UHS_DDR50)))
+ ddr = MMC_1_2V_DDR_MODE;
+
+ err = mmc_select_bus_width(card, ddr, ext_csd);
+ if (err)
+ goto out;
+
+ if (host->ios.bus_width == MMC_BUS_WIDTH_1) {
+ pr_err("%s: failed to switch to wide bus\n",
+ mmc_hostname(host));
+ goto out;
+ }
+
+ /*
+ * eMMC cards can support 3.3V to 1.2V i/o (vccq)
+ * signaling.
+ *
+ * EXT_CSD_CARD_TYPE_DDR_1_8V means 3.3V or 1.8V vccq.
+ *
+ * 1.8V vccq at 3.3V core voltage (vcc) is not required
+ * in the JEDEC spec for DDR.
+ *
+ * Do not force change in vccq since we are obviously
+ * working and no change to vccq is needed.
+ *
+ * WARNING: eMMC rules are NOT the same as SD DDR
+ */
+ if (ddr == MMC_1_2V_DDR_MODE) {
+ err = mmc_set_signal_voltage(host,
+ MMC_SIGNAL_VOLTAGE_120, 0);
+ if (err)
+ goto out;
+ }
+ mmc_card_set_ddr_mode(card);
+ mmc_set_timing(host, MMC_TIMING_UHS_DDR50);
+ mmc_set_bus_width(host, host->ios.bus_width);
+
+out:
+ if (err && err != -EOPNOTSUPP)
+ pr_warning("%s: Switch to HighSpeed DDR mode failed (err:%d)\n",
+ mmc_hostname(host), err);
+ return err;
+}
+
+/*
+ * Select the desired buswidth and switch to HS200 mode
+ * if bus width set without error
+ */
+static int mmc_select_hs200(struct mmc_card *card, u8 *ext_csd)
+{
+ int err = 0;
+ struct mmc_host *host;
+
+ host = card->host;
+
+ if (!(host->caps2 & MMC_CAP2_HS200) ||
+ !(card->ext_csd.card_type & EXT_CSD_CARD_TYPE_HS200)) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
if (card->ext_csd.card_type & EXT_CSD_CARD_TYPE_SDR_1_2V &&
host->caps2 & MMC_CAP2_HS200_1_2V_SDR)
if (mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_120, 0))
err = mmc_set_signal_voltage(host,
MMC_SIGNAL_VOLTAGE_180, 0);
-
/* If fails try again during next card power cycle */
if (err)
- goto err;
-
- idx = (host->caps & MMC_CAP_8_BIT_DATA) ? 1 : 0;
+ goto out;
/*
- * Unlike SD, MMC cards dont have a configuration register to notify
- * supported bus width. So bus test command should be run to identify
- * the supported bus width or compare the ext csd values of current
- * bus width and ext csd values of 1 bit mode read earlier.
+ * For devices supporting HS200 mode, the bus width has
+ * to be set before executing the tuning function. If
+ * set before tuning, then device will respond with CRC
+ * errors for responses on CMD line. So for HS200 the
+ * sequence will be
+ * 1. set bus width 4bit / 8 bit (1 bit not supported)
+ * 2. switch to HS200 mode
+ * 3. set the clock to > 52Mhz <=200MHz and
+ * 4. execute tuning for HS200
*/
- for (; idx >= 0; idx--) {
+ err = mmc_select_bus_width(card, 0, ext_csd);
+ if (err)
+ goto out;
- /*
- * Host is capable of 8bit transfer, then switch
- * the device to work in 8bit transfer mode. If the
- * mmc switch command returns error then switch to
- * 4bit transfer mode. On success set the corresponding
- * bus width on the host.
- */
- err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
- EXT_CSD_BUS_WIDTH,
- ext_csd_bits[idx],
- card->ext_csd.generic_cmd6_time);
- if (err)
- continue;
-
- mmc_set_bus_width(card->host, bus_widths[idx]);
-
- if (!(host->caps & MMC_CAP_BUS_WIDTH_TEST))
- err = mmc_compare_ext_csds(card, bus_widths[idx]);
- else
- err = mmc_bus_test(card, bus_widths[idx]);
- if (!err)
- break;
+ if (host->ios.bus_width == MMC_BUS_WIDTH_1) {
+ pr_err("%s: failed to switch to wide bus\n",
+ mmc_hostname(host));
+ goto out;
}
/* switch to HS200 mode if bus width set successfully */
- if (!err)
- err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
- EXT_CSD_HS_TIMING, 2, 0);
-err:
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_HS_TIMING, 2, 0);
+
+ if (err && err != -EBADMSG)
+ goto out;
+
+ mmc_set_timing(host, MMC_TIMING_MMC_HS200);
+ mmc_set_clock(host, card->ext_csd.hs_max_dtr);
+
+ if (host->ops->execute_tuning) {
+ mmc_host_clk_hold(host);
+ err = host->ops->execute_tuning(host,
+ MMC_SEND_TUNING_BLOCK_HS200);
+ mmc_host_clk_release(host);
+ }
+ if (err) {
+ pr_warning("%s: tuning execution failed\n",
+ mmc_hostname(host));
+ goto out;
+ }
+ mmc_card_set_hs200(card);
+
+out:
+ if (err && err != -EOPNOTSUPP)
+ pr_warning("%s: Switch to HS200 mode failed (err:%d)\n",
+ mmc_hostname(host), err);
return err;
}
@@ -919,6 +1114,33 @@
}
/*
+ * Activate highest bus speed mode supported by both host and card.
+ * On failure activate the next supported highest bus speed mode.
+ */
+static int mmc_select_bus_speed(struct mmc_card *card, u8 *ext_csd)
+{
+ int err = 0;
+
+ BUG_ON(!card);
+
+ if (!mmc_select_hs200(card, ext_csd))
+ goto out;
+ if (!mmc_select_hsddr(card, ext_csd))
+ goto out;
+ if (!mmc_select_hs(card, ext_csd))
+ goto out;
+
+ /*
+ * Select the default speed and wide bus if supported
+ */
+ mmc_set_clock(card->host, card->csd.max_dtr);
+ err = mmc_select_bus_width(card, 0, ext_csd);
+
+out:
+ return err;
+}
+
+/*
* Handle the detection and initialisation of a card.
*
* In the case of a resume, "oldcard" will contain the card
@@ -928,9 +1150,8 @@
struct mmc_card *oldcard)
{
struct mmc_card *card;
- int err, ddr = 0;
+ int err = 0;
u32 cid[4];
- unsigned int max_dtr;
u32 rocr;
u8 *ext_csd = NULL;
@@ -1132,209 +1353,11 @@
}
/*
- * Activate high speed (if supported)
+ * Activate highest bus speed mode supported by both host and card.
*/
- if (card->ext_csd.hs_max_dtr != 0) {
- err = 0;
- if (card->ext_csd.hs_max_dtr > 52000000 &&
- host->caps2 & MMC_CAP2_HS200)
- err = mmc_select_hs200(card);
- else if (host->caps & MMC_CAP_MMC_HIGHSPEED)
- err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
- EXT_CSD_HS_TIMING, 1,
- card->ext_csd.generic_cmd6_time);
-
- if (err && err != -EBADMSG)
- goto free_card;
-
- if (err) {
- pr_warning("%s: switch to highspeed failed\n",
- mmc_hostname(card->host));
- err = 0;
- } else {
- if (card->ext_csd.hs_max_dtr > 52000000 &&
- host->caps2 & MMC_CAP2_HS200) {
- mmc_card_set_hs200(card);
- mmc_set_timing(card->host,
- MMC_TIMING_MMC_HS200);
- } else {
- mmc_card_set_highspeed(card);
- mmc_set_timing(card->host, MMC_TIMING_MMC_HS);
- }
- }
- }
-
- /*
- * Compute bus speed.
- */
- max_dtr = (unsigned int)-1;
-
- if (mmc_card_highspeed(card) || mmc_card_hs200(card)) {
- if (max_dtr > card->ext_csd.hs_max_dtr)
- max_dtr = card->ext_csd.hs_max_dtr;
- } else if (max_dtr > card->csd.max_dtr) {
- max_dtr = card->csd.max_dtr;
- }
-
- mmc_set_clock(host, max_dtr);
-
- /*
- * Indicate DDR mode (if supported).
- */
- if (mmc_card_highspeed(card)) {
- if ((card->ext_csd.card_type & EXT_CSD_CARD_TYPE_DDR_1_8V)
- && ((host->caps & (MMC_CAP_1_8V_DDR |
- MMC_CAP_UHS_DDR50))
- == (MMC_CAP_1_8V_DDR | MMC_CAP_UHS_DDR50)))
- ddr = MMC_1_8V_DDR_MODE;
- else if ((card->ext_csd.card_type & EXT_CSD_CARD_TYPE_DDR_1_2V)
- && ((host->caps & (MMC_CAP_1_2V_DDR |
- MMC_CAP_UHS_DDR50))
- == (MMC_CAP_1_2V_DDR | MMC_CAP_UHS_DDR50)))
- ddr = MMC_1_2V_DDR_MODE;
- }
-
- /*
- * Indicate HS200 SDR mode (if supported).
- */
- if (mmc_card_hs200(card)) {
- u32 ext_csd_bits;
- u32 bus_width = card->host->ios.bus_width;
-
- /*
- * For devices supporting HS200 mode, the bus width has
- * to be set before executing the tuning function. If
- * set before tuning, then device will respond with CRC
- * errors for responses on CMD line. So for HS200 the
- * sequence will be
- * 1. set bus width 4bit / 8 bit (1 bit not supported)
- * 2. switch to HS200 mode
- * 3. set the clock to > 52Mhz <=200MHz and
- * 4. execute tuning for HS200
- */
- if ((host->caps2 & MMC_CAP2_HS200) &&
- card->host->ops->execute_tuning) {
- mmc_host_clk_hold(card->host);
- err = card->host->ops->execute_tuning(card->host,
- MMC_SEND_TUNING_BLOCK_HS200);
- mmc_host_clk_release(card->host);
- }
- if (err) {
- pr_warning("%s: tuning execution failed\n",
- mmc_hostname(card->host));
- goto err;
- }
-
- ext_csd_bits = (bus_width == MMC_BUS_WIDTH_8) ?
- EXT_CSD_BUS_WIDTH_8 : EXT_CSD_BUS_WIDTH_4;
- err = mmc_select_powerclass(card, ext_csd_bits, ext_csd);
- if (err)
- pr_warning("%s: power class selection to bus width %d"
- " failed\n", mmc_hostname(card->host),
- 1 << bus_width);
- }
-
- /*
- * Activate wide bus and DDR (if supported).
- */
- if (!mmc_card_hs200(card) &&
- (card->csd.mmca_vsn >= CSD_SPEC_VER_4) &&
- (host->caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA))) {
- static unsigned ext_csd_bits[][2] = {
- { EXT_CSD_BUS_WIDTH_8, EXT_CSD_DDR_BUS_WIDTH_8 },
- { EXT_CSD_BUS_WIDTH_4, EXT_CSD_DDR_BUS_WIDTH_4 },
- { EXT_CSD_BUS_WIDTH_1, EXT_CSD_BUS_WIDTH_1 },
- };
- static unsigned bus_widths[] = {
- MMC_BUS_WIDTH_8,
- MMC_BUS_WIDTH_4,
- MMC_BUS_WIDTH_1
- };
- unsigned idx, bus_width = 0;
-
- if (host->caps & MMC_CAP_8_BIT_DATA)
- idx = 0;
- else
- idx = 1;
- for (; idx < ARRAY_SIZE(bus_widths); idx++) {
- bus_width = bus_widths[idx];
- if (bus_width == MMC_BUS_WIDTH_1)
- ddr = 0; /* no DDR for 1-bit width */
- err = mmc_select_powerclass(card, ext_csd_bits[idx][0],
- ext_csd);
- if (err)
- pr_warning("%s: power class selection to "
- "bus width %d failed\n",
- mmc_hostname(card->host),
- 1 << bus_width);
-
- err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
- EXT_CSD_BUS_WIDTH,
- ext_csd_bits[idx][0],
- card->ext_csd.generic_cmd6_time);
- if (!err) {
- mmc_set_bus_width(card->host, bus_width);
-
- /*
- * If controller can't handle bus width test,
- * compare ext_csd previously read in 1 bit mode
- * against ext_csd at new bus width
- */
- if (!(host->caps & MMC_CAP_BUS_WIDTH_TEST))
- err = mmc_compare_ext_csds(card,
- bus_width);
- else
- err = mmc_bus_test(card, bus_width);
- if (!err)
- break;
- }
- }
-
- if (!err && ddr) {
- err = mmc_select_powerclass(card, ext_csd_bits[idx][1],
- ext_csd);
- if (err)
- pr_warning("%s: power class selection to "
- "bus width %d ddr %d failed\n",
- mmc_hostname(card->host),
- 1 << bus_width, ddr);
-
- err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
- EXT_CSD_BUS_WIDTH,
- ext_csd_bits[idx][1],
- card->ext_csd.generic_cmd6_time);
- }
- if (err) {
- pr_warning("%s: switch to bus width %d ddr %d "
- "failed\n", mmc_hostname(card->host),
- 1 << bus_width, ddr);
- goto free_card;
- } else if (ddr) {
- /*
- * eMMC cards can support 3.3V to 1.2V i/o (vccq)
- * signaling.
- *
- * EXT_CSD_CARD_TYPE_DDR_1_8V means 3.3V or 1.8V vccq.
- *
- * 1.8V vccq at 3.3V core voltage (vcc) is not required
- * in the JEDEC spec for DDR.
- *
- * Do not force change in vccq since we are obviously
- * working and no change to vccq is needed.
- *
- * WARNING: eMMC rules are NOT the same as SD DDR
- */
- if (ddr == MMC_1_2V_DDR_MODE) {
- err = mmc_set_signal_voltage(host,
- MMC_SIGNAL_VOLTAGE_120, 0);
- if (err)
- goto err;
- }
- mmc_card_set_ddr_mode(card);
- mmc_set_timing(card->host, MMC_TIMING_UHS_DDR50);
- mmc_set_bus_width(card->host, bus_width);
- }
- }
+ err = mmc_select_bus_speed(card, ext_csd);
+ if (err)
+ goto free_card;
/*
* Enable HPI feature (if supported)
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index 2cb297e..335ca93 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -514,7 +514,9 @@
#define mmc_card_set_present(c) ((c)->state |= MMC_STATE_PRESENT)
#define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY)
#define mmc_card_set_highspeed(c) ((c)->state |= MMC_STATE_HIGHSPEED)
+#define mmc_card_clr_highspeed(c) ((c)->state &= ~MMC_STATE_HIGHSPEED)
#define mmc_card_set_hs200(c) ((c)->state |= MMC_STATE_HIGHSPEED_200)
+#define mmc_card_clr_hs200(c) ((c)->state &= ~MMC_STATE_HIGHSPEED_200)
#define mmc_card_set_blockaddr(c) ((c)->state |= MMC_STATE_BLOCKADDR)
#define mmc_card_set_ddr_mode(c) ((c)->state |= MMC_STATE_HIGHSPEED_DDR)
#define mmc_card_set_uhs(c) ((c)->state |= MMC_STATE_ULTRAHIGHSPEED)
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index dbafdfc..5796b9d 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -245,6 +245,7 @@
/* DDR mode at 1.8V */
#define MMC_CAP_1_2V_DDR (1 << 12) /* can support */
/* DDR mode at 1.2V */
+#define MMC_CAP_HSDDR (MMC_CAP_1_8V_DDR | MMC_CAP_1_2V_DDR)
#define MMC_CAP_POWER_OFF_CARD (1 << 13) /* Can power off after boot */
#define MMC_CAP_BUS_WIDTH_TEST (1 << 14) /* CMD14/CMD19 bus width ok */
#define MMC_CAP_UHS_SDR12 (1 << 15) /* Host supports UHS SDR12 mode */
diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h
index de145d6..292c47e 100644
--- a/include/linux/mmc/mmc.h
+++ b/include/linux/mmc/mmc.h
@@ -369,6 +369,8 @@
#define EXT_CSD_CARD_TYPE_SDR_1_8V (1<<4) /* Card can run at 200MHz */
#define EXT_CSD_CARD_TYPE_SDR_1_2V (1<<5) /* Card can run at 200MHz */
/* SDR mode @1.2V I/O */
+#define EXT_CSD_CARD_TYPE_HS200 (EXT_CSD_CARD_TYPE_SDR_1_8V \
+ | EXT_CSD_CARD_TYPE_SDR_1_2V)
#define EXT_CSD_BUS_WIDTH_1 0 /* Card is in 1 bit mode */
#define EXT_CSD_BUS_WIDTH_4 1 /* Card is in 4 bit mode */