vxKernel/src/drivers/mpu/sh/sh7305/tmu/tmu.c

401 lines
9.5 KiB
C

#include <vhex/driver/mpu/sh/sh7305/tmu.h>
#include <vhex/driver/mpu/sh/sh7305/cpu.h>
#include <vhex/driver/mpu/sh/sh7305/cpg.h>
#include <vhex/driver/mpu/sh/sh7305/intc.h>
#include <vhex/driver/cpu.h>
#include <vhex/driver.h>
#include <vhex/timer.h>
#include <vhex/timer/interface.h>
/* Callbacks for all timers */
timer_call_t sh7305_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)
/* define the private TimerUnit context structure */
struct tmu_ctx {
struct tmu_state_stored_timer {
uint32_t TCOR;
uint32_t TCNT;
uint16_t TCR;
uint16_t TSTR;
} timer[9];
uint8_t TSTR;
};
//---
// Internal helpers
//---
/* conf(): Configure a fixed timer */
static int conf(tid_t id, uint32_t delay, int clock, timer_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 (-1);
/* 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 (-1);
/* No clock input and clock edge here */
set(T->TCR.UNF, 0);
set(T->TCOR, delay);
set(T->TCNT, delay);
T->TCR.UNIE = 1;
}
sh7305_tmu_callbacks[id] = call;
return (id);
}
/* sh7305_tmu_control() - start or stop a timer
@id Timer ID to configure
@state 0 to start the timer, 1 to stop it (nothing else!) */
static int sh7305_tmu_control(tid_t id, int state)
{
if (id < 0 || id > 9)
return (-1);
if(id < 3) {
*TSTR = (*TSTR | (1 << id)) ^ (state << id);
} else {
ETMU[id-3].TSTR = state ^ 1;
}
return (0);
}
/* available(): Check if a timer is available (UNIE cleared, not running) */
static int available(int id)
{
if (id < 0 || id > 9)
return (-1);
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;
}
}
/* sh7305_tmu_delay() - compute a delay constant from a duration in seconds */
static uint32_t sh7305_tmu_delay(int id, uint64_t delay_us, int clock)
{
struct cpg_clock_frequency cpg_freq;
uint64_t freq;
if(id < 3) {
cpg_clock_freq(&cpg_freq);
freq = cpg_freq.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;
}
/* stop_callback(): Empty callback that stops the timer */
static int stop_callback(void)
{
return TIMER_STOP;
}
//--
// Public timer-API
//---
int sh7305_tmu_configure(uint64_t delay, timer_call_t call)
{
int clock = 0;
/* Default behavior for the callback */
if(!call.function) call = TIMER_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 = 8; id >= 0; id--)
{
if(available(id) != 0)
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) {
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 */
delay = sh7305_tmu_delay(id, delay, clock);
return conf(id, delay, clock, call);
}
return (-1);
}
/* sh7305_tmu_start() - start a configured timer */
int sh7305_tmu_start(tid_t id)
{
return sh7305_tmu_control(id, 0);
}
/* sh7305_tmu_reload() - change a timer's delay constant for next interrupts */
int sh7305_tmu_reload(tid_t id, uint64_t delay)
{
//FIXME
if (id < 0 || id > 9)
return (-1);
if(id < 3) {
TMU[id].TCOR = delay;
} else {
ETMU[id-3].TCOR = delay;
}
return (0);
}
/* sh7305_tmu_pause() - stop a running timer */
int sh7305_tmu_pause(tid_t id)
{
return sh7305_tmu_control(id, 1);
}
/* sh7305_tmu_stop() - stop and free a timer */
int sh7305_tmu_stop(tid_t id)
{
if (id < 0 || id > 9)
return (-1);
/* Stop the timer and disable UNIE to indicate that it's free */
sh7305_tmu_pause(id);
if(id < 3) {
TMU[id].TCR.UNIE = 0;
TMU[id].TCR.UNF = 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);
}
return (0);
}
/* sh7305_tmu_wait(): Wait for a timer to stop */
int sh7305_tmu_wait(tid_t id)
{
if (id < 0 || id > 9)
return (-1);
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) cpu_sleep();
} else {
etmu_t *T = &ETMU[id-3];
while(T->TSTR) if(T->TCR.UNIE) cpu_sleep();
}
}
/* sh7305_tmu_spinwait(): Start a timer and actively wait for UNF */
int sh7305_tmu_spinwait(tid_t id)
{
if (id < 0 || id > 9)
return (-1);
if(id < 3) {
tmu_t *T = &TMU[id];
T->TCR.UNIE = 0;
sh7305_tmu_start(id);
while(!T->TCR.UNF) {}
} else {
etmu_t *T = &ETMU[id-3];
set(T->TCR.UNIE, 0);
sh7305_tmu_start(id);
while(!T->TCR.UNF) {}
}
return (0);
}
//---
// hardware primitives
//---
/* __tmu_configure() : configure the SH7305 TMU/ETMU module */
static void __tmu_configure(struct tmu_ctx *s)
{
/* prepare timers comtext */
s->TSTR = 0;
for(int i = 0; i < 9; i++)
{
s->timer[i].TCOR = 0xffffffff;
s->timer[i].TCNT = 0xffffffff;
s->timer[i].TCR = 0;
s->timer[i].TSTR = 0;
}
/* install the TMUs interupt handler */
extern uint32_t sh7305_inth_tmu;
sh7305_intc_install_inth(0x400, &sh7305_inth_tmu, 96);
/* install all ETMUx interrupt handler
The interruptions handling for the ETMU is more complexe that the
"classical" timer (TMU) because, instead of the TMU that each
interrupt vector is grouped (TMU0:0x400, TMU1:0x420, TMU2:0x440), each
ETMUx interrupt vector is not followed so we should relocalize and
update each interrut gate.
We know that the ETMU4 (which have the interrupt vector 0xd00) is
followed by excatly 3 free gates (where not interrupt exist). We will
use this place to store all the ETMUx interrupt logic. */
extern uint32_t sh7305_inth_etmu4;
extern uint32_t sh7305_inth_etmux;
void *h4;
void *h;
uint16_t etmu_evt[6] = { 0x9e0, 0xc20, 0xc40, 0x900, 0xd00, 0xfa0 };
h4 = sh7305_intc_install_inth(etmu_evt[4], &sh7305_inth_etmu4, 96);
for (int i = 0; i < 6; ++i) {
/* skip the core ETMUx core handler */
if (i == 4)
continue;
/* install the default interrupt handler */
h = sh7305_intc_install_inth(etmu_evt[i], &sh7305_inth_etmux, 32);
/* Distance from VBR handler to ETMU4, used to jump */
*(uint32_t *)(h + 20) += (uintptr_t)h4 - (uintptr_t)cpu_get_vbr();
/* Timer ID, used for sh7305_tmu_stop() after the callback */
*(uint16_t *)(h + 18) = i;
/* Pointer to the callback */
*(void **)(h + 24) += i;
/* TCR address to acknowledge the interrupt */
*(void volatile **)(h + 28) = &ETMU[i].TCR;
}
}
/* __tmu_hsave() : save hardware information */
static void __tmu_hsave(struct tmu_ctx *s)
{
struct tmu_state_stored_timer *c;
s->TSTR = *TSTR;
/* classic timer */
for(int i = 0; i < 3; i++) {
s->timer[i].TCOR = TMU[i].TCOR;
s->timer[i].TCNT = TMU[i].TCNT;
s->timer[i].TCR = TMU[i].TCR.word;
}
/* extra timer */
c = &s->timer[3];
for(int i = 0; i < 6; i++) {
/* Don't snapshot an interrupt state, because the timer state
is sometimes garbage protected by a masked interrupt. */
c[i].TCOR = ETMU[i].TCOR ? ETMU[i].TCOR : 0xffffffff;
c[i].TCNT = ETMU[i].TCNT ? ETMU[i].TCNT : c->TCOR;
c[i].TCR = ETMU[i].TCR.byte & 0xd;
c[i].TSTR = ETMU[i].TSTR;
}
}
/* __tmu_hrestore() : restore hadware information */
static void __tmu_hrestore(struct tmu_ctx *s)
{
struct tmu_state_stored_timer const *c;
*TSTR = 0;
/* classic timer */
for(int i = 0; i < 3; i++)
{
TMU[i].TCOR = s->timer[i].TCOR;
TMU[i].TCNT = s->timer[i].TCNT;
TMU[i].TCR.word = s->timer[i].TCR;
}
/* extra timer */
c = &s->timer[3];
for(int i = 0; i < 6; i++)
{
set(ETMU[i].TCOR, c[i].TCOR);
set(ETMU[i].TCNT, c[i].TCNT);
set(ETMU[i].TCR.byte, c[i].TCR);
set(ETMU[i].TSTR, c[i].TSTR);
}
*TSTR = s->TSTR;
}
/* declare the TMU driver */
struct vhex_driver drv_tmu = {
.name = "TMU",
.hsave = (void*)&__tmu_hsave,
.hrestore = (void*)&__tmu_hrestore,
.configure = (void*)&__tmu_configure,
.state_size = sizeof(struct tmu_ctx),
.flags = {
.TIMER = 1,
.SHARED = 0,
.UNUSED = 0
},
.module_data = &(struct timer_drv_interface){
.timer_configure = &sh7305_tmu_configure,
.timer_start = &sh7305_tmu_start,
.timer_pause = &sh7305_tmu_pause,
.timer_stop = &sh7305_tmu_stop,
.timer_wait = &sh7305_tmu_wait,
.timer_spinwait = &sh7305_tmu_spinwait,
.timer_reload = &sh7305_tmu_reload
}
};
VHEX_DECLARE_DRIVER(03, drv_tmu);