#include <sys/cdefs.h>
#include "opt_platform.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/rman.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 <dev/clk/clk.h>
#include <arm/ti/ti_sysc.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;
char tmr_name[12];
uint32_t tclr;
struct timecounter tc;
int pps_curmode;
struct cdev * pps_cdev;
struct pps_state pps_state;
struct mtx pps_mtx;
clk_t clk_fck;
uint64_t sysclk_freq;
};
static int dmtpps_tmr_num;
static struct ofw_compat_data compat_data[] = {
{"ti,am335x-timer", 1},
{"ti,am335x-timer-1ms", 1},
{NULL, 0},
};
SIMPLEBUS_PNP_INFO(compat_data);
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}
};
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))
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);
}
static int
dmtpps_find_tmr_num_by_tunable(void)
{
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);
}
printf("am335x_dmtpps: unknown pin name '%s'\n", iname);
return (-1);
}
static int
dmtpps_find_tmr_num_by_padconf(void)
{
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);
}
return (0);
}
static int
dmtpps_find_tmr_num(void)
{
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;
}
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 (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);
mtx_lock_spin(&sc->pps_mtx);
pps_event(&sc->pps_state, PPS_CAPTUREASSERT);
mtx_unlock_spin(&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;
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;
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;
mtx_lock_spin(&sc->pps_mtx);
err = pps_ioctl(cmd, data, &sc->pps_state);
mtx_unlock_spin(&sc->pps_mtx);
if (err != 0)
return (err);
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)
{
int tmr_num;
uint64_t rev_address;
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
return (ENXIO);
if (dmtpps_tmr_num == 0)
dmtpps_tmr_num = dmtpps_find_tmr_num();
rev_address = ti_sysc_get_rev_address(device_get_parent(dev));
switch (rev_address) {
case DMTIMER1_1MS_REV:
tmr_num = 1;
break;
case DMTIMER2_REV:
tmr_num = 2;
break;
case DMTIMER3_REV:
tmr_num = 3;
break;
case DMTIMER4_REV:
tmr_num = 4;
break;
case DMTIMER5_REV:
tmr_num = 5;
break;
case DMTIMER6_REV:
tmr_num = 6;
break;
case DMTIMER7_REV:
tmr_num = 7;
break;
default:
return (ENXIO);
}
if (dmtpps_tmr_num != tmr_num)
return (ENXIO);
device_set_descf(dev, "AM335x PPS-Capture DMTimer%d", tmr_num);
return(BUS_PROBE_DEFAULT);
}
static int
dmtpps_attach(device_t dev)
{
struct dmtpps_softc *sc;
struct make_dev_args mda;
int err;
clk_t sys_clkin;
uint64_t rev_address;
sc = device_get_softc(dev);
sc->dev = dev;
rev_address = ti_sysc_get_rev_address(device_get_parent(dev));
switch (rev_address) {
case DMTIMER1_1MS_REV:
sc->tmr_num = 1;
break;
case DMTIMER2_REV:
sc->tmr_num = 2;
break;
case DMTIMER3_REV:
sc->tmr_num = 3;
break;
case DMTIMER4_REV:
sc->tmr_num = 4;
break;
case DMTIMER5_REV:
sc->tmr_num = 5;
break;
case DMTIMER6_REV:
sc->tmr_num = 6;
break;
case DMTIMER7_REV:
sc->tmr_num = 7;
break;
}
snprintf(sc->tmr_name, sizeof(sc->tmr_name), "DMTimer%d", sc->tmr_num);
err = clk_get_by_ofw_index(dev, 0, 0, &sc->clk_fck);
if (err != 0) {
device_printf(dev, "Cant find clock index 0. err: %d\n", err);
return (ENXIO);
}
err = clk_get_by_name(dev, "sys_clkin_ck@40", &sys_clkin);
if (err != 0) {
device_printf(dev, "Cant find sys_clkin_ck@40 err: %d\n", err);
return (ENXIO);
}
err = clk_set_parent_by_clk(sc->clk_fck, sys_clkin);
if (err != 0) {
device_printf(dev, "Cant set mux to CLK_M_OSC\n");
return (ENXIO);
}
err = ti_sysc_clock_enable(device_get_parent(dev));
if (err != 0) {
device_printf(dev, "Cant enable sysc clkctrl, err %d\n", err);
return (ENXIO);
}
err = clk_get_freq(sc->clk_fck, &sc->sysclk_freq);
if (err != 0) {
device_printf(dev, "Cant get sysclk frequency, err %d\n", err);
return (ENXIO);
}
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
&sc->mem_rid, RF_ACTIVE);
if (sc->mem_res == NULL) {
return (ENXIO);
}
sc->tclr = DMT_TCLR_GPO_CFG;
DMTIMER_WRITE4(sc, DMT_TCLR, sc->tclr);
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);
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 = sc->sysclk_freq;
sc->tc.tc_quality = 1000;
sc->tc.tc_priv = sc;
tc_init(&sc->tc);
mtx_init(&sc->pps_mtx, "dmtpps", NULL, MTX_SPIN);
sc->pps_state.flags = PPSFLAG_MTX_SPIN;
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);
make_dev_args_init(&mda);
mda.mda_flags = MAKEDEV_WAITOK;
mda.mda_devsw = &dmtpps_cdevsw;
mda.mda_cr = NULL;
mda.mda_uid = UID_ROOT;
mda.mda_gid = GID_WHEEL;
mda.mda_mode = 0600;
mda.mda_unit = device_get_unit(dev);
mda.mda_si_drv1 = sc;
if ((err = make_dev_s(&mda, &sc->pps_cdev, PPS_CDEV_NAME)) != 0) {
device_printf(dev, "Failed to create cdev %s\n", PPS_CDEV_NAME);
return (err);
}
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)
{
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),
};
DRIVER_MODULE(am335x_dmtpps, simplebus, dmtpps_driver, 0, 0);
MODULE_DEPEND(am335x_dmtpps, ti_sysc, 1, 1, 1);