svn commit: r282129 - head/sys/arm/amlogic/aml8726

Ganbold Tsagaankhuu ganbold at FreeBSD.org
Tue Apr 28 08:27:45 UTC 2015


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


More information about the svn-src-all mailing list