svn commit: r254281 - head/sys/arm/freescale/imx

Ian Lepore ian at FreeBSD.org
Tue Aug 13 13:14:14 UTC 2013


Author: ian
Date: Tue Aug 13 13:14:13 2013
New Revision: 254281
URL: http://svnweb.freebsd.org/changeset/base/254281

Log:
  Add imx6 compatibility and make the driver work for any clock frequency.
  
  There are still a couple references to imx51 ccm driver functions that will
  need to be changed after an imx6 ccm driver is written.
  
  Reviewed by:	ray

Modified:
  head/sys/arm/freescale/imx/imx_gpt.c
  head/sys/arm/freescale/imx/imx_gptreg.h

Modified: head/sys/arm/freescale/imx/imx_gpt.c
==============================================================================
--- head/sys/arm/freescale/imx/imx_gpt.c	Tue Aug 13 10:24:42 2013	(r254280)
+++ head/sys/arm/freescale/imx/imx_gpt.c	Tue Aug 13 13:14:13 2013	(r254281)
@@ -57,8 +57,6 @@ __FBSDID("$FreeBSD$");
 #include <sys/kdb.h>
 #include <arm/freescale/imx/imx51_ccmvar.h>
 
-#define	MIN_PERIOD		100LLU
-
 #define	WRITE4(_sc, _r, _v)						\
 	    bus_space_write_4((_sc)->sc_iot, (_sc)->sc_ioh, (_r), (_v))
 #define	READ4(_sc, _r)							\
@@ -82,11 +80,27 @@ static struct timecounter imx_gpt_timeco
 	.tc_get_timecount  = imx_gpt_get_timecount,
 	.tc_counter_mask   = ~0u,
 	.tc_frequency      = 0,
-	.tc_quality        = 500,
+	.tc_quality        = 1000,
 };
 
+/* Global softc pointer for use in DELAY(). */
 struct imx_gpt_softc *imx_gpt_sc = NULL;
-static volatile int imx_gpt_delay_count = 300;
+
+/*
+ * Hand-calibrated delay-loop counter.  This was calibrated on an i.MX6 running
+ * at 792mhz.  It will delay a bit too long on slower processors -- that's
+ * better than not delaying long enough.  In practice this is unlikely to get
+ * used much since the clock driver is one of the first to start up, and once
+ * we're attached the delay loop switches to using the timer hardware.
+ */
+static const int imx_gpt_delay_count = 78;
+
+/* Try to divide down an available fast clock to this frequency. */
+#define	TARGET_FREQUENCY	1000000
+
+/* Don't try to set an event timer period smaller than this. */
+#define	MIN_ET_PERIOD		10LLU
+
 
 static struct resource_spec imx_gpt_spec[] = {
 	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
@@ -101,7 +115,7 @@ imx_gpt_probe(device_t dev)
 	if (!ofw_bus_is_compatible(dev, "fsl,imx51-gpt"))
 		return (ENXIO);
 
-	device_set_desc(dev, "Freescale i.MXxxx GPT timer");
+	device_set_desc(dev, "Freescale i.MX GPT timer");
 	return (BUS_PROBE_DEFAULT);
 }
 
@@ -109,7 +123,8 @@ static int
 imx_gpt_attach(device_t dev)
 {
 	struct imx_gpt_softc *sc;
-	int err;
+	int ctlreg, err;
+	uint32_t basefreq, prescale;
 
 	sc = device_get_softc(dev);
 
@@ -119,49 +134,96 @@ imx_gpt_attach(device_t dev)
 	}
 
 	sc->sc_dev = dev;
-	sc->sc_clksrc = GPT_CR_CLKSRC_IPG;
 	sc->sc_iot = rman_get_bustag(sc->res[0]);
 	sc->sc_ioh = rman_get_bushandle(sc->res[0]);
 
+	/*
+	 * For now, just automatically choose a good clock for the hardware
+	 * we're running on.  Eventually we could allow selection from the fdt;
+	 * the code in this driver will cope with any clock frequency.
+	 */
+	if (ofw_bus_is_compatible(dev, "fsl,imx6-gpt"))
+		sc->sc_clksrc = GPT_CR_CLKSRC_24M;
+	else
+		sc->sc_clksrc = GPT_CR_CLKSRC_IPG;
+
+	ctlreg = 0;
+
 	switch (sc->sc_clksrc) {
-	case GPT_CR_CLKSRC_NONE:
-		device_printf(dev, "can't run timer without clock source\n");
-		return (EINVAL);
-	case GPT_CR_CLKSRC_EXT:
-		device_printf(dev, "Not implemented. Geve me the way to get "
-		    "external clock source frequency\n");
-		return (EINVAL);
 	case GPT_CR_CLKSRC_32K:
-		sc->clkfreq = 32768;
+		basefreq = 32768;
+		break;
+	case GPT_CR_CLKSRC_IPG:
+		basefreq = imx51_get_clock(IMX51CLK_IPG_CLK_ROOT);
 		break;
 	case GPT_CR_CLKSRC_IPG_HIGH:
-		sc->clkfreq = imx51_get_clock(IMX51CLK_IPG_CLK_ROOT) * 2;
+		basefreq = imx51_get_clock(IMX51CLK_IPG_CLK_ROOT) * 2;
+		break;
+	case GPT_CR_CLKSRC_24M:
+		ctlreg |= GPT_CR_24MEN;
+		basefreq = 24000000;
 		break;
+	case GPT_CR_CLKSRC_NONE:/* Can't run without a clock. */
+	case GPT_CR_CLKSRC_EXT:	/* No way to get the freq of an ext clock. */
 	default:
-		sc->clkfreq = imx51_get_clock(IMX51CLK_IPG_CLK_ROOT);
+		device_printf(dev, "Unsupported clock source '%d'\n", 
+		    sc->sc_clksrc);
+		return (EINVAL);
 	}
-	device_printf(dev, "Run on %dKHz clock.\n", sc->clkfreq / 1000);
 
-	/* Reset */
-	WRITE4(sc, IMX_GPT_CR, GPT_CR_SWR);
-	/* Enable and setup counters */
-	WRITE4(sc, IMX_GPT_CR,
-	    GPT_CR_CLKSRC_IPG |	/* Use IPG clock */
+	/*
+	 * The following setup sequence is from the I.MX6 reference manual,
+	 * "Selecting the clock source".  First, disable the clock and
+	 * interrupts.  This also clears input and output mode bits and in
+	 * general completes several of the early steps in the procedure.
+	 */
+	WRITE4(sc, IMX_GPT_CR, 0);
+	WRITE4(sc, IMX_GPT_IR, 0);
+
+	/* Choose the clock and the power-saving behaviors. */
+	ctlreg |=
+	    sc->sc_clksrc |	/* Use selected clock */
 	    GPT_CR_FRR |	/* Just count (FreeRunner mode) */
 	    GPT_CR_STOPEN |	/* Run in STOP mode */
+	    GPT_CR_DOZEEN |	/* Run in DOZE mode */
 	    GPT_CR_WAITEN |	/* Run in WAIT mode */
-	    GPT_CR_DBGEN);	/* Run in DEBUG mode */
+	    GPT_CR_DBGEN;	/* Run in DEBUG mode */
+	WRITE4(sc, IMX_GPT_CR, ctlreg);
 
-	/* Disable interrupts */
-	WRITE4(sc, IMX_GPT_IR, 0);
+	/*
+	 * The datasheet says to do the software reset after choosing the clock
+	 * source.  It says nothing about needing to wait for the reset to
+	 * complete, but the register description does document the fact that
+	 * the reset isn't complete until the SWR bit reads 0, so let's be safe.
+	 * The reset also clears all registers except for a few of the bits in
+	 * CR, but we'll rewrite all the CR bits when we start the counter.
+	 */
+	WRITE4(sc, IMX_GPT_CR, ctlreg | GPT_CR_SWR);
+	while (READ4(sc, IMX_GPT_CR) & GPT_CR_SWR)
+		continue;
+
+	/* Set a prescaler value that gets us near the target frequency. */
+	if (basefreq < TARGET_FREQUENCY) {
+		prescale = 0;
+		sc->clkfreq = basefreq;
+	} else {
+		prescale = basefreq / TARGET_FREQUENCY;
+		sc->clkfreq = basefreq / prescale;
+		prescale -= 1; /* 1..n range is 0..n-1 in hardware. */
+	}
+	WRITE4(sc, IMX_GPT_PR, prescale);
+
+	/* Clear the status register. */
+	WRITE4(sc, IMX_GPT_SR, GPT_IR_ALL);
 
-	/* Tick every 10us */
-	/* XXX: must be calculated from clock source frequency */
-	WRITE4(sc, IMX_GPT_PR, 665);
-	/* Use 100 KHz */
-	sc->clkfreq = 100000;
+	/* Start the counter. */
+	WRITE4(sc, IMX_GPT_CR, ctlreg | GPT_CR_EN);
 
-	/* Setup and enable the timer interrupt */
+	if (bootverbose)
+		device_printf(dev, "Running on %dKHz clock, base freq %uHz CR=0x%08x, PR=0x%08x\n",
+		    sc->clkfreq / 1000, basefreq, READ4(sc, IMX_GPT_CR), READ4(sc, IMX_GPT_PR));
+
+	/* Setup the timer interrupt. */
 	err = bus_setup_intr(dev, sc->res[1], INTR_TYPE_CLK, imx_gpt_intr,
 	    NULL, sc, &sc->sc_ih);
 	if (err != 0) {
@@ -171,35 +233,25 @@ imx_gpt_attach(device_t dev)
 		return (ENXIO);
 	}
 
+	/* Register as an eventtimer. */
 	sc->et.et_name = "i.MXxxx GPT Eventtimer";
 	sc->et.et_flags = ET_FLAGS_ONESHOT | ET_FLAGS_PERIODIC;
 	sc->et.et_quality = 1000;
 	sc->et.et_frequency = sc->clkfreq;
-	sc->et.et_min_period = (MIN_PERIOD << 32) / sc->et.et_frequency;
+	sc->et.et_min_period = (MIN_ET_PERIOD << 32) / sc->et.et_frequency;
 	sc->et.et_max_period = (0xfffffffeLLU << 32) / sc->et.et_frequency;
 	sc->et.et_start = imx_gpt_timer_start;
 	sc->et.et_stop = imx_gpt_timer_stop;
 	sc->et.et_priv = sc;
 	et_register(&sc->et);
 
-	/* Disable interrupts */
-	WRITE4(sc, IMX_GPT_IR, 0);
-	/* ACK any panding interrupts */
-	WRITE4(sc, IMX_GPT_SR, (GPT_IR_ROV << 1) - 1);
-
-	if (device_get_unit(dev) == 0)
-	    imx_gpt_sc = sc;
-
+	/* Register as a timecounter. */
 	imx_gpt_timecounter.tc_frequency = sc->clkfreq;
 	tc_init(&imx_gpt_timecounter);
 
-	printf("clock: hz=%d stathz = %d\n", hz, stathz);
-
-	device_printf(sc->sc_dev, "timer clock frequency %d\n", sc->clkfreq);
-
-	imx_gpt_delay_count = imx51_get_clock(IMX51CLK_ARM_ROOT) / 4000000;
-
-	SET4(sc, IMX_GPT_CR, GPT_CR_EN);
+	/* If this is the first unit, store the softc for use in DELAY. */
+	if (device_get_unit(dev) == 0)
+	    imx_gpt_sc = sc;
 
 	return (0);
 }
@@ -267,16 +319,11 @@ void
 cpu_initclocks(void)
 {
 
-	if (!imx_gpt_sc) {
-		panic("%s: driver has not been initialized!", __func__);
+	if (imx_gpt_sc == NULL) {
+		panic("%s: i.MX GPT driver has not been initialized!", __func__);
 	}
 
 	cpu_initclocks_bsp();
-
-	/* Switch to DELAY using counter */
-	imx_gpt_delay_count = 0;
-	device_printf(imx_gpt_sc->sc_dev,
-	    "switch DELAY to use H/W counter\n");
 }
 
 static int
@@ -342,29 +389,30 @@ EARLY_DRIVER_MODULE(imx_gpt, simplebus, 
 void
 DELAY(int usec)
 {
-	int32_t counts;
-	uint32_t last;
+	uint64_t curcnt, endcnt, startcnt, ticks;
 
-	/*
-	 * Check the timers are setup, if not just use a for loop for the
-	 * meantime.
-	 */
-	if (imx_gpt_delay_count) {
-		for (; usec > 0; usec--)
-			for (counts = imx_gpt_delay_count; counts > 0;
-			    counts--)
-				/* Prevent optimizing out the loop */
+	/* If the timer hardware is not accessible, just use a loop. */
+	if (imx_gpt_sc == NULL) {
+		while (usec-- > 0)
+			for (ticks = 0; ticks < imx_gpt_delay_count; ++ticks)
 				cpufunc_nullop();
 		return;
 	}
 
-	/* At least 1 count */
-	usec = MAX(1, usec / 100);
-
-	last = READ4(imx_gpt_sc, IMX_GPT_CNT) + usec;
-	while (READ4(imx_gpt_sc, IMX_GPT_CNT) < last) {
-		/* Prevent optimizing out the loop */
-		cpufunc_nullop();
+	/*
+	 * Calculate the tick count with 64-bit values so that it works for any
+	 * clock frequency.  Loop until the hardware count reaches start+ticks.
+	 * If the 32-bit hardware count rolls over while we're looping, just
+	 * manually do a carry into the high bits after each read; don't worry
+	 * that doing this on each loop iteration is inefficient -- we're trying
+	 * to waste time here.
+	 */
+	ticks = 1 + ((uint64_t)usec * imx_gpt_sc->clkfreq) / 1000000;
+	curcnt = startcnt = READ4(imx_gpt_sc, IMX_GPT_CNT);
+	endcnt = startcnt + ticks;
+	while (curcnt < endcnt) {
+		curcnt = READ4(imx_gpt_sc, IMX_GPT_CNT);
+		if (curcnt < startcnt)
+			curcnt += 1ULL << 32;
 	}
-	/* TODO: use interrupt on OCR2 */
 }

Modified: head/sys/arm/freescale/imx/imx_gptreg.h
==============================================================================
--- head/sys/arm/freescale/imx/imx_gptreg.h	Tue Aug 13 10:24:42 2013	(r254280)
+++ head/sys/arm/freescale/imx/imx_gptreg.h	Tue Aug 13 13:14:13 2013	(r254281)
@@ -55,13 +55,16 @@
 #define		GPT_CR_IMX_FEDGE	2
 #define		GPT_CR_IMX_BOTH		3
 #define		GPT_CR_SWR		(1 << 15)
+#define		GPT_CR_24MEN		(1 << 10)
 #define		GPT_CR_FRR		(1 << 9)
-#define		GPT_CR_CLKSRC_NONE	0x00000000
-#define		GPT_CR_CLKSRC_IPG	0x00000040
-#define		GPT_CR_CLKSRC_IPG_HIGH	0x00000080
-#define		GPT_CR_CLKSRC_EXT	0x000000c0
-#define		GPT_CR_CLKSRC_32K	0x00000100
+#define		GPT_CR_CLKSRC_NONE	(0 << 6)
+#define		GPT_CR_CLKSRC_IPG	(1 << 6)
+#define		GPT_CR_CLKSRC_IPG_HIGH	(2 << 6)
+#define		GPT_CR_CLKSRC_EXT	(3 << 6)
+#define		GPT_CR_CLKSRC_32K	(4 << 6)
+#define		GPT_CR_CLKSRC_24M	(5 << 6)
 #define		GPT_CR_STOPEN		(1 << 5)
+#define		GPT_CR_DOZEEN		(1 << 4)
 #define		GPT_CR_WAITEN		(1 << 3)
 #define		GPT_CR_DBGEN		(1 << 2)
 #define		GPT_CR_ENMOD		(1 << 1)
@@ -70,6 +73,8 @@
 #define	IMX_GPT_PR	0x0004 /* Prescaler Register        R/W */
 #define		GPT_PR_VALUE_SHIFT	0
 #define		GPT_PR_VALUE_MASK	0x00000fff
+#define		GPT_PR_VALUE_SHIFT_24M	12
+#define		GPT_PR_VALUE_MASK_24M	0x0000f000
 
 /* Same map for SR and IR */
 #define	IMX_GPT_SR	0x0008 /* Status Register           R/W */


More information about the svn-src-all mailing list