diff options
author | ian <ian@FreeBSD.org> | 2014-05-12 22:33:22 +0000 |
---|---|---|
committer | ian <ian@FreeBSD.org> | 2014-05-12 22:33:22 +0000 |
commit | 2a67370d6908eb7a4340689dfc266e7e591d301f (patch) | |
tree | c36b7e8b6a64a76a9671125721a3628a3d244cf9 /sys/arm/ti/am335x | |
parent | 6c09b7134e113ccb1708681c3edf9d50ee568af9 (diff) | |
download | FreeBSD-src-2a67370d6908eb7a4340689dfc266e7e591d301f.zip FreeBSD-src-2a67370d6908eb7a4340689dfc266e7e591d301f.tar.gz |
MFC r259750, r260245: Add PPS support to the am335x timer driver.
Diffstat (limited to 'sys/arm/ti/am335x')
-rw-r--r-- | sys/arm/ti/am335x/am335x_dmtimer.c | 387 |
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 = |