timer: final iteration on the API

This commit minimally changes the signature of timer_setup() to greatly
simplify timer management, allowing to user to let the library choose
available timers dynamically depending on the settings.
This commit is contained in:
Lephe 2020-06-20 22:45:46 +02:00
parent a91a0a483b
commit dc83d5ee1f
Signed by untrusted user: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
7 changed files with 325 additions and 169 deletions

View File

@ -5,6 +5,8 @@
#ifndef GINT_CLOCK
#define GINT_CLOCK
#include <gint/defs/types.h>
//---
// Clock signals
//---
@ -59,22 +61,13 @@ const clock_frequency_t *clock_freq(void);
add-in is idle, for instance while waiting for keyboard input. */
#define sleep() __asm__("sleep")
/* sleep_us() - sleep for a definite duration in microseconds
/* sleep_us(): Sleep for a fixed duration in microseconds
Stops the processor until the specified delay in microseconds has elapsed.
(The processor will still wake up occasionally to handle interrupts.) This
function selects a timer with timer_setup() called with TIMER_ANY. */
void sleep_us(uint64_t delay_us);
Stops the processor until [delay_us] microseconds have elapsed. Interrupts
may occur during that time (especially timers firing), in which case the
events will be treated as usual. The processor will resume sleeping after
handling them.
The user may choose the timer used to time out the sleep. Remember that only
timers 0 to 2 have microsecond-level resolution; other timers count in units
of about 30 us.
@timer Which timer to use to time out the sleep
@us_delay How long to sleep (in microseconds) */
void sleep_us(int timer, int us_delay);
/* sleep_ms() - sleep for a definite duration in milliseconds */
#define sleep_ms(timer, ms_delay) sleep_us(timer, (ms_delay) * 1000)
/* sleep_ms(): Sleep for a fixed duration in milliseconds */
#define sleep_ms(delay_ms) sleep_us((delay_ms) * 1000ull)
#endif /* GINT_CLOCK */

View File

@ -28,11 +28,12 @@
/* Aligned variables */
#define GALIGNED(x) __attribute__((aligned(x)))
/* Packed structures. I require explicit alignment because if it's unspecified,
GCC cannot optimize access size, and reads to memory-mapped I/O with invalid
access sizes silently fail - honestly you don't want this to happen */
#define GPACKED(x) __attribute__((packed, aligned(x)))
/* Transparent unions */
#define GTRANSPARENT __attribute__((transparent_union))
/* Weak symbols */
#define GWEAK __attribute__((weak))

View File

@ -9,114 +9,203 @@
#include <gint/mpu/tmu.h>
#include <gint/hardware.h>
/* Timer identifiers
/* Timer types and numbers
Hardware timers are numbered with integers starting from 0. You can freely
access all the available timers by using their number once you have
configured them with timer_setup(). The number of timers depends on the MPU:
If you're new to timers, read this comment and then check timer_setup() and
timer_start(): most of the time you only need these.
SH3-based: 4 timers, ids 0..3 [SH7355, SH7337]
SH4-based: 9 timers, ids 0..8 [SH7305]
There are two types of timers on the calculator: normal timers called TMU,
and extra timers added by Casio called ETMU. The main difference is that TMU
are very precise (about 4 MHz; the resolution is about 250 ns) while ETMU
are much less precise (32 kHz; the resolution is about 30 µs).
You should be aware that some of these timers are used by default by gint:
- Timer 0 is used by the gray engine on fx9860g.
- Timer 3/8 is used by the keyboard on SH3/SH4.
The number of available timers also depends on the platform:
* SH3-based fx9860g have 3 TMU (ID 0,1,2) and 1 ETMU (ID 3)
* SH4-based fx9860g and fxcg50 have 3 TMU (ID 0,1,2) and 6 ETMU (ID 3..8)
timer_setup() will fail if you try to use a timer that's already running.
Always check the return value of timer_setup()! Using a timer id that has
not been validated by timer_setup() will work, but do *something else* than
what you intended. */
You can request "a" timer with timer_setup(), and gint will choose an
available timer depending on the precision you requested. Or, if you want
one specifically, you ask for an ID of your choice.
/* timer_count() - tells how many timers are available on the platform */
gint uses 1 to 2 timers by default:
* One for the keyboard on all calculators, always an ETMU
* The gray engine on fx9860g uses TMU0 for interrupt priority reasons; TMU0
is normally available for it unless you've used all TMUs at the same time
libprof also uses a TMU.
Most of the time you can just use timer_setup() and specify TIMER_ANY. If
you want to be sure that you have a TMU or an ETMU, you can do so by
specifying TIMER_TMU or TIMER_ETMU. If you further want to have a specific
timer with specific settings, then you can:
* Set a specific ID in timer_setup(), in which case the delay is no longer
interpreter as count of µs, but as a TCOR value.
* If this ID is a TMU, you can further add (with + or |) a prescaler
specification, one of TIMER_Po_{4,16,64,256}.
* Regardless of how the timer was obtained, you can use timer_reload() to
replace the value of TCOR.
* Also note that TMU0, TMU1, TMU2 and the ETMU have respective interrupt
priority levels of 13, 11, 9, and 7. The gray engine uses TMU0 to
guarantee maximum visual stability in the presence of interrupts.
In this module, timers are manipulated through their ID. timer_setup()
returns the ID of a timer which was allocated to you. You can check it to
determine whether your timer is a TMU (0,1,2) or an ETMU (3 or more). */
/* timer_count(): Number of timers available on the platform */
#define timer_count() (isSH3() ? 4 : 9)
/* Clock input
Timers count down when their input clock ticks, and fire when their counter
reach 0. The choice of the input clock influences the resolution of the
timer, but if the clock is too fast, the 32-bit counter might not be able to
represent long delays.
Standard TMU can count at different speeds. A fast speed offers more
precision but a slower speed offers longer delays. gint automatically
selects suitable speed by default.
Several input clocks are available. The peripheral clock (Po) can be divided
by 4, 16, 64 or 256; as an alternative the external clock TCLK can be used
for counting. I suspect TCLK runs at a fixed frequency of 32768 Hz, but this
has yet to be verified.
If you want something very particular, you can add (with + or |) a prescaler
value to a chosen ID in timer_setup() to request that specific value. The
default prescaler if the ID is fixed is TIMER_Pphi_4. */
enum {
TIMER_Pphi_4 = 0x00,
TIMER_Pphi_16 = 0x10,
TIMER_Pphi_64 = 0x20,
TIMER_Pphi_256 = 0x30,
};
You don't really need to choose an input clock unless you are doing
something very specific. In most practical cases you can use timer_default
which is 0. See the timer_delay() function for more information. */
typedef enum
/* Timer selection; see timer_setup() */
enum {
TIMER_ANY = -1,
TIMER_TMU = -2,
TIMER_ETMU = -3,
};
/* Type of callback functions
This module used to require callbacks of type int (*)(volatile void *), but
callbacks are rarely this generic. Now timer_setup() accepts functions of
any of the types below. */
typedef union
{
timer_Po_4 = 0,
timer_Po_16 = 1,
timer_Po_64 = 2,
timer_Po_256 = 3,
timer_TCLK = 5,
/* No argument, returns either TIMER_CONTINUE or TIMER_STOP */
int (*v)(void);
/* Single integer argument */
int (*i)(int);
/* Single pointer argument, cv-qualified as needed */
int (*pv) (void *);
int (*pVv) (volatile void *);
int (*pCv) (const void *);
int (*pCVv)(volatile const void *);
timer_default = timer_Po_4,
} GTRANSPARENT timer_callback_t;
} timer_input_t;
/* Return value for timer callbacks, indicating whether the timer should
continue running and fire again, or stop now */
enum {
TIMER_CONTINUE = 0,
TIMER_STOP = 1,
};
//---
// Timer functions
//---
/* timer_setup() - set up a timer
/* timer_setup(): Reserve and configure a timer
This function configures the requested timer without starting it. On
success, it returns the first argument "timer", which is used as a timer
identifier in all other timer functions. If the requested timer is already
in use, this function fails and returns a negative number.
This function finds and configures a timer (without starting it). On
success, it returns the ID of the configured timer, which is used in all
other timer functions. If no timer matching the requested settings is
available, this function returns -1.
This function sets the timer delay, the clock source, and registers a
callback function to be called when the timer fires. An argument can be
supplied to the callback function in the form of a pointer.
When started, the configured timer will run for the requested delay and call
the supplied callback function at the end of this delay. The callback
function can then decide whether to leave the timer running (and be called
again after the same delay) or stop the timer.
When the timer fires, the callback function is called with the provided
argument pointer. The callback decides whether the timer should continue
running (by returning 0) or stop (by returning nonzero). In the latter case,
events accumulated while the callback was running are dropped.
The first argument specifies what kind of timer you want.
* TIMER_ANY will let timer_setup() choose any available timer. timer_setup()
will only use an ETMU if the delay is more than 0.1 ms to avoid resolution
issues. Most of the time this is what you need.
* TIMER_TMU or TIMER_ETMU will let timer_setup() choose an available TMU or
ETMU, respectively.
* Specifying an ID (0..8) will request exactly that timer. In this case, and
if the ID is a TMU (0,1,2), you may add (with + or |) a prescaler value to
specify the clock input. Otherwise the clock is set to Pphi/4.
If no timer matching the supplied settings is available, timer_setup()
returns -1.
It is sometimes difficult to choose a timer constant and a clock source
given a wished delay in seconds, especially when overclock is used. The
timer_delay() function is provided for this purpose.
The second argument is the delay. With TIMER_ANY, TIMER_TMU and TIMER_ETMU,
the delay is interpreted as a number of µs. With an explicit ID, the delay
is interpreted as a value of TCOR; see timer_delay() in this case. Note that
TCOR values are sensitive to the overclock level!
@timer Requested timer id
@delay Delay between each event (the unit depends on the clock source)
@clock Clock source used by the timer for counting down
@callback Callback function (called when the timer fires)
@arg Passed as argument to the callback function */
int timer_setup(int timer, uint32_t delay, timer_input_t clock,
int (*callback)(volatile void *arg), volatile void *arg);
The third argument is a function to be called when the timer expires. It may
have a single 4-byte argument (int or pointer), in which case you should
provide the value to call it with as an optional argument (you can only use
a single argument). It must return an int equal to either TIMER_CONTINUE or
TIMER_STOP to control the subsequent operation of the timer. See the
definition of timer_callback_t above for the possible function types. If
this argument is NULL, a default function that stops the timer will be used.
/* timer_delay() - compute a delay constant from a duration in seconds
On success, the configured timer becomes reserved; it can no longer be
returned by timer_setup() until:
* Either timer_stop() is called,
* Or the callback returns TIMER_STOP (which also stops the timer).
Remember than the returned timer is not started yet; see timer_start().
This function can used as a facility to calculate the [delay] argument to
the timer_setup() function. It takes a microsecond delay as an argument and
returns the corresponding timer constant. A typical use to start a timer
with a 25 ms interval would be:
@timer Requested timer; TIMER_{ANY,TMU,ETMU} or an ID with prescaler
@delay Delay between each event, in µs unless first argument is an ID
@callback Function to be called when the timer fires
@... If the callback takes an argument, specify that value here */
int timer_setup(int timer, uint64_t delay_us, timer_callback_t callback, ...);
timer_setup(0, timer_delay(0, 25 * 1000), 0, callback, arg);
/* Makes sure an argument is always provided, for va_arg() */
#define timer_setup(...) timer_setup(__VA_ARGS__, 0)
WARNING: Only timers 0 to 2 can count microseconds! Other timers have a
resolution of around 30 us. Counting in ms is safe for all timers, though.
For standard timers (0 to 2) it uses Po / 4 as clock input, which is very
precise and can represent up to 3 minutes' time; for extra timers (3 and
above) the clock is fixed to 32768 Hz.
@timer The timer you are planning to use
@delay_us Requested delay in microseconds */
uint32_t timer_delay(int timer, uint64_t delay_us);
/* timer_start() - start a configured timer
The specified timer will start counting down and fire callbacks at regular
intervals.
@timer Timer id, as returned by timer_setup() */
/* timer_start(): Start a configured timer
The specified timer will start counting down and call its callback function
at regular intervals until it is paused or stopped. */
void timer_start(int timer);
/* timer_reload() - change a timer's delay constant for next interrupts
/* timer_pause(): Pause a timer without freeing it
Pauses the specified timer will be paused. The timer stops counting but is
not freed and can be resumed by calling timer_start(). */
void timer_pause(int timer);
/* timer_stop(): Stop and free a timer
Stops and frees a timer, making it available to timer_setup(). This timer
should no longer be used unless it is returned again by timer_setup(). */
void timer_stop(int timer);
/* timer_wait(): Wait for a timer to stop
Waits until the timer pauses or stops. If the timer is not running, returns
immediately. Even after timer_wait(), the timer may not be available since
it may have only paused. If the timer never stops, you're in trouble. */
void timer_wait(int timer);
//---
// Low-level functions
//---
/* timer_delay(): Compute a timer constant from a duration in seconds
This function calculates the timer constant for a given delay. timer_setup()
does this computation when TIMER_ANY, TIMER_TMU or TIMER_ETMU is provided,
but expects you to provide the exact constant when an explicit timer ID is
requested. timer_reload() also expects the constant.
For TMU the clock can be Pphi/4, Pphi/16, Pphi/64 and Pphi/256, which can
respectively count up to about 350 seconds, 23 minutes, 95 minutes and 381
minutes.
For ETMU the clock is TCLK at 32768 Hz, which can count up to 36 hours. Any
longer delay should probably be managed by the RTC (which counts even when
the calculator is off).
@timer The timer you are planning to use
@delay_us Requested delay in microseconds
@clock The clock value, irrelevant if timer >= 3 */
uint32_t timer_delay(int timer, uint64_t delay_us, int clock);
/* timer_reload(): Change a timer's delay constant for next interrupts
Changes the delay constant of the given timer. Nothing will happen until the
next callback; then the timer will update its delay to reflect the new
@ -126,35 +215,14 @@ void timer_start(int timer);
@delay New delay (unit depends on the clock source) */
void timer_reload(int timer, uint32_t delay);
/* timer_pause() - stop a running timer
The specified timer will be paused; its counter will not be reset. A stopped
timer can be resumed anytime by calling timer_start(). If you want to also
reset the counter, use timer_reload().
@timer Timer id, as returned by timer_setup() */
void timer_pause(int timer);
/* timer_stop() - stop and free a timer
Stops and destroys a timer, making its id free for re-use. The id must not
be used anymore until it is returned by a further call to timer_setup().
@timer Timer id, as returned by timer_setup() */
void timer_stop(int timer);
/* timer_wait() - wait for a timer to stop
Waits until the specified timer stops running. If the timer is not running,
returns immediately. The timer might not be free if it has just been paused
instead of stopped entirely. */
void timer_wait(int timer);
//---
// Predefined timer callbacks
//---
/* timer_timeout() - callback that sets a flag and halts the timer
This predefined callback may be used when a timeout is required. It sets its
argument pointer to 1 and halts the timer. The pointer must be of type
int * and you must declare the variable as volatile int. */
/* timer_timeout(): Callback that sets a flag and halts the timer
This predefined callback may be used when a timeout is required. It takes a
single argument which must be a pointer to int (or other integer type of 4
bytes) and increments it. */
int timer_timeout(volatile void *arg);
#endif /* GINT_TIMER */

View File

@ -74,13 +74,13 @@ GDESTRUCTOR static void gray_quit(void)
}
/* gray_int(): Interrupt handler */
int gray_int(GUNUSED volatile void *arg)
int gray_int(void)
{
t6k11_display(vrams[st ^ 2], 0, 64, 16);
timer_reload(GRAY_TIMER, delays[(st ^ 3) & 1]);
st ^= 1;
return 0;
return TIMER_CONTINUE;
}
/* gray_start(): Start the gray engine */
@ -92,9 +92,8 @@ void gray_start(void)
if(runs) return;
int free = timer_setup(GRAY_TIMER, delays[0], timer_Po_64, gray_int,
NULL);
if(free != GRAY_TIMER) return;
int timer = timer_setup(GRAY_TIMER|TIMER_Pphi_64, delays[0], gray_int);
if(timer != GRAY_TIMER) return;
timer_start(GRAY_TIMER);
st = 0;

View File

@ -248,7 +248,7 @@ int keydown_any(int key, ...)
// Driver initialization
//---
static int callback(GUNUSED volatile void *arg)
static int callback(void)
{
keysc_frame();
time++;
@ -258,20 +258,18 @@ static int callback(GUNUSED volatile void *arg)
/* init() - setup the support timer */
static void init(void)
{
int tid = isSH3() ? 3 : 8;
/* Configure the timer to do 128 keyboard scans per second. This
frequency *must* be high for the KEYSC interface to work! */
/* Note: the supporting timer always runs at 32768 Hz. */
int delay = 32768 / KEYBOARD_SCAN_FREQUENCY;
int delay = 1000000 / KEYBOARD_SCAN_FREQUENCY;
if(!delay) delay = 1;
/* Set the default repeat times (milliseconds) */
getkey_repeat(400, 40);
/* The timer will be stopped when the timer driver is unloaded */
timer_setup(tid, delay, 0, callback, NULL);
timer_start(tid);
int tid = timer_setup(TIMER_ANY, delay, callback);
if(tid >= 0) timer_start(tid);
gint[HWKBD] = HW_LOADED | (isSH3() ? HWKBD_IO : HWKBD_KSI);
gint[HWKBDSF] = KEYBOARD_SCAN_FREQUENCY;

View File

@ -5,15 +5,14 @@
#include <gint/clock.h>
#include <gint/timer.h>
/* sleep_us() - sleep for a definite duration in microseconds */
void sleep_us(int tid, int us_delay)
/* sleep_us(): Sleep for a fixed duration in microseconds */
void sleep_us(uint64_t delay_us)
{
volatile int flag = 0;
uint32_t delay = timer_delay(tid, us_delay);
int free = timer_setup(tid, delay, 0, timer_timeout, &flag);
if(free < 0) return;
int timer = timer_setup(TIMER_ANY, delay_us, timer_timeout, &flag);
if(timer < 0) return;
timer_start(tid);
while(!flag) sleep();
timer_start(timer);
timer_wait(timer);
}

View File

@ -7,12 +7,11 @@
#include <gint/gint.h>
#include <gint/clock.h>
#include <gint/intc.h>
#include <gint/mpu/intc.h>
#include <gint/mpu/tmu.h>
#include <gint/defs/attributes.h>
#include <gint/defs/types.h>
#include <stdarg.h>
#undef timer_setup
//---
// Driver storage
@ -21,9 +20,9 @@
/* inth_data_t - data storage inside interrupt handlers */
typedef struct
{
int (*cb)(volatile void *arg); /* User-provided callback */
volatile void *arg; /* Argument for [callback] */
volatile void *TCR; /* TCR address for TMU */
void *function; /* User-provided callback */
uint32_t arg; /* Argument for function */
volatile void *TCR; /* TCR address for TMU */
} GPACKED(4) inth_data_t;
@ -37,21 +36,17 @@ GDATA static etmu_t *ETMU = SH7305_ETMU;
GDATA static volatile uint8_t *TSTR = &SH7305_TMU.TSTR;
//---
// Timer API
// Local functions
//---
/* timer_setup() - set up a timer */
int timer_setup(int id, uint32_t delay, timer_input_t clock,
int (*callback)(volatile void *arg), volatile void *arg)
/* configure(): Configure a fixed timer */
static void configure(int id, uint32_t delay, int clock, void *f, uint32_t arg)
{
/* Timer is not installed (TCR address is really required) */
if(!timers[id]) return -1;
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;
if(T->TCR.UNIE || *TSTR & (1 << id)) return;
/* Configure the counter, clear interrupt flag*/
T->TCOR = delay;
@ -67,10 +62,10 @@ int timer_setup(int id, uint32_t delay, timer_input_t clock,
else
{
etmu_t *T = &ETMU[id-3];
if(T->TCR.UNIE) return -1;
if(T->TCR.UNIE) return;
/* No clock input and clock edge here. But TCR and TCNT need a
delay to retain the value */
/* 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);
@ -83,25 +78,128 @@ int timer_setup(int id, uint32_t delay, timer_input_t clock,
T->TCR.UNIE = 1;
}
timers[id]->cb = callback;
timers[id]->function = f;
timers[id]->arg = arg;
return id;
}
/* 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);
configure(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)
uint32_t timer_delay(int id, uint64_t delay_us, int clock)
{
/* TODO: Proper timer_delay() */
const clock_frequency_t *clock = clock_freq();
uint64_t freq = clock->Pphi_f >> 2;
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;
/* Extra timers all run at 32768 Hz */
if(id >= 3) freq = 32768;
uint64_t product = freq * delay_us;
return product / 1000000;
}
@ -184,11 +282,11 @@ void timer_wait(int id)
//---
/* timer_timeout() - callback that sets a flag and halts the timer */
int timer_timeout(volatile void *arg)
int timer_timeout(void volatile *arg)
{
volatile int *x = arg;
(*x)++;
return 1;
int volatile *x = arg;
if(x) (*x)++;
return TIMER_STOP;
}
//---