Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * bcm2835 timers
 *      System timers run at 1MHz (timers 1 and 2 are used by GPU)
 *      ARM timer usually runs at 250MHz (may be slower in low power modes)
 *      Cycle counter runs at 700MHz (unless overclocked)
 *    All are free-running up-counters
 *
 * Use system timer 3 (64 bits) for hzclock interrupts and fastticks
 * Use ARM timer (32 bits) for perfticks
 * Use ARM timer to force immediate interrupt
 * Use cycle counter for cycles()
 */

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"

enum {
        SYSTIMERS       = VIRTIO+0x3000,
        ARMTIMER        = VIRTIO+0xB400,

        SystimerFreq    = 1*Mhz,
        MaxPeriod       = SystimerFreq / HZ,
        MinPeriod       = SystimerFreq / (100*HZ),
};

typedef struct Systimers Systimers;
typedef struct Armtimer Armtimer;

struct Systimers {
        u32int  cs;
        u32int  clo;
        u32int  chi;
        u32int  c0;
        u32int  c1;
        u32int  c2;
        u32int  c3;
};

struct Armtimer {
        u32int  load;
        u32int  val;
        u32int  ctl;
        u32int  irqack;
        u32int  irq;
        u32int  maskedirq;
        u32int  reload;
        u32int  predivider;
        u32int  count;
};

enum {
        CntPrescaleShift= 16,   /* freq is sys_clk/(prescale+1) */
        CntPrescaleMask = 0xFF,
        CntEnable       = 1<<9,
        TmrDbgHalt      = 1<<8,
        TmrEnable       = 1<<7,
        TmrIntEnable    = 1<<5,
        TmrPrescale1    = 0x00<<2,
        TmrPrescale16   = 0x01<<2,
        TmrPrescale256  = 0x02<<2,
        CntWidth16      = 0<<1,
        CntWidth32      = 1<<1,
};

static void
clockintr(Ureg *ureg, void *)
{
        Systimers *tn;

        tn = (Systimers*)SYSTIMERS;
        /* dismiss interrupt */
        tn->cs = 1<<3;
        timerintr(ureg, 0);
}

void
clockshutdown(void)
{
        Armtimer *tm;

        tm = (Armtimer*)ARMTIMER;
        tm->ctl = 0;
        wdogoff();
}

void
clockinit(void)
{
        Systimers *tn;
        Armtimer *tm;
        u32int t0, t1, tstart, tend;

        tn = (Systimers*)SYSTIMERS;
        tm = (Armtimer*)ARMTIMER;
        tm->load = 0;
        tm->ctl = TmrPrescale1|CntEnable|CntWidth32;

        tstart = tn->clo;
        do{
                t0 = lcycles();
        }while(tn->clo == tstart);
        tend = tstart + 10000;
        do{
                t1 = lcycles();
        }while(tn->clo != tend);
        t1 -= t0;
        m->cpuhz = 100 * t1;
        m->cpumhz = (m->cpuhz + Mhz/2 - 1) / Mhz;
        m->cyclefreq = m->cpuhz;

        tn->c3 = tn->clo - 1;
        intrenable(IRQtimer3, clockintr, nil, 0, "clock");
}

void
timerset(uvlong next)
{
        Systimers *tn;
        vlong now, period;

        tn = (Systimers*)SYSTIMERS;
        now = fastticks(nil);
        period = next - fastticks(nil);
        if(period < MinPeriod)
                next = now + MinPeriod;
        else if(period > MaxPeriod)
                next = now + MaxPeriod;
        tn->c3 = (ulong)next;
}

uvlong
fastticks(uvlong *hz)
{
        Systimers *tn;
        ulong lo, hi;

        tn = (Systimers*)SYSTIMERS;
        if(hz)
                *hz = SystimerFreq;
        do{
                hi = tn->chi;
                lo = tn->clo;
        }while(tn->chi != hi);
        m->fastclock = (uvlong)hi<<32 | lo;
        return m->fastclock;
}

ulong
perfticks(void)
{
        Armtimer *tm;

        tm = (Armtimer*)ARMTIMER;
        return tm->count;
}

void
armtimerset(int n)
{
        Armtimer *tm;

        tm = (Armtimer*)ARMTIMER;
        if(n > 0){
                tm->ctl |= TmrEnable|TmrIntEnable;
                tm->load = n;
        }else{
                tm->load = 0;
                tm->ctl &= ~(TmrEnable|TmrIntEnable);
                tm->irq = 1;
        }
}

ulong
µs(void)
{
        if(SystimerFreq != 1*Mhz)
                return fastticks2us(fastticks(nil));
        return fastticks(nil);
}

void
microdelay(int n)
{
        Systimers *tn;
        u32int now, diff;

        tn = (Systimers*)SYSTIMERS;
        diff = n + 1;
        now = tn->clo;
        while(tn->clo - now < diff)
                ;
}

void
delay(int n)
{
        while(--n >= 0)
                microdelay(1000);
}