#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/rman.h>
#include <sys/timeet.h>
#include <sys/timetc.h>
#include <sys/watchdog.h>
#include <machine/bus.h>
#include <machine/cpu.h>
#include <machine/intr.h>
#include <machine/machdep.h>
#include <dev/fdt/fdt_common.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <arm/freescale/imx/imx_ccmvar.h>
#include <arm/freescale/imx/imx_machdep.h>
#define EPIT_CR 0x00
#define EPIT_CR_CLKSRC_SHIFT 24
#define EPIT_CR_CLKSRC_OFF 0
#define EPIT_CR_CLKSRC_IPG 1
#define EPIT_CR_CLKSRC_HFCLK 2
#define EPIT_CR_CLKSRC_LFCLK 3
#define EPIT_CR_STOPEN (1u << 21)
#define EPIT_CR_WAITEN (1u << 19)
#define EPIT_CR_DBGEN (1u << 18)
#define EPIT_CR_IOVW (1u << 17)
#define EPIT_CR_SWR (1u << 16)
#define EPIT_CR_RLD (1u << 3)
#define EPIT_CR_OCIEN (1u << 2)
#define EPIT_CR_ENMOD (1u << 1)
#define EPIT_CR_EN (1u << 0)
#define EPIT_SR 0x04
#define EPIT_SR_OCIF (1u << 0)
#define EPIT_LR 0x08
#define EPIT_CMPR 0x0c
#define EPIT_CNR 0x10
#define ET_MIN_TICKS 2
#define ET_MAX_TICKS 0xfffffffe
static u_int epit_tc_get_timecount(struct timecounter *tc);
struct epit_softc {
device_t dev;
struct resource * memres;
struct resource * intres;
void * inthandle;
uint32_t clkfreq;
uint32_t ctlreg;
uint32_t period;
struct timecounter tc;
struct eventtimer et;
bool oneshot;
};
static const uint32_t imx51_epit_ioaddr[2] = {0x73fac000, 0x73fb0000};
static const uint32_t imx53_epit_ioaddr[2] = {0x53fac000, 0x53fb0000};
static const uint32_t imx6_epit_ioaddr[2] = {0x020d0000, 0x020d4000};
static struct ofw_compat_data compat_data[] = {
{"fsl,imx6ul-epit", 1},
{"fsl,imx6sx-epit", 1},
{"fsl,imx6q-epit", 1},
{"fsl,imx6dl-epit", 1},
{"fsl,imx53-epit", 2},
{"fsl,imx51-epit", 2},
{"fsl,imx31-epit", 2},
{"fsl,imx27-epit", 2},
{"fsl,imx25-epit", 2},
{NULL, 0}
};
static inline uint32_t
RD4(struct epit_softc *sc, bus_size_t offset)
{
return (bus_read_4(sc->memres, offset));
}
static inline void
WR4(struct epit_softc *sc, bus_size_t offset, uint32_t value)
{
bus_write_4(sc->memres, offset, value);
}
static inline void
WR4B(struct epit_softc *sc, bus_size_t offset, uint32_t value)
{
bus_write_4(sc->memres, offset, value);
bus_barrier(sc->memres, offset, 4, BUS_SPACE_BARRIER_WRITE);
}
static u_int
epit_read_counter(struct epit_softc *sc)
{
return (0xffffffff - RD4(sc, EPIT_CNR));
}
static void
epit_do_delay(int usec, void *arg)
{
struct epit_softc *sc = arg;
uint64_t curcnt, endcnt, startcnt, ticks;
ticks = 1 + ((uint64_t)usec * sc->clkfreq) / 1000000;
curcnt = startcnt = epit_read_counter(sc);
endcnt = startcnt + ticks;
while (curcnt < endcnt) {
curcnt = epit_read_counter(sc);
if (curcnt < startcnt)
curcnt += 1ULL << 32;
}
}
static u_int
epit_tc_get_timecount(struct timecounter *tc)
{
return (epit_read_counter(tc->tc_priv));
}
static int
epit_tc_attach(struct epit_softc *sc)
{
WR4(sc, EPIT_LR, 0xffffffff);
WR4(sc, EPIT_CR, sc->ctlreg | EPIT_CR_EN);
sc->tc.tc_name = "EPIT";
sc->tc.tc_quality = 1000;
sc->tc.tc_frequency = sc->clkfreq;
sc->tc.tc_counter_mask = 0xffffffff;
sc->tc.tc_get_timecount = epit_tc_get_timecount;
sc->tc.tc_priv = sc;
tc_init(&sc->tc);
arm_set_delay(epit_do_delay, sc);
return (0);
}
static int
epit_et_start(struct eventtimer *et, sbintime_t first, sbintime_t period)
{
struct epit_softc *sc;
uint32_t ticks;
sc = (struct epit_softc *)et->et_priv;
WR4(sc, EPIT_CR, sc->ctlreg);
WR4(sc, EPIT_SR, EPIT_SR_OCIF);
if (period != 0) {
sc->oneshot = false;
ticks = ((uint32_t)et->et_frequency * period) >> 32;
} else if (first != 0) {
sc->oneshot = true;
ticks = ((uint32_t)et->et_frequency * first) >> 32;
} else {
return (EINVAL);
}
WR4(sc, EPIT_LR, ticks);
WR4B(sc, EPIT_CR, sc->ctlreg | EPIT_CR_EN);
return (0);
}
static int
epit_et_stop(struct eventtimer *et)
{
struct epit_softc *sc;
sc = (struct epit_softc *)et->et_priv;
WR4(sc, EPIT_CR, sc->ctlreg);
WR4B(sc, EPIT_SR, EPIT_SR_OCIF);
return (0);
}
static int
epit_intr(void *arg)
{
struct epit_softc *sc;
uint32_t status;
sc = arg;
if (sc->oneshot)
WR4(sc, EPIT_CR, sc->ctlreg);
status = RD4(sc, EPIT_SR);
WR4B(sc, EPIT_SR, status);
if ((status & EPIT_SR_OCIF) == 0)
return (FILTER_STRAY);
if (sc->et.et_active)
sc->et.et_event_cb(&sc->et, sc->et.et_arg);
return (FILTER_HANDLED);
}
static int
epit_et_attach(struct epit_softc *sc)
{
int err, rid;
rid = 0;
sc->intres = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &rid,
RF_ACTIVE);
if (sc->intres == NULL) {
device_printf(sc->dev, "could not allocate interrupt\n");
return (ENXIO);
}
err = bus_setup_intr(sc->dev, sc->intres, INTR_TYPE_CLK | INTR_MPSAFE,
epit_intr, NULL, sc, &sc->inthandle);
if (err != 0) {
device_printf(sc->dev, "unable to setup the irq handler\n");
return (err);
}
sc->ctlreg |= EPIT_CR_OCIEN;
sc->et.et_name = "EPIT";
sc->et.et_flags = ET_FLAGS_ONESHOT | ET_FLAGS_PERIODIC;
sc->et.et_quality = 1000;
sc->et.et_frequency = sc->clkfreq;
sc->et.et_min_period = ((uint64_t)ET_MIN_TICKS << 32) / sc->clkfreq;
sc->et.et_max_period = ((uint64_t)ET_MAX_TICKS << 32) / sc->clkfreq;
sc->et.et_start = epit_et_start;
sc->et.et_stop = epit_et_stop;
sc->et.et_priv = sc;
et_register(&sc->et);
return (0);
}
static int
epit_probe(device_t dev)
{
struct resource *memres;
rman_res_t ioaddr;
int num_units, rid, unit;
if (!ofw_bus_status_okay(dev))
return (ENXIO);
unit = device_get_unit(dev);
num_units = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
if (unit < num_units) {
device_set_desc(dev, "i.MX EPIT timer");
return (BUS_PROBE_DEFAULT);
}
if (strstr(ofw_bus_get_name(dev), "epit") == NULL)
return (ENXIO);
rid = 0;
memres = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_UNMAPPED);
if (memres == NULL)
return (ENXIO);
ioaddr = rman_get_start(memres);
bus_free_resource(dev, SYS_RES_MEMORY, memres);
if (imx_soc_family() == 6) {
if (unit > 0)
return (ENXIO);
if (ioaddr != imx6_epit_ioaddr[unit])
return (ENXIO);
} else {
if (unit > 1)
return (ENXIO);
switch (imx_soc_type()) {
case IMXSOC_51:
if (ioaddr != imx51_epit_ioaddr[unit])
return (ENXIO);
break;
case IMXSOC_53:
if (ioaddr != imx53_epit_ioaddr[unit])
return (ENXIO);
break;
default:
return (ENXIO);
}
return (ENXIO);
}
device_set_desc(dev, "i.MX EPIT timer");
return (BUS_PROBE_DEFAULT);
}
static int
epit_attach(device_t dev)
{
struct epit_softc *sc;
int err, rid;
uint32_t clksrc;
sc = device_get_softc(dev);
sc->dev = dev;
rid = 0;
sc->memres = bus_alloc_resource_any(sc->dev, SYS_RES_MEMORY, &rid,
RF_ACTIVE);
if (sc->memres == NULL) {
device_printf(sc->dev, "could not allocate registers\n");
return (ENXIO);
}
clksrc = EPIT_CR_CLKSRC_IPG;
switch (clksrc) {
default:
device_printf(dev,
"Unsupported clock source '%d', using IPG\n", clksrc);
case EPIT_CR_CLKSRC_IPG:
sc->clkfreq = imx_ccm_ipg_hz();
break;
case EPIT_CR_CLKSRC_HFCLK:
sc->clkfreq = imx_ccm_perclk_hz();
break;
case EPIT_CR_CLKSRC_LFCLK:
sc->clkfreq = 32768;
break;
}
WR4(sc, EPIT_CR, 0);
sc->ctlreg =
(clksrc << EPIT_CR_CLKSRC_SHIFT) |
EPIT_CR_ENMOD |
EPIT_CR_RLD |
EPIT_CR_STOPEN |
EPIT_CR_WAITEN |
EPIT_CR_DBGEN;
WR4B(sc, EPIT_CR, sc->ctlreg | EPIT_CR_SWR);
while (RD4(sc, EPIT_CR) & EPIT_CR_SWR)
continue;
if (device_get_unit(sc->dev) == 0)
err = epit_tc_attach(sc);
else
err = epit_et_attach(sc);
return (err);
}
static device_method_t epit_methods[] = {
DEVMETHOD(device_probe, epit_probe),
DEVMETHOD(device_attach, epit_attach),
DEVMETHOD_END
};
static driver_t epit_driver = {
"imx_epit",
epit_methods,
sizeof(struct epit_softc),
};
EARLY_DRIVER_MODULE(imx_epit, simplebus, epit_driver, 0, 0, BUS_PASS_TIMER);