421 lines
9.4 KiB
C
421 lines
9.4 KiB
C
//---
|
|
// gint:tmu - Timer operation
|
|
//---
|
|
|
|
#include <gint/timer.h>
|
|
#include <gint/drivers.h>
|
|
#include <gint/drivers/states.h>
|
|
#include <gint/clock.h>
|
|
#include <gint/intc.h>
|
|
#include <gint/cpu.h>
|
|
#include <gint/mpu/tmu.h>
|
|
#include <stdarg.h>
|
|
|
|
/* Callbacks for all timers */
|
|
gint_call_t tmu_callbacks[9];
|
|
|
|
/* Arrays of standard and extra timers */
|
|
static tmu_t *TMU = SH7305_TMU.TMU;
|
|
static etmu_t *ETMU = SH7305_ETMU;
|
|
|
|
/* TSTR register for standard timers */
|
|
static volatile uint8_t *TSTR = &SH7305_TMU.TSTR;
|
|
|
|
/* Shortcut to set registers that are slow to update */
|
|
#define set(lval, rval) do(lval = rval); while(rval != lval)
|
|
|
|
//---
|
|
// Local functions
|
|
//---
|
|
|
|
/* conf(): Configure a fixed timer */
|
|
static void conf(int id, uint32_t delay, int clock, gint_call_t call)
|
|
{
|
|
if(id < 3)
|
|
{
|
|
/* Refuse to setup timers that are already in use */
|
|
tmu_t *T = &TMU[id];
|
|
if(T->TCR.UNIE || *TSTR & (1 << id)) return;
|
|
|
|
/* Configure the counter, clear interrupt flag*/
|
|
T->TCOR = delay;
|
|
T->TCNT = delay;
|
|
T->TCR.TPSC = clock;
|
|
set(T->TCR.UNF, 0);
|
|
|
|
/* Enable interrupt and count on rising edge (SH7705) */
|
|
T->TCR.UNIE = 1;
|
|
T->TCR.CKEG = 0;
|
|
}
|
|
else
|
|
{
|
|
etmu_t *T = &ETMU[id-3];
|
|
if(T->TCR.UNIE) return;
|
|
|
|
/* No clock input and clock edge here */
|
|
set(T->TCR.UNF, 0);
|
|
set(T->TCOR, delay);
|
|
set(T->TCNT, delay);
|
|
T->TCR.UNIE = 1;
|
|
}
|
|
|
|
tmu_callbacks[id] = call;
|
|
}
|
|
|
|
/* matches(): Check if a timer matches the provided specification and delay */
|
|
static int matches(int id, int spec, uint32_t delay)
|
|
{
|
|
/* A specific idea only matches the exact timer */
|
|
if(spec >= 0) return id == (spec & 0xf);
|
|
/* TIMER_ANY always matches ETMU only for delays at least 100 µs */
|
|
if(spec == TIMER_ANY) return (id < 3 || delay >= 100);
|
|
/* TIMER_TMU and TIMER_ETMU match as you'd expect */
|
|
if(spec == TIMER_TMU) return (id < 3);
|
|
if(spec == TIMER_ETMU) return (id >= 3);
|
|
/* Default is not matching */
|
|
return 0;
|
|
}
|
|
|
|
/* available(): Check if a timer is available (UNIE cleared, not running) */
|
|
static int available(int id)
|
|
{
|
|
if(id >= timer_count()) return 0;
|
|
|
|
if(id < 3)
|
|
{
|
|
tmu_t *T = &TMU[id];
|
|
return !T->TCR.UNIE && !(*TSTR & (1 << id));
|
|
}
|
|
else
|
|
{
|
|
etmu_t *T = &ETMU[id-3];
|
|
return !T->TCR.UNIE && !T->TSTR;
|
|
}
|
|
}
|
|
|
|
/* stop_callback(): Empty callback that stops the timer */
|
|
static int stop_callback(void)
|
|
{
|
|
return TIMER_STOP;
|
|
}
|
|
|
|
//---
|
|
// Timer API
|
|
//---
|
|
|
|
int timer_configure(int spec, uint64_t delay, gint_call_t call)
|
|
{
|
|
int clock = 0;
|
|
|
|
/* Default behavior for the callback */
|
|
if(!call.function) call = GINT_CALL(stop_callback);
|
|
|
|
/* Find a matching timer, starting from the slowest timers with the
|
|
smallest interrupt priorities all the way up to TMU0 */
|
|
for(int id = timer_count() - 1; id >= 0; id--)
|
|
{
|
|
if(!matches(id, spec, delay) || !available(id)) continue;
|
|
|
|
/* If ID is a TMU, choose a timer prescaler. Assuming the worst
|
|
running Pphi of ~48 MHz, select the finest resolution that
|
|
allows the requested delay to be represented. */
|
|
if(id < 3 && spec >= 0)
|
|
{
|
|
/* Explicit timers with clock in the specification */
|
|
clock = (spec >> 4) & 0xf;
|
|
}
|
|
else if(id < 3)
|
|
{
|
|
uint64_t sec = 1000000;
|
|
|
|
/* Pphi/4 until 350 seconds */
|
|
if(delay <= 350 * sec) clock = TIMER_Pphi_4;
|
|
/* Pphi/16 until 1430 seconds */
|
|
else if(delay <= 1430 * sec) clock = TIMER_Pphi_16;
|
|
/* Pphi/64 until 5720 seconds */
|
|
else if(delay <= 5720 * sec) clock = TIMER_Pphi_64;
|
|
/* Pphi/256 otherwise */
|
|
else clock = TIMER_Pphi_256;
|
|
}
|
|
|
|
/* Find the delay constant for that timer and clock */
|
|
if(spec < 0) delay = timer_delay(id, delay, clock);
|
|
|
|
conf(id, delay, clock, call);
|
|
return id;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* timer_delay() - compute a delay constant from a duration in seconds */
|
|
uint32_t timer_delay(int id, uint64_t delay_us, int clock)
|
|
{
|
|
uint64_t freq;
|
|
|
|
if(id < 3)
|
|
{
|
|
const clock_frequency_t *cpg = clock_freq();
|
|
freq = cpg->Pphi_f;
|
|
if(clock == TIMER_Pphi_4) freq >>= 2;
|
|
if(clock == TIMER_Pphi_16) freq >>= 4;
|
|
if(clock == TIMER_Pphi_64) freq >>= 6;
|
|
if(clock == TIMER_Pphi_256) freq >>= 8;
|
|
}
|
|
else
|
|
{
|
|
/* ETMU all run on TCLK at 32768 Hz */
|
|
freq = 32768;
|
|
}
|
|
|
|
uint64_t product = freq * delay_us;
|
|
return product / 1000000;
|
|
}
|
|
|
|
/* timer_control() - start or stop a timer
|
|
@id Timer ID to configure
|
|
@state 0 to start the timer, 1 to stop it (nothing else!) */
|
|
static void timer_control(int id, int state)
|
|
{
|
|
if(id < 3) *TSTR = (*TSTR | (1 << id)) ^ (state << id);
|
|
else ETMU[id-3].TSTR = state ^ 1;
|
|
}
|
|
|
|
/* timer_start() - start a configured timer */
|
|
void timer_start(int id)
|
|
{
|
|
timer_control(id, 0);
|
|
}
|
|
|
|
/* timer_reload() - change a timer's delay constant for next interrupts */
|
|
void timer_reload(int id, uint32_t delay)
|
|
{
|
|
if(id < 3) TMU[id].TCOR = delay;
|
|
else ETMU[id-3].TCOR = delay;
|
|
}
|
|
|
|
/* timer_pause() - stop a running timer */
|
|
void timer_pause(int id)
|
|
{
|
|
timer_control(id, 1);
|
|
}
|
|
|
|
/* timer_stop() - stop and free a timer */
|
|
void timer_stop(int id)
|
|
{
|
|
/* Stop the timer and disable UNIE to indicate that it's free */
|
|
timer_pause(id);
|
|
|
|
if(id < 3)
|
|
{
|
|
TMU[id].TCR.UNIE = 0;
|
|
TMU[id].TCOR = 0xffffffff;
|
|
TMU[id].TCNT = 0xffffffff;
|
|
}
|
|
else
|
|
{
|
|
/* Extra timers generate interrupts when TCNT=0 even if TSTR=0.
|
|
We always keep TCOR/TCNT to non-zero values when idle. */
|
|
etmu_t *T = &ETMU[id-3];
|
|
T->TCR.UNIE = 0;
|
|
set(T->TCOR, 0xffffffff);
|
|
set(T->TCNT, 0xffffffff);
|
|
set(T->TCR.UNF, 0);
|
|
}
|
|
}
|
|
|
|
/* timer_wait(): Wait for a timer to stop */
|
|
void timer_wait(int id)
|
|
{
|
|
if(id < 3)
|
|
{
|
|
tmu_t *T = &TMU[id];
|
|
/* Sleep only if an interrupt will be there to wake us up */
|
|
while(*TSTR & (1 << id)) if(T->TCR.UNIE) sleep();
|
|
}
|
|
else
|
|
{
|
|
etmu_t *T = &ETMU[id-3];
|
|
while(T->TSTR) if(T->TCR.UNIE) sleep();
|
|
}
|
|
}
|
|
|
|
/* timer_spinwait(): Actively wait for a timer to raise UNF */
|
|
void timer_spinwait(int id)
|
|
{
|
|
if(id < 3)
|
|
{
|
|
tmu_t *T = &TMU[id];
|
|
while(!T->TCR.UNF) {}
|
|
}
|
|
else
|
|
{
|
|
etmu_t *T = &ETMU[id-3];
|
|
while(!T->TCR.UNF) {}
|
|
}
|
|
}
|
|
|
|
//---
|
|
// Deprecated API
|
|
//---
|
|
|
|
#undef timer_setup
|
|
int timer_setup(int spec, uint64_t delay, timer_callback_t function, ...)
|
|
{
|
|
va_list va;
|
|
va_start(va, function);
|
|
uint32_t arg = va_arg(va, uint32_t);
|
|
va_end(va);
|
|
|
|
return timer_configure(spec, delay, GINT_CALL(function.v, arg));
|
|
}
|
|
|
|
int timer_timeout(void volatile *arg)
|
|
{
|
|
int volatile *x = arg;
|
|
if(x) (*x)++;
|
|
return TIMER_STOP;
|
|
}
|
|
|
|
//---
|
|
// Driver initialization
|
|
//---
|
|
|
|
/* Interrupt handlers for standard timers (3 gates) */
|
|
extern void inth_tmu(void);
|
|
/* Interrupt handlers for extra timers */
|
|
extern void inth_etmu4(void);
|
|
extern void inth_etmux(void);
|
|
|
|
static void constructor(void)
|
|
{
|
|
if(isSH3())
|
|
{
|
|
TMU = SH7705_TMU.TMU;
|
|
ETMU = SH7705_ETMU;
|
|
TSTR = &SH7705_TMU.TSTR;
|
|
}
|
|
}
|
|
|
|
static void configure(void)
|
|
{
|
|
uint16_t etmu_event[6] = { 0x9e0, 0xc20, 0xc40, 0x900, 0xd00, 0xfa0 };
|
|
*TSTR = 0;
|
|
|
|
/* Install the TMU handlers and adjust the TCR0 value on SH3 */
|
|
void *h = intc_handler(0x400, inth_tmu, 96);
|
|
if(isSH3()) *(void volatile **)(h + 88) = &TMU[0].TCR;
|
|
|
|
/* Clear all timers */
|
|
for(int i = 0; i < 3; i++)
|
|
{
|
|
set(TMU[i].TCR.word, 0);
|
|
TMU[i].TCOR = 0xffffffff;
|
|
TMU[i].TCNT = 0xffffffff;
|
|
}
|
|
for(int i = 3; i < timer_count(); i++)
|
|
{
|
|
etmu_t *T = &ETMU[i-3];
|
|
T->TSTR = 0;
|
|
set(T->TCOR, 0xffffffff);
|
|
set(T->TCNT, 0xffffffff);
|
|
set(T->TCR.byte, 0);
|
|
}
|
|
|
|
/* Install the ETMU4 handler, which contains the logic for ETMUs */
|
|
void *h4 = intc_handler(etmu_event[4], inth_etmu4, 96);
|
|
|
|
/* Install the other ETMU handlers, and set their parameters */
|
|
for(int i = 3; i < timer_count(); i++) if(i != 7)
|
|
{
|
|
void *h = intc_handler(etmu_event[i-3], inth_etmux, 32);
|
|
|
|
/* Distance from VBR handler to ETMU4, used to jump */
|
|
*(uint32_t *)(h + 20) += (uint32_t)h4 - cpu_getVBR();
|
|
/* Timer ID, used for timer_stop() after the callback */
|
|
*(uint16_t *)(h + 18) = i;
|
|
/* Pointer to the callback */
|
|
*(gint_call_t **)(h + 24) += i;
|
|
/* TCR address to acknowledge the interrupt */
|
|
*(void volatile **)(h + 28) = &ETMU[i-3].TCR;
|
|
}
|
|
|
|
/* Enable TMU0 at level 13, TMU1 at level 11, TMU2 at level 9 */
|
|
intc_priority(INTC_TMU_TUNI0, 13);
|
|
intc_priority(INTC_TMU_TUNI1, 11);
|
|
intc_priority(INTC_TMU_TUNI2, 9);
|
|
|
|
/* Enable the extra TMUs at level 7 */
|
|
intc_priority(INTC_ETMU_TUNI0, 7);
|
|
if(isSH4())
|
|
{
|
|
intc_priority(INTC_ETMU_TUNI1, 7);
|
|
intc_priority(INTC_ETMU_TUNI2, 7);
|
|
intc_priority(INTC_ETMU_TUNI3, 7);
|
|
intc_priority(INTC_ETMU_TUNI4, 7);
|
|
intc_priority(INTC_ETMU_TUNI5, 7);
|
|
}
|
|
}
|
|
|
|
//---
|
|
// State and driver metadata
|
|
//---
|
|
|
|
static void hsave(tmu_state_t *s)
|
|
{
|
|
s->TSTR = *TSTR;
|
|
|
|
for(int i = 0; i < 3; i++)
|
|
{
|
|
s->t[i].TCOR = TMU[i].TCOR;
|
|
s->t[i].TCNT = TMU[i].TCNT;
|
|
s->t[i].TCR = TMU[i].TCR.word;
|
|
}
|
|
for(int i = 3; i < timer_count(); i++)
|
|
{
|
|
struct tmu_state_stored_timer *c = &s->t[i];
|
|
etmu_t *T = &ETMU[i-3];
|
|
|
|
/* Don't snapshot an interrupt state, because the timer state
|
|
is sometimes garbage protected by a masked interrupt. */
|
|
c->TCOR = T->TCOR ? T->TCOR : 0xffffffff;
|
|
c->TCNT = T->TCNT ? T->TCNT : c->TCOR;
|
|
c->TCR = T->TCR.byte & 0xd;
|
|
c->TSTR = T->TSTR;
|
|
}
|
|
}
|
|
|
|
static void hrestore(tmu_state_t const *s)
|
|
{
|
|
*TSTR = 0;
|
|
|
|
for(int i = 0; i < 3; i++)
|
|
{
|
|
TMU[i].TCOR = s->t[i].TCOR;
|
|
TMU[i].TCNT = s->t[i].TCNT;
|
|
TMU[i].TCR.word = s->t[i].TCR;
|
|
}
|
|
for(int i = 3; i < timer_count(); i++)
|
|
{
|
|
struct tmu_state_stored_timer const *c = &s->t[i];
|
|
etmu_t *T = &ETMU[i-3];
|
|
|
|
set(T->TCOR, c->TCOR);
|
|
T->TSTR = c->TSTR;
|
|
set(T->TCNT, c->TCNT);
|
|
set(T->TCR.byte, c->TCR);
|
|
}
|
|
|
|
*TSTR = s->TSTR;
|
|
}
|
|
|
|
gint_driver_t drv_tmu = {
|
|
.name = "TMU",
|
|
.constructor = constructor,
|
|
.configure = configure,
|
|
.hsave = (void *)hsave,
|
|
.hrestore = (void *)hrestore,
|
|
.state_size = sizeof(tmu_state_t),
|
|
};
|
|
GINT_DECLARE_DRIVER(13, drv_tmu);
|