/*
* 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++)
                       ;
}