summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorian <ian@FreeBSD.org>2014-05-12 22:33:22 +0000
committerian <ian@FreeBSD.org>2014-05-12 22:33:22 +0000
commit2a67370d6908eb7a4340689dfc266e7e591d301f (patch)
treec36b7e8b6a64a76a9671125721a3628a3d244cf9
parent6c09b7134e113ccb1708681c3edf9d50ee568af9 (diff)
downloadFreeBSD-src-2a67370d6908eb7a4340689dfc266e7e591d301f.zip
FreeBSD-src-2a67370d6908eb7a4340689dfc266e7e591d301f.tar.gz
MFC r259750, r260245: Add PPS support to the am335x timer driver.
-rw-r--r--sys/arm/ti/am335x/am335x_dmtimer.c387
1 files changed, 333 insertions, 54 deletions
diff --git a/sys/arm/ti/am335x/am335x_dmtimer.c b/sys/arm/ti/am335x/am335x_dmtimer.c
index aef0435..8007ca1 100644
--- a/sys/arm/ti/am335x/am335x_dmtimer.c
+++ b/sys/arm/ti/am335x/am335x_dmtimer.c
@@ -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_dmtimer_softc *sc, uint32_t reg,
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;
+ initial_count = reload_count;
- /* 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;
-
- /* 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 *et)
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);
+ }
+
+ /* 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; register tc. */
+ /* 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 =
OpenPOWER on IntegriCloud