mmc: Test bus-width for old MMC devices
authorAries Lee <arieslee@jmicron.com>
Wed, 15 Dec 2010 07:14:24 +0000 (08:14 +0100)
committerChris Ball <cjb@laptop.org>
Sun, 9 Jan 2011 04:52:09 +0000 (23:52 -0500)
Some old MMC devices fail with the 4/8 bits the driver tries to use
exclusively.  This patch adds a test for the given bus setup and falls
back to the lower bit mode (until 1-bit mode) when the test fails.

[Major rework and refactoring by tiwai]
[Quirk addition and many fixes by prakity]

Signed-off-by: Aries Lee <arieslee@jmicron.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Philip Rakity <prakity@marvell.com>
Tested-by: Philip Rakity <prakity@marvell.com>
Signed-off-by: Chris Ball <cjb@laptop.org>
drivers/mmc/core/mmc.c
drivers/mmc/core/mmc_ops.c
drivers/mmc/core/mmc_ops.h
drivers/mmc/host/sdhci.c
drivers/mmc/host/sdhci.h
include/linux/mmc/host.h
include/linux/mmc/mmc.h

index 76bb621e9aa922df274e9a3b192ebdd663f05e9e..1d8409fcf1558cbad6b5fc72103f2e684e67bf48 100644 (file)
@@ -534,39 +534,57 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
         */
        if ((card->csd.mmca_vsn >= CSD_SPEC_VER_4) &&
            (host->caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA))) {
-               unsigned ext_csd_bit, bus_width;
-
-               if (host->caps & MMC_CAP_8_BIT_DATA) {
-                       if (ddr)
-                               ext_csd_bit = EXT_CSD_DDR_BUS_WIDTH_8;
-                       else
-                               ext_csd_bit = EXT_CSD_BUS_WIDTH_8;
-                       bus_width = MMC_BUS_WIDTH_8;
-               } else {
-                       if (ddr)
-                               ext_csd_bit = EXT_CSD_DDR_BUS_WIDTH_4;
-                       else
-                               ext_csd_bit = EXT_CSD_BUS_WIDTH_4;
-                       bus_width = MMC_BUS_WIDTH_4;
+               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_switch(card, EXT_CSD_CMD_SET_NORMAL,
+                                        EXT_CSD_BUS_WIDTH,
+                                        ext_csd_bits[idx][0]);
+                       if (!err) {
+                               /*
+                                * If controller can't handle bus width test,
+                                * use the highest bus width to maintain
+                                * compatibility with previous MMC behavior.
+                                */
+                               if (!(host->caps & MMC_CAP_BUS_WIDTH_TEST))
+                                       break;
+                               mmc_set_bus_width_ddr(card->host,
+                                                     bus_width, MMC_SDR_MODE);
+                               err = mmc_bus_test(card, bus_width);
+                               if (!err)
+                                       break;
+                       }
                }
 
-               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
-                                EXT_CSD_BUS_WIDTH, ext_csd_bit);
-
-               if (err && err != -EBADMSG)
-                       goto free_card;
-
+               if (!err && ddr) {
+                       err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+                                       EXT_CSD_BUS_WIDTH,
+                                       ext_csd_bits[idx][1]);
+               }
                if (err) {
                        printk(KERN_WARNING "%s: switch to bus width %d ddr %d "
-                              "failed\n", mmc_hostname(card->host),
-                              1 << bus_width, ddr);
-                       err = 0;
-               } else {
-                       if (ddr)
-                               mmc_card_set_ddr_mode(card);
-                       else
-                               ddr = MMC_SDR_MODE;
-
+                               "failed\n", mmc_hostname(card->host),
+                               1 << bus_width, ddr);
+                       goto free_card;
+               } else if (ddr) {
+                       mmc_card_set_ddr_mode(card);
                        mmc_set_bus_width_ddr(card->host, bus_width, ddr);
                }
        }
index 326447c9ede8aa5a9153f8019a47f706f20917ec..60842f878dedb015789d49b84baca3b7db8e7e7b 100644 (file)
@@ -462,3 +462,104 @@ int mmc_send_status(struct mmc_card *card, u32 *status)
        return 0;
 }
 
+static int
+mmc_send_bus_test(struct mmc_card *card, struct mmc_host *host, u8 opcode,
+                 u8 len)
+{
+       struct mmc_request mrq;
+       struct mmc_command cmd;
+       struct mmc_data data;
+       struct scatterlist sg;
+       u8 *data_buf;
+       u8 *test_buf;
+       int i, err;
+       static u8 testdata_8bit[8] = { 0x55, 0xaa, 0, 0, 0, 0, 0, 0 };
+       static u8 testdata_4bit[4] = { 0x5a, 0, 0, 0 };
+
+       /* dma onto stack is unsafe/nonportable, but callers to this
+        * routine normally provide temporary on-stack buffers ...
+        */
+       data_buf = kmalloc(len, GFP_KERNEL);
+       if (!data_buf)
+               return -ENOMEM;
+
+       if (len == 8)
+               test_buf = testdata_8bit;
+       else if (len == 4)
+               test_buf = testdata_4bit;
+       else {
+               printk(KERN_ERR "%s: Invalid bus_width %d\n",
+                      mmc_hostname(host), len);
+               kfree(data_buf);
+               return -EINVAL;
+       }
+
+       if (opcode == MMC_BUS_TEST_W)
+               memcpy(data_buf, test_buf, len);
+
+       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 = opcode;
+       cmd.arg = 0;
+
+       /* NOTE HACK:  the MMC_RSP_SPI_R1 is always correct here, but we
+        * rely on callers to never use this with "native" calls for reading
+        * CSD or CID.  Native versions of those commands use the R2 type,
+        * not R1 plus a data block.
+        */
+       cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+
+       data.blksz = len;
+       data.blocks = 1;
+       if (opcode == MMC_BUS_TEST_R)
+               data.flags = MMC_DATA_READ;
+       else
+               data.flags = MMC_DATA_WRITE;
+
+       data.sg = &sg;
+       data.sg_len = 1;
+       sg_init_one(&sg, data_buf, len);
+       mmc_wait_for_req(host, &mrq);
+       err = 0;
+       if (opcode == MMC_BUS_TEST_R) {
+               for (i = 0; i < len / 4; i++)
+                       if ((test_buf[i] ^ data_buf[i]) != 0xff) {
+                               err = -EIO;
+                               break;
+                       }
+       }
+       kfree(data_buf);
+
+       if (cmd.error)
+               return cmd.error;
+       if (data.error)
+               return data.error;
+
+       return err;
+}
+
+int mmc_bus_test(struct mmc_card *card, u8 bus_width)
+{
+       int err, width;
+
+       if (bus_width == MMC_BUS_WIDTH_8)
+               width = 8;
+       else if (bus_width == MMC_BUS_WIDTH_4)
+               width = 4;
+       else if (bus_width == MMC_BUS_WIDTH_1)
+               return 0; /* no need for test */
+       else
+               return -EINVAL;
+
+       /*
+        * Ignore errors from BUS_TEST_W.  BUS_TEST_R will fail if there
+        * is a problem.  This improves chances that the test will work.
+        */
+       mmc_send_bus_test(card, card->host, MMC_BUS_TEST_W, width);
+       err = mmc_send_bus_test(card, card->host, MMC_BUS_TEST_R, width);
+       return err;
+}
index 653eb8e841789d0c440875e07bacbef5cc6877fe..e6d44b8a18db52b438f7c422970c3cc6444588fc 100644 (file)
@@ -26,6 +26,7 @@ int mmc_send_cid(struct mmc_host *host, u32 *cid);
 int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp);
 int mmc_spi_set_crc(struct mmc_host *host, int use_crc);
 int mmc_card_sleepawake(struct mmc_host *host, int sleep);
+int mmc_bus_test(struct mmc_card *card, u8 bus_width);
 
 #endif
 
index 55698864c2cdf252e8f257e9f215ccbfbeca7983..d5febe584b050c2b36d23e31c178f3a6860effab 100644 (file)
@@ -23,6 +23,7 @@
 
 #include <linux/leds.h>
 
+#include <linux/mmc/mmc.h>
 #include <linux/mmc/host.h>
 
 #include "sdhci.h"
@@ -1521,7 +1522,11 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
 
        if (intmask & SDHCI_INT_DATA_TIMEOUT)
                host->data->error = -ETIMEDOUT;
-       else if (intmask & (SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_END_BIT))
+       else if (intmask & SDHCI_INT_DATA_END_BIT)
+               host->data->error = -EILSEQ;
+       else if ((intmask & SDHCI_INT_DATA_CRC) &&
+               SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND))
+                       != MMC_BUS_TEST_R)
                host->data->error = -EILSEQ;
        else if (intmask & SDHCI_INT_ADMA_ERROR) {
                printk(KERN_ERR "%s: ADMA error\n", mmc_hostname(host->mmc));
index 1efe7dc5255b7e29df7ef33f2a0083fb9fa9b881..6e0969e40650709c4e785267e63e8c1656f2a7b0 100644 (file)
@@ -52,6 +52,7 @@
 #define  SDHCI_CMD_RESP_SHORT_BUSY 0x03
 
 #define SDHCI_MAKE_CMD(c, f) (((c & 0xff) << 8) | (f & 0xff))
+#define SDHCI_GET_CMD(c) ((c>>8) & 0x3f)
 
 #define SDHCI_RESPONSE         0x10
 
index 3a85e73a38a956d33785f08d1356e9604df31f79..bcb793ec7374f61d2552cca43f573bdd1f100ece 100644 (file)
@@ -172,6 +172,7 @@ struct mmc_host {
 #define MMC_CAP_1_2V_DDR       (1 << 12)       /* can support */
                                                /* DDR mode at 1.2V */
 #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 */
 
        mmc_pm_flag_t           pm_caps;        /* supported pm features */
 
index 956fbd8776924d97394cbc4f253aa4bdbae8b83a..612301f85d144608066f1131bc330a3007a7c1e0 100644 (file)
@@ -40,7 +40,9 @@
 #define MMC_READ_DAT_UNTIL_STOP  11   /* adtc [31:0] dadr        R1  */
 #define MMC_STOP_TRANSMISSION    12   /* ac                      R1b */
 #define MMC_SEND_STATUS          13   /* ac   [31:16] RCA        R1  */
+#define MMC_BUS_TEST_R           14   /* adtc                    R1  */
 #define MMC_GO_INACTIVE_STATE    15   /* ac   [31:16] RCA            */
+#define MMC_BUS_TEST_W           19   /* adtc                    R1  */
 #define MMC_SPI_READ_OCR         58   /* spi                  spi_R3 */
 #define MMC_SPI_CRC_ON_OFF       59   /* spi  [0:0] flag      spi_R1 */