Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 12 May 2014 22:33:22 +0000 (UTC)
From:      Ian Lepore <ian@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-10@freebsd.org
Subject:   svn commit: r265940 - stable/10/sys/arm/ti/am335x
Message-ID:  <201405122233.s4CMXMsR095410@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: ian
Date: Mon May 12 22:33:22 2014
New Revision: 265940
URL: http://svnweb.freebsd.org/changeset/base/265940

Log:
  MFC r259750, r260245: Add PPS support to the am335x timer driver.

Modified:
  stable/10/sys/arm/ti/am335x/am335x_dmtimer.c

Modified: stable/10/sys/arm/ti/am335x/am335x_dmtimer.c
==============================================================================
--- stable/10/sys/arm/ti/am335x/am335x_dmtimer.c	Mon May 12 22:30:10 2014	(r265939)
+++ stable/10/sys/arm/ti/am335x/am335x_dmtimer.c	Mon May 12 22:33:22 2014	(r265940)
@@ -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; i < 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 =



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