svn commit: r259750 - head/sys/arm/ti/am335x

Ian Lepore ian at FreeBSD.org
Sun Dec 22 23:03:30 UTC 2013


Author: ian
Date: Sun Dec 22 23:03:29 2013
New Revision: 259750
URL: http://svnweb.freebsd.org/changeset/base/259750

Log:
  Add PPS support to the am335x timer driver.  This uses the timer hardware's
  capture mode together with the timecounter's PPS polling feature to get
  very accurate PPS capture without any interrupt processing (or latency).
  
  Hardware timers 4 through 7 have associated capture-trigger input pins.
  When the PPS support is compiled in the code automatically chooses the
  first timer it finds that has the capture-trigger pin set to input mode
  (this is configured via the fdt data).

Modified:
  head/sys/arm/ti/am335x/am335x_dmtimer.c

Modified: head/sys/arm/ti/am335x/am335x_dmtimer.c
==============================================================================
--- head/sys/arm/ti/am335x/am335x_dmtimer.c	Sun Dec 22 22:56:45 2013	(r259749)
+++ head/sys/arm/ti/am335x/am335x_dmtimer.c	Sun Dec 22 23:03:29 2013	(r259750)
@@ -30,17 +30,22 @@ __FBSDID("$FreeBSD$");
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/bus.h>
+#include <sys/conf.h>
 #include <sys/kernel.h>
 #include <sys/module.h>
 #include <sys/malloc.h>
 #include <sys/rman.h>
+#include <sys/taskqueue.h>
 #include <sys/timeet.h>
+#include <sys/timepps.h>
 #include <sys/timetc.h>
 #include <sys/watchdog.h>
 #include <machine/bus.h>
 #include <machine/cpu.h>
 #include <machine/intr.h>
 
+#include "opt_ntp.h"
+
 #include <dev/fdt/fdt_common.h>
 #include <dev/ofw/openfirm.h>
 #include <dev/ofw/ofw_bus.h>
@@ -50,6 +55,7 @@ __FBSDID("$FreeBSD$");
 #include <machine/fdt.h>
 
 #include <arm/ti/ti_prcm.h>
+#include <arm/ti/ti_scm.h>
 
 #define	AM335X_NUM_TIMERS	8
 
@@ -62,9 +68,9 @@ __FBSDID("$FreeBSD$");
 #define	DMT_IRQENABLE_SET	0x2c		/* IRQSTATUS Set Reg */
 #define	DMT_IRQENABLE_CLR	0x30		/* IRQSTATUS Clear Reg */
 #define	DMT_IRQWAKEEN		0x34		/* IRQ Wakeup Enable Reg */
-#define	  DMT_IRQ_TCAR		  (1 << 0)	/* IRQ: Capture */
+#define	  DMT_IRQ_MAT		  (1 << 0)	/* IRQ: Match */
 #define	  DMT_IRQ_OVF		  (1 << 1)	/* IRQ: Overflow */
-#define	  DMT_IRQ_MAT		  (1 << 2)	/* IRQ: Match */
+#define	  DMT_IRQ_TCAR		  (1 << 2)	/* IRQ: Capture */
 #define	  DMT_IRQ_MASK		  (DMT_IRQ_TCAR | DMT_IRQ_OVF | DMT_IRQ_MAT)
 #define	DMT_TCLR		0x38		/* Control Register */
 #define	  DMT_TCLR_START	  (1 << 0)	/* Start timer */
@@ -92,17 +98,32 @@ __FBSDID("$FreeBSD$");
 #define	DMT_TMAR		0x4C		/* Match Reg */
 #define	DMT_TCAR1		0x50		/* Capture Reg */
 #define	DMT_TSICR		0x54		/* Synchr. Interface Ctrl Reg */
-#define	  DMT_TSICR_RESET	0x02		/* TSICR perform soft reset */
+#define	  DMT_TSICR_RESET	  (1 << 1)	/* TSICR perform soft reset */
 #define	DMT_TCAR2		0x48		/* Capture Reg */
 
+/*
+ * Use timer 2 for the eventtimer.  When PPS support is not compiled in, there's
+ * no need to use a timer that has an associated capture-input pin, so use timer
+ * 3 for timecounter.  When PPS is compiled in we ignore the default and use
+ * whichever of timers 4-7 have the capture pin configured.
+ */
+#define	DEFAULT_ET_TIMER	2
+#define	DEFAULT_TC_TIMER	3
+
 struct am335x_dmtimer_softc {
 	struct resource *	tmr_mem_res[AM335X_NUM_TIMERS];
 	struct resource *	tmr_irq_res[AM335X_NUM_TIMERS];
 	uint32_t		sysclk_freq;
 	uint32_t		tc_num;		/* Which timer number is tc. */
-	uint32_t		et_num;		/* Which timer number is et. */
+	uint32_t		tc_tclr;	/* Cached tc TCLR register. */
 	struct resource *	tc_memres;	/* Resources for tc timer. */
+	uint32_t		et_num;		/* Which timer number is et. */
+	uint32_t		et_tclr;	/* Cached et TCLR register. */
 	struct resource *	et_memres;	/* Resources for et timer. */
+	int			pps_curmode;	/* Edge mode now set in hw. */
+	struct task 		pps_task;	/* For pps_event handling. */
+	struct cdev *		pps_cdev;
+	struct pps_state 	pps;
 	struct timecounter	tc;
 	struct eventtimer	et;
 };
@@ -162,6 +183,255 @@ am335x_dmtimer_et_write_4(struct am335x_
 	bus_write_4(sc->et_memres, reg, val);
 }
 
+/*
+ * PPS driver routines, included when the kernel is built with option PPS_SYNC.
+ *
+ * Note that this PPS driver does not use an interrupt.  Instead it uses the
+ * hardware's ability to latch the timer's count register in response to a
+ * signal on an IO pin.  Each of timers 4-7 have an associated pin, and this
+ * code allows any one of those to be used.
+ *
+ * The timecounter routines in kern_tc.c call the pps poll routine periodically
+ * to see if a new counter value has been latched.  When a new value has been
+ * latched, the only processing done in the poll routine is to capture the
+ * current set of timecounter timehands (done with pps_capture()) and the
+ * latched value from the timer.  The remaining work (done by pps_event()) is
+ * scheduled to be done later in a non-interrupt context.
+ */
+#ifdef PPS_SYNC
+
+#define	PPS_CDEV_NAME	"pps"
+
+static void
+am335x_dmtimer_set_capture_mode(struct am335x_dmtimer_softc *sc, bool force_off)
+{
+	int newmode;
+
+	if (force_off)
+		newmode = 0;
+	else
+		newmode = sc->pps.ppsparam.mode & PPS_CAPTUREBOTH;
+
+	if (newmode == sc->pps_curmode)
+		return;
+
+	sc->pps_curmode = newmode;
+	sc->tc_tclr &= ~DMT_TCLR_CAPTRAN_MASK;
+	switch (newmode) {
+	case PPS_CAPTUREASSERT:
+		sc->tc_tclr |= DMT_TCLR_CAPTRAN_LOHI;
+		break;
+	case PPS_CAPTURECLEAR:
+		sc->tc_tclr |= DMT_TCLR_CAPTRAN_HILO;
+		break;
+	default:
+		/* It can't be BOTH, so it's disabled. */
+		break;
+	}
+	am335x_dmtimer_tc_write_4(sc, DMT_TCLR, sc->tc_tclr);
+}
+
+static void
+am335x_dmtimer_tc_poll_pps(struct timecounter *tc)
+{
+	struct am335x_dmtimer_softc *sc;
+
+	sc = tc->tc_priv;
+
+	/*
+	 * Note that we don't have the TCAR interrupt enabled, but the hardware
+	 * still provides the status bits in the "RAW" status register even when
+	 * they're masked from generating an irq.  However, when clearing the
+	 * TCAR status to re-arm the capture for the next second, we have to
+	 * write to the IRQ status register, not the RAW register.  Quirky.
+	 */
+	if (am335x_dmtimer_tc_read_4(sc, DMT_IRQSTATUS_RAW) & DMT_IRQ_TCAR) {
+		pps_capture(&sc->pps);
+		sc->pps.capcount = am335x_dmtimer_tc_read_4(sc, DMT_TCAR1);
+		am335x_dmtimer_tc_write_4(sc, DMT_IRQSTATUS, DMT_IRQ_TCAR);
+		taskqueue_enqueue_fast(taskqueue_fast, &sc->pps_task);
+	}
+}
+
+static void
+am335x_dmtimer_process_pps_event(void *arg, int pending)
+{
+	struct am335x_dmtimer_softc *sc;
+
+	sc = arg;
+
+	/* This is the task function that gets enqueued by poll_pps.  Once the
+	 * time has been captured in the hw interrupt context, the remaining
+	 * (more expensive) work to process the event is done later in a
+	 * non-fast-interrupt context.
+	 *
+	 * We only support capture of the rising or falling edge, not both at
+	 * once; tell the kernel to process whichever mode is currently active.
+	 */
+	pps_event(&sc->pps, sc->pps.ppsparam.mode & PPS_CAPTUREBOTH);
+}
+
+static int
+am335x_dmtimer_pps_open(struct cdev *dev, int flags, int fmt, 
+    struct thread *td)
+{
+	struct am335x_dmtimer_softc *sc;
+
+	sc = dev->si_drv1;
+
+	/* Enable capture on open.  Harmless if already open. */
+	am335x_dmtimer_set_capture_mode(sc, 0);
+
+	return 0;
+}
+
+static	int
+am335x_dmtimer_pps_close(struct cdev *dev, int flags, int fmt, 
+    struct thread *td)
+{
+	struct am335x_dmtimer_softc *sc;
+
+	sc = dev->si_drv1;
+
+	/*
+	 * Disable capture on last close.  Use the force-off flag to override
+	 * the configured mode and turn off the hardware capture.
+	 */
+	am335x_dmtimer_set_capture_mode(sc, 1);
+
+	return 0;
+}
+
+static int
+am335x_dmtimer_pps_ioctl(struct cdev *dev, u_long cmd, caddr_t data, 
+    int flags, struct thread *td)
+{
+	struct am335x_dmtimer_softc *sc;
+	int err;
+
+	sc = dev->si_drv1;
+
+	/*
+	 * The hardware has a "capture both edges" mode, but we can't do
+	 * anything useful with it in terms of PPS capture, so don't even try.
+	 */
+	if ((sc->pps.ppsparam.mode & PPS_CAPTUREBOTH) == PPS_CAPTUREBOTH)
+		return (EINVAL);
+
+	/* Let the kernel do the heavy lifting for ioctl. */
+	err = pps_ioctl(cmd, data, &sc->pps);
+	if (err != 0)
+		return (err);
+
+	/*
+	 * The capture mode could have changed, set the hardware to whatever
+	 * mode is now current.  Effectively a no-op if nothing changed.
+	 */
+	am335x_dmtimer_set_capture_mode(sc, 0);
+
+	return (err);
+}
+
+static struct cdevsw am335x_dmtimer_pps_cdevsw = {
+	.d_version =    D_VERSION,
+	.d_open =       am335x_dmtimer_pps_open,
+	.d_close =      am335x_dmtimer_pps_close,
+	.d_ioctl =      am335x_dmtimer_pps_ioctl,
+	.d_name =       PPS_CDEV_NAME,
+};
+
+/*
+ * Set up the PPS cdev and the the kernel timepps stuff.
+ *
+ * Note that this routine cannot touch the hardware, because bus space resources
+ * are not fully set up yet when this is called.
+ */
+static int
+am335x_dmtimer_pps_init(device_t dev, struct am335x_dmtimer_softc *sc)
+{
+	int i, timer_num, unit;
+	unsigned int padstate;
+	const char * padmux;
+	struct padinfo {
+		char * ballname;
+		char * muxname;
+		int    timer_num;
+	} padinfo[] = {
+		{"GPMC_ADVn_ALE", "timer4", 4}, 
+		{"GPMC_BEn0_CLE", "timer5", 5},
+		{"GPMC_WEn",      "timer6", 6},
+		{"GPMC_OEn_REn",  "timer7", 7},
+	};
+
+	/*
+	 * Figure out which pin the user has set up for pps.  We'll use the
+	 * first timer that has an external caputure pin configured as input.
+	 *
+	 * XXX The hieroglyphic "(padstate & (0x01 << 5)))" checks that the pin
+	 * is configured for input.  The right symbolic values aren't exported
+	 * yet from ti_scm.h.
+	 */
+	timer_num = 0;
+	for (i = 0; nitems(padinfo) && timer_num == 0; ++i) {
+		if (ti_scm_padconf_get(padinfo[i].ballname, &padmux, 
+		    &padstate) == 0) {
+			if (strcasecmp(padinfo[i].muxname, padmux) == 0 &&
+			    (padstate & (0x01 << 5)))
+				timer_num = padinfo[i].timer_num;
+		}
+	}
+
+	if (timer_num == 0) {
+		device_printf(dev, "No DMTimer found with capture pin "
+		    "configured as input; PPS driver disabled.\n");
+		return (DEFAULT_TC_TIMER);
+	}
+
+	/*
+	 * Indicate our capabilities (pretty much just capture of either edge).
+	 * Have the kernel init its part of the pps_state struct and add its
+	 * capabilities.
+	 */
+	sc->pps.ppscap = PPS_CAPTUREBOTH;
+	pps_init(&sc->pps);
+
+	/*
+	 * Set up to capture the PPS via timecounter polling, and init the task
+	 * that does deferred pps_event() processing after capture.
+	 */
+	sc->tc.tc_poll_pps = am335x_dmtimer_tc_poll_pps;
+	TASK_INIT(&sc->pps_task, 0, am335x_dmtimer_process_pps_event, sc);
+
+	/* Create the PPS cdev.  */
+	unit = device_get_unit(dev);
+	sc->pps_cdev = make_dev(&am335x_dmtimer_pps_cdevsw, unit, 
+	    UID_ROOT, GID_WHEEL, 0600, PPS_CDEV_NAME "%d", unit);
+	sc->pps_cdev->si_drv1 = sc;
+
+	device_printf(dev, "Using DMTimer%d for PPS device /dev/%s%d\n", 
+	    timer_num, PPS_CDEV_NAME, unit);
+
+	return (timer_num);
+}
+
+#else /* PPS_SYNC */
+
+static int
+am335x_dmtimer_pps_init(device_t dev, struct am335x_dmtimer_softc *sc)
+{
+
+	/*
+	 * When PPS support is not compiled in, there's no need to use a timer
+	 * that has an associated capture-input pin, so use the default.
+	 */
+	return (DEFAULT_TC_TIMER);
+}
+
+#endif /* PPS_SYNC */
+/*
+ * End of PPS driver code.
+ */
+
 static unsigned
 am335x_dmtimer_tc_get_timecount(struct timecounter *tc)
 {
@@ -176,43 +446,50 @@ static int
 am335x_dmtimer_start(struct eventtimer *et, sbintime_t first, sbintime_t period)
 {
 	struct am335x_dmtimer_softc *sc;
-	uint32_t count, load, tclr;
+	uint32_t initial_count, reload_count;
 
 	sc = et->et_priv;
 
-	tclr = 0;
+	/*
+	 * Stop the timer before changing it.  This routine will often be called
+	 * while the timer is still running, to either lengthen or shorten the
+	 * current event time.  We need to ensure the timer doesn't expire while
+	 * we're working with it.
+	 *
+	 * Also clear any pending interrupt status, because it's at least
+	 * theoretically possible that we're running in a primary interrupt
+	 * context now, and a timer interrupt could be pending even before we
+	 * stopped the timer.  The more likely case is that we're being called
+	 * from the et_event_cb() routine dispatched from our own handler, but
+	 * it's not clear to me that that's the only case possible.
+	 */
+	sc->et_tclr &= ~(DMT_TCLR_START | DMT_TCLR_AUTOLOAD);
+	am335x_dmtimer_et_write_4(sc, DMT_TCLR, sc->et_tclr);
+	am335x_dmtimer_et_write_4(sc, DMT_IRQSTATUS, DMT_IRQ_OVF);
+
 	if (period != 0) {
-		load = ((uint32_t)et->et_frequency * period) >> 32;
-		tclr |= DMT_TCLR_AUTOLOAD;
-		panic("periodic timer not implemented\n");
+		reload_count = ((uint32_t)et->et_frequency * period) >> 32;
+		sc->et_tclr |= DMT_TCLR_AUTOLOAD;
 	} else {
-		load = 0;
+		reload_count = 0;
 	}
 
 	if (first != 0)
-		count = ((uint32_t)et->et_frequency * first) >> 32;
+		initial_count = ((uint32_t)et->et_frequency * first) >> 32;
 	else
-		count = load;
-
-	/* Reset Timer */
-	am335x_dmtimer_et_write_4(sc, DMT_TSICR, DMT_TSICR_RESET);
-
-	/* Wait for reset to complete */
-	while (am335x_dmtimer_et_read_4(sc, DMT_TIOCP_CFG) & DMT_TIOCP_RESET)
-		continue;
+		initial_count = reload_count;
 
-	/* set load value */
-	am335x_dmtimer_et_write_4(sc, DMT_TLDR, 0xFFFFFFFE - load);
-
-	/* set counter value */
-	am335x_dmtimer_et_write_4(sc, DMT_TCRR, 0xFFFFFFFE - count);
+	/*
+	 * Set auto-reload and current-count values.  This timer hardware counts
+	 * up from the initial/reload value and interrupts on the zero rollover.
+	 */
+	am335x_dmtimer_et_write_4(sc, DMT_TLDR, 0xFFFFFFFF - reload_count);
+	am335x_dmtimer_et_write_4(sc, DMT_TCRR, 0xFFFFFFFF - initial_count);
 
-	/* enable overflow interrupt */
+	/* Enable overflow interrupt, and start the timer. */
 	am335x_dmtimer_et_write_4(sc, DMT_IRQENABLE_SET, DMT_IRQ_OVF);
-
-	/* start timer(ST) */
-	tclr |= DMT_TCLR_START;
-	am335x_dmtimer_et_write_4(sc, DMT_TCLR, tclr);
+	sc->et_tclr |= DMT_TCLR_START;
+	am335x_dmtimer_et_write_4(sc, DMT_TCLR, sc->et_tclr);
 
 	return (0);
 }
@@ -224,12 +501,11 @@ am335x_dmtimer_stop(struct eventtimer *e
 
 	sc = et->et_priv;
 
-	/* Disable all interrupts */
-	am335x_dmtimer_et_write_4(sc, DMT_IRQENABLE_CLR, DMT_IRQ_MASK);
-
-	/* Stop Timer */
-	am335x_dmtimer_et_write_4(sc, DMT_TCLR, 0);
-
+	/* Stop timer, disable and clear interrupt. */
+	sc->et_tclr &= ~(DMT_TCLR_START | DMT_TCLR_AUTOLOAD);
+	am335x_dmtimer_et_write_4(sc, DMT_TCLR, sc->et_tclr);
+	am335x_dmtimer_et_write_4(sc, DMT_IRQENABLE_CLR, DMT_IRQ_OVF);
+	am335x_dmtimer_et_write_4(sc, DMT_IRQSTATUS, DMT_IRQ_OVF);
 	return (0);
 }
 
@@ -239,7 +515,8 @@ am335x_dmtimer_intr(void *arg)
 	struct am335x_dmtimer_softc *sc;
 
 	sc = arg;
-	/* Ack interrupt */
+
+	/* Ack the interrupt, and invoke the callback if it's still enabled. */
 	am335x_dmtimer_et_write_4(sc, DMT_IRQSTATUS, DMT_IRQ_OVF);
 	if (sc->et.et_active)
 		sc->et.et_event_cb(&sc->et, sc->et.et_arg);
@@ -300,51 +577,53 @@ am335x_dmtimer_attach(device_t dev)
 		return (ENXIO);
 	}
 
-	/* Configure DMTimer3 as eventtimer and DMTimer4 as timecounter.  */
-	sc->et_num = 3;
-	sc->tc_num = 2;
+	/*
+	 * Use the default eventtimer.  Let the PPS init routine decide which
+	 * timer to use for the timecounter.
+	 */
+	sc->et_num = DEFAULT_ET_TIMER;
+	sc->tc_num = am335x_dmtimer_pps_init(dev, sc);
+
 	sc->et_memres = sc->tmr_mem_res[sc->et_num];
 	sc->tc_memres = sc->tmr_mem_res[sc->tc_num];
 
+	/* Enable clocks and power on the chosen devices. */
 	err  = ti_prcm_clk_set_source(DMTIMER0_CLK + sc->et_num, SYSCLK_CLK);
 	err |= ti_prcm_clk_enable(DMTIMER0_CLK + sc->et_num);
 	err |= ti_prcm_clk_set_source(DMTIMER0_CLK + sc->tc_num, SYSCLK_CLK);
 	err |= ti_prcm_clk_enable(DMTIMER0_CLK + sc->tc_num);
 	if (err) {
-		device_printf(dev, "Error: could not setup timer clock\n");
+		device_printf(dev, "Error: could not enable timer clock\n");
 		return (ENXIO);
 	}
 
-	/* Set up timecounter; register tc. */
+	/* Setup eventtimer interrupt handler. */
+	if (bus_setup_intr(dev, sc->tmr_irq_res[sc->et_num], INTR_TYPE_CLK,
+			am335x_dmtimer_intr, NULL, sc, &ihl) != 0) {
+		device_printf(dev, "Unable to setup the clock irq handler.\n");
+		return (ENXIO);
+	}
+
+	/* Set up timecounter, start it, register it. */
 	am335x_dmtimer_tc_write_4(sc, DMT_TSICR, DMT_TSICR_RESET);
 	while (am335x_dmtimer_tc_read_4(sc, DMT_TIOCP_CFG) & DMT_TIOCP_RESET)
 		continue;
 
+	sc->tc_tclr |= DMT_TCLR_START | DMT_TCLR_AUTOLOAD;
 	am335x_dmtimer_tc_write_4(sc, DMT_TLDR, 0);
 	am335x_dmtimer_tc_write_4(sc, DMT_TCRR, 0);
-	am335x_dmtimer_tc_write_4(sc, DMT_TCLR, 
-	    DMT_TCLR_START | DMT_TCLR_AUTOLOAD);
+	am335x_dmtimer_tc_write_4(sc, DMT_TCLR, sc->tc_tclr);
 
 	sc->tc.tc_name           = "AM335x Timecounter";
 	sc->tc.tc_get_timecount  = am335x_dmtimer_tc_get_timecount;
-	sc->tc.tc_poll_pps       = NULL;
 	sc->tc.tc_counter_mask   = ~0u;
 	sc->tc.tc_frequency      = sc->sysclk_freq;
 	sc->tc.tc_quality        = 1000;
 	sc->tc.tc_priv           = sc;
 	tc_init(&sc->tc);
 
-        /* Setup eventtimer; register et. */
-	if (bus_setup_intr(dev, sc->tmr_irq_res[sc->et_num], INTR_TYPE_CLK,
-			am335x_dmtimer_intr, NULL, sc, &ihl) != 0) {
-		bus_release_resources(dev, am335x_dmtimer_irq_spec,
-			sc->tmr_irq_res);
-		device_printf(dev, "Unable to setup the clock irq handler.\n");
-		return (ENXIO);
-	}
-
 	sc->et.et_name = "AM335x Eventtimer";
-	sc->et.et_flags = ET_FLAGS_ONESHOT;
+	sc->et.et_flags = ET_FLAGS_PERIODIC | ET_FLAGS_ONESHOT;
 	sc->et.et_quality = 1000;
 	sc->et.et_frequency = sc->sysclk_freq;
 	sc->et.et_min_period =


More information about the svn-src-all mailing list