gint/src/tmu/tmu.c
Lephe c9264a06d5
kernel: driver and world system overhaul
Changes in the driver and world system:

* Rewrite driver logic to include more advanced concepts. The notion of
  binding a driver to a device is introduced to formalize wait(); power
  management is now built-in instead of being handled by the drivers
  (for instance DMA). The new driver model is described in great detail
  in <gint/drivers.h>

* Formalized the concept of "world switch" where the hardware state is
  saved and later restored. As a tool, the world switch turns out to be
  very stable, and allows a lot of hardware manipulation that would be
  edgy at best when running in the OS world.

* Added a GINT_DRV_SHARED flag for drivers to specify that their state
  is shared between worlds and not saved/restored. This has a couple of
  uses.

* Exposed a lot more of the internal driver/world system as their is no
  particular downside to it. This includes stuff in <gint/drivers.h>
  and the driver's state structures in <gint/drivers/states.h>. This is
  useful for debugging and for cracked concepts, but there is no
  API stability guarantee.

* Added a more flexible driver level system that allows any 2-digit
  level to be used.

Feature changes:

* Added a CPU driver that provides the VBR change as its state save.
  Because the whole context switch relied on interrupts being disabled
  anyway, there is no longer an inversion of control when setting the
  VBR; this is just part of the CPU driver's configuration. The CPU
  driver may also support other features such as XYRAM block transfer
  in the future.

* Moved gint_inthandler() to the INTC driver under the name
  intc_handler(), pairing up again with intc_priority().

* Added a reentrant atomic lock based on the test-and-set primitive.
  Interrupts are disabled with IMASK=15 for the duration of atomic
  operations.

* Enabled the DMA driver on SH7305-based fx-9860G. The DMA provides
  little benefit on this platform because the RAM is generally faster
  and buffers are ultimately small. The DMA is still not available on
  SH3-based fx-9860G models.

* Solved an extremely obnoxious bug in timer_spin_wait() where the
  timer is not freed, causing the callback to be called when interrupts
  are re-enabled. This increments a random value on the stack. As a
  consequence of the change, removed the long delays in the USB driver
  since they are not actually needed.

Minor changes:

* Deprecated some of the elements in <gint/hardware.h>. There really is
  no good way to "enumerate" devices yet.

* Deprecated gint_switch() in favor of a new function
  gint_world_switch() which uses the GINT_CALL abstraction.

* Made the fx-9860G VRAM 32-aligned so that it can be used for tests
  with the DMA.

Some features of the driver and world systems have not been implemented
yet, but may be in the future:

* Some driver flags should be per-world in order to create multiple
  gint worlds. This would be useful in Yatis' hypervisor.
* A GINT_DRV_LAZY flag would be useful for drivers that don't want to
  be started up automatically during a world switch. This is relevant
  for drivers that have a slow start/stop sequence. However, this is
  tricky to do correctly as it requires dynamic start/stop and also
  tracking which world the current hardware state belongs to.
2021-04-23 20:44:08 +02:00

474 lines
11 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/mpu/tmu.h>
#include <stdarg.h>
#undef timer_setup
//---
// Driver storage
//---
/* inth_data_t - data storage inside interrupt handlers */
typedef struct
{
void *function; /* User-provided callback */
uint32_t arg; /* Argument for function */
volatile void *TCR; /* TCR address for TMU */
} GPACKED(4) inth_data_t;
/* This array references the storage areas of all timer handlers */
static inth_data_t *timers[9] = { NULL };
/* 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;
//---
// Local functions
//---
/* conf(): Configure a fixed timer */
static void conf(int id, uint32_t delay, int clock, void *f, uint32_t arg)
{
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;
do T->TCR.UNF = 0;
while(T->TCR.UNF);
/* 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. But TCR and TCNT need
some time to execute the write */
do T->TCR.UNF = 0;
while(T->TCR.UNF);
do T->TCOR = delay;
while(T->TCOR != delay);
do T->TCNT = delay;
while(T->TCNT != delay);
T->TCR.UNIE = 1;
}
timers[id]->function = f;
timers[id]->arg = arg;
}
/* 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)
{
/* The timer should also be installed... */
if(!timers[id]) 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
//---
/* timer_setup(): Reserve and configure a timer */
int timer_setup(int spec, uint64_t delay, timer_callback_t function, ...)
{
int clock = 0;
/* Get the optional argument */
va_list va;
va_start(va, function);
uint32_t arg = va_arg(va, uint32_t);
va_end(va);
/* Default value for the callback */
if(!function.v) function.v = 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, function.v, arg);
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;
}
/* fxcg50: Calculated = 29491200 but it's too low */
/* TODO: Account for down spread spectrum in the CPG */
// uint64_t freq = 29020000 >> 2;
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
{
etmu_t *T = &ETMU[id-3];
T->TCR.UNIE = 0;
/* Also clear TCOR and TCNT to avoid spurious interrupts */
do T->TCOR = 0xffffffff;
while(T->TCOR + 1);
do T->TCNT = 0xffffffff;
while(T->TCNT + 1);
do T->TCR.UNF = 0;
while(T->TCR.UNF);
}
}
/* 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) {}
}
}
//---
// Predefined timer callbacks
//---
/* timer_timeout() - callback that sets a flag and halts the timer */
int timer_timeout(void volatile *arg)
{
int volatile *x = arg;
if(x) (*x)++;
return TIMER_STOP;
}
//---
// Driver initialization
//---
/* Interrupt handlers for standard timers (4 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 standard TMU's interrupt handlers */
void *h = intc_handler(0x400, inth_tmu, 128);
timers[0] = h + 84;
timers[1] = h + 104;
timers[2] = h + 116;
/* Clear every timer to avoid surprises */
for(int id = 0; id < 3; id++)
{
do TMU[id].TCR.word = 0;
while(TMU[id].TCR.word);
TMU[id].TCOR = 0xffffffff;
TMU[id].TCNT = 0xffffffff;
/* Standard timers: TCR is provided to the interrupt handler */
timers[id]->TCR = &TMU[id].TCR;
}
for(int id = 0; id < timer_count()-3; id++)
{
etmu_t *T = &ETMU[id];
/* Extra timers seem to generate interrupts as long as TCNT=0,
regardless of TSTR. I'm not entirely sure about this weird
behaviour, but for safety I'll set TCOR/TCNT to non-zero. */
T->TSTR = 0;
do T->TCOR = 0xffffffff;
while(T->TCOR + 1);
/* Also TCNT and TCR take some time to record changes */
do T->TCNT = 0xffffffff;
while(T->TCNT + 1);
do T->TCR.byte = 0;
while(T->TCR.byte);
}
/* Install the extra timers. On SH3, only ETMU0 is available */
for(int i = 3; i < timer_count(); i++) if(i != 7)
{
void *h = intc_handler(etmu_event[i-3], inth_etmux, 32);
timers[i] = h + 20;
/* On SH3, the ETMU handler is not at an offset of 0x900 (event
code 0xd00) but at an offset of 0xa0 */
uint32_t *etmu_offset = h + 16;
if(isSH3()) *etmu_offset = *etmu_offset - 0xf40 + 0x2a0;
uint16_t *data_id = h + 14;
*data_id = i;
uint32_t *TCR = h + 28;
*TCR = (uint32_t)&ETMU[i-3].TCR;
}
/* Also install ETMU4, even on SH3, because it contains common code */
h = intc_handler(etmu_event[4], inth_etmu4, 96);
timers[7] = h + 84;
*(uint32_t *)(h + 92) = (uint32_t)&ETMU[4].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];
do T->TCOR = c->TCOR;
while(T->TCOR != c->TCOR);
T->TSTR = c->TSTR;
do T->TCNT = c->TCNT;
while(T->TCNT != c->TCNT);
do T->TCR.byte = c->TCR;
while(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);