summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorrapha <rapha@cvs.openbsd.org>2013-05-21 14:41:06 +0000
committerrapha <rapha@cvs.openbsd.org>2013-05-21 14:41:06 +0000
commite522297f801d6b82dd539e2e6baadd40937fff2d (patch)
treec75c20448ff4f61ccc465970ead1ad88c9ae6309 /sys
parent137df0ad117c9873dc54d072904fbcb3ccee2089 (diff)
Add a driver for the am335x timers, to be used by the beaglebone.
Derived from the existing omap3 gptimer. ok patrick@
Diffstat (limited to 'sys')
-rw-r--r--sys/arch/beagle/conf/GENERIC3
-rw-r--r--sys/arch/beagle/conf/RAMDISK3
-rw-r--r--sys/arch/beagle/conf/files.beagle6
-rw-r--r--sys/arch/beagle/dev/dmtimer.c414
4 files changed, 423 insertions, 3 deletions
diff --git a/sys/arch/beagle/conf/GENERIC b/sys/arch/beagle/conf/GENERIC
index c9280937b21..1f0e73414bf 100644
--- a/sys/arch/beagle/conf/GENERIC
+++ b/sys/arch/beagle/conf/GENERIC
@@ -1,4 +1,4 @@
-# $OpenBSD: GENERIC,v 1.17 2013/05/09 15:16:53 patrick Exp $
+# $OpenBSD: GENERIC,v 1.18 2013/05/21 14:41:05 rapha Exp $
#
# GENERIC machine description file
#
@@ -71,6 +71,7 @@ prcm* at soc? # power/clock controller
omdog* at soc? # watchdog timer
omgpio* at soc? # user-visible GPIO pins?
gptimer* at soc? # general purpose timers
+dmtimer* at soc? # am335x dual mode timers
ommmc* at soc? # SD/MMC card controller
omusbtll* at soc?
diff --git a/sys/arch/beagle/conf/RAMDISK b/sys/arch/beagle/conf/RAMDISK
index d2105fc1b06..3526c507634 100644
--- a/sys/arch/beagle/conf/RAMDISK
+++ b/sys/arch/beagle/conf/RAMDISK
@@ -1,4 +1,4 @@
-# $OpenBSD: RAMDISK,v 1.19 2013/05/09 15:16:53 patrick Exp $
+# $OpenBSD: RAMDISK,v 1.20 2013/05/21 14:41:05 rapha Exp $
#
# GENERIC machine description file
#
@@ -76,6 +76,7 @@ prcm* at soc? # power/clock controller
omdog* at soc? # watchdog timer
omgpio* at soc? # user-visible GPIO pins?
gptimer* at soc? # general purpose timers
+dmtimer* at soc? # am335x dual mode timers
ommmc* at soc? # SD/MMC card controller
omusbtll* at soc?
diff --git a/sys/arch/beagle/conf/files.beagle b/sys/arch/beagle/conf/files.beagle
index 12255bbe6e5..86c58cac65f 100644
--- a/sys/arch/beagle/conf/files.beagle
+++ b/sys/arch/beagle/conf/files.beagle
@@ -1,4 +1,4 @@
-# $OpenBSD: files.beagle,v 1.14 2013/05/09 15:16:53 patrick Exp $
+# $OpenBSD: files.beagle,v 1.15 2013/05/21 14:41:05 rapha Exp $
#
# First try for arm-specific configuration info
#
@@ -54,6 +54,10 @@ device gptimer
attach gptimer at soc
file arch/beagle/dev/gptimer.c gptimer
+device dmtimer
+attach dmtimer at soc
+file arch/beagle/dev/dmtimer.c dmtimer
+
device omapid
attach omapid at soc
file arch/beagle/dev/omapid.c omapid
diff --git a/sys/arch/beagle/dev/dmtimer.c b/sys/arch/beagle/dev/dmtimer.c
new file mode 100644
index 00000000000..0abcf9bd18c
--- /dev/null
+++ b/sys/arch/beagle/dev/dmtimer.c
@@ -0,0 +1,414 @@
+/*
+ * Copyright (c) 2007,2009 Dale Rahn <drahn@openbsd.org>
+ * Copyright (c) 2013 Raphael Graf <r@undefined.ch>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * WARNING - this timer initializion has not been checked
+ * to see if it will do _ANYTHING_ sane if the omap enters
+ * low power mode.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/time.h>
+#include <sys/evcount.h>
+#include <sys/device.h>
+#include <sys/timetc.h>
+#include <dev/clock_subr.h>
+#include <machine/bus.h>
+#include <beagle/dev/omapvar.h>
+#include <beagle/dev/prcmvar.h>
+
+#include <machine/intr.h>
+#include <arm/cpufunc.h>
+
+/* registers */
+#define DM_TIDR 0x000
+#define DM_TIDR_MAJOR 0x00000700
+#define DM_TIDR_MINOR 0x0000003f
+#define DM_TIOCP_CFG 0x010
+#define DM_TIOCP_CFG_IDLEMODE (3<<2)
+#define DM_TIOCP_CFG_EMUFREE (1<<1)
+#define DM_TIOCP_CFG_SOFTRESET (1<<0)
+#define DM_TISR 0x028
+#define DM_TISR_TCAR (1<<2)
+#define DM_TISR_OVF (1<<1)
+#define DM_TISR_MAT (1<<0)
+#define DM_TIER 0x2c
+#define DM_TIER_TCAR_EN (1<<2)
+#define DM_TIER_OVF_EN (1<<1)
+#define DM_TIER_MAT_EN (1<<0)
+#define DM_TIECR 0x30
+#define DM_TIECR_TCAR_EN (1<<2)
+#define DM_TIECR_OVF_EN (1<<1)
+#define DM_TIECR_MAT_EN (1<<0)
+#define DM_TWER 0x034
+#define DM_TWER_TCAR_EN (1<<2)
+#define DM_TWER_OVF_EN (1<<1)
+#define DM_TWER_MAT_EN (1<<0)
+#define DM_TCLR 0x038
+#define DM_TCLR_GPO (1<<14)
+#define DM_TCLR_CAPT (1<<13)
+#define DM_TCLR_PT (1<<12)
+#define DM_TCLR_TRG (3<<10)
+#define DM_TCLR_TRG_O (1<<10)
+#define DM_TCLR_TRG_OM (2<<10)
+#define DM_TCLR_TCM (3<<8)
+#define DM_TCLR_TCM_RISE (1<<8)
+#define DM_TCLR_TCM_FALL (2<<8)
+#define DM_TCLR_TCM_BOTH (3<<8)
+#define DM_TCLR_SCPWM (1<<7)
+#define DM_TCLR_CE (1<<6)
+#define DM_TCLR_PRE (1<<5)
+#define DM_TCLR_PTV (7<<2)
+#define DM_TCLR_AR (1<<1)
+#define DM_TCLR_ST (1<<0)
+#define DM_TCRR 0x03c
+#define DM_TLDR 0x040
+#define DM_TTGR 0x044
+#define DM_TWPS 0x048
+#define DM_TWPS_TMAR (1<<4)
+#define DM_TWPS_TTGR (1<<3)
+#define DM_TWPS_TLDR (1<<2)
+#define DM_TWPS_TCLR (1<<0)
+#define DM_TWPS_TCRR (1<<1)
+#define DM_TWPS_ALL 0x1f
+#define DM_TMAR 0x04c
+#define DM_TCAR 0x050
+#define DM_TSICR 0x054
+#define DM_TSICR_POSTED (1<<2)
+#define DM_TSICR_SFT (1<<1)
+#define DM_TCAR2 0x058
+
+#define TIMER_FREQUENCY 32768 /* 32kHz is used, selectable */
+#define MAX_TIMERS 2
+
+static struct evcount clk_count;
+static struct evcount stat_count;
+
+void dmtimer_attach(struct device *parent, struct device *self, void *args);
+int dmtimer_intr(void *frame);
+void dmtimer_wait(int reg);
+void dmtimer_cpu_initclocks(void);
+void dmtimer_delay(u_int);
+void dmtimer_setstatclockrate(int newhz);
+
+u_int dmtimer_get_timecount(struct timecounter *);
+
+static struct timecounter dmtimer_timecounter = {
+ dmtimer_get_timecount, NULL, 0xffffffff, 0, "dmtimer", 0, NULL
+};
+
+bus_space_handle_t dmtimer_ioh0;
+int dmtimer_irq = 0;
+
+struct dmtimer_softc {
+ struct device sc_dev;
+ bus_space_tag_t sc_iot;
+ bus_space_handle_t sc_ioh[MAX_TIMERS];
+ u_int32_t sc_irq;
+ u_int32_t sc_ticks_per_second;
+ u_int32_t sc_ticks_per_intr;
+ u_int32_t sc_ticks_err_cnt;
+ u_int32_t sc_ticks_err_sum;
+ u_int32_t sc_statvar;
+ u_int32_t sc_statmin;
+ u_int32_t sc_nexttickevent;
+ u_int32_t sc_nextstatevent;
+};
+
+struct cfattach dmtimer_ca = {
+ sizeof (struct dmtimer_softc), NULL, dmtimer_attach
+};
+
+struct cfdriver dmtimer_cd = {
+ NULL, "dmtimer", DV_DULL
+};
+
+void
+dmtimer_attach(struct device *parent, struct device *self, void *args)
+{
+ struct dmtimer_softc *sc = (struct dmtimer_softc *)self;
+ struct omap_attach_args *oa = args;
+ bus_space_handle_t ioh;
+ u_int32_t rev, cfg;
+
+ sc->sc_iot = oa->oa_iot;
+
+ if (bus_space_map(sc->sc_iot, oa->oa_dev->mem[0].addr,
+ oa->oa_dev->mem[0].size, 0, &ioh))
+ panic("%s: bus_space_map failed!\n", __func__);
+
+
+ prcm_setclock(1, PRCM_CLK_SPEED_32);
+ prcm_setclock(2, PRCM_CLK_SPEED_32);
+ prcm_enablemodule(PRCM_TIMER2);
+ prcm_enablemodule(PRCM_TIMER3);
+
+ /* reset */
+ bus_space_write_4(sc->sc_iot, ioh, DM_TIOCP_CFG,
+ DM_TIOCP_CFG_SOFTRESET);
+ while (bus_space_read_4(sc->sc_iot, ioh, DM_TIOCP_CFG)
+ & DM_TIOCP_CFG_SOFTRESET)
+ ;
+
+ if (self->dv_unit == 0) {
+ dmtimer_ioh0 = ioh;
+ dmtimer_irq = oa->oa_dev->irq[0];
+ /* enable write posted mode */
+ bus_space_write_4(sc->sc_iot, ioh, DM_TSICR, DM_TSICR_POSTED);
+ /* stop timer */
+ bus_space_write_4(sc->sc_iot, ioh, DM_TCLR, 0);
+ } else if (self->dv_unit == 1) {
+ /* start timer because it is used in delay */
+ /* interrupts and posted mode are disabled */
+ sc->sc_irq = dmtimer_irq;
+ sc->sc_ioh[0] = dmtimer_ioh0;
+ sc->sc_ioh[1] = ioh;
+
+ bus_space_write_4(sc->sc_iot, ioh, DM_TCRR, 0);
+ bus_space_write_4(sc->sc_iot, ioh, DM_TLDR, 0);
+ bus_space_write_4(sc->sc_iot, ioh, DM_TCLR,
+ DM_TCLR_AR | DM_TCLR_ST);
+
+ dmtimer_timecounter.tc_frequency = TIMER_FREQUENCY;
+ dmtimer_timecounter.tc_priv = sc;
+ tc_init(&dmtimer_timecounter);
+ arm_clock_register(dmtimer_cpu_initclocks, dmtimer_delay,
+ dmtimer_setstatclockrate, NULL);
+ }
+ else
+ panic("attaching too many dmtimers at 0x%x",
+ oa->oa_dev->mem[0].addr);
+
+ /* set IDLEMODE to smart-idle */
+ cfg = bus_space_read_4(sc->sc_iot, ioh, DM_TIOCP_CFG);
+ bus_space_write_4(sc->sc_iot, ioh, DM_TIOCP_CFG,
+ (cfg & ~DM_TIOCP_CFG_IDLEMODE) | 0x02);
+
+ rev = bus_space_read_4(sc->sc_iot, ioh, DM_TIDR);
+ printf(" rev %d.%d\n", (rev & DM_TIDR_MAJOR) >> 8, rev & DM_TIDR_MINOR);
+}
+
+/*
+ * See comment in arm/xscale/i80321_clock.c
+ *
+ * Counter is count up, but with autoreload timers it is not possible
+ * to detect how many interrupts passed while interrupts were blocked.
+ * Also it is not possible to atomically add to the register.
+ *
+ * To work around this two timers are used, one is used as a reference
+ * clock without reload, however we just disable the interrupt it
+ * could generate.
+ *
+ * Internally this keeps track of when the next timer should fire
+ * and based on that time and the current value of the reference
+ * clock a number is written into the timer count register to schedule
+ * the next event.
+ */
+
+int
+dmtimer_intr(void *frame)
+{
+ struct dmtimer_softc *sc = dmtimer_cd.cd_devs[1];
+ u_int32_t now, r, nextevent;
+ int32_t duration;
+
+ now = bus_space_read_4(sc->sc_iot, sc->sc_ioh[1], DM_TCRR);
+
+ while ((int32_t) (sc->sc_nexttickevent - now) <= 0) {
+ sc->sc_nexttickevent += sc->sc_ticks_per_intr;
+ sc->sc_ticks_err_sum += sc->sc_ticks_err_cnt;
+
+ while (sc->sc_ticks_err_sum > hz) {
+ sc->sc_nexttickevent += 1;
+ sc->sc_ticks_err_sum -= hz;
+ }
+
+ clk_count.ec_count++;
+ hardclock(frame);
+ }
+
+ while ((int32_t) (sc->sc_nextstatevent - now) <= 0) {
+ do {
+ r = random() & (sc->sc_statvar - 1);
+ } while (r == 0); /* random == 0 not allowed */
+ sc->sc_nextstatevent += sc->sc_statmin + r;
+ stat_count.ec_count++;
+ statclock(frame);
+ }
+ if ((now - sc->sc_nexttickevent) < (now - sc->sc_nextstatevent))
+ nextevent = sc->sc_nexttickevent;
+ else
+ nextevent = sc->sc_nextstatevent;
+
+ duration = nextevent -
+ bus_space_read_4(sc->sc_iot, sc->sc_ioh[1], DM_TCRR);
+
+ if (duration <= 0) {
+ printf("%s: negative duration!\n", __func__);
+ duration = 1; /* trigger immediately. */
+ }
+
+ if (duration > sc->sc_ticks_per_intr + 1) {
+ printf("%s: time lost!\n", __func__);
+ /*
+ * If interrupts are blocked too long, like during
+ * the root prompt or ddb, the timer can roll over,
+ * this will allow the system to continue to run
+ * even if time is lost.
+ */
+ duration = sc->sc_ticks_per_intr;
+ sc->sc_nexttickevent = now;
+ sc->sc_nextstatevent = now;
+ }
+
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh[0], DM_TISR,
+ bus_space_read_4(sc->sc_iot, sc->sc_ioh[0], DM_TISR));
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh[0], DM_TCRR, -duration);
+ dmtimer_wait(DM_TWPS_ALL);
+
+ return 1;
+}
+
+/*
+ * would be interesting to play with trigger mode while having one timer
+ * in 32KHz mode, and the other timer running in sysclk mode and use
+ * the high resolution speeds (matters more for delay than tick timer
+ */
+
+void
+dmtimer_cpu_initclocks()
+{
+ struct dmtimer_softc *sc = dmtimer_cd.cd_devs[1];
+
+ stathz = 128;
+ profhz = 1024;
+
+ sc->sc_ticks_per_second = TIMER_FREQUENCY; /* 32768 */
+
+ setstatclockrate(stathz);
+
+ sc->sc_ticks_per_intr = sc->sc_ticks_per_second / hz;
+ sc->sc_ticks_err_cnt = sc->sc_ticks_per_second % hz;
+ sc->sc_ticks_err_sum = 0;
+
+ /* establish interrupts */
+ arm_intr_establish(sc->sc_irq, IPL_CLOCK, dmtimer_intr,
+ NULL, "tick");
+
+ /* setup timer 0 */
+
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh[0], DM_TLDR, 0);
+
+ sc->sc_nexttickevent = sc->sc_nextstatevent = bus_space_read_4(sc->sc_iot,
+ sc->sc_ioh[1], DM_TCRR) + sc->sc_ticks_per_intr;
+
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh[0], DM_TIER, DM_TIER_OVF_EN);
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh[0], DM_TWER, DM_TWER_OVF_EN);
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh[0], DM_TISR, /*clear interrupt flags */
+ bus_space_read_4(sc->sc_iot, sc->sc_ioh[0], DM_TISR));
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh[0], DM_TCRR, -sc->sc_ticks_per_intr);
+ dmtimer_wait(DM_TWPS_ALL);
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh[0], DM_TCLR, /* autoreload and start */
+ DM_TCLR_AR | DM_TCLR_ST);
+ dmtimer_wait(DM_TWPS_ALL);
+}
+
+void
+dmtimer_wait(int reg)
+{
+ struct dmtimer_softc *sc = dmtimer_cd.cd_devs[1];
+ while (bus_space_read_4(sc->sc_iot, sc->sc_ioh[0], DM_TWPS) & reg)
+ ;
+}
+
+void
+dmtimer_delay(u_int usecs)
+{
+ struct dmtimer_softc *sc = dmtimer_cd.cd_devs[1];
+ u_int32_t clock, oclock, delta, delaycnt;
+ volatile int j;
+ int csec, usec;
+
+ if (usecs > (0x80000000 / (TIMER_FREQUENCY))) {
+ csec = usecs / 10000;
+ usec = usecs % 10000;
+
+ delaycnt = (TIMER_FREQUENCY / 100) * csec +
+ (TIMER_FREQUENCY / 100) * usec / 10000;
+ } else {
+ delaycnt = TIMER_FREQUENCY * usecs / 1000000;
+ }
+ if (delaycnt <= 1)
+ for (j = 100; j > 0; j--)
+ ;
+
+ if (sc->sc_ioh[1] == 0) {
+ /* BAH */
+ for (; usecs > 0; usecs--)
+ for (j = 100; j > 0; j--)
+ ;
+ return;
+ }
+ oclock = bus_space_read_4(sc->sc_iot, sc->sc_ioh[1], DM_TCRR);
+ while (1) {
+ for (j = 100; j > 0; j--)
+ ;
+ clock = bus_space_read_4(sc->sc_iot, sc->sc_ioh[1], DM_TCRR);
+ delta = clock - oclock;
+ if (delta > delaycnt)
+ break;
+ }
+
+}
+
+void
+dmtimer_setstatclockrate(int newhz)
+{
+ struct dmtimer_softc *sc = dmtimer_cd.cd_devs[1];
+ int minint, statint;
+ int s;
+
+ s = splclock();
+
+ statint = sc->sc_ticks_per_second / newhz;
+ /* calculate largest 2^n which is smaller than just over half statint */
+ sc->sc_statvar = 0x40000000; /* really big power of two */
+ minint = statint / 2 + 100;
+ while (sc->sc_statvar > minint)
+ sc->sc_statvar >>= 1;
+
+ sc->sc_statmin = statint - (sc->sc_statvar >> 1);
+
+ splx(s);
+
+ /*
+ * XXX this allows the next stat timer to occur then it switches
+ * to the new frequency. Rather than switching instantly.
+ */
+}
+
+
+u_int
+dmtimer_get_timecount(struct timecounter *tc)
+{
+ struct dmtimer_softc *sc = dmtimer_timecounter.tc_priv;
+
+ return bus_space_read_4(sc->sc_iot, sc->sc_ioh[1], DM_TCRR);
+}