summaryrefslogtreecommitdiffstats
path: root/drivers/mmc/core/mmc.c
diff options
context:
space:
mode:
authorPhilip Rakity <prakity@marvell.com>2011-05-24 18:14:58 -0700
committerChris Ball <cjb@laptop.org>2011-05-25 16:52:32 -0400
commit08ee80cc397ac1a306ca689a22ede954d92d0db1 (patch)
treeb36819e6b52c9fd939dd6c886af734cb6ccbc45f /drivers/mmc/core/mmc.c
parent4f3d3e9b50931a3ec70f324d53fb7437e2a39388 (diff)
downloadlinux-08ee80cc397ac1a306ca689a22ede954d92d0db1.tar.gz
linux-08ee80cc397ac1a306ca689a22ede954d92d0db1.tar.bz2
linux-08ee80cc397ac1a306ca689a22ede954d92d0db1.zip
mmc: core: eMMC bus width may not work on all platforms
CMD19 -- The offical way to validate bus widths from the JEDEC spec does not work on all platforms. Some platforms that use PCI/PCIe to connect their SD controllers are known to fail. If the quirk MMC_BUS_WIDTH_TEST is not defined we try to figure out the bus width by reading the ext_csd at different bus widths and compare this against the ext_csd read in 1 bit mode. If no ext_csd is available we default to 1 bit operations. Code has been tested on mmp2 against 8 bit eMMC and Transcend 2GB card that is known to not work in 4 bit mode. The physical pins on the card are not present to support 4 bit operation. Signed-off-by: Philip Rakity <prakity@marvell.com> Signed-off-by: Chris Ball <cjb@laptop.org>
Diffstat (limited to 'drivers/mmc/core/mmc.c')
-rw-r--r--drivers/mmc/core/mmc.c112
1 files changed, 102 insertions, 10 deletions
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 0433fe66cbad..2a7e43bc796d 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -174,14 +174,17 @@ static int mmc_decode_csd(struct mmc_card *card)
}
/*
- * Read and decode extended CSD.
+ * Read extended CSD.
*/
-static int mmc_read_ext_csd(struct mmc_card *card)
+static int mmc_get_ext_csd(struct mmc_card *card, u8 **new_ext_csd)
{
int err;
u8 *ext_csd;
BUG_ON(!card);
+ BUG_ON(!new_ext_csd);
+
+ *new_ext_csd = NULL;
if (card->csd.mmca_vsn < CSD_SPEC_VER_4)
return 0;
@@ -199,12 +202,15 @@ static int mmc_read_ext_csd(struct mmc_card *card)
err = mmc_send_ext_csd(card, ext_csd);
if (err) {
+ kfree(ext_csd);
+ *new_ext_csd = NULL;
+
/* If the host or the card can't do the switch,
* fail more gracefully. */
if ((err != -EINVAL)
&& (err != -ENOSYS)
&& (err != -EFAULT))
- goto out;
+ return err;
/*
* High capacity cards should have this "magic" size
@@ -222,9 +228,23 @@ static int mmc_read_ext_csd(struct mmc_card *card)
mmc_hostname(card->host));
err = 0;
}
+ } else
+ *new_ext_csd = ext_csd;
- goto out;
- }
+ return err;
+}
+
+/*
+ * Decode extended CSD.
+ */
+static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd)
+{
+ int err = 0;
+
+ BUG_ON(!card);
+
+ if (!ext_csd)
+ return 0;
/* Version is coded in the CSD_STRUCTURE byte in the EXT_CSD register */
if (card->csd.structure == 3) {
@@ -372,8 +392,69 @@ static int mmc_read_ext_csd(struct mmc_card *card)
card->erased_byte = 0x0;
out:
+ return err;
+}
+
+static inline void mmc_free_ext_csd(u8 *ext_csd)
+{
kfree(ext_csd);
+}
+
+static int mmc_compare_ext_csds(struct mmc_card *card, u8 *ext_csd,
+ unsigned bus_width)
+{
+ u8 *bw_ext_csd;
+ int err;
+
+ err = mmc_get_ext_csd(card, &bw_ext_csd);
+ if (err)
+ return err;
+
+ if ((ext_csd == NULL || bw_ext_csd == NULL)) {
+ if (bus_width != MMC_BUS_WIDTH_1)
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (bus_width == MMC_BUS_WIDTH_1)
+ goto out;
+
+ /* only compare read only fields */
+ err = (!(ext_csd[EXT_CSD_PARTITION_SUPPORT] ==
+ bw_ext_csd[EXT_CSD_PARTITION_SUPPORT]) &&
+ (ext_csd[EXT_CSD_ERASED_MEM_CONT] ==
+ bw_ext_csd[EXT_CSD_ERASED_MEM_CONT]) &&
+ (ext_csd[EXT_CSD_REV] ==
+ bw_ext_csd[EXT_CSD_REV]) &&
+ (ext_csd[EXT_CSD_STRUCTURE] ==
+ bw_ext_csd[EXT_CSD_STRUCTURE]) &&
+ (ext_csd[EXT_CSD_CARD_TYPE] ==
+ bw_ext_csd[EXT_CSD_CARD_TYPE]) &&
+ (ext_csd[EXT_CSD_S_A_TIMEOUT] ==
+ bw_ext_csd[EXT_CSD_S_A_TIMEOUT]) &&
+ (ext_csd[EXT_CSD_HC_WP_GRP_SIZE] ==
+ bw_ext_csd[EXT_CSD_HC_WP_GRP_SIZE]) &&
+ (ext_csd[EXT_CSD_ERASE_TIMEOUT_MULT] ==
+ bw_ext_csd[EXT_CSD_ERASE_TIMEOUT_MULT]) &&
+ (ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] ==
+ bw_ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]) &&
+ (ext_csd[EXT_CSD_SEC_TRIM_MULT] ==
+ bw_ext_csd[EXT_CSD_SEC_TRIM_MULT]) &&
+ (ext_csd[EXT_CSD_SEC_ERASE_MULT] ==
+ bw_ext_csd[EXT_CSD_SEC_ERASE_MULT]) &&
+ (ext_csd[EXT_CSD_SEC_FEATURE_SUPPORT] ==
+ bw_ext_csd[EXT_CSD_SEC_FEATURE_SUPPORT]) &&
+ (ext_csd[EXT_CSD_TRIM_MULT] ==
+ bw_ext_csd[EXT_CSD_TRIM_MULT]) &&
+ memcmp(&ext_csd[EXT_CSD_SEC_CNT],
+ &bw_ext_csd[EXT_CSD_SEC_CNT],
+ 4) != 0);
+ if (err)
+ err = -EINVAL;
+
+out:
+ mmc_free_ext_csd(bw_ext_csd);
return err;
}
@@ -438,6 +519,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
u32 cid[4];
unsigned int max_dtr;
u32 rocr;
+ u8 *ext_csd = NULL;
BUG_ON(!host);
WARN_ON(!host->claimed);
@@ -536,7 +618,11 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
/*
* Fetch and process extended CSD.
*/
- err = mmc_read_ext_csd(card);
+
+ err = mmc_get_ext_csd(card, &ext_csd);
+ if (err)
+ goto free_card;
+ err = mmc_read_ext_csd(card, ext_csd);
if (err)
goto free_card;
@@ -676,14 +762,18 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
0);
if (!err) {
mmc_set_bus_width(card->host, bus_width);
+
/*
* If controller can't handle bus width test,
- * use the highest bus width to maintain
- * compatibility with previous MMC behavior.
+ * compare ext_csd previously read in 1 bit mode
+ * against ext_csd at new bus width
*/
if (!(host->caps & MMC_CAP_BUS_WIDTH_TEST))
- break;
- err = mmc_bus_test(card, bus_width);
+ err = mmc_compare_ext_csds(card,
+ ext_csd,
+ bus_width);
+ else
+ err = mmc_bus_test(card, bus_width);
if (!err)
break;
}
@@ -730,12 +820,14 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
if (!oldcard)
host->card = card;
+ mmc_free_ext_csd(ext_csd);
return 0;
free_card:
if (!oldcard)
mmc_remove_card(card);
err:
+ mmc_free_ext_csd(ext_csd);
return err;
}