Add a new PPS driver for AM335x (beaglebone) timer hardware. This can be
used as a module or compiled-in.
This commit is contained in:
parent
cc787e3d0e
commit
4159fbab87
163
share/man/man4/man4.arm/am335x_dmtpps.4
Normal file
163
share/man/man4/man4.arm/am335x_dmtpps.4
Normal file
@ -0,0 +1,163 @@
|
||||
.\"
|
||||
.\" Copyright (c) 2015 Ian Lepore <ian@freebsd.org>
|
||||
.\" All rights reserved.
|
||||
.\"
|
||||
.\" Redistribution and use in source and binary forms, with or without
|
||||
.\" modification, are permitted provided that the following conditions
|
||||
.\" are met:
|
||||
.\"
|
||||
.\" 1. Redistributions of source code must retain the above copyright
|
||||
.\" notice, this list of conditions and the following disclaimer.
|
||||
.\" 2. Redistributions in binary form must reproduce the above copyright
|
||||
.\" notice, this list of conditions and the following disclaimer in the
|
||||
.\" documentation and/or other materials provided with the distribution.
|
||||
.\"
|
||||
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
.\"
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd August 12, 2015
|
||||
.Dt AM335X_DMTPPS 4
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm am335x_dmtpps
|
||||
.Nd RFC 2783 Pulse Per Second API driver for AM335x systems
|
||||
.Sh SYNOPSIS
|
||||
.Cd "device am335x_dmtpps"
|
||||
.Pp
|
||||
Optional in
|
||||
.Pa /boot/loader.conf :
|
||||
.Cd hw.am335x_dmtpps.input="pin name"
|
||||
.\"
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
device driver provides a system time counter that includes precise
|
||||
capture of Pulse Per Second (PPS) signals emitted by GPS receivers
|
||||
and other timing devices.
|
||||
The
|
||||
.Nm
|
||||
driver may be compiled into the kernel or loaded as a module.
|
||||
.Pp
|
||||
The AM335x timer hardware captures the value of the system time counter
|
||||
on the leading edge of the PPS pulse.
|
||||
Because the capture is done by the hardware there is no interrupt
|
||||
latency in the measurement.
|
||||
The time counter runs at 24Mhz, providing a measurement resolution
|
||||
of 42 nanoseconds.
|
||||
.Pp
|
||||
To use the PPS timing information provided by this driver with
|
||||
.Xr ntpd 8 ,
|
||||
symlink the
|
||||
.Va /dev/dmtpps
|
||||
device to
|
||||
.Va /dev/pps0
|
||||
and configure server
|
||||
.Va 127.127.22.0
|
||||
in
|
||||
.Xr ntp.conf 5
|
||||
to configure a type 22 (ATOM) refclock.
|
||||
.\"
|
||||
.Sh DRIVER CONFIGURATION
|
||||
The AM335x hardware provides four timer devices with a capture input
|
||||
pin, DMTimer4 through DMTimer7.
|
||||
Because it also provides the active system time counter,
|
||||
only one instance of the
|
||||
.Nm
|
||||
driver can be active at a time.
|
||||
The driver uses system pin configuration to determine which hardware
|
||||
timer device to use.
|
||||
Configure the timer input pin in the system's FDT data, or by
|
||||
supplying the pin name using a tunable variable in
|
||||
.Xr loader.conf 5 .
|
||||
.Pp
|
||||
To use a standard kernel and FDT data, use
|
||||
.Xr loader.conf 5
|
||||
to load the
|
||||
.Nm
|
||||
module and set the
|
||||
.Va hw.am335x_dmtpps.input
|
||||
tunable variable to the name of the input pin, one of the following:
|
||||
.Pp
|
||||
.Bl -tag -width "GPMC_ADVn_ALE MMMM" -offset MMMM -compact
|
||||
.It Em Name
|
||||
.Em Hardware
|
||||
.It P8-7
|
||||
DMTimer4; Beaglebone P8 header pin 7.
|
||||
.It P8-8
|
||||
DMTimer7; Beaglebone P8 header pin 8.
|
||||
.It P8-9
|
||||
DMTimer5; Beaglebone P8 header pin 9.
|
||||
.It P8-10
|
||||
DMTimer6; Beaglebone P8 header pin 10.
|
||||
.It GPMC_ADVn_ALE
|
||||
DMTimer4.
|
||||
.It GPMC_BEn0_CLE
|
||||
DMTimer5.
|
||||
.It GPMC_WEn
|
||||
DMTimer6.
|
||||
.It GPMC_OEn_REn
|
||||
DMTimer7.
|
||||
.El
|
||||
.Pp
|
||||
To configure the
|
||||
.Nm
|
||||
driver using FDT data, create a new pinctrl node by referencing the standard
|
||||
.Va am33xx_pinmux
|
||||
driver node (which is defined in am33xx.dtsi) in your dts file.
|
||||
For example:
|
||||
.Bd -literal
|
||||
&am33xx_pinmux {
|
||||
timer4_pins: timer4_pins {
|
||||
pinctrl-single,pins = <0x90 (PIN_INPUT | MUX_MODE2)>;
|
||||
};
|
||||
};
|
||||
.Ed
|
||||
.Pp
|
||||
Add pinctrl properties referencing
|
||||
.Va timer4_pins
|
||||
to the standard
|
||||
.Va timer4
|
||||
device node (also defined in am33xx.dtsi) by referencing it in
|
||||
your dts file as follows:
|
||||
.Bd -literal
|
||||
&timer4 {
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&timer4_pins>;
|
||||
};
|
||||
.Ed
|
||||
.\"
|
||||
.Sh FILES
|
||||
.Bl -tag -width ".Pa /dev/dmtpps" -compact
|
||||
.It Pa /dev/dmtpps
|
||||
The device providing
|
||||
.Xr ioctl 2
|
||||
access to the RFC 2783 API.
|
||||
.El
|
||||
.\"
|
||||
.Sh SEE ALSO
|
||||
.Xr timecounters 4 ,
|
||||
.Xr loader.conf 5 ,
|
||||
.Xr ntp.conf 5 ,
|
||||
.Xr ntpd 8
|
||||
.\"
|
||||
.Sh HISTORY
|
||||
The
|
||||
.Nm
|
||||
device driver first appeared in
|
||||
.Fx 11.0 .
|
||||
.\"
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
device driver and this manual page were written by
|
||||
.An Ian Lepore Aq Mt ian@freebsd.org .
|
@ -26,7 +26,7 @@ ident BEAGLEBONE
|
||||
include "std.armv6"
|
||||
include "../ti/am335x/std.am335x"
|
||||
|
||||
makeoptions MODULES_EXTRA="dtb/am335x"
|
||||
makeoptions MODULES_EXTRA="dtb/am335x am335x_dmtpps"
|
||||
|
||||
# DTrace support
|
||||
options KDTRACE_HOOKS # Kernel DTrace hooks
|
||||
@ -77,6 +77,7 @@ device ti_i2c
|
||||
device am335x_pmic # AM335x Power Management IC (TPC65217)
|
||||
|
||||
device am335x_rtc # RTC support (power management only)
|
||||
#define am335x_dmtpps # Pulse Per Second capture driver
|
||||
|
||||
# Console and misc
|
||||
device uart
|
||||
|
549
sys/arm/ti/am335x/am335x_dmtpps.c
Normal file
549
sys/arm/ti/am335x/am335x_dmtpps.c
Normal file
@ -0,0 +1,549 @@
|
||||
/*-
|
||||
* Copyright (c) 2015 Ian lepore <ian@freebsd.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* AM335x PPS driver using DMTimer capture.
|
||||
*
|
||||
* 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() while
|
||||
* holding a mutex) is scheduled to be done later in a non-interrupt context.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__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/timepps.h>
|
||||
#include <sys/timetc.h>
|
||||
#include <machine/bus.h>
|
||||
|
||||
#include <dev/ofw/openfirm.h>
|
||||
#include <dev/ofw/ofw_bus.h>
|
||||
#include <dev/ofw/ofw_bus_subr.h>
|
||||
|
||||
#include <arm/ti/ti_prcm.h>
|
||||
#include <arm/ti/ti_hwmods.h>
|
||||
#include <arm/ti/ti_pinmux.h>
|
||||
#include <arm/ti/am335x/am335x_scm_padconf.h>
|
||||
|
||||
#include "am335x_dmtreg.h"
|
||||
|
||||
#define PPS_CDEV_NAME "dmtpps"
|
||||
|
||||
struct dmtpps_softc {
|
||||
device_t dev;
|
||||
int mem_rid;
|
||||
struct resource * mem_res;
|
||||
int tmr_num; /* N from hwmod str "timerN" */
|
||||
char tmr_name[12]; /* "DMTimerN" */
|
||||
uint32_t tclr; /* Cached TCLR register. */
|
||||
struct timecounter tc;
|
||||
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_state;
|
||||
struct mtx pps_mtx;
|
||||
};
|
||||
|
||||
static int dmtpps_tmr_num; /* Set by probe() */
|
||||
|
||||
/* List of compatible strings for FDT tree */
|
||||
static struct ofw_compat_data compat_data[] = {
|
||||
{"ti,am335x-timer", 1},
|
||||
{"ti,am335x-timer-1ms", 1},
|
||||
{NULL, 0},
|
||||
};
|
||||
|
||||
/*
|
||||
* A table relating pad names to the hardware timer number they can be mux'd to.
|
||||
*/
|
||||
struct padinfo {
|
||||
char * ballname;
|
||||
int tmr_num;
|
||||
};
|
||||
static struct padinfo dmtpps_padinfo[] = {
|
||||
{"GPMC_ADVn_ALE", 4},
|
||||
{"I2C0_SDA", 4},
|
||||
{"MII1_TX_EN", 4},
|
||||
{"XDMA_EVENT_INTR0", 4},
|
||||
{"GPMC_BEn0_CLE", 5},
|
||||
{"MDC", 5},
|
||||
{"MMC0_DAT3", 5},
|
||||
{"UART1_RTSn", 5},
|
||||
{"GPMC_WEn", 6},
|
||||
{"MDIO", 6},
|
||||
{"MMC0_DAT2", 6},
|
||||
{"UART1_CTSn", 6},
|
||||
{"GPMC_OEn_REn", 7},
|
||||
{"I2C0_SCL", 7},
|
||||
{"UART0_CTSn", 7},
|
||||
{"XDMA_EVENT_INTR1", 7},
|
||||
{NULL, 0}
|
||||
};
|
||||
|
||||
/*
|
||||
* This is either brilliantly user-friendly, or utterly lame...
|
||||
*
|
||||
* The am335x chip is used on the popular Beaglebone boards. Those boards have
|
||||
* pins for all four capture-capable timers available on the P8 header. Allow
|
||||
* users to configure the input pin by giving the name of the header pin.
|
||||
*/
|
||||
struct nicknames {
|
||||
const char * nick;
|
||||
const char * name;
|
||||
};
|
||||
static struct nicknames dmtpps_pin_nicks[] = {
|
||||
{"P8-7", "GPMC_ADVn_ALE"},
|
||||
{"P8-9", "GPMC_BEn0_CLE"},
|
||||
{"P8-10", "GPMC_WEn"},
|
||||
{"P8-8", "GPMC_OEn_REn",},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
#define DMTIMER_READ4(sc, reg) bus_read_4((sc)->mem_res, (reg))
|
||||
#define DMTIMER_WRITE4(sc, reg, val) bus_write_4((sc)->mem_res, (reg), (val))
|
||||
|
||||
/*
|
||||
* Translate a short friendly case-insensitive name to its canonical name.
|
||||
*/
|
||||
static const char *
|
||||
dmtpps_translate_nickname(const char *nick)
|
||||
{
|
||||
struct nicknames *nn;
|
||||
|
||||
for (nn = dmtpps_pin_nicks; nn->nick != NULL; nn++)
|
||||
if (strcasecmp(nick, nn->nick) == 0)
|
||||
return nn->name;
|
||||
return (nick);
|
||||
}
|
||||
|
||||
/*
|
||||
* See if our tunable is set to the name of the input pin. If not, that's NOT
|
||||
* an error, return 0. If so, try to configure that pin as a timer capture
|
||||
* input pin, and if that works, then we have our timer unit number and if it
|
||||
* fails that IS an error, return -1.
|
||||
*/
|
||||
static int
|
||||
dmtpps_find_tmr_num_by_tunable()
|
||||
{
|
||||
struct padinfo *pi;
|
||||
char iname[20];
|
||||
char muxmode[12];
|
||||
const char * ballname;
|
||||
int err;
|
||||
|
||||
if (!TUNABLE_STR_FETCH("hw.am335x_dmtpps.input", iname, sizeof(iname)))
|
||||
return (0);
|
||||
ballname = dmtpps_translate_nickname(iname);
|
||||
for (pi = dmtpps_padinfo; pi->ballname != NULL; pi++) {
|
||||
if (strcmp(ballname, pi->ballname) != 0)
|
||||
continue;
|
||||
snprintf(muxmode, sizeof(muxmode), "timer%d", pi->tmr_num);
|
||||
err = ti_pinmux_padconf_set(pi->ballname, muxmode,
|
||||
PADCONF_INPUT);
|
||||
if (err != 0) {
|
||||
printf("am335x_dmtpps: unable to configure capture pin "
|
||||
"for %s to input mode\n", muxmode);
|
||||
return (-1);
|
||||
} else if (bootverbose) {
|
||||
printf("am335x_dmtpps: configured pin %s as input "
|
||||
"for %s\n", iname, muxmode);
|
||||
}
|
||||
return (pi->tmr_num);
|
||||
}
|
||||
|
||||
/* Invalid name in the tunable, that's an error. */
|
||||
printf("am335x_dmtpps: unknown pin name '%s'\n", iname);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Ask the pinmux driver whether any pin has been configured as a TIMER4..TIMER7
|
||||
* input pin. If so, return the timer number, if not return 0.
|
||||
*/
|
||||
static int
|
||||
dmtpps_find_tmr_num_by_padconf()
|
||||
{
|
||||
int err;
|
||||
unsigned int padstate;
|
||||
const char * padmux;
|
||||
struct padinfo *pi;
|
||||
char muxmode[12];
|
||||
|
||||
for (pi = dmtpps_padinfo; pi->ballname != NULL; pi++) {
|
||||
err = ti_pinmux_padconf_get(pi->ballname, &padmux, &padstate);
|
||||
snprintf(muxmode, sizeof(muxmode), "timer%d", pi->tmr_num);
|
||||
if (err == 0 && (padstate & RXACTIVE) != 0 &&
|
||||
strcmp(muxmode, padmux) == 0)
|
||||
return (pi->tmr_num);
|
||||
}
|
||||
/* Nothing found, not an error. */
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Figure out which hardware timer number to use based on input pin
|
||||
* configuration. This is done just once, the first time probe() runs.
|
||||
*/
|
||||
static int
|
||||
dmtpps_find_tmr_num()
|
||||
{
|
||||
int tmr_num;
|
||||
|
||||
if ((tmr_num = dmtpps_find_tmr_num_by_tunable()) == 0)
|
||||
tmr_num = dmtpps_find_tmr_num_by_padconf();
|
||||
|
||||
if (tmr_num <= 0) {
|
||||
printf("am335x_dmtpps: PPS driver not enabled: unable to find "
|
||||
"or configure a capture input pin\n");
|
||||
tmr_num = -1; /* Must return non-zero to prevent re-probing. */
|
||||
}
|
||||
return (tmr_num);
|
||||
}
|
||||
|
||||
static void
|
||||
dmtpps_set_hw_capture(struct dmtpps_softc *sc, bool force_off)
|
||||
{
|
||||
int newmode;
|
||||
|
||||
if (force_off)
|
||||
newmode = 0;
|
||||
else
|
||||
newmode = sc->pps_state.ppsparam.mode & PPS_CAPTUREASSERT;
|
||||
|
||||
if (newmode == sc->pps_curmode)
|
||||
return;
|
||||
sc->pps_curmode = newmode;
|
||||
|
||||
if (newmode == PPS_CAPTUREASSERT)
|
||||
sc->tclr |= DMT_TCLR_CAPTRAN_LOHI;
|
||||
else
|
||||
sc->tclr &= ~DMT_TCLR_CAPTRAN_MASK;
|
||||
DMTIMER_WRITE4(sc, DMT_TCLR, sc->tclr);
|
||||
}
|
||||
|
||||
static unsigned
|
||||
dmtpps_get_timecount(struct timecounter *tc)
|
||||
{
|
||||
struct dmtpps_softc *sc;
|
||||
|
||||
sc = tc->tc_priv;
|
||||
|
||||
return (DMTIMER_READ4(sc, DMT_TCRR));
|
||||
}
|
||||
|
||||
static void
|
||||
dmtpps_poll(struct timecounter *tc)
|
||||
{
|
||||
struct dmtpps_softc *sc;
|
||||
|
||||
sc = tc->tc_priv;
|
||||
|
||||
/*
|
||||
* If a new value has been latched we've got a PPS event. Capture the
|
||||
* timecounter data, then override the capcount field (pps_capture()
|
||||
* populates it from the current DMT_TCRR register) with the latched
|
||||
* value from the TCAR1 register.
|
||||
*
|
||||
* There is no locking here, by design. pps_capture() writes into an
|
||||
* area of struct pps_state which is read only by pps_event(). The
|
||||
* synchronization of access to that area is temporal rather than
|
||||
* interlock based... we write in this routine and trigger the task that
|
||||
* will read the data, so no simultaneous access can occur.
|
||||
*
|
||||
* 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 (DMTIMER_READ4(sc, DMT_IRQSTATUS_RAW) & DMT_IRQ_TCAR) {
|
||||
pps_capture(&sc->pps_state);
|
||||
sc->pps_state.capcount = DMTIMER_READ4(sc, DMT_TCAR1);
|
||||
DMTIMER_WRITE4(sc, DMT_IRQSTATUS, DMT_IRQ_TCAR);
|
||||
taskqueue_enqueue_fast(taskqueue_fast, &sc->pps_task);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dmtpps_event(void *arg, int pending)
|
||||
{
|
||||
struct dmtpps_softc *sc;
|
||||
|
||||
sc = arg;
|
||||
|
||||
/* This is the task function that gets enqueued by poll_pps. Once the
|
||||
* time has been captured by the timecounter polling code which runs in
|
||||
* primary interrupt context, the remaining (more expensive) work to
|
||||
* process the event is done later in a threaded context.
|
||||
*
|
||||
* Here there is an interlock that protects the event data in struct
|
||||
* pps_state. That data can be accessed at any time from userland via
|
||||
* ioctl() calls so we must ensure that there is no read access to
|
||||
* partially updated data while pps_event() does its work.
|
||||
*/
|
||||
mtx_lock(&sc->pps_mtx);
|
||||
pps_event(&sc->pps_state, PPS_CAPTUREASSERT);
|
||||
mtx_unlock(&sc->pps_mtx);
|
||||
}
|
||||
|
||||
static int
|
||||
dmtpps_open(struct cdev *dev, int flags, int fmt,
|
||||
struct thread *td)
|
||||
{
|
||||
struct dmtpps_softc *sc;
|
||||
|
||||
sc = dev->si_drv1;
|
||||
|
||||
/*
|
||||
* Begin polling for pps and enable capture in the hardware whenever the
|
||||
* device is open. Doing this stuff again is harmless if this isn't the
|
||||
* first open.
|
||||
*/
|
||||
sc->tc.tc_poll_pps = dmtpps_poll;
|
||||
dmtpps_set_hw_capture(sc, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dmtpps_close(struct cdev *dev, int flags, int fmt,
|
||||
struct thread *td)
|
||||
{
|
||||
struct dmtpps_softc *sc;
|
||||
|
||||
sc = dev->si_drv1;
|
||||
|
||||
/*
|
||||
* Stop polling and disable capture on last close. Use the force-off
|
||||
* flag to override the configured mode and turn off the hardware.
|
||||
*/
|
||||
sc->tc.tc_poll_pps = NULL;
|
||||
dmtpps_set_hw_capture(sc, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dmtpps_ioctl(struct cdev *dev, u_long cmd, caddr_t data,
|
||||
int flags, struct thread *td)
|
||||
{
|
||||
struct dmtpps_softc *sc;
|
||||
int err;
|
||||
|
||||
sc = dev->si_drv1;
|
||||
|
||||
/* Let the kernel do the heavy lifting for ioctl. */
|
||||
mtx_lock(&sc->pps_mtx);
|
||||
err = pps_ioctl(cmd, data, &sc->pps_state);
|
||||
mtx_unlock(&sc->pps_mtx);
|
||||
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.
|
||||
*/
|
||||
dmtpps_set_hw_capture(sc, false);
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
static struct cdevsw dmtpps_cdevsw = {
|
||||
.d_version = D_VERSION,
|
||||
.d_open = dmtpps_open,
|
||||
.d_close = dmtpps_close,
|
||||
.d_ioctl = dmtpps_ioctl,
|
||||
.d_name = PPS_CDEV_NAME,
|
||||
};
|
||||
|
||||
static int
|
||||
dmtpps_probe(device_t dev)
|
||||
{
|
||||
char strbuf[64];
|
||||
int tmr_num;
|
||||
|
||||
if (!ofw_bus_status_okay(dev))
|
||||
return (ENXIO);
|
||||
|
||||
if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
|
||||
return (ENXIO);
|
||||
|
||||
/*
|
||||
* If we haven't chosen which hardware timer to use yet, go do that now.
|
||||
* We need to know that to decide whether to return success for this
|
||||
* hardware timer instance or not.
|
||||
*/
|
||||
if (dmtpps_tmr_num == 0)
|
||||
dmtpps_tmr_num = dmtpps_find_tmr_num();
|
||||
|
||||
/*
|
||||
* Figure out which hardware timer is being probed and see if it matches
|
||||
* the configured timer number determined earlier.
|
||||
*/
|
||||
tmr_num = ti_hwmods_get_unit(dev, "timer");
|
||||
if (dmtpps_tmr_num != tmr_num)
|
||||
return (ENXIO);
|
||||
|
||||
snprintf(strbuf, sizeof(strbuf), "AM335x PPS-Capture DMTimer%d",
|
||||
tmr_num);
|
||||
device_set_desc_copy(dev, strbuf);
|
||||
|
||||
return(BUS_PROBE_DEFAULT);
|
||||
}
|
||||
|
||||
static int
|
||||
dmtpps_attach(device_t dev)
|
||||
{
|
||||
struct dmtpps_softc *sc;
|
||||
clk_ident_t timer_id;
|
||||
int err, sysclk_freq;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
sc->dev = dev;
|
||||
|
||||
/* Get the base clock frequency. */
|
||||
err = ti_prcm_clk_get_source_freq(SYS_CLK, &sysclk_freq);
|
||||
|
||||
/* Enable clocks and power on the device. */
|
||||
if ((timer_id = ti_hwmods_get_clock(dev)) == INVALID_CLK_IDENT)
|
||||
return (ENXIO);
|
||||
if ((err = ti_prcm_clk_set_source(timer_id, SYSCLK_CLK)) != 0)
|
||||
return (err);
|
||||
if ((err = ti_prcm_clk_enable(timer_id)) != 0)
|
||||
return (err);
|
||||
|
||||
/* Request the memory resources. */
|
||||
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
|
||||
&sc->mem_rid, RF_ACTIVE);
|
||||
if (sc->mem_res == NULL) {
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
/* Figure out which hardware timer this is and set the name string. */
|
||||
sc->tmr_num = ti_hwmods_get_unit(dev, "timer");
|
||||
snprintf(sc->tmr_name, sizeof(sc->tmr_name), "DMTimer%d", sc->tmr_num);
|
||||
|
||||
/* Set up timecounter hardware, start it. */
|
||||
DMTIMER_WRITE4(sc, DMT_TSICR, DMT_TSICR_RESET);
|
||||
while (DMTIMER_READ4(sc, DMT_TIOCP_CFG) & DMT_TIOCP_RESET)
|
||||
continue;
|
||||
|
||||
sc->tclr |= DMT_TCLR_START | DMT_TCLR_AUTOLOAD;
|
||||
DMTIMER_WRITE4(sc, DMT_TLDR, 0);
|
||||
DMTIMER_WRITE4(sc, DMT_TCRR, 0);
|
||||
DMTIMER_WRITE4(sc, DMT_TCLR, sc->tclr);
|
||||
|
||||
/* Register the timecounter. */
|
||||
sc->tc.tc_name = sc->tmr_name;
|
||||
sc->tc.tc_get_timecount = dmtpps_get_timecount;
|
||||
sc->tc.tc_counter_mask = ~0u;
|
||||
sc->tc.tc_frequency = sysclk_freq;
|
||||
sc->tc.tc_quality = 1000;
|
||||
sc->tc.tc_priv = sc;
|
||||
|
||||
tc_init(&sc->tc);
|
||||
|
||||
/*
|
||||
* Indicate our PPS capabilities. Have the kernel init its part of the
|
||||
* pps_state struct and add its capabilities.
|
||||
*
|
||||
* While the hardware has a mode to capture each edge, it's not clear we
|
||||
* can use it that way, because there's only a single interrupt/status
|
||||
* bit to say something was captured, but not which edge it was. For
|
||||
* now, just say we can only capture assert events (the positive-going
|
||||
* edge of the pulse).
|
||||
*/
|
||||
mtx_init(&sc->pps_mtx, "dmtpps", NULL, MTX_DEF);
|
||||
sc->pps_state.ppscap = PPS_CAPTUREASSERT;
|
||||
sc->pps_state.driver_abi = PPS_ABI_VERSION;
|
||||
sc->pps_state.driver_mtx = &sc->pps_mtx;
|
||||
pps_init_abi(&sc->pps_state);
|
||||
|
||||
/*
|
||||
* Init the task that does deferred pps_event() processing after
|
||||
* the polling routine has captured a pps pulse time.
|
||||
*/
|
||||
TASK_INIT(&sc->pps_task, 0, dmtpps_event, sc);
|
||||
|
||||
/* Create the PPS cdev. */
|
||||
sc->pps_cdev = make_dev(&dmtpps_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600,
|
||||
PPS_CDEV_NAME);
|
||||
sc->pps_cdev->si_drv1 = sc;
|
||||
|
||||
if (bootverbose)
|
||||
device_printf(sc->dev, "Using %s for PPS device /dev/%s\n",
|
||||
sc->tmr_name, PPS_CDEV_NAME);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
dmtpps_detach(device_t dev)
|
||||
{
|
||||
|
||||
/*
|
||||
* There is no way to remove a timecounter once it has been registered,
|
||||
* even if it's not in use, so we can never detach. If we were
|
||||
* dynamically loaded as a module this will prevent unloading.
|
||||
*/
|
||||
return (EBUSY);
|
||||
}
|
||||
|
||||
static device_method_t dmtpps_methods[] = {
|
||||
DEVMETHOD(device_probe, dmtpps_probe),
|
||||
DEVMETHOD(device_attach, dmtpps_attach),
|
||||
DEVMETHOD(device_detach, dmtpps_detach),
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
static driver_t dmtpps_driver = {
|
||||
"am335x_dmtpps",
|
||||
dmtpps_methods,
|
||||
sizeof(struct dmtpps_softc),
|
||||
};
|
||||
|
||||
static devclass_t dmtpps_devclass;
|
||||
|
||||
DRIVER_MODULE(am335x_dmtpps, simplebus, dmtpps_driver, dmtpps_devclass, 0, 0);
|
||||
MODULE_DEPEND(am335x_dmtpps, am335x_prcm, 1, 1, 1);
|
||||
|
@ -3,6 +3,7 @@
|
||||
arm/ti/aintc.c standard
|
||||
|
||||
arm/ti/am335x/am335x_dmtimer.c standard
|
||||
arm/ti/am335x/am335x_dmtpps.c optional am335x_dmtpps
|
||||
arm/ti/am335x/am335x_gpio.c optional gpio
|
||||
arm/ti/am335x/am335x_lcd.c optional sc | vt
|
||||
arm/ti/am335x/am335x_lcd_syscons.c optional sc
|
||||
|
8
sys/modules/am335x_dmtpps/Makefile
Normal file
8
sys/modules/am335x_dmtpps/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
# $FreeBSD$
|
||||
|
||||
.PATH: ${.CURDIR}/../../arm/ti/am335x
|
||||
|
||||
KMOD= am335x_dmtpps
|
||||
SRCS= am335x_dmtpps.c
|
||||
|
||||
.include <bsd.kmod.mk>
|
Loading…
Reference in New Issue
Block a user