Author: ganbold
Date: Tue Apr 28 08:27:44 2015
New Revision: 282129
URL: https://svnweb.freebsd.org/changeset/base/282129

Log:
  Update Amlogic MMC driver:
  
    1) Advertise the actual min / max speeds the hardware is capable
         of supporting given the reference clock used by the board.
  
    2) Rather than attempting to extend the hardware's timeout register
         in software (the hardware doesn't have sufficient bits to directly
              support long timeouts), simply implement the same timeout approach
                 used in the SDXC driver.
  
    3) Set the timeout for a linked command (e.g. STOP TRANSMISSION) based
         on the previous multiblock read / write.
  
  The changes have been smoke tested on both the ODROID-C1 and the VSATV102-M6
  using the following cards:
  
  * PQI 2GB microSD
  * SanDisk 2GB microSD
  * PQI 8GB SDHC (not a microSD so only tested on the ATV-102)
  * PNY 8GB microSDHC
  * SanDisk Ultra 32GB microSDHC
  
  Submitted by:  John Wehle

Modified:
  head/sys/arm/amlogic/aml8726/aml8726_mmc.c
  head/sys/arm/amlogic/aml8726/aml8726_mmc.h

Modified: head/sys/arm/amlogic/aml8726/aml8726_mmc.c
==============================================================================
--- head/sys/arm/amlogic/aml8726/aml8726_mmc.c  Tue Apr 28 08:20:23 2015        
(r282128)
+++ head/sys/arm/amlogic/aml8726/aml8726_mmc.c  Tue Apr 28 08:27:44 2015        
(r282129)
@@ -70,6 +70,7 @@ struct aml8726_mmc_softc {
        device_t                dev;
        struct resource         *res[2];
        struct mtx              mtx;
+       struct callout          ch;
        uint32_t                port;
        unsigned int            ref_freq;
        struct aml8726_mmc_gpio pwr_en;
@@ -81,7 +82,7 @@ struct aml8726_mmc_softc {
        struct mmc_host         host;
        int                     bus_busy;
        struct mmc_command      *cmd;
-       unsigned int            timeout_remaining;
+       uint32_t                stop_timeout;
 };
 
 static struct resource_spec aml8726_mmc_spec[] = {
@@ -92,6 +93,7 @@ static struct resource_spec aml8726_mmc_
 
 #define        AML_MMC_LOCK(sc)                mtx_lock(&(sc)->mtx)
 #define        AML_MMC_UNLOCK(sc)              mtx_unlock(&(sc)->mtx)
+#define        AML_MMC_LOCK_ASSERT(sc)         mtx_assert(&(sc)->mtx, MA_OWNED)
 #define        AML_MMC_LOCK_INIT(sc)           \
     mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev),       \
     "mmc", MTX_DEF)
@@ -107,6 +109,10 @@ static struct resource_spec aml8726_mmc_
 #define        PWR_OFF_FLAG(pol)               ((pol) == 0 ? GPIO_PIN_HIGH :   
\
     GPIO_PIN_LOW)
 
+#define        MSECS_TO_TICKS(ms)              (((ms)*hz)/1000 + 1)
+
+static void aml8726_mmc_timeout(void *arg);
+
 static unsigned int
 aml8726_mmc_clk(phandle_t node)
 {
@@ -126,6 +132,37 @@ aml8726_mmc_clk(phandle_t node)
        return ((unsigned int)prop);
 }
 
+static uint32_t
+aml8726_mmc_freq(struct aml8726_mmc_softc *sc, uint32_t divisor)
+{
+
+       return (sc->ref_freq / ((divisor + 1) * 2));
+}
+
+static uint32_t
+aml8726_mmc_div(struct aml8726_mmc_softc *sc, uint32_t desired_freq)
+{
+       uint32_t divisor;
+
+       divisor = sc->ref_freq / (desired_freq * 2);
+
+       if (divisor == 0)
+               divisor = 1;
+
+       divisor -= 1;
+
+       if (aml8726_mmc_freq(sc, divisor) > desired_freq)
+               divisor += 1;
+
+       if (divisor > (AML_MMC_CONFIG_CMD_CLK_DIV_MASK >>
+           AML_MMC_CONFIG_CMD_CLK_DIV_SHIFT)) {
+               divisor = AML_MMC_CONFIG_CMD_CLK_DIV_MASK >>
+                   AML_MMC_CONFIG_CMD_CLK_DIV_SHIFT;
+       }
+
+       return (divisor);
+}
+
 static void
 aml8726_mmc_mapmem(void *arg, bus_dma_segment_t *segs, int nseg, int error)
 {
@@ -162,25 +199,18 @@ aml8726_mmc_power_on(struct aml8726_mmc_
            PWR_ON_FLAG(sc->pwr_en.pol)));
 }
 
-static int
-aml8726_mmc_restart_timer(struct aml8726_mmc_softc *sc)
+static void
+aml8726_mmc_soft_reset(struct aml8726_mmc_softc *sc, boolean_t enable_irq)
 {
-       uint32_t count;
-       uint32_t isr;
-
-       if (sc->cmd == NULL || sc->timeout_remaining == 0)
-               return (0);
-
-       count = (sc->timeout_remaining > 0x1fff) ? 0x1fff :
-           sc->timeout_remaining;
-       sc->timeout_remaining -= count;
+       uint32_t icr;
 
-       isr = (count << AML_MMC_IRQ_STATUS_TIMER_CNT_SHIFT) |
-           AML_MMC_IRQ_STATUS_TIMER_EN | AML_MMC_IRQ_STATUS_TIMEOUT_IRQ;
+       icr = AML_MMC_IRQ_CONFIG_SOFT_RESET;
 
-       CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, isr);
+       if (enable_irq == true)
+               icr |= AML_MMC_IRQ_CONFIG_CMD_DONE_EN;
 
-       return (1);
+       CSR_WRITE_4(sc, AML_MMC_IRQ_CONFIG_REG, icr);
+       CSR_BARRIER(sc, AML_MMC_IRQ_CONFIG_REG);
 }
 
 static int
@@ -191,7 +221,6 @@ aml8726_mmc_start_command(struct aml8726
        uint32_t block_size;
        uint32_t bus_width;
        uint32_t cmdr;
-       uint32_t cycles_per_msec;
        uint32_t extr;
        uint32_t mcfgr;
        uint32_t nbits_per_pkg;
@@ -203,14 +232,9 @@ aml8726_mmc_start_command(struct aml8726
                return (MMC_ERR_INVALID);
 
        /*
-        * Ensure the hardware state machine is in a known state,
-        * the command done interrupt is enabled, and previous
-        * IRQ status bits have been cleared.
+        * Ensure the hardware state machine is in a known state.
         */
-       CSR_WRITE_4(sc, AML_MMC_IRQ_CONFIG_REG,
-           (AML_MMC_IRQ_CONFIG_SOFT_RESET | AML_MMC_IRQ_CONFIG_CMD_DONE_EN));
-       CSR_BARRIER(sc, AML_MMC_IRQ_CONFIG_REG);
-       CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, AML_MMC_IRQ_STATUS_CLEAR_IRQ);
+       aml8726_mmc_soft_reset(sc, true);
 
        /*
         * Start and transmission bits are per section 4.7.2 of the:
@@ -225,6 +249,13 @@ aml8726_mmc_start_command(struct aml8726
        mcfgr = sc->port;
        timeout = AML_MMC_CMD_TIMEOUT;
 
+       /*
+        * If this is a linked command, then use the previous timeout.
+        */
+       if (cmd == cmd->mrq->stop && sc->stop_timeout)
+               timeout = sc->stop_timeout;
+       sc->stop_timeout = 0;
+
        if ((cmd->flags & MMC_RSP_136) != 0) {
                cmdr |= AML_MMC_CMD_RESP_CRC7_FROM_8;
                cmdr |= (133 << AML_MMC_CMD_RESP_BITS_SHIFT);
@@ -291,32 +322,24 @@ aml8726_mmc_start_command(struct aml8726
                        timeout = AML_MMC_WRITE_TIMEOUT *
                            (data->len / block_size);
                }
+
+               /*
+                * Stop terminates a multiblock read / write and thus
+                * can take as long to execute as an actual read / write.
+                */
+               if (cmd->mrq->stop != NULL)
+                       sc->stop_timeout = timeout;
        }
 
        sc->cmd = cmd;
 
        cmd->error = MMC_ERR_NONE;
 
-       /*
-        * Round up while calculating the number of cycles which
-        * correspond to a millisecond.  Use that to determine
-        * the count from the desired timeout in milliseconds.
-        *
-        * The counter has a limited range which is not sufficient
-        * for directly implementing worst case timeouts at high clock
-        * rates so a 32 bit counter is implemented in software.
-        *
-        * The documentation isn't clear on when the timer starts
-        * so add 48 cycles for the command and 136 cycles for the
-        * response (the values are from the previously mentioned
-        * standard).
-        */
        if (timeout > AML_MMC_MAX_TIMEOUT)
                timeout = AML_MMC_MAX_TIMEOUT;
-       cycles_per_msec = (ios->clock + 1000 - 1) / 1000;
-       sc->timeout_remaining = 48 + 136 + timeout * cycles_per_msec;
 
-       aml8726_mmc_restart_timer(sc);
+       callout_reset(&sc->ch, MSECS_TO_TICKS(timeout),
+           aml8726_mmc_timeout, sc);
 
        CSR_WRITE_4(sc, AML_MMC_CMD_ARGUMENT_REG, cmd->arg);
        CSR_WRITE_4(sc, AML_MMC_MULT_CONFIG_REG, mcfgr);
@@ -330,20 +353,96 @@ aml8726_mmc_start_command(struct aml8726
 }
 
 static void
-aml8726_mmc_intr(void *arg)
+aml8726_mmc_finish_command(struct aml8726_mmc_softc *sc, int mmc_error)
 {
-       struct aml8726_mmc_softc *sc = (struct aml8726_mmc_softc *)arg;
+       int mmc_stop_error;
        struct mmc_command *cmd;
        struct mmc_command *stop_cmd;
        struct mmc_data *data;
+
+       AML_MMC_LOCK_ASSERT(sc);
+
+       /* Clear all interrupts since the request is no longer in flight. */
+       CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, AML_MMC_IRQ_STATUS_CLEAR_IRQ);
+       CSR_BARRIER(sc, AML_MMC_IRQ_STATUS_REG);
+
+       /* In some cases (e.g. finish called via timeout) this is a NOP. */
+       callout_stop(&sc->ch);
+
+       cmd = sc->cmd;
+       sc->cmd = NULL;
+
+       cmd->error = mmc_error;
+
+       data = cmd->data;
+
+       if (data && data->len &&
+           (data->flags & (MMC_DATA_READ | MMC_DATA_WRITE)) != 0) {
+               if ((data->flags & MMC_DATA_READ) != 0)
+                       bus_dmamap_sync(sc->dmatag, sc->dmamap,
+                           BUS_DMASYNC_POSTREAD);
+               else
+                       bus_dmamap_sync(sc->dmatag, sc->dmamap,
+                           BUS_DMASYNC_POSTWRITE);
+               bus_dmamap_unload(sc->dmatag, sc->dmamap);
+       }
+
+       /*
+        * If there's a linked stop command, then start the stop command.
+        * In order to establish a known state attempt the stop command
+        * even if the original request encountered an error.
+        */
+
+       stop_cmd = (cmd->mrq->stop != cmd) ? cmd->mrq->stop : NULL;
+
+       if (stop_cmd != NULL) {
+               mmc_stop_error = aml8726_mmc_start_command(sc, stop_cmd);
+               if (mmc_stop_error == MMC_ERR_NONE) {
+                       AML_MMC_UNLOCK(sc);
+                       return;
+               }
+               stop_cmd->error = mmc_stop_error;
+       }
+
+       AML_MMC_UNLOCK(sc);
+
+       /* Execute the callback after dropping the lock. */
+       if (cmd->mrq)
+               cmd->mrq->done(cmd->mrq);
+}
+
+static void
+aml8726_mmc_timeout(void *arg)
+{
+       struct aml8726_mmc_softc *sc = (struct aml8726_mmc_softc *)arg;
+
+       /*
+        * The command failed to complete in time so forcefully
+        * terminate it.
+        */
+       aml8726_mmc_soft_reset(sc, false);
+
+       /*
+        * Ensure the command has terminated before continuing on
+        * to things such as bus_dmamap_sync / bus_dmamap_unload.
+        */
+       while ((CSR_READ_4(sc, AML_MMC_IRQ_STATUS_REG) &
+           AML_MMC_IRQ_STATUS_CMD_BUSY) != 0)
+               cpu_spinwait();
+
+       aml8726_mmc_finish_command(sc, MMC_ERR_TIMEOUT);
+}
+
+static void
+aml8726_mmc_intr(void *arg)
+{
+       struct aml8726_mmc_softc *sc = (struct aml8726_mmc_softc *)arg;
        uint32_t cmdr;
-       uint32_t icr;
        uint32_t isr;
        uint32_t mcfgr;
        uint32_t previous_byte;
        uint32_t resp;
        int mmc_error;
-       int mmc_stop_error;
        unsigned int i;
 
        AML_MMC_LOCK(sc);
@@ -367,12 +466,6 @@ aml8726_mmc_intr(void *arg)
                if ((cmdr & AML_MMC_CMD_CMD_HAS_DATA) != 0 &&
                    (isr & AML_MMC_IRQ_STATUS_WR_CRC16_OK) == 0)
                        mmc_error = MMC_ERR_BADCRC;
-       } else if ((isr & AML_MMC_IRQ_STATUS_TIMEOUT_IRQ) != 0) {
-               if (aml8726_mmc_restart_timer(sc) != 0) {
-                       AML_MMC_UNLOCK(sc);
-                       return;
-               }
-               mmc_error = MMC_ERR_TIMEOUT;
        } else {
 spurious:
 
@@ -389,49 +482,12 @@ spurious:
                return;
        }
 
-       if ((isr & AML_MMC_IRQ_STATUS_CMD_BUSY) != 0 &&
-           /*
-            * A multiblock operation may keep the hardware
-            * busy until stop transmission is executed.
-            */
-           (isr & AML_MMC_IRQ_STATUS_CMD_DONE_IRQ) == 0) {
-               if (mmc_error == MMC_ERR_NONE)
-                       mmc_error = MMC_ERR_FAILED;
-
-               /*
-                * Issue a soft reset (while leaving the command complete
-                * interrupt enabled) to terminate the command.
-                *
-                * Ensure the command has terminated before continuing on
-                * to things such as bus_dmamap_sync / bus_dmamap_unload.
-                */
-
-               icr = AML_MMC_IRQ_CONFIG_SOFT_RESET |
-                   AML_MMC_IRQ_CONFIG_CMD_DONE_EN;
-
-               CSR_WRITE_4(sc, AML_MMC_IRQ_CONFIG_REG, icr);
-
-               while ((CSR_READ_4(sc, AML_MMC_IRQ_STATUS_REG) &
-                   AML_MMC_IRQ_STATUS_CMD_BUSY) != 0)
-                       cpu_spinwait();
-       }
-
-       /* Clear all interrupts since the request is no longer in flight. */
-       CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, AML_MMC_IRQ_STATUS_CLEAR_IRQ);
-       CSR_BARRIER(sc, AML_MMC_IRQ_STATUS_REG);
-
-       cmd = sc->cmd;
-       sc->cmd = NULL;
-
-       cmd->error = mmc_error;
-
-       if ((cmd->flags & MMC_RSP_PRESENT) != 0 &&
-           mmc_error == MMC_ERR_NONE) {
+       if ((cmdr & AML_MMC_CMD_RESP_BITS_MASK) != 0) {
                mcfgr = sc->port;
                mcfgr |= AML_MMC_MULT_CONFIG_RESP_READOUT_EN;
                CSR_WRITE_4(sc, AML_MMC_MULT_CONFIG_REG, mcfgr);
 
-               if ((cmd->flags & MMC_RSP_136) != 0) {
+               if ((cmdr & AML_MMC_CMD_RESP_CRC7_FROM_8) != 0) {
 
                        /*
                         * Controller supplies 135:8 instead of
@@ -444,48 +500,39 @@ spurious:
 
                        for (i = 0; i < 4; i++) {
                                resp = CSR_READ_4(sc, AML_MMC_CMD_ARGUMENT_REG);
-                               cmd->resp[3 - i] = (resp << 8) | previous_byte;
+                               sc->cmd->resp[3 - i] = (resp << 8) |
+                                   previous_byte;
                                previous_byte = (resp >> 24) & 0xff;
                        }
                } else
-                       cmd->resp[0] = CSR_READ_4(sc, AML_MMC_CMD_ARGUMENT_REG);
+                       sc->cmd->resp[0] = CSR_READ_4(sc,
+                           AML_MMC_CMD_ARGUMENT_REG);
        }
 
-       data = cmd->data;
-
-       if (data && data->len &&
-           (data->flags & (MMC_DATA_READ | MMC_DATA_WRITE)) != 0) {
-               if ((data->flags & MMC_DATA_READ) != 0)
-                       bus_dmamap_sync(sc->dmatag, sc->dmamap,
-                           BUS_DMASYNC_POSTREAD);
-               else
-                       bus_dmamap_sync(sc->dmatag, sc->dmamap,
-                           BUS_DMASYNC_POSTWRITE);
-               bus_dmamap_unload(sc->dmatag, sc->dmamap);
-       }
+       if ((isr & AML_MMC_IRQ_STATUS_CMD_BUSY) != 0 &&
+           /*
+            * A multiblock operation may keep the hardware
+            * busy until stop transmission is executed.
+            */
+           (isr & AML_MMC_IRQ_STATUS_CMD_DONE_IRQ) == 0) {
+               if (mmc_error == MMC_ERR_NONE)
+                       mmc_error = MMC_ERR_FAILED;
 
-       /*
-        * If there's a linked stop command, then start the stop command.
-        * In order to establish a known state attempt the stop command
-        * even if the original request encountered an error.
-        */
+               /*
+                * Issue a soft reset to terminate the command.
+                *
+                * Ensure the command has terminated before continuing on
+                * to things such as bus_dmamap_sync / bus_dmamap_unload.
+                */
 
-       stop_cmd = (cmd->mrq->stop != cmd) ? cmd->mrq->stop : NULL;
+               aml8726_mmc_soft_reset(sc, false);
 
-       if (stop_cmd != NULL) {
-               mmc_stop_error = aml8726_mmc_start_command(sc, stop_cmd);
-               if (mmc_stop_error == MMC_ERR_NONE) {
-                       AML_MMC_UNLOCK(sc);
-                       return;
-               }
-               stop_cmd->error = mmc_stop_error;
+               while ((CSR_READ_4(sc, AML_MMC_IRQ_STATUS_REG) &
+                   AML_MMC_IRQ_STATUS_CMD_BUSY) != 0)
+                       cpu_spinwait();
        }
 
-       AML_MMC_UNLOCK(sc);
-
-       /* Execute the callback after dropping the lock. */
-       if (cmd->mrq)
-               cmd->mrq->done(cmd->mrq);
+       aml8726_mmc_finish_command(sc, mmc_error);
 }
 
 static int
@@ -698,8 +745,10 @@ aml8726_mmc_attach(device_t dev)
                goto fail;
        }
 
-       sc->host.f_min = 200000;
-       sc->host.f_max = 50000000;
+       callout_init_mtx(&sc->ch, &sc->mtx, CALLOUT_RETURNUNLOCKED);
+
+       sc->host.f_min = aml8726_mmc_freq(sc, aml8726_mmc_div(sc, 200000));
+       sc->host.f_max = aml8726_mmc_freq(sc, aml8726_mmc_div(sc, 50000000));
        sc->host.host_ocr = sc->voltages[0] | sc->voltages[1];
        sc->host.caps = MMC_CAP_4_BIT_DATA | MMC_CAP_HSPEED;
 
@@ -756,10 +805,12 @@ aml8726_mmc_detach(device_t dev)
         * disable the interrupts, and clear the interrupts.
         */
        (void)aml8726_mmc_power_off(sc);
-       CSR_WRITE_4(sc, AML_MMC_IRQ_CONFIG_REG, AML_MMC_IRQ_CONFIG_SOFT_RESET);
-       CSR_BARRIER(sc, AML_MMC_IRQ_CONFIG_REG);
+       aml8726_mmc_soft_reset(sc, false);
        CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, AML_MMC_IRQ_STATUS_CLEAR_IRQ);
 
+       /* This should be a NOP since no command was in flight. */
+       callout_stop(&sc->ch);
+
        AML_MMC_UNLOCK(sc);
 
        bus_generic_detach(dev);
@@ -787,8 +838,7 @@ aml8726_mmc_shutdown(device_t dev)
         * disable the interrupts, and clear the interrupts.
         */
        (void)aml8726_mmc_power_off(sc);
-       CSR_WRITE_4(sc, AML_MMC_IRQ_CONFIG_REG, AML_MMC_IRQ_CONFIG_SOFT_RESET);
-       CSR_BARRIER(sc, AML_MMC_IRQ_CONFIG_REG);
+       aml8726_mmc_soft_reset(sc, false);
        CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, AML_MMC_IRQ_STATUS_CLEAR_IRQ);
 
        return (0);
@@ -799,7 +849,6 @@ aml8726_mmc_update_ios(device_t bus, dev
 {
        struct aml8726_mmc_softc *sc = device_get_softc(bus);
        struct mmc_ios *ios = &sc->host.ios;
-       unsigned int divisor;
        int error;
        int i;
        uint32_t cfgr;
@@ -820,15 +869,8 @@ aml8726_mmc_update_ios(device_t bus, dev
                return (EINVAL);
        }
 
-       divisor = sc->ref_freq  / (ios->clock * 2) - 1;
-       if (divisor == 0 || divisor == -1)
-               divisor = 1;
-       if ((sc->ref_freq / ((divisor + 1) * 2)) > ios->clock)
-               divisor += 1;
-       if (divisor > 0x3ff)
-               divisor = 0x3ff;
-
-       cfgr |= divisor;
+       cfgr |= aml8726_mmc_div(sc, ios->clock) <<
+           AML_MMC_CONFIG_CMD_CLK_DIV_SHIFT;
 
        CSR_WRITE_4(sc, AML_MMC_CONFIG_REG, cfgr);
 

Modified: head/sys/arm/amlogic/aml8726/aml8726_mmc.h
==============================================================================
--- head/sys/arm/amlogic/aml8726/aml8726_mmc.h  Tue Apr 28 08:20:23 2015        
(r282128)
+++ head/sys/arm/amlogic/aml8726/aml8726_mmc.h  Tue Apr 28 08:27:44 2015        
(r282129)
@@ -47,20 +47,6 @@
 #define        AML_MMC_WRITE_TIMEOUT                   500
 #define        AML_MMC_MAX_TIMEOUT                     5000
 
-/*
- * Internally the timeout is implemented by counting clock cycles.
- *
- * Since the hardware implements timeouts by counting cycles
- * the minimum read / write timeout (assuming the minimum
- * conversion factor of 1 cycle per usec) is:
- *
- *   (8 bits * 512 bytes per block + 16 bits CRC) = 4112 usec
- */
-#if ((AML_MMC_READ_TIMEOUT * 1000) < 4112 ||   \
-    (AML_MMC_WRITE_TIMEOUT * 1000) < 4112)
-#error "Single block timeout is smaller than supported"
-#endif
-
 #define        AML_MMC_CMD_ARGUMENT_REG                0
 
 #define        AML_MMC_CMD_SEND_REG                    4
_______________________________________________
svn-src-all@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to