Hi,

it seems like some eMMCs are not capable of doing 8-bit operation,
even if the controller supports it.  I was questioning our drivers
first, but it looks like it's the same on Linux.  In the case that
8-bit doesn't work, they seem to fall back to lower values to make
that HW work.

This diff implements a mechanism that tries 8-bit, if available,
then 4-bit and in the end falls back to 1-bit.  This makes my HW
work, but I would like to have this tested by a broader audience.

Apparently there's a "bus test" command, but it isn't implemented
on all host controllers.  Hence I simply try to read the EXT_CSD
to make sure the transfer works.

For testing, a print like

    printf("%s: using %u-bit width\n", DEVNAME(sc), width);

could be added at line 928.

What could possible regressions be?  The width could become smaller
then previously.  This would reduce the read/write transfer speed.
Also it's possible that eMMCs are not recognized/initialized anymore.

What could possible improvements be?  eMMCs that previously didn't
work now work, with at least 1-bit or 4-bit wide transfers.

Please note that this only works for eMMCs.  SD cards are *not* using
this code path.  SD cards have a different initialization code path.

Please report any changes or non-changes.  If nothing changes, that's
perfect.

Patrick

diff --git a/sys/dev/sdmmc/sdmmc_mem.c b/sys/dev/sdmmc/sdmmc_mem.c
index 59bcb1b4a11..5856b9bb1b3 100644
--- a/sys/dev/sdmmc/sdmmc_mem.c
+++ b/sys/dev/sdmmc/sdmmc_mem.c
@@ -56,6 +56,8 @@ int   sdmmc_mem_signal_voltage(struct sdmmc_softc *, int);
 
 int    sdmmc_mem_sd_init(struct sdmmc_softc *, struct sdmmc_function *);
 int    sdmmc_mem_mmc_init(struct sdmmc_softc *, struct sdmmc_function *);
+int    sdmmc_mem_mmc_select_bus_width(struct sdmmc_softc *,
+           struct sdmmc_function *, int);
 int    sdmmc_mem_single_read_block(struct sdmmc_function *, int, u_char *,
        size_t);
 int    sdmmc_mem_read_block_subr(struct sdmmc_function *, bus_dmamap_t,
@@ -908,31 +910,20 @@ sdmmc_mem_mmc_init(struct sdmmc_softc *sc, struct 
sdmmc_function *sf)
                            ext_csd[EXT_CSD_CARD_TYPE]);
                }
 
-               if (ISSET(sc->sc_caps, SMC_CAPS_8BIT_MODE)) {
+               if (ISSET(sc->sc_caps, SMC_CAPS_8BIT_MODE) &&
+                   sdmmc_mem_mmc_select_bus_width(sc, sf, 8) == 0)
                        width = 8;
-                       value = EXT_CSD_BUS_WIDTH_8;
-               } else if (ISSET(sc->sc_caps, SMC_CAPS_4BIT_MODE)) {
+               else if (ISSET(sc->sc_caps, SMC_CAPS_4BIT_MODE) &&
+                   sdmmc_mem_mmc_select_bus_width(sc, sf, 4) == 0)
                        width = 4;
-                       value = EXT_CSD_BUS_WIDTH_4;
-               } else {
-                       width = 1;
-                       value = EXT_CSD_BUS_WIDTH_1;
-               }
-
-               if (width != 1) {
-                       error = sdmmc_mem_mmc_switch(sf, EXT_CSD_CMD_SET_NORMAL,
-                           EXT_CSD_BUS_WIDTH, value);
-                       if (error == 0)
-                               error = sdmmc_chip_bus_width(sc->sct,
-                                   sc->sch, width);
-                       else {
+               else {
+                       error = sdmmc_mem_mmc_select_bus_width(sc, sf, 1);
+                       if (error != 0) {
                                DPRINTF(("%s: can't change bus width"
                                    " (%d bit)\n", DEVNAME(sc), width));
                                return error;
                        }
-
-                       /* XXXX: need bus test? (using by CMD14 & CMD19) */
-                       sdmmc_delay(10000);
+                       width = 1;
                }
 
                if (timing != SDMMC_TIMING_LEGACY) {
@@ -1041,6 +1032,59 @@ sdmmc_mem_mmc_init(struct sdmmc_softc *sc, struct 
sdmmc_function *sf)
        return error;
 }
 
+int
+sdmmc_mem_mmc_select_bus_width(struct sdmmc_softc *sc, struct sdmmc_function 
*sf,
+    int width)
+{
+       u_int8_t ext_csd[512];
+       int error, value;
+
+       switch (width) {
+       case 8:
+               value = EXT_CSD_BUS_WIDTH_8;
+               break;
+       case 4:
+               value = EXT_CSD_BUS_WIDTH_4;
+               break;
+       case 1:
+               value = EXT_CSD_BUS_WIDTH_1;
+               break;
+       default:
+               printf("%s: invalid bus width\n", DEVNAME(sc));
+               return EINVAL;
+       }
+
+       error = sdmmc_mem_mmc_switch(sf, EXT_CSD_CMD_SET_NORMAL,
+           EXT_CSD_BUS_WIDTH, value);
+       if (error != 0) {
+               DPRINTF(("%s: can't change card bus width"
+                   " (%d bit)\n", DEVNAME(sc), width));
+               return error;
+       }
+
+       error = sdmmc_chip_bus_width(sc->sct,
+           sc->sch, width);
+       if (error != 0) {
+               DPRINTF(("%s: can't change host bus width"
+                   " (%d bit)\n", DEVNAME(sc), width));
+               return error;
+       }
+
+       /* XXX: need bus test? (using by CMD14 & CMD19) */
+       sdmmc_delay(10000);
+
+       /* XXX: read EXT_CSD again, alternative to bus test */
+       error = sdmmc_mem_send_cxd_data(sc,
+           MMC_SEND_EXT_CSD, ext_csd, sizeof(ext_csd));
+       if (error != 0) {
+               DPRINTF(("%s: can't read EXT_CSD with bus width"
+                   " (%d bit)\n", DEVNAME(sc), width));
+               return error;
+       }
+
+       return error;
+}
+
 /*
  * Get or set the card's memory OCR value (SD or MMC).
  */

Reply via email to