240 lines
9.8 KiB
C
240 lines
9.8 KiB
C
//---
|
|
// gint:timer - Timer operation
|
|
//---
|
|
|
|
#ifndef GINT_TIMER
|
|
#define GINT_TIMER
|
|
|
|
#include <gint/defs/types.h>
|
|
#include <gint/mpu/tmu.h>
|
|
#include <gint/hardware.h>
|
|
|
|
/* Timer types and numbers
|
|
|
|
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.
|
|
|
|
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).
|
|
|
|
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)
|
|
|
|
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.
|
|
|
|
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_Pphi_{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
|
|
|
|
Standard TMU can count at different speeds. A fast speed offers more
|
|
precision but a slower speed offers longer delays. gint automatically
|
|
selects suitable speeds by default.
|
|
|
|
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,
|
|
};
|
|
|
|
/* 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
|
|
{
|
|
/* 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 *);
|
|
/* Integer pointer argument, cv-qualified as needed */
|
|
int (*pi) (int *);
|
|
int (*pVi) (volatile int *);
|
|
int (*pCi) (const int *);
|
|
int (*pCVi)(volatile const int *);
|
|
|
|
} GTRANSPARENT timer_callback_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(): Reserve and configure a timer
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
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!
|
|
|
|
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.
|
|
|
|
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().
|
|
|
|
@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, ...);
|
|
|
|
/* Makes sure an argument is always provided, for va_arg() */
|
|
#define timer_setup(...) timer_setup(__VA_ARGS__, 0)
|
|
|
|
/* 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_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);
|
|
|
|
/* timer_spinwait(): Actively wait for a timer to raise UNF
|
|
Waits until the timer raises UNF, without sleeping. This is useful for
|
|
delays in driver code that is run when interrupts are disabled. This relies
|
|
neither on the interrupt signal nor on the UNIE flag. */
|
|
void timer_spinwait(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
|
|
constant. The new delay can be calculated by the timer_delay() function.
|
|
|
|
@timer Timer id, as returned by timer_setup()
|
|
@delay New delay (unit depends on the clock source) */
|
|
void timer_reload(int timer, uint32_t delay);
|
|
|
|
//---
|
|
// 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 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 */
|