Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 15 Feb 2014 17:55:35 +0000 (UTC)
From:      Ian Lepore <ian@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r261939 - head/sys/arm/freescale/imx
Message-ID:  <201402151755.s1FHtZiQ033508@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: ian
Date: Sat Feb 15 17:55:35 2014
New Revision: 261939
URL: http://svnweb.freebsd.org/changeset/base/261939

Log:
  Convert the "R1B fix" from a busy-loop in the interrupt handler to a callout.

Modified:
  head/sys/arm/freescale/imx/imx_sdhci.c

Modified: head/sys/arm/freescale/imx/imx_sdhci.c
==============================================================================
--- head/sys/arm/freescale/imx/imx_sdhci.c	Sat Feb 15 17:19:55 2014	(r261938)
+++ head/sys/arm/freescale/imx/imx_sdhci.c	Sat Feb 15 17:55:35 2014	(r261939)
@@ -35,13 +35,18 @@ __FBSDID("$FreeBSD$");
 
 #include <sys/param.h>
 #include <sys/systm.h>
+#include <sys/types.h>
 #include <sys/bus.h>
+#include <sys/callout.h>
 #include <sys/kernel.h>
+#include <sys/lock.h>
 #include <sys/malloc.h>
 #include <sys/module.h>
+#include <sys/mutex.h>
 #include <sys/resource.h>
 #include <sys/rman.h>
 #include <sys/taskqueue.h>
+#include <sys/time.h>
 
 #include <machine/bus.h>
 #include <machine/resource.h>
@@ -65,6 +70,8 @@ struct imx_sdhci_softc {
 	struct resource *	irq_res;
 	void *			intr_cookie;
 	struct sdhci_slot	slot;
+	struct callout		r1bfix_callout;
+	sbintime_t		r1bfix_timeout_at;
 	uint32_t		baseclk_hz;
 	uint32_t		sdclockreg_freq_bits;
 	uint32_t		cmd_and_mode;
@@ -130,6 +137,8 @@ struct imx_sdhci_softc {
 #define	 SDHC_PROT_CDTL		(1 << 6)
 #define	 SDHC_PROT_CDSS		(1 << 7)
 
+#define	SDHC_INT_STATUS		0x30
+
 #define	SDHC_CLK_IPGEN		(1 << 0)
 #define	SDHC_CLK_HCKEN		(1 << 1)
 #define	SDHC_CLK_PEREN		(1 << 2)
@@ -147,6 +156,7 @@ static struct ofw_compat_data compat_dat
 };;
 
 static void imx_sdhc_set_clock(struct imx_sdhci_softc *sc, int enable);
+static void imx_sdhci_r1bfix_func(void *arg);
 
 static inline uint32_t
 RD4(struct imx_sdhci_softc *sc, bus_size_t off)
@@ -532,46 +542,107 @@ imx_sdhc_set_clock(struct imx_sdhci_soft
 	    RD4(sc, SDHC_VEND_SPEC) | SDHC_VEND_FRC_SDCLK_ON);
 }
 
+static boolean_t
+imx_sdhci_r1bfix_is_wait_done(struct imx_sdhci_softc *sc)
+{
+	uint32_t inhibit;
+
+	mtx_assert(&sc->slot.mtx, MA_OWNED);
+
+	/*
+	 * Check the DAT0 line status using both the DLA (data line active) and
+	 * CDIHB (data inhibit) bits in the present state register.  In theory
+	 * just DLA should do the trick,  but in practice it takes both.  If the
+	 * DAT0 line is still being held and we're not yet beyond the timeout
+	 * point, just schedule another callout to check again later.
+	 */
+	inhibit = RD4(sc, SDHC_PRES_STATE) & (SDHC_PRES_DLA | SDHC_PRES_CDIHB);
+
+	if (inhibit && getsbinuptime() < sc->r1bfix_timeout_at) {
+		callout_reset_sbt(&sc->r1bfix_callout, SBT_1MS, 0, 
+		    imx_sdhci_r1bfix_func, sc, 0);
+		return (false);
+	}
+
+	/*
+	 * If we reach this point with the inhibit bits still set, we've got a
+	 * timeout, synthesize a DATA_TIMEOUT interrupt.  Otherwise the DAT0
+	 * line has been released, and we synthesize a DATA_END, and if the type
+	 * of fix needed was on a command-without-data we also now add in the
+	 * original INT_RESPONSE that we suppressed earlier.
+	 */
+	if (inhibit)
+		sc->r1bfix_intmask |= SDHCI_INT_DATA_TIMEOUT;
+	else {
+		sc->r1bfix_intmask |= SDHCI_INT_DATA_END;
+		if (sc->r1bfix_type == R1BFIX_NODATA)
+			sc->r1bfix_intmask |= SDHCI_INT_RESPONSE;
+	}
+
+	sc->r1bfix_type = R1BFIX_NONE;
+	return (true);
+}
+
+static void
+imx_sdhci_r1bfix_func(void * arg)
+{
+	struct imx_sdhci_softc *sc = arg;
+	boolean_t r1bwait_done;
+
+	mtx_lock(&sc->slot.mtx);
+	r1bwait_done = imx_sdhci_r1bfix_is_wait_done(sc);
+	mtx_unlock(&sc->slot.mtx);
+	if (r1bwait_done)
+		sdhci_generic_intr(&sc->slot);
+}
+
 static void
 imx_sdhci_intr(void *arg)
 {
 	struct imx_sdhci_softc *sc = arg;
 	uint32_t intmask;
 
-	intmask = RD4(sc, SDHCI_INT_STATUS);
+	mtx_lock(&sc->slot.mtx);
 
 	/*
 	 * Manually check the DAT0 line for R1B response types that the
-	 * controller fails to handle properly.
+	 * controller fails to handle properly.  The controller asserts the done
+	 * interrupt while the card is still asserting busy with the DAT0 line.
 	 *
-	 * To do the NODATA fix, when the RESPONSE (COMMAND_COMPLETE) interrupt
-	 * occurs, we have to wait for the DAT0 line to be released, then
-	 * synthesize a DATA_END (TRANSFER_COMPLETE) interrupt, which we do by
-	 * storing SDHCI_INT_DATA_END into a variable that gets ORed into the
-	 * return value when the SDHCI_INT_STATUS register is read.
+	 * We check DAT0 immediately because most of the time, especially on a
+	 * read, the card will actually be done by time we get here.  If it's
+	 * not, then the wait_done routine will schedule a callout to re-check
+	 * periodically until it is done.  In that case we clear the interrupt
+	 * out of the hardware now so that we can present it later when the DAT0
+	 * line is released.
 	 *
-	 * For the AC12 fix, when the DATA_END interrupt occurs we wait for the
-	 * DAT0 line to be released, and the waiting is all the fix we need.
-	 */
-	if ((sc->r1bfix_type == R1BFIX_NODATA && 
-	    (intmask & SDHCI_INT_RESPONSE)) || 
-	    (sc->r1bfix_type == R1BFIX_AC12 && 
-	    (intmask & SDHCI_INT_DATA_END))) {
-		uint32_t count;
-		count = 0;
-		/* XXX use a callout or something instead of busy-waiting. */
-		while (count < 250000 && 
-		   (RD4(sc, SDHC_PRES_STATE) & SDHC_PRES_DLA)) {
-			++count;
-			DELAY(1);
+	 * If we need to wait for the the DAT0 line to be released, we set up a
+	 * timeout point 250ms in the future.  This number comes from the SD
+	 * spec, which allows a command to take that long.  In the real world,
+	 * cards tend to take 10-20ms for a long-running command such as a write
+	 * or erase that spans two pages.
+	 */
+	switch (sc->r1bfix_type) {
+	case R1BFIX_NODATA:
+		intmask = RD4(sc, SDHC_INT_STATUS) & SDHCI_INT_RESPONSE;
+		break;
+	case R1BFIX_AC12:
+		intmask = RD4(sc, SDHC_INT_STATUS) & SDHCI_INT_DATA_END;
+		break;
+	default:
+		intmask = 0;
+		break;
+	}
+	if (intmask) {
+		sc->r1bfix_timeout_at = getsbinuptime() + 250 * SBT_1MS;
+		if (!imx_sdhci_r1bfix_is_wait_done(sc)) {
+			WR4(sc, SDHC_INT_STATUS, intmask);
+			bus_barrier(sc->mem_res, SDHC_INT_STATUS, 4, 
+			    BUS_SPACE_BARRIER_WRITE);
 		}
-		if (count >= 250000)
-			sc->r1bfix_intmask = SDHCI_INT_DATA_TIMEOUT;
-		else if (sc->r1bfix_type == R1BFIX_NODATA)
-			sc->r1bfix_intmask = SDHCI_INT_DATA_END;
-		sc->r1bfix_type = R1BFIX_NONE;
 	}
 
+	mtx_unlock(&sc->slot.mtx);
 	sdhci_generic_intr(&sc->slot);
 }
 
@@ -660,6 +731,7 @@ imx_sdhci_attach(device_t dev)
 	}
 
 	sdhci_init_slot(dev, &sc->slot, 0);
+	callout_init(&sc->r1bfix_callout, true);
 
 	/*
 	 * If the slot is flagged with the non-removable property, set our flag
@@ -670,7 +742,7 @@ imx_sdhci_attach(device_t dev)
 	 * We don't have gpio support yet.  If there's a cd-gpios property just
 	 * force the SDHCI_CARD_PRESENT bit on for now.  If there isn't really a
 	 * card there it will fail to probe at the mmc layer and nothing bad
-	 * happens except instantiating a /dev/mmcN device for an empty slot.
+	 * happens except instantiating an mmcN device for an empty slot.
 	 */
 	node = ofw_bus_get_node(dev);
 	if (OF_hasprop(node, "non-removable"))



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201402151755.s1FHtZiQ033508>