summaryrefslogtreecommitdiff
path: root/sys/src/9/teg2/clock.c
diff options
context:
space:
mode:
authorcinap_lenrek <cinap_lenrek@gmx.de>2013-01-26 17:33:21 +0100
committercinap_lenrek <cinap_lenrek@gmx.de>2013-01-26 17:33:21 +0100
commitea108c8ca6e726ac008f75775ab83775ec233171 (patch)
tree982816b58d50e1b12b7eeb2c29fe22ca8d9c195b /sys/src/9/teg2/clock.c
parent43e09c468b4c6562c93c9375a316012e238d21b2 (diff)
add tegra2 soc kernel (from sources)
Diffstat (limited to 'sys/src/9/teg2/clock.c')
-rw-r--r--sys/src/9/teg2/clock.c624
1 files changed, 624 insertions, 0 deletions
diff --git a/sys/src/9/teg2/clock.c b/sys/src/9/teg2/clock.c
new file mode 100644
index 000000000..d54722957
--- /dev/null
+++ b/sys/src/9/teg2/clock.c
@@ -0,0 +1,624 @@
+/*
+ * cortex-a clocks; excludes tegra 2 SoC clocks
+ *
+ * cortex-a processors include private `global' and local timers
+ * at soc.scu + 0x200 (global) and + 0x600 (local).
+ * the global timer is a single count-up timer shared by all cores
+ * but with per-cpu comparator and auto-increment registers.
+ * a local count-down timer can be used as a watchdog.
+ *
+ * v7 arch provides a 32-bit count-up cycle counter (at about 1GHz in our case)
+ * but it's unsuitable as our source of fastticks, because it stops advancing
+ * when the cpu is suspended by WFI.
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "arm.h"
+
+enum {
+ Debug = 0,
+
+ Basetickfreq = Mhz, /* soc.µs rate in Hz */
+ /* the local timers seem to run at half the expected rate */
+ Clockfreqbase = 250*Mhz / 2, /* private timer rate (PERIPHCLK/2) */
+ Tcycles = Clockfreqbase / HZ, /* cycles per clock tick */
+
+ MinPeriod = Tcycles / 100,
+ MaxPeriod = Tcycles,
+
+ Dogtimeout = Dogsectimeout * Clockfreqbase,
+};
+
+typedef struct Ltimer Ltimer;
+typedef struct Pglbtmr Pglbtmr;
+typedef struct Ploctmr Ploctmr;
+
+/*
+ * cortex-a private-intr local timer registers. all cpus see their
+ * own local timers at the same base address.
+ */
+struct Ltimer {
+ ulong load; /* new value + 1 */
+ ulong cnt; /* counts down */
+ ulong ctl;
+ ulong isr;
+
+ /* watchdog only */
+ ulong wdrst;
+ ulong wddis; /* wo */
+
+ ulong _pad0[2];
+};
+struct Ploctmr {
+ Ltimer loc;
+ Ltimer wd;
+};
+
+enum {
+ /* ctl bits */
+ Tmrena = 1<<0, /* timer enabled */
+ Wdogena = Tmrena, /* watchdog enabled */
+ Xreload = 1<<1, /* reload on intr; periodic interrupts */
+ Tintena = 1<<2, /* enable irq 29 at cnt==0 (30 for watchdog) */
+ Wdog = 1<<3, /* watchdog, not timer, mode */
+ Xsclrshift = 8,
+ Xsclrmask = MASK(8),
+
+ /* isr bits */
+ Xisrclk = 1<<0, /* write to clear */
+
+ /* wdrst bits */
+ Wdrst = 1<<0,
+
+ /* wddis values */
+ Wdon = 1,
+ Wdoff1 = 0x12345678, /* send these two to switch to timer mode */
+ Wdoff2 = 0x87654321,
+};
+
+/* cortex-a private-intr globl timer registers */
+struct Pglbtmr {
+ ulong cnt[2]; /* counts up; little-endian uvlong */
+ ulong ctl;
+ ulong isr;
+ ulong cmp[2]; /* little-endian uvlong */
+ ulong inc;
+};
+
+enum {
+ /* unique ctl bits (otherwise see X* above) */
+ Gcmp = 1<<1,
+// Gtintena= 1<<2, /* enable irq 27 */
+ Gincr = 1<<3,
+};
+
+/*
+ * until 5[cl] inline vlong ops, avoid them where possible,
+ * they are currently slow function calls.
+ */
+typedef union Vlong Vlong;
+union Vlong {
+ uvlong uvl;
+ struct { /* little-endian */
+ ulong low;
+ ulong high;
+ };
+};
+
+static int fired;
+static int ticking[MAXMACH];
+
+/* no lock is needed to update our local timer. splhi keeps it tight. */
+static void
+setltimer(Ltimer *tn, ulong ticks)
+{
+ int s;
+
+ assert(ticks <= Clockfreqbase);
+ s = splhi();
+ tn->load = ticks - 1;
+ coherence();
+ tn->ctl = Tmrena | Tintena | Xreload;
+ coherence();
+ splx(s);
+}
+
+static void
+ckstuck(int cpu, long myticks, long histicks)
+{
+ if (labs(histicks - myticks) > HZ) {
+// iprint("cpu%d: clock ticks %ld (vs myticks %ld cpu0 %ld); "
+// "apparently stopped\n",
+// cpu, histicks, myticks, MACHP(0)->ticks);
+ if (!ticking[cpu])
+ panic("cpu%d: clock not interrupting", cpu);
+ }
+}
+
+static void
+mpclocksanity(void)
+{
+ int cpu, mycpu;
+ long myticks, histicks;
+
+ if (conf.nmach <= 1 || active.exiting || navailcpus == 0)
+ return;
+
+ mycpu = m->machno;
+ myticks = m->ticks;
+ if (myticks == HZ)
+ ticking[mycpu] = 1;
+
+ if (myticks < 5*HZ)
+ return;
+
+ for (cpu = 0; cpu < navailcpus; cpu++) {
+ if (cpu == mycpu)
+ continue;
+ histicks = MACHP(cpu)->ticks;
+ if (myticks == 5*HZ || histicks > 1)
+ ckstuck(cpu, myticks, histicks);
+ }
+}
+
+static void
+clockintr(Ureg* ureg, void *arg)
+{
+ Ltimer *wd, *tn;
+ Ploctmr *lt;
+
+ lt = (Ploctmr *)arg;
+ tn = &lt->loc;
+ tn->isr = Xisrclk;
+ coherence();
+
+ timerintr(ureg, 0);
+
+#ifdef watchdog_not_bloody_useless
+ /* appease the dogs */
+ wd = &lt->wd;
+ if (wd->cnt == 0 &&
+ (wd->ctl & (Wdog | Wdogena | Tintena)) == (Wdog | Wdogena))
+ panic("cpu%d: zero watchdog count but no system reset",
+ m->machno);
+ wd->load = Dogtimeout - 1;
+ coherence();
+#endif
+ SET(wd); USED(wd);
+ tegclockintr();
+
+ mpclocksanity();
+}
+
+void
+clockprod(Ureg *ureg)
+{
+ Ltimer *tn;
+
+ timerintr(ureg, 0);
+ tegclockintr();
+ if (m->machno != 0) { /* cpu1 gets stuck */
+ tn = &((Ploctmr *)soc.loctmr)->loc;
+ setltimer(tn, Tcycles);
+ }
+}
+
+static void
+clockreset(Ltimer *tn)
+{
+ if (probeaddr((uintptr)tn) < 0)
+ panic("no clock at %#p", tn);
+ tn->ctl = 0;
+ coherence();
+}
+
+void
+watchdogoff(Ltimer *wd)
+{
+ wd->ctl &= ~Wdogena;
+ coherence();
+ wd->wddis = Wdoff1;
+ coherence();
+ wd->wddis = Wdoff2;
+ coherence();
+}
+
+/* clear any pending watchdog intrs or causes */
+void
+wdogclrintr(Ltimer *wd)
+{
+#ifdef watchdog_not_bloody_useless
+ wd->isr = Xisrclk;
+ coherence();
+ wd->wdrst = Wdrst;
+ coherence();
+#endif
+ USED(wd);
+}
+
+/*
+ * stop clock interrupts on this cpu and disable the local watchdog timer,
+ * and, if on cpu0, shutdown the shared tegra2 watchdog timer.
+ */
+void
+clockshutdown(void)
+{
+ Ploctmr *lt;
+
+ lt = (Ploctmr *)soc.loctmr;
+ clockreset(&lt->loc);
+ watchdogoff(&lt->wd);
+
+ tegclockshutdown();
+}
+
+enum {
+ Instrs = 10*Mhz,
+};
+
+/* we assume that perfticks are microseconds */
+static long
+issue1loop(void)
+{
+ register int i;
+ long st;
+
+ i = Instrs;
+ st = perfticks();
+ do {
+ --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
+ --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
+ --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
+ --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
+ --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
+ --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
+ --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
+ --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
+ --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
+ --i; --i; --i; --i; --i; --i; --i; --i; --i;
+ } while(--i >= 0);
+ return perfticks() - st;
+}
+
+static long
+issue2loop(void)
+{
+ register int i, j;
+ long st;
+
+ i = Instrs / 2; /* j gets half the decrements */
+ j = 0;
+ st = perfticks();
+ do {
+ --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
+ } while(--i >= 0);
+ return perfticks() - st;
+}
+
+/* estimate instructions/s. */
+static void
+guessmips(long (*loop)(void), char *lab)
+{
+ int s;
+ long tcks;
+
+ do {
+ s = splhi();
+ tcks = loop();
+ splx(s);
+ if (tcks < 0)
+ iprint("again...");
+ } while (tcks < 0);
+ /*
+ * Instrs instructions took tcks ticks @ Basetickfreq Hz.
+ * round the result.
+ */
+ s = (((vlong)Basetickfreq * Instrs) / tcks + 500000) / 1000000;
+ if (Debug)
+ iprint("%ud mips (%s-issue)", s, lab);
+ USED(s);
+}
+
+void
+wdogintr(Ureg *, void *ltmr)
+{
+#ifdef watchdog_not_bloody_useless
+ Ltimer *wd;
+
+ wd = ltmr;
+ fired++;
+ wdogclrintr(wd);
+#endif
+ USED(ltmr);
+}
+
+static void
+ckcounting(Ltimer *lt)
+{
+ ulong old;
+
+ old = lt->cnt;
+ if (old == lt->cnt)
+ delay(1);
+ if (old == lt->cnt)
+ panic("cpu%d: watchdog timer not counting down", m->machno);
+}
+
+/* test fire with interrupt to see that it's working */
+static void
+ckwatchdog(Ltimer *wd)
+{
+#ifdef watchdog_not_bloody_useless
+ int s;
+
+ fired = 0;
+ wd->load = Tcycles - 1;
+ coherence();
+ /* Tintena is supposed to be ignored in watchdog mode */
+ wd->ctl |= Wdogena | Tintena;
+ coherence();
+
+ ckcounting(wd);
+
+ s = spllo();
+ delay(2 * 1000/HZ);
+ splx(s);
+ if (!fired)
+ /* useless local watchdog */
+ iprint("cpu%d: local watchdog failed to interrupt\n", m->machno);
+ /* clean up */
+ wd->ctl &= ~Wdogena;
+ coherence();
+#endif
+ USED(wd);
+}
+
+static void
+startwatchdog(void)
+{
+#ifdef watchdog_not_bloody_useless
+ Ltimer *wd;
+ Ploctmr *lt;
+
+ lt = (Ploctmr *)soc.loctmr;
+ wd = &lt->wd;
+ watchdogoff(wd);
+ wdogclrintr(wd);
+ irqenable(Wdtmrirq, wdogintr, wd, "watchdog");
+
+ ckwatchdog(wd);
+
+ /* set up for normal use, causing reset */
+ wd->ctl &= ~Tintena; /* reset, don't interrupt */
+ coherence();
+ wd->ctl |= Wdog;
+ coherence();
+ wd->load = Dogtimeout - 1;
+ coherence();
+ wd->ctl |= Wdogena;
+ coherence();
+
+ ckcounting(wd);
+#endif
+}
+
+static void
+clock0init(Ltimer *tn)
+{
+ int s;
+ ulong old, fticks;
+
+ /*
+ * calibrate fastclock
+ */
+ s = splhi();
+ tn->load = ~0ul >> 1;
+ coherence();
+ tn->ctl = Tmrena;
+ coherence();
+
+ old = perfticks();
+ fticks = tn->cnt;
+ delay(1);
+ fticks = abs(tn->cnt - fticks);
+ old = perfticks() - old;
+ splx(s);
+ if (Debug)
+ iprint("cpu%d: fastclock %ld/%ldµs = %ld fastticks/µs (MHz)\n",
+ m->machno, fticks, old, (fticks + old/2 - 1) / old);
+ USED(fticks, old);
+
+ if (Debug)
+ iprint("cpu%d: ", m->machno);
+ guessmips(issue1loop, "single");
+ if (Debug)
+ iprint(", ");
+ guessmips(issue2loop, "dual");
+ if (Debug)
+ iprint("\n");
+
+ /*
+ * m->delayloop should be the number of delay loop iterations
+ * needed to consume 1 ms. 2 is instr'ns in the delay loop.
+ */
+ m->delayloop = m->cpuhz / (1000 * 2);
+// iprint("cpu%d: m->delayloop = %lud\n", m->machno, m->delayloop);
+
+ tegclock0init();
+}
+
+/*
+ * the local timer is the interrupting timer and does not
+ * participate in measuring time. It is initially set to HZ.
+ */
+void
+clockinit(void)
+{
+ ulong old;
+ Ltimer *tn;
+ Ploctmr *lt;
+
+ clockshutdown();
+
+ /* turn my cycle counter on */
+ cpwrsc(0, CpCLD, CpCLDena, CpCLDenacyc, 1<<31);
+
+ /* turn all my counters on and clear my cycle counter */
+ cpwrsc(0, CpCLD, CpCLDena, CpCLDenapmnc, 1<<2 | 1);
+
+ /* let users read my cycle counter directly */
+ cpwrsc(0, CpCLD, CpCLDuser, CpCLDenapmnc, 1);
+
+ /* verify µs counter sanity */
+ tegclockinit();
+
+ lt = (Ploctmr *)soc.loctmr;
+ tn = &lt->loc;
+ if (m->machno == 0)
+ irqenable(Loctmrirq, clockintr, lt, "clock");
+ else
+ intcunmask(Loctmrirq);
+
+ /*
+ * verify sanity of local timer
+ */
+ tn->load = Clockfreqbase / 1000;
+ tn->isr = Xisrclk;
+ coherence();
+ tn->ctl = Tmrena;
+ coherence();
+
+ old = tn->cnt;
+ delay(5);
+ /* m->ticks won't be incremented here because timersinit hasn't run. */
+ if (tn->cnt == old)
+ panic("cpu%d: clock not ticking at all", m->machno);
+ else if ((long)tn->cnt > 0)
+ panic("cpu%d: clock ticking slowly", m->machno);
+
+ if (m->machno == 0)
+ clock0init(tn);
+
+ /* if pci gets stuck, maybe one of the many watchdogs will nuke us. */
+ startwatchdog();
+
+ /*
+ * desynchronize the processor clocks so that they all don't
+ * try to resched at the same time.
+ */
+ delay(m->machno*2);
+ setltimer(tn, Tcycles);
+}
+
+/* our fastticks are at 1MHz (Basetickfreq), so the conversion is trivial. */
+ulong
+µs(void)
+{
+ return fastticks2us(fastticks(nil));
+}
+
+/* Tval is supposed to be in fastticks units. */
+void
+timerset(Tval next)
+{
+ int s;
+ long offset;
+ Ltimer *tn;
+
+ tn = &((Ploctmr *)soc.loctmr)->loc;
+ s = splhi();
+ offset = fastticks2us(next - fastticks(nil));
+ /* offset is now in µs (MHz); convert to Clockfreqbase Hz. */
+ offset *= Clockfreqbase / Mhz;
+ if(offset < MinPeriod)
+ offset = MinPeriod;
+ else if(offset > MaxPeriod)
+ offset = MaxPeriod;
+
+ setltimer(tn, offset);
+ splx(s);
+}
+
+static ulong
+cpucycles(void) /* cpu clock rate, except when waiting for intr (unused) */
+{
+ ulong v;
+
+ /* reads 32-bit cycle counter (counting up) */
+// v = cprdsc(0, CpCLD, CpCLDcyc, 0);
+ v = getcyc(); /* fast asm */
+ /* keep it non-negative; prevent m->fastclock ever going to 0 */
+ return v == 0? 1: v;
+}
+
+long
+lcycles(void)
+{
+ return perfticks();
+}
+
+uvlong
+fastticks(uvlong *hz)
+{
+ int s;
+ ulong newticks;
+ Vlong *fcp;
+
+ if(hz)
+ *hz = Basetickfreq;
+
+ fcp = (Vlong *)&m->fastclock;
+ /* avoid reentry on interrupt or trap, to prevent recursion */
+ s = splhi();
+ newticks = perfticks();
+ if(newticks < fcp->low) /* low word must have wrapped */
+ fcp->high++;
+ fcp->low = newticks;
+ splx(s);
+
+ if (fcp->low == 0 && fcp->high == 0 && m->ticks > HZ/10)
+ panic("fastticks: zero m->fastclock; ticks %lud fastclock %#llux",
+ m->ticks, m->fastclock);
+ return m->fastclock;
+}
+
+void
+microdelay(int l)
+{
+ for (l = l * (vlong)m->delayloop / 1000; --l >= 0; )
+ ;
+}
+
+void
+delay(int l)
+{
+ int i, d;
+
+ d = m->delayloop;
+ while(--l >= 0)
+ for (i = d; --i >= 0; )
+ ;
+}