mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-21 11:13:30 +00:00
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).
This commit is contained in:
parent
f56bc132df
commit
fb962e6dcf
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=259750
@ -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; 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);
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* 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 =
|
||||
|
Loading…
Reference in New Issue
Block a user