diff options
author | cinap_lenrek <cinap_lenrek@gmx.de> | 2013-01-26 17:33:21 +0100 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@gmx.de> | 2013-01-26 17:33:21 +0100 |
commit | ea108c8ca6e726ac008f75775ab83775ec233171 (patch) | |
tree | 982816b58d50e1b12b7eeb2c29fe22ca8d9c195b /sys/src/9/teg2/clock.c | |
parent | 43e09c468b4c6562c93c9375a316012e238d21b2 (diff) |
add tegra2 soc kernel (from sources)
Diffstat (limited to 'sys/src/9/teg2/clock.c')
-rw-r--r-- | sys/src/9/teg2/clock.c | 624 |
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 = <->loc; + tn->isr = Xisrclk; + coherence(); + + timerintr(ureg, 0); + +#ifdef watchdog_not_bloody_useless + /* appease the dogs */ + wd = <->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(<->loc); + watchdogoff(<->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 = <->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 = <->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; ) + ; +} |