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 */
_______________________________________________
svn-src-head@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to