plan9fox/sys/src/9/omap/clock.c
2011-03-30 19:35:09 +03:00

490 lines
9 KiB
C

/*
* omap3530 clocks
*
* timers count up to zero.
*
* the source clock signals for the timers are sometimes selectable. for
* WDTIMER[23] and GPTIMER12, it's always the 32kHz clock. for the
* others, it can be the 32kHz clock or the system clock. we use only
* WDTIMER2 and GPTIMER[12], and configure GPTIMER[12] in archomap.c to
* use the 32kHZ clock. WDTIMER1 is not accessible to us on GP
* (general-purpose) omaps.
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "arm.h"
enum {
Debug = 0,
Tn0 = PHYSTIMER1,
Tn1 = PHYSTIMER2,
/* irq 36 is watchdog timer module 3 overflow */
Tn0irq = 37, /* base IRQ for all timers */
Freebase = 1, /* base of free-running timer */
/*
* clock is 32K (32,768) Hz, so one tick is 30.517µs,
* so 327.68 ticks is 10ms, 32.768 ticks is 1ms.
*/
Clockfreqbase = 32 * 1024, /* base rate in Hz */
Tcycles = Clockfreqbase / HZ, /* cycles per clock tick */
MinPeriod = (Tcycles / 100 < 2? 2: Tcycles / 100),
MaxPeriod = Tcycles,
Dogtimeout = 20 * Clockfreqbase, /* was 4 s.; must be ≤ 21 s. */
};
enum {
/* ticpcfg bits */
Noidle = 1<<3,
Softreset = 1<<1,
/* tistat bits */
Resetdone = 1<<0,
/* tisr/tier bits */
Ovf_it = 1<<1, /* gp: overflow intr */
Mat_it = 1<<0, /* gp: match intr */
Wdovf_it = 1<<0, /* wdog: overflow intr */
/* tclr bits */
Ar = 1<<1, /* gp only: autoreload mode overflow */
St = 1<<0, /* gp only: start the timer */
};
/* omap35x timer registers */
typedef struct Timerregs Timerregs;
struct Timerregs {
/* common to all timers, gp and watchdog */
uchar pad0[0x10];
ulong ticpcfg;
ulong tistat; /* ro: low bit: reset done */
ulong tisr;
ulong tier;
ulong twer;
ulong tclr;
ulong tcrr; /* counter: cycles to zero */
ulong tldr;
ulong ttgr; /* trigger */
ulong twps; /* ro: write posted pending */
/* gp timers only, unused by us */
ulong tmar; /* value to compare with counter */
ulong tcar1; /* ro */
ulong tsicr;
ulong tcar2; /* ro */
union {
ulong tpir; /* gp: 1 ms tick generation: +ve */
ulong wspr; /* wdog: start/stop control */
};
ulong tnir; /* 1 ms tick generation: -ve */
ulong tcvr; /* 1 ms tick generation: next counter value */
ulong tocr; /* intr mask for n ticks */
ulong towr;
};
static int ticks; /* for sanity checking; m->ticks doesn't always get called */
static Lock clklck;
static ulong rdcycles(void), rdbaseticks(void);
/* write a watchdog timer's start/stop register */
static void
wdogwrss(Timerregs *tn, ulong val)
{
while (tn->twps & (1 << 4)) /* pending write to start/stop reg? */
;
tn->wspr = val;
coherence();
while (tn->twps & (1 << 4)) /* pending write to start/stop reg? */
;
}
static void
resetwait(Timerregs *tn)
{
long bound;
for (bound = 400*Mhz; !(tn->tistat & Resetdone) && bound > 0; bound--)
;
if (bound <= 0)
iprint("clock reset didn't complete\n");
}
static void
wdogoff(Timerregs *tn)
{
resetwait(tn);
wdogwrss(tn, 0xaaaa); /* magic off sequence */
wdogwrss(tn, 0x5555);
tn->tldr = 1;
coherence();
tn->tcrr = 1; /* paranoia */
coherence();
}
static void wdogassure(void);
static void
wdogon(Timerregs *tn)
{
static int beenhere;
resetwait(tn);
tn->tldr = -Dogtimeout;
tn->tcrr = -Dogtimeout;
coherence();
wdogwrss(tn, 0xbbbb); /* magic on sequence */
wdogwrss(tn, 0x4444); /* magic on sequence */
if (!beenhere) {
beenhere = 1;
/* touching the dog is not quick, so do it infrequently */
addclock0link(wdogassure, HZ);
}
}
static void
wdogassure(void) /* reset the watch dog's counter */
{
Timerregs *tn;
tn = (Timerregs *)PHYSWDOG;
wdogoff(tn);
tn->tcrr = -Dogtimeout;
coherence();
wdogon(tn);
}
static void
clockintr(Ureg* ureg, void *arg)
{
Timerregs *tn;
static int nesting;
ticks++;
coherence();
if (nesting == 0) { /* if the clock interrupted itself, bail out */
++nesting;
timerintr(ureg, 0);
--nesting;
}
tn = arg;
tn->tisr = Ovf_it; /* dismiss the interrupt */
coherence();
}
static void
clockreset(Timerregs *tn)
{
if (probeaddr((uintptr)&tn->ticpcfg) < 0)
panic("no clock at %#p", tn);
tn->ticpcfg = Softreset | Noidle;
coherence();
resetwait(tn);
tn->tier = tn->tclr = 0;
coherence();
}
/* stop clock interrupts and disable the watchdog timer */
void
clockshutdown(void)
{
clockreset((Timerregs *)PHYSWDT2);
wdogoff((Timerregs *)PHYSWDT2);
clockreset((Timerregs *)PHYSWDT3);
wdogoff((Timerregs *)PHYSWDT3);
clockreset((Timerregs *)Tn0);
clockreset((Timerregs *)Tn1);
}
enum {
Instrs = 10*Mhz,
};
static long
issue1loop(void)
{
register int i;
long st;
st = rdbaseticks();
i = Instrs;
do {
--i; --i; --i; --i; --i;
--i; --i; --i; --i;
} while(--i >= 0);
return rdbaseticks() - st;
}
static long
issue2loop(void)
{
register int i, j;
long st;
st = rdbaseticks();
i = Instrs / 2;
j = 0;
do {
--i; --j; --i; --j;
--i; --j; --i; --j;
--j;
} while(--i >= 0);
return rdbaseticks() - st;
}
/* estimate instructions/s. using 32kHz clock */
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 @ Clockfreqbase Hz.
*/
s = ((vlong)Clockfreqbase * Instrs) / tcks / 1000000;
if (Debug)
iprint("%ud mips (%s-issue)", s, lab);
USED(s);
}
void
clockinit(void)
{
int i, s;
Timerregs *tn;
clockshutdown();
/* turn cycle counter on */
cpwrsc(0, CpCLD, CpCLDena, CpCLDenacyc, 1<<31);
/* turn all counters on and clear the cycle counter */
cpwrsc(0, CpCLD, CpCLDena, CpCLDenapmnc, 1<<2 | 1);
/* let users read the cycle counter directly */
cpwrsc(0, CpCLD, CpCLDena, CpCLDenapmnc, 1);
ilock(&clklck);
m->fastclock = 1;
m->ticks = ticks = 0;
/*
* T0 is a freerunning timer (cycle counter); it wraps,
* automatically reloads, and does not dispatch interrupts.
*/
tn = (Timerregs *)Tn0;
tn->tcrr = Freebase; /* count up to 0 */
tn->tldr = Freebase;
coherence();
tn->tclr = Ar | St;
iunlock(&clklck);
/*
* T1 is the interrupting timer and does not participate
* in measuring time. It is initially set to HZ.
*/
tn = (Timerregs *)Tn1;
irqenable(Tn0irq+1, clockintr, tn, "clock");
ilock(&clklck);
tn->tcrr = -Tcycles; /* approx.; count up to 0 */
tn->tldr = -Tcycles;
coherence();
tn->tclr = Ar | St;
coherence();
tn->tier = Ovf_it;
coherence();
iunlock(&clklck);
/*
* verify sanity of timer1
*/
s = spllo(); /* risky */
for (i = 0; i < 5 && ticks == 0; i++) {
delay(10);
cachedwbinvse(&ticks, sizeof ticks);
}
splx(s);
if (ticks == 0) {
if (tn->tcrr == 0)
panic("clock not interrupting");
else if (tn->tcrr == tn->tldr)
panic("clock not ticking at all");
#ifdef PARANOID
else
panic("clock running very slowly");
#endif
}
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 min. instructions in the delay loop.
*/
m->delayloop = m->cpuhz / (1000 * 2);
// iprint("m->delayloop = %lud\n", m->delayloop);
/*
* desynchronize the processor clocks so that they all don't
* try to resched at the same time.
*/
delay(m->machno*2);
}
void
watchdoginit(void)
{
wdogassure();
}
ulong
µs(void)
{
return fastticks2us(fastticks(nil));
}
void
timerset(Tval next)
{
long offset;
Timerregs *tn = (Timerregs *)Tn1;
static Lock setlck;
ilock(&setlck);
offset = next - fastticks(nil);
if(offset < MinPeriod)
offset = MinPeriod;
else if(offset > MaxPeriod)
offset = MaxPeriod;
tn->tcrr = -offset;
coherence();
iunlock(&setlck);
}
static ulong
rdcycles(void)
{
ulong v;
/* reads 32-bit cycle counter (counting up) */
v = cprdsc(0, CpCLD, CpCLDcyc, 0);
/* keep it positive; prevent m->fastclock ever going to 0 */
return v == 0? 1: v;
}
static ulong
rdbaseticks(void)
{
ulong v;
v = ((Timerregs *)Tn0)->tcrr; /* tcrr should be counting up */
/* keep it positive; prevent m->fastclock ever going to 0 */
return v == 0? 1: v;
}
ulong
perfticks(void)
{
return rdcycles();
}
long
lcycles(void)
{
return perfticks();
}
/*
* until 5[cal] inline vlong ops, avoid them where possible,
* they are currently slow function calls.
*/
typedef union Counter Counter;
union Counter {
uvlong uvl;
struct { /* little-endian */
ulong low;
ulong high;
};
};
enum {
Fastvlongops = 0,
};
uvlong
fastticks(uvlong *hz)
{
Counter now, sclnow;
if(hz)
*hz = m->cpuhz;
ilock(&clklck);
if (m->ticks > HZ/10 && m->fastclock == 0)
panic("fastticks: zero m->fastclock; ticks %lud fastclock %#llux",
m->ticks, m->fastclock);
now.uvl = m->fastclock;
now.low = rdcycles();
if(now.uvl < m->fastclock) /* low bits must have wrapped */
now.high++;
m->fastclock = now.uvl;
coherence();
sclnow.uvl = now.uvl;
iunlock(&clklck);
return sclnow.uvl;
}
void
microdelay(int l)
{
int i;
l = l * (vlong)m->delayloop / 1000;
if(l <= 0)
l = 1;
for(i = 0; i < l; i++)
;
}
void
delay(int l)
{
ulong i, j;
j = m->delayloop;
while(l-- > 0)
for(i=0; i < j; i++)
;
}