mirror of
https://git.planet-casio.com/Lephenixnoir/gint.git
synced 2024-12-28 04:23:36 +01:00
use GINT_CALL() in every API that has callbacks
* Change gint_inth_callback() * Add intc_handler_function() to use C functions as handlers instead of writing assembler, and use it in the RTC and USB * Revisit the TMU handlers, which after moving out the callbacks, now fit into 3 gates (great!), and adapt the ETMU handler * Improve the timer driver (less code = better code, removed magic constants assuming the VBR layout on SH3/SH4, etc.) * Remove 2 gates and a gap from the compact scheme on SH3 * Define timer_configure() to replace timer_setup(), which could not be cleanly updated to support GINT_CALL() * Replace rtc_start/stop_timer with rtc_periodic_enable/disable, which is less confusing because of ETMU being "RTC timers"
This commit is contained in:
parent
85d30fa59b
commit
c37f150600
19 changed files with 399 additions and 523 deletions
|
@ -26,6 +26,7 @@ set(SOURCES_COMMON
|
|||
src/dma/memcpy.c
|
||||
src/dma/memset.c
|
||||
src/intc/intc.c
|
||||
src/intc/inth.s
|
||||
src/kernel/exch.c
|
||||
src/kernel/exch.s
|
||||
src/kernel/hardware.c
|
||||
|
@ -55,7 +56,6 @@ set(SOURCES_COMMON
|
|||
src/render/dtext.c
|
||||
src/render/dvline.c
|
||||
src/render/topti.c
|
||||
src/rtc/inth.s
|
||||
src/rtc/rtc.c
|
||||
src/rtc/rtc_ticks.c
|
||||
src/spu/spu.c
|
||||
|
@ -74,7 +74,6 @@ set(SOURCES_COMMON
|
|||
src/tmu/tmu.c
|
||||
src/usb/classes/ff-bulk.c
|
||||
src/usb/configure.c
|
||||
src/usb/inth.s
|
||||
src/usb/pipes.c
|
||||
src/usb/setup.c
|
||||
src/usb/string.c
|
||||
|
|
|
@ -66,36 +66,27 @@ static GINLINE void *gint_inthandler(int code, void const *h, size_t size) {
|
|||
return intc_handler(code, h, size);
|
||||
}
|
||||
|
||||
/* gint_inth_callback(): Call back arbitrary code from an interrupt handler
|
||||
/* gint_inth_callback(): Callback from interrupt handler to userland
|
||||
|
||||
Calls the specified function with the given argument after saving the user
|
||||
context, enabling interrupts and going to user bank. This function is used
|
||||
to call user code from interrupt handlers, typically from timer or RTC
|
||||
callbacks. You can think of it as a way to escape the SR.BL=1 environment to
|
||||
safely call back virtualized and interrupt-based functions during interrupt
|
||||
handling.
|
||||
This function performs an indirect call as with gint_call(), afters saving
|
||||
the user context, enabling interrupts and going to user bank. This is useful
|
||||
to call user code from interrupt handlers. You can think of it as a kernel-
|
||||
space escape to virtualized userland during interrupt handling.
|
||||
|
||||
It is not safe to call from C code in any capacity and is mentioned here
|
||||
only for documentation purposes; you should really only call this from
|
||||
an interrupt handler's assembler code, typically like this:
|
||||
This function can only be useful in an interrupt handler's assembler code.
|
||||
It is loaded at a runtime-determined address and accessed through a function
|
||||
pointer, like this:
|
||||
|
||||
mov.l .callback, r0
|
||||
mov.l @r0, r0 # because function pointer
|
||||
|
||||
mov <function>, r4
|
||||
mov <argument>, r5
|
||||
mov <address of gint_call_t object>, r4
|
||||
jsr @r0
|
||||
nop
|
||||
|
||||
.callback:
|
||||
.long _gint_inth_callback
|
||||
|
||||
This function is loaded to a platform-dependent address determined at
|
||||
runtime; call it indirectly through the function pointer.
|
||||
|
||||
@callback Callback function, may take no argument in practice
|
||||
@arg Argument
|
||||
@call Address of a gint_call_t object
|
||||
Returns the return value of the callback. */
|
||||
extern int (*gint_inth_callback)(int (*function)(void *arg), void *arg);
|
||||
extern int (*gint_inth_callback)(gint_call_t const *call);
|
||||
|
||||
#endif /* GINT_GINT */
|
||||
|
|
|
@ -117,4 +117,17 @@ int intc_priority(int intname, int level);
|
|||
Returns the VBR address where the handler was installed. */
|
||||
void *intc_handler(int event_code, void const *handler, size_t size);
|
||||
|
||||
/* intc_handler_function(): Install a function as an interrupt handler
|
||||
|
||||
This function can be used to install simple interrupt handlers. It installs
|
||||
a pre-written interrupt handler that calls back the provided function.
|
||||
Essentially it means that the interrupt handler can be written in C without
|
||||
the numerous constraints of intc_handler(), at the cost of always going back
|
||||
to userspace and a small time overhead.
|
||||
|
||||
@event_code Identifier of the interrupt block
|
||||
@function Function to use as a handler
|
||||
Returns true on success, false if the event code is invalid. */
|
||||
bool intc_handler_function(int event_code, void (*function)(void));
|
||||
|
||||
#endif /* GINT_INTC */
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#define GINT_RTC
|
||||
|
||||
#include <gint/defs/types.h>
|
||||
#include <gint/defs/call.h>
|
||||
#include <gint/timer.h>
|
||||
|
||||
//---
|
||||
|
@ -43,7 +44,7 @@ void rtc_set_time(rtc_time_t const *time);
|
|||
uint32_t rtc_ticks(void);
|
||||
|
||||
//---
|
||||
// RTC timer
|
||||
// RTC periodic interrupt
|
||||
// The real-time clock produces a regular interrupt which may be used as a
|
||||
// timer with a maximum frequency of 256 Hz. It is also useful to check
|
||||
// that the clock settings (see <gint/clock.h>) are properly detected, by
|
||||
|
@ -63,34 +64,36 @@ enum
|
|||
RTC_NONE = 0,
|
||||
};
|
||||
|
||||
/* rtc_start_timer(): Configure the RTC timer
|
||||
/* rtc_periodic_enable(): Enable the periodic interrupt
|
||||
|
||||
This function sets up the RTC timer to invoke the provided callback function
|
||||
with its argument regularly. This works like standard timers. The callback
|
||||
is a function, the allowed types are listed in <gint/timer.h>. It may have
|
||||
zero or one argument, and must return either TIMER_CONTINUE or TIMER_STOP.
|
||||
This function sets up the periodic interrupt to invoke the provided callback
|
||||
regularly. As with timers, the callback must return either TIMER_CONTINUE or
|
||||
TIMER_STOP.
|
||||
|
||||
Do not confuse this "RTC timer" with Casio's added timers that run at
|
||||
32768 Hz (which are called "RTC timers" in CPU73050.dll). These timers are
|
||||
called Extra TMU in gint and are handled by <gint/timer.h>.
|
||||
Do not confuse this interrupt with CASIO's extra timers that run at 32768 Hz
|
||||
(which are called "RTC timers" in CPU73050.dll). These timers are called
|
||||
Extra TMU or ETMU in gint and are handled by <gint/timer.h>.
|
||||
|
||||
Note that the timing of the first callback is always uncertain. A 1 Hz timer
|
||||
set up when half of the current second is already elapsed will be called for
|
||||
the first time after only 500 ms, for instance.
|
||||
|
||||
@freq Timer frequency
|
||||
@callback Function to call back at the specified frequency
|
||||
@... Up to one 4-byte argument for the callback
|
||||
Fails and returns non-zero if the RTC timer is already in use. */
|
||||
int rtc_start_timer(int freq, timer_callback_t callback, ...);
|
||||
@frequency Periodic interrupt frequency
|
||||
@callback Function to call back at the specified frequency
|
||||
Returns true on success, false if the interrupt is already in use. */
|
||||
bool rtc_periodic_enable(int frequency, gint_call_t callback);
|
||||
|
||||
/* Makes sure an argument is always provided, for va_arg() */
|
||||
/* rtc_periodic_disable(): Stop the periodic interrupt
|
||||
|
||||
This has the same effect as returning TIMER_STOP from the callback, or
|
||||
setting RTC_NONE as the parameter for rtc_periodic_enable(). */
|
||||
void rtc_periodic_disable(void);
|
||||
|
||||
/* Deprecated versions with old-style callbacks and more confusing names */
|
||||
__attribute__((deprecated("Use rtc_periodic_enable() instead")))
|
||||
int rtc_start_timer(int frequency, timer_callback_t callback, ...);
|
||||
#define rtc_start_timer(...) rtc_start_timer(__VA_ARGS__, 0)
|
||||
|
||||
/* rtc_stop_timer(): Stop the RTC timer
|
||||
This function stops the RTC timer that was set up with rtc_start_timer(). If
|
||||
the decision of stopping the timer comes from the callback, it is preferable
|
||||
to return TIMER_STOP. */
|
||||
__attribute__((deprecated("Use rtc_periodic_disable() instead")))
|
||||
void rtc_stop_timer(void);
|
||||
|
||||
#endif /* GINT_RTC */
|
||||
|
|
|
@ -5,14 +5,15 @@
|
|||
#ifndef GINT_TIMER
|
||||
#define GINT_TIMER
|
||||
|
||||
#include <gint/defs/types.h>
|
||||
#include <gint/mpu/tmu.h>
|
||||
#include <gint/hardware.h>
|
||||
#include <gint/defs/types.h>
|
||||
#include <gint/defs/call.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.
|
||||
If you're new to timers, read this comment and then check timer_configure()
|
||||
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
|
||||
|
@ -23,7 +24,7 @@
|
|||
* 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
|
||||
You can request "a" timer with timer_configure(), 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.
|
||||
|
||||
|
@ -33,13 +34,13 @@
|
|||
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
|
||||
Most of the time you can just use timer_configure() 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.
|
||||
* Set a specific ID in timer_configure(), 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
|
||||
|
@ -48,7 +49,7 @@
|
|||
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()
|
||||
In this module, timers are manipulated through their ID. timer_configure()
|
||||
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). */
|
||||
|
||||
|
@ -62,8 +63,8 @@
|
|||
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. */
|
||||
value to a chosen ID in timer_configure() 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,
|
||||
|
@ -71,37 +72,13 @@ enum {
|
|||
TIMER_Pphi_256 = 0x30,
|
||||
};
|
||||
|
||||
/* Timer selection; see timer_setup() */
|
||||
/* Timer selection; see timer_configure() */
|
||||
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 {
|
||||
|
@ -113,7 +90,7 @@ enum {
|
|||
// Timer functions
|
||||
//---
|
||||
|
||||
/* timer_setup(): Reserve and configure a timer
|
||||
/* timer_configure(): 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
|
||||
|
@ -126,15 +103,15 @@ enum {
|
|||
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.
|
||||
* TIMER_ANY will let timer_configure() choose any available timer.
|
||||
timer_configure() 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_configure() 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()
|
||||
If no timer matching the supplied settings is available, timer_configure()
|
||||
returns -1.
|
||||
|
||||
The second argument is the delay. With TIMER_ANY, TIMER_TMU and TIMER_ETMU,
|
||||
|
@ -142,28 +119,23 @@ enum {
|
|||
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.
|
||||
The third argument is an indirect function call to be made when the timer
|
||||
expires. You can create one with GINT_CALL(); the function must return an
|
||||
int equal to either TIMER_CONTINUE or TIMER_STOP to control the subsequent
|
||||
operation of the timer. Default calls are available in <gint/defs/call.h>
|
||||
with common operations that are useful in timers. If GINT_CALL_NULL is
|
||||
passed, 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:
|
||||
returned by timer_configure() 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)
|
||||
@callback Function to be called when the timer fires */
|
||||
int timer_configure(int timer, uint64_t delay_us, gint_call_t callback);
|
||||
|
||||
/* timer_start(): Start a configured timer
|
||||
The specified timer will start counting down and call its callback function
|
||||
|
@ -227,13 +199,41 @@ uint32_t timer_delay(int timer, uint64_t delay_us, int clock);
|
|||
void timer_reload(int timer, uint32_t delay);
|
||||
|
||||
//---
|
||||
// Predefined timer callbacks
|
||||
// Deprecated API
|
||||
//
|
||||
// These types and functions were used in previous versions of gint but have
|
||||
// been replaced. They are still here for compatibility until gint 3.
|
||||
//---
|
||||
|
||||
/* Type of callback functions; a primitive predecessor to GINT_CALL() */
|
||||
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;
|
||||
|
||||
/* timer_setup(): Old variant of timer_configure() */
|
||||
__attribute__((deprecated("Use timer_configure() instead")))
|
||||
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_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. */
|
||||
Use GINT_CALL_SET_STOP() with timer_configure() instead. */
|
||||
int timer_timeout(volatile void *arg);
|
||||
|
||||
#endif /* GINT_TIMER */
|
||||
|
|
|
@ -101,7 +101,8 @@ GCONSTRUCTOR static void gray_init(void)
|
|||
}
|
||||
|
||||
/* Try to obtain the timer right away */
|
||||
timer = timer_setup(GRAY_TIMER | GRAY_CLOCK, 1000, gray_int);
|
||||
timer = timer_configure(GRAY_TIMER | GRAY_CLOCK, 1000,
|
||||
GINT_CALL(gray_int));
|
||||
|
||||
/* On failure, release the resources that we obtained */
|
||||
if(!gray_isinit()) gray_quit();
|
||||
|
|
|
@ -82,19 +82,16 @@ static struct info {
|
|||
The _inth_remap table in src/kernel/inth.S combines the SH3-SH4 translation
|
||||
with the compact translation, hence its entry for 0xf00 (the SH3 event code
|
||||
for ETMU0 underflow) is the offset in this table where 0x9e0 (the SH4 event
|
||||
code for the same event) is stored, which is 4. */
|
||||
code for the same event) is stored, which is 3. */
|
||||
static const uint16_t sh3_vbr_map[] = {
|
||||
0x400, /* TMU0 underflow */
|
||||
0x420, /* TMU1 underflow */
|
||||
0x440, /* TMU2 underflow */
|
||||
0x460, /* (gint custom: TMU helper) */
|
||||
0x9e0, /* ETMU0 underflow */
|
||||
0xd00, /* ETMU4 underflow (used as helper on SH3) */
|
||||
0xd20, /* (gint custom: ETMU helper) */
|
||||
0xd40, /* (gint custom: ETMU helper) */
|
||||
0xd00, /* ETMU logic #1 (ETMU4 underflow) */
|
||||
1, /* ETMU logic #2 */
|
||||
1, /* ETMU logic #3 */
|
||||
0xaa0, /* RTC Periodic Interrupt */
|
||||
1, /* (Filler to maintain the gap between 0xaa0 and 0xae0) */
|
||||
0xae0, /* (gint custom: RTC helper) */
|
||||
0
|
||||
};
|
||||
|
||||
|
@ -173,6 +170,19 @@ void *intc_handler(int event_code, const void *handler, size_t size)
|
|||
return memcpy(dest, handler, size);
|
||||
}
|
||||
|
||||
bool intc_handler_function(int event_code, void (*function)(void))
|
||||
{
|
||||
/* Install the genric handler */
|
||||
extern void intc_generic_handler(void);
|
||||
void *h = intc_handler(event_code, intc_generic_handler, 32);
|
||||
if(!h) return false;
|
||||
|
||||
/* Set the function to be called */
|
||||
*(void **)(h + 24) = function;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//---
|
||||
// State and driver metadata
|
||||
//---
|
||||
|
|
33
src/intc/inth.s
Normal file
33
src/intc/inth.s
Normal file
|
@ -0,0 +1,33 @@
|
|||
/* gint.intc.inth: Standard interrupt handler
|
||||
|
||||
This is a generic interrupt handler that calls back into a C function,
|
||||
useful for complex handling or simple drivers that benefit more from
|
||||
simplicity than razor-sharp performance. */
|
||||
|
||||
.global _intc_generic_handler /* 32 bytes */
|
||||
|
||||
.section .gint.blocks, "ax"
|
||||
.align 4
|
||||
|
||||
_intc_generic_handler:
|
||||
/* Create a GINT_CALL() object with no arguments */
|
||||
sts.l pr, @-r15
|
||||
add #-20, r15
|
||||
mov.l .function, r0
|
||||
mov.l r0, @r15
|
||||
|
||||
/* Call back to the function in userspace */
|
||||
mov.l .gint_inth_callback, r0
|
||||
mov.l @r0, r0
|
||||
jsr @r0
|
||||
mov r15, r4
|
||||
|
||||
add #20, r15
|
||||
lds.l @r15+, pr
|
||||
rts
|
||||
nop
|
||||
|
||||
.function:
|
||||
.long 0 /* Function, set when handler is installed */
|
||||
.gint_inth_callback:
|
||||
.long _gint_inth_callback
|
|
@ -160,9 +160,9 @@ _gint_inth_7705:
|
|||
|
||||
VBR offset SH3 events Description
|
||||
-------------------------------------------------------------------
|
||||
0x200 400 420 440 --- TMU0, TMU1, TMU2 and a helper
|
||||
0x280 f00 --- --- --- ETMU0, ETMU1, ETMU2 and a helper
|
||||
0x300 4a0 [ ] --- RTC Periodic Interrupt, gap and helper
|
||||
0x200 400 420 440 TMU0, TMU1, TMU2
|
||||
0x260 f00 --- --- --- ETMU0, 3-gate logic at ETMU4
|
||||
0x2e0 4a0 RTC Periodic Interrupt
|
||||
-------------------------------------------------------------------
|
||||
0x600 --- --- Entry gate
|
||||
-------------------------------------------------------------------
|
||||
|
@ -171,7 +171,7 @@ _gint_inth_7705:
|
|||
the interrupt entry gate at VBR + 0x640. */
|
||||
|
||||
_inth_remap:
|
||||
.byte 0, 1, 2, 0xff, 0xff, 8, 0xff, 0xff
|
||||
.byte 0, 1, 2, 0xff, 0xff, 7, 0xff, 0xff
|
||||
.byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
.byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
.byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
|
@ -182,10 +182,12 @@ _inth_remap:
|
|||
.byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
.byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
.byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
.byte 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
.byte 3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
|
||||
#endif
|
||||
|
||||
.section .gint.mapped, "ax"
|
||||
|
||||
/* CALLBACK HELPER
|
||||
This function implements the callback with context saving. It is a general
|
||||
function and does not need to reside in VBR space which is block-structured.
|
||||
|
@ -193,15 +195,9 @@ _inth_remap:
|
|||
switches back to user bank and executes the callback. It does not save other
|
||||
registers (pr/mach/macl/gbr) which are managed by the handler entry. */
|
||||
|
||||
/* gint_inth_callback()
|
||||
Calls the specified function with the given argument after saving the user
|
||||
context, enabling interrupts and going to user bank.
|
||||
|
||||
@r4 Callback function (volatile void * -> int)
|
||||
@r5 Argument (volatile void *)
|
||||
Returns the return value of the callback (int). */
|
||||
|
||||
.section .gint.mapped, "ax"
|
||||
/* gint_inth_callback: Indirect call from kernel space to userland
|
||||
@r4 Address of callback function
|
||||
-> Returns the return value of the callback (int). */
|
||||
_gint_inth_callback_reloc:
|
||||
stc.l r0_bank, @-r15
|
||||
stc.l r1_bank, @-r15
|
||||
|
@ -218,7 +214,6 @@ _gint_inth_callback_reloc:
|
|||
/* Save some values to user bank; once we enable interrupts, the kernel
|
||||
bank might be overwritten at any moment. */
|
||||
ldc r4, r0_bank
|
||||
ldc r5, r4_bank
|
||||
|
||||
/* Enable interrupts and go back to user bank. On SH4, SR.IMASK is set
|
||||
to the level of the current interrupt, which makes sure we can only
|
||||
|
@ -248,10 +243,15 @@ _gint_inth_callback_reloc:
|
|||
.load_sr:
|
||||
ldc r1, sr
|
||||
|
||||
/* We are now in the user bank with r0 and r4 set. Call back. We want
|
||||
/* We are now in the user bank with r0 set. Perform the call. We want
|
||||
to forward the return value to kernel bank, but this bank can be
|
||||
changed at any moment since interrupts are enabled. */
|
||||
sts.l pr, @-r15
|
||||
mov.l @(4, r0), r4
|
||||
mov.l @(8, r0), r5
|
||||
mov.l @(12, r0), r6
|
||||
mov.l @(16, r0), r7
|
||||
mov.l @r0, r0
|
||||
jsr @r0
|
||||
nop
|
||||
lds.l @r15+, pr
|
||||
|
|
|
@ -167,7 +167,8 @@ static void configure(void)
|
|||
getkey_repeat(400, 40);
|
||||
|
||||
/* The timer will be stopped when the timer driver is unloaded */
|
||||
keysc_tid = timer_setup(TIMER_ANY, scan_frequency_us, keysc_tick);
|
||||
keysc_tid = timer_configure(TIMER_ANY, scan_frequency_us,
|
||||
GINT_CALL(keysc_tick));
|
||||
if(keysc_tid >= 0) timer_start(keysc_tid);
|
||||
|
||||
gint[HWKBD] = HW_LOADED | (isSH3() ? HWKBD_IO : HWKBD_KSI);
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
** gint:rtc:inth - Interrupt handler for the Real-Time Clock
|
||||
** This one is fairly simple, just a flag to clear and potentially a timer to
|
||||
** stop if the callback returns non-zero.
|
||||
*/
|
||||
|
||||
.global _inth_rtc_pri
|
||||
.global _inth_rtc_pri_helper
|
||||
.section .gint.blocks, "ax"
|
||||
.align 4
|
||||
|
||||
/* RTC PERIODIC INTERRUPT HANDLER - 56 BYTES */
|
||||
|
||||
_inth_rtc_pri:
|
||||
/* Invoke the callback function with its argument */
|
||||
sts.l pr, @-r15
|
||||
mov.l .gint_inth_callback, r0
|
||||
mov.l @r0, r0
|
||||
mov.l 1f, r4
|
||||
mov.l 2f, r5
|
||||
jsr @r0
|
||||
nop
|
||||
|
||||
/* Jump to another gate to finish the work:
|
||||
- 0xc is the size of storage below
|
||||
- 0x20 is the size of the gap before next gate (alarm interrupt) */
|
||||
mov #0x2c, r2
|
||||
braf r2
|
||||
nop
|
||||
|
||||
1: .long 0 /* Callback function: edited dynamically */
|
||||
2: .long 0 /* Argument to callback function */
|
||||
|
||||
.gint_inth_callback:
|
||||
.long _gint_inth_callback
|
||||
|
||||
_inth_rtc_pri_helper:
|
||||
|
||||
/* Save the return value */
|
||||
mov r0, r3
|
||||
|
||||
.clear:
|
||||
/* Clear the interrupt flag */
|
||||
mov.l .RCR2, r1
|
||||
mov.b @r1, r0
|
||||
tst #0x80, r0
|
||||
and #0x7f, r0
|
||||
bf.s .clear
|
||||
mov.b r0, @r1
|
||||
|
||||
/* Stop the timer if the return value of the callback was non-zero */
|
||||
tst r3, r3
|
||||
bt .end
|
||||
and #0x8f, r0
|
||||
mov.b r0, @r1
|
||||
|
||||
.end:
|
||||
lds.l @r15+, pr
|
||||
rts
|
||||
nop
|
||||
|
||||
.RCR2: .long 0xa413fede /* RCR2 address, edited at startup on SH3 */
|
|
@ -13,19 +13,9 @@
|
|||
|
||||
#include <stdarg.h>
|
||||
|
||||
#undef rtc_start_timer
|
||||
|
||||
//---
|
||||
// Real-Time Clock peripheral registers
|
||||
//---
|
||||
|
||||
/* RTC address on SH7305, adjusted at startup on SH7337 and SH7355 */
|
||||
static rtc_t *RTC = &SH7305_RTC;
|
||||
/* Address of interrupt handler parameters */
|
||||
GBSS static struct {
|
||||
void *function;
|
||||
uint32_t arg;
|
||||
} GPACKED(4) *timer_params;
|
||||
|
||||
|
||||
//---
|
||||
// Time management
|
||||
|
@ -94,42 +84,67 @@ void rtc_set_time(rtc_time_t const *time)
|
|||
}
|
||||
|
||||
//---
|
||||
// RTC timer
|
||||
// RTC periodic interrupt
|
||||
//---
|
||||
|
||||
/* rtc_start_timer(): Configure the RTC timer */
|
||||
int rtc_start_timer(int freq, timer_callback_t f, ...)
|
||||
{
|
||||
/* Refuse to override an existing callback */
|
||||
if(RTC->RCR2.PES != RTC_NONE) return 1;
|
||||
if(freq == RTC_NONE) return 1;
|
||||
static gint_call_t rtc_periodic_callback;
|
||||
|
||||
/* Get the argument */
|
||||
va_list args;
|
||||
va_start(args, f);
|
||||
uint32_t arg = va_arg(args, uint32_t);
|
||||
va_end(args);
|
||||
bool rtc_periodic_enable(int frequency, gint_call_t callback)
|
||||
{
|
||||
/* Refuse to override an existing interrupt */
|
||||
if(RTC->RCR2.PES != RTC_NONE) return false;
|
||||
if(frequency == RTC_NONE)
|
||||
{
|
||||
rtc_periodic_disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Temporarily disable the interrupt and set up the callback */
|
||||
RTC->RCR2.PES = RTC_NONE;
|
||||
timer_params->function = f.v;
|
||||
timer_params->arg = arg;
|
||||
rtc_periodic_callback = callback;
|
||||
|
||||
/* Clear the interrupt flag */
|
||||
do RTC->RCR2.PEF = 0;
|
||||
while(RTC->RCR2.PEF);
|
||||
|
||||
/* Enable the interrupt */
|
||||
RTC->RCR2.PES = freq;
|
||||
return 0;
|
||||
RTC->RCR2.PES = frequency;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* rtc_stop_timer(): Stop the RTC timer */
|
||||
void rtc_stop_timer(void)
|
||||
void rtc_periodic_interrupt(void)
|
||||
{
|
||||
int rc = gint_call(rtc_periodic_callback);
|
||||
|
||||
/* Clear the interrupt flag */
|
||||
do RTC->RCR2.PEF = 0;
|
||||
while(RTC->RCR2.PEF);
|
||||
|
||||
/* Stop the interrupt if the callback returns non-zero */
|
||||
if(rc) rtc_periodic_disable();
|
||||
}
|
||||
|
||||
void rtc_periodic_disable(void)
|
||||
{
|
||||
RTC->RCR2.PES = RTC_NONE;
|
||||
}
|
||||
|
||||
/* Deprecated versions */
|
||||
#undef rtc_start_timer
|
||||
int rtc_start_timer(int frequency, timer_callback_t function, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, function);
|
||||
uint32_t arg = va_arg(args, uint32_t);
|
||||
va_end(args);
|
||||
|
||||
return !rtc_periodic_enable(frequency, GINT_CALL(function.v, arg));
|
||||
}
|
||||
void rtc_stop_timer(void)
|
||||
{
|
||||
return rtc_periodic_disable();
|
||||
}
|
||||
|
||||
//---
|
||||
// Driver initialization
|
||||
//---
|
||||
|
@ -148,19 +163,8 @@ static void configure(void)
|
|||
/* Clear the periodic interrupt flag */
|
||||
RTC->RCR2.PEF = 0;
|
||||
|
||||
/* Interrupt handlers provided by rtc/inth.s */
|
||||
extern void inth_rtc_pri(void);
|
||||
extern void inth_rtc_pri_helper(void);
|
||||
|
||||
/* Install the RTC interrupt handler */
|
||||
GUNUSED void *h0, *h1;
|
||||
h0 = intc_handler(0xaa0, inth_rtc_pri, 32);
|
||||
h1 = intc_handler(0xae0, inth_rtc_pri_helper, 32);
|
||||
|
||||
timer_params = h0 + 20;
|
||||
|
||||
volatile uint8_t **RCR2_pointer = h1 + 28;
|
||||
*RCR2_pointer = &RTC->RCR2.byte;
|
||||
intc_handler_function(0xaa0, rtc_periodic_interrupt);
|
||||
|
||||
/* Disable the RTC interrupts for now. Give them priority 1; higher
|
||||
priorities cause freezes when going back to the system on SH3
|
||||
|
|
|
@ -1,80 +1,77 @@
|
|||
/*
|
||||
** gint:tmu:inth-etmu - Interrupt handlers for the RTC-bound timers
|
||||
** gint:tmu:inth-etmu - Interrupt handlers for the RTC-bound timers
|
||||
**
|
||||
** This handler uses 3 consecutive blocks like the TMU handler. However this
|
||||
** time 2 empty blocks after ETMU4 (0xd20, 0xd40) are used because blocks for
|
||||
** ETMU are not consecutive in memory.
|
||||
**
|
||||
** It would be possible to communicate between any interrupt handlers in non-
|
||||
** consecutive gates. A simple way is to store at runtime a pointer to the
|
||||
** desired object in one handler. But that costs a lot of space. If the
|
||||
** relative position of interrupt handlers is known, the best option left is
|
||||
** the unnatural @(disp,pc) addressing mode, and it doesn't even work with the
|
||||
** SH3's compact VBR scheme.
|
||||
*/
|
||||
|
||||
/* Gates for the extra timers (informally called ETMU) */
|
||||
.global _inth_etmu4
|
||||
.global _inth_etmux
|
||||
.global _inth_etmu4 /* 96 bytes */
|
||||
.global _inth_etmux /* 32 bytes */
|
||||
|
||||
.section .gint.blocks, "ax"
|
||||
.align 4
|
||||
|
||||
/* EXTRA TMU INTERRUPT HANDLERS - 96 BYTES
|
||||
To implement the same functionalities as the standard timers, several blocks
|
||||
are once again needed. This time, 2 empty blocks after ETMU4 (0xd20, 0xd40)
|
||||
are used for convenience.
|
||||
|
||||
It would be possible to communicate between any interrupt handlers in non-
|
||||
consecutive gates. A simple way is to store at runtime a pointer to the
|
||||
desired object in one handler. But that costs a lot fo space. If the
|
||||
relative position of interrupt handlers is known, the best option left is
|
||||
the unnatural @(disp,pc) addressing mode, and it doesn't even work with the
|
||||
SH3's compact VBR scheme. */
|
||||
|
||||
/* FIRST GATE - ETMU4 and two empty blocks */
|
||||
/* 3-block handler installed at the ETMU4 gate. */
|
||||
_inth_etmu4:
|
||||
mova .storage_etmu4, r0
|
||||
mov #7, r2
|
||||
|
||||
.shared:
|
||||
mov.l r2, @-r15
|
||||
mov.l r8, @-r15
|
||||
sts.l pr, @-r15
|
||||
mov r0, r1
|
||||
|
||||
/* Prepare an indirect call to timer_stop(<id>) */
|
||||
add #-20, r15
|
||||
mov.l r2, @(4, r15)
|
||||
|
||||
/* Clear interrupt flag in TCR */
|
||||
mov.l @(8, r1), r3
|
||||
1:
|
||||
mov.b @r3, r0
|
||||
mov r0, r1
|
||||
mov.l @(4, r1), r3
|
||||
1: mov.b @r3, r0
|
||||
tst #0x02, r0
|
||||
and #0xfd, r0
|
||||
bf/s 1b
|
||||
mov.b r0, @r3
|
||||
|
||||
/* Prepare invoking the callback function */
|
||||
/* Invoke callback */
|
||||
mov.l .gint_inth_callback, r8
|
||||
mov.l @r8, r8
|
||||
mov.l @r1, r4
|
||||
jsr @r8
|
||||
mov.l @(4, r1), r5
|
||||
mov.l @r1, r4
|
||||
tst r0, r0
|
||||
bt 2f
|
||||
|
||||
/* Invoke callback; if return value is non-zero, stop timer */
|
||||
mov.l .timer_stop, r4
|
||||
/* If return value is non-zero, stop the timer with another callback */
|
||||
mov.l .timer_stop, r0
|
||||
mov.l r0, @r15
|
||||
jsr @r8
|
||||
mov.l @(8, r15), r5
|
||||
mov r15, r4
|
||||
|
||||
/* Clear the flag and possibly stop the timer */
|
||||
2:
|
||||
2: add #20, r15
|
||||
lds.l @r15+, pr
|
||||
mov.l @r15+, r8
|
||||
rts
|
||||
add #4, r15
|
||||
mov.l @r15+, r8
|
||||
|
||||
.zero 24
|
||||
.zero 26
|
||||
|
||||
.gint_inth_callback:
|
||||
.long _gint_inth_callback
|
||||
.timer_stop:
|
||||
.long _timer_stop
|
||||
|
||||
.gint_inth_callback:
|
||||
.long _gint_inth_callback
|
||||
.storage_etmu4:
|
||||
.long 0 /* Callback: Configured dynamically */
|
||||
.long 0 /* Argument: Configured dynamically */
|
||||
.long 0 /* TCR: Configured dynamically */
|
||||
.long _tmu_callbacks + 140
|
||||
.long 0xa44d00bc /* RTCR4 */
|
||||
|
||||
/* SECOND GATE - All other ETMU entries, falling back to ETMU2 */
|
||||
/* Generic gate for all other ETMU handlers, falling back to ETMU4. */
|
||||
_inth_etmux:
|
||||
/* Dynamically compute the target of the jump */
|
||||
stc vbr, r3
|
||||
|
@ -85,20 +82,15 @@ _inth_etmux:
|
|||
mov.w .id_etmux, r2
|
||||
jmp @r3
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
|
||||
.id_etmux:
|
||||
.word 0 /* Timer ID */
|
||||
.word 0 /* Timer ID */
|
||||
|
||||
/* Offset from VBR where extra timer 2 is located:
|
||||
* 0x600 to reach the interrupt handlers
|
||||
* 0x040 to jump over the entry gate
|
||||
* 0x900 to reach the handler of ETMU4
|
||||
* Skip over the first instructions
|
||||
This is different on SH3 due to the compact scheme so it's edited
|
||||
dynamically at install time. */
|
||||
1: .long 0xf40 + (.shared - _inth_etmu4)
|
||||
/* Offset from VBR where ETMU4 is located; set during configure */
|
||||
1: .long (.shared - _inth_etmu4)
|
||||
|
||||
.storage_etmux:
|
||||
.long 0 /* Callback: Configured dynamically */
|
||||
.long 0 /* Argument: Configured dynamically */
|
||||
.long 0 /* TCR: Configured dynamically */
|
||||
.long _tmu_callbacks
|
||||
.long 0 /* TCR address */
|
||||
|
|
|
@ -1,121 +1,94 @@
|
|||
/*
|
||||
** gint:tmu:inth-tmu - Interrupt handlers for the timer units
|
||||
** Perhaps the most technical of my interrupt handlers. They implement a
|
||||
** simple kind of interrupt handler communication by letting the control flow
|
||||
** from each interrupt handler to the next.
|
||||
** gint:tmu:inth-tmu - Interrupt handlers for the timer units
|
||||
**
|
||||
** This handler consists of 3 consecutive gates that operate as a block. It
|
||||
** clears the interrupt flags, invokes a GINT_CALL() in userspace, and stops
|
||||
** the timer if the callback returns non-zero.
|
||||
**
|
||||
** It is important to notice that the code of the gates is continuous in this
|
||||
** file and thus must be continuous in memory, as the assembler will use
|
||||
** relative addressing methods. This "block operations" is only possible for
|
||||
** handlers that are mapped to consecutive event codes.
|
||||
*/
|
||||
|
||||
/* Gates for the standard Timer Unit (TMU) */
|
||||
.global _inth_tmu /* 128 bytes */
|
||||
.global _inth_tmu /* 96 bytes */
|
||||
|
||||
.section .gint.blocks, "ax"
|
||||
.align 4
|
||||
|
||||
/* TMU INTERRUPT HANDLERS - 128 BYTES
|
||||
Unfortunately I did not manage to write a handler that cleared the interrupt
|
||||
flag and invoked a callback in less than 34 bytes data included. So I
|
||||
decided to make several gates operate as a whole and add a bit more features
|
||||
in them. Basically, these handlers:
|
||||
- Clear the interrupt flag
|
||||
- Invoke a callback function and pass it a user-provided argument
|
||||
- Stop the timer if the callback returns non-zero
|
||||
- Host their own callback pointers and arguments
|
||||
|
||||
It is important to notice that the code of the following gates looks like
|
||||
they are contiguous in memory. The assembler will make that assumption, and
|
||||
turn any address reference between two gates into a *relative displacement*.
|
||||
If the gates don't have the same relative location at runtime, the code will
|
||||
crash because we will have broken the references. This is why we can only do
|
||||
it with handlers that are mapped to consecutive event codes. */
|
||||
|
||||
/* TMU0 entry and interrupt flag clearing. */
|
||||
_inth_tmu:
|
||||
mov #0, r5
|
||||
mov #0, r6
|
||||
mov #0, r7
|
||||
|
||||
/* FIRST GATE - TMU0 entry, clear underflow flag and call back */
|
||||
_inth_tmu_0:
|
||||
mova .storage0, r0
|
||||
mov #0, r1
|
||||
|
||||
/*** This is the first shared section ***/
|
||||
.shared1:
|
||||
mov.l .TCR0, r1
|
||||
add r6, r1
|
||||
|
||||
/* Save the timer ID on the stack */
|
||||
mov.l r8, @-r15
|
||||
sts.l pr, @-r15
|
||||
mov.l r1, @-r15
|
||||
mov.l r5, @-r15
|
||||
|
||||
/* Load the TCR address */
|
||||
mov.l .mask, r3
|
||||
not r3, r4
|
||||
mov.l @(8, r0), r1
|
||||
/* Clear the interrupt flag. Because r5 contains 0, 1 or 2 the 16 top
|
||||
bits are 0 so we can compare without extending */
|
||||
1: mov.w @r1, r5
|
||||
extu.b r5, r3
|
||||
cmp/eq r5, r3
|
||||
bf/s 1b
|
||||
mov.w r3, @r1
|
||||
|
||||
/* Clear the interrupt flag */
|
||||
1: mov.w @r1, r2
|
||||
tst r4, r2
|
||||
and r3, r2
|
||||
mov.w r2, @r1
|
||||
bf 1b
|
||||
|
||||
/* Prepare callback and jump to second section */
|
||||
mov.l .gint_inth_callback, r8
|
||||
/* Prepare to run the callback */
|
||||
mov.l .callback, r8
|
||||
bra .shared2
|
||||
mov.l @r8, r8
|
||||
|
||||
/* SECOND GATE - TMU1 entry and stop timer */
|
||||
/* TMU1 entry, callback and timer stop logic. */
|
||||
_inth_tmu_1:
|
||||
mova .storage1, r0
|
||||
mov #1, r5
|
||||
mov #12, r6
|
||||
bra .shared1
|
||||
mov #1, r1
|
||||
mov #20, r7
|
||||
|
||||
/*** This is the second shared section ***/
|
||||
.shared2:
|
||||
/* Invoke callback */
|
||||
mov.l @r0, r4
|
||||
mov.l .tmu_callbacks, r4
|
||||
jsr @r8
|
||||
mov.l @(4, r0), r5
|
||||
|
||||
/* Stop the timer if the return value is not zero */
|
||||
mov.l @r15+, r5
|
||||
add r7, r4
|
||||
tst r0, r0
|
||||
bt 2f
|
||||
mov.l .timer_stop, r4
|
||||
mov.l .timer_stop, r2
|
||||
bt/s .shared3
|
||||
mov.l r2, @-r15
|
||||
|
||||
/* Stop the timer if the return value is not zero. We use the top of
|
||||
the stack as a gint_call_t object; only the function and first
|
||||
argument matter, timer_stop() will ignore the rest. */
|
||||
jsr @r8
|
||||
mov r15, r4
|
||||
bra .shared3
|
||||
nop
|
||||
nop
|
||||
|
||||
2:
|
||||
/* TMU2 entry, shared exit and storage. */
|
||||
_inth_tmu_2:
|
||||
mov #2, r5
|
||||
mov #24, r6
|
||||
bra .shared1
|
||||
mov #40, r7
|
||||
|
||||
.shared3:
|
||||
add #8, r15
|
||||
lds.l @r15+, pr
|
||||
rts
|
||||
mov.l @r15+, r8
|
||||
|
||||
.zero 2
|
||||
|
||||
/* THIRD GATE - TMU2 entry and storage for TMU0 */
|
||||
_inth_tmu_2:
|
||||
mova .storage2, r0
|
||||
bra .shared1
|
||||
mov #2, r1
|
||||
|
||||
.zero 10
|
||||
|
||||
.gint_inth_callback:
|
||||
.long _gint_inth_callback
|
||||
|
||||
.storage0:
|
||||
.long 0 /* Callback: Configured dynamically */
|
||||
.long 0 /* Argument: Configured dynamically */
|
||||
.long 0xa4490010 /* TCR0: Overridden at startup on SH3 */
|
||||
|
||||
/* FOURTH GATE - Storage for TMU1, TMU2 and other values */
|
||||
_inth_tmu_storage:
|
||||
|
||||
.mask:
|
||||
.long 0xfffffeff
|
||||
.timer_stop:
|
||||
.long _timer_stop
|
||||
|
||||
.storage1:
|
||||
.long 0 /* Callback: Configured dynamically */
|
||||
.long 0 /* Argument: Configured dynamically */
|
||||
.long 0xa449001c /* TCR1: Overridden at startup on SH3 */
|
||||
|
||||
.storage2:
|
||||
.long 0 /* Callback: Configured dynamically */
|
||||
.long 0 /* Argument: Configured dynamically */
|
||||
.long 0xa4490028 /* TCR2: Overridden at startup on SH3 */
|
||||
.callback:
|
||||
.long _gint_inth_callback
|
||||
.TCR0:
|
||||
.long 0xa4490010
|
||||
.tmu_callbacks:
|
||||
.long _tmu_callbacks
|
||||
|
|
|
@ -9,7 +9,8 @@ static void do_sleep(uint64_t delay_us, int spin)
|
|||
{
|
||||
volatile int flag = 0;
|
||||
|
||||
int timer = timer_setup(TIMER_ANY, delay_us, timer_timeout, &flag);
|
||||
int timer = timer_configure(TIMER_ANY, delay_us,
|
||||
GINT_CALL_SET_STOP(&flag));
|
||||
if(timer < 0) return;
|
||||
|
||||
timer_start(timer);
|
||||
|
|
184
src/tmu/tmu.c
184
src/tmu/tmu.c
|
@ -7,40 +7,29 @@
|
|||
#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>
|
||||
|
||||
#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 };
|
||||
/* 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, void *f, uint32_t arg)
|
||||
static void conf(int id, uint32_t delay, int clock, gint_call_t call)
|
||||
{
|
||||
if(id < 3)
|
||||
{
|
||||
|
@ -52,8 +41,7 @@ static void conf(int id, uint32_t delay, int clock, void *f, uint32_t arg)
|
|||
T->TCOR = delay;
|
||||
T->TCNT = delay;
|
||||
T->TCR.TPSC = clock;
|
||||
do T->TCR.UNF = 0;
|
||||
while(T->TCR.UNF);
|
||||
set(T->TCR.UNF, 0);
|
||||
|
||||
/* Enable interrupt and count on rising edge (SH7705) */
|
||||
T->TCR.UNIE = 1;
|
||||
|
@ -64,22 +52,14 @@ static void conf(int id, uint32_t delay, int clock, void *f, uint32_t arg)
|
|||
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);
|
||||
|
||||
/* No clock input and clock edge here */
|
||||
set(T->TCR.UNF, 0);
|
||||
set(T->TCOR, delay);
|
||||
set(T->TCNT, delay);
|
||||
T->TCR.UNIE = 1;
|
||||
}
|
||||
|
||||
timers[id]->function = f;
|
||||
timers[id]->arg = arg;
|
||||
tmu_callbacks[id] = call;
|
||||
}
|
||||
|
||||
/* matches(): Check if a timer matches the provided specification and delay */
|
||||
|
@ -99,8 +79,7 @@ static int matches(int id, int spec, uint32_t delay)
|
|||
/* 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 >= timer_count()) return 0;
|
||||
|
||||
if(id < 3)
|
||||
{
|
||||
|
@ -124,19 +103,12 @@ static int stop_callback(void)
|
|||
// Timer API
|
||||
//---
|
||||
|
||||
/* timer_setup(): Reserve and configure a timer */
|
||||
int timer_setup(int spec, uint64_t delay, timer_callback_t function, ...)
|
||||
int timer_configure(int spec, uint64_t delay, gint_call_t call)
|
||||
{
|
||||
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;
|
||||
/* 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 */
|
||||
|
@ -169,7 +141,7 @@ int timer_setup(int spec, uint64_t delay, timer_callback_t function, ...)
|
|||
/* 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);
|
||||
conf(id, delay, clock, call);
|
||||
return id;
|
||||
}
|
||||
|
||||
|
@ -196,10 +168,6 @@ uint32_t timer_delay(int id, uint64_t delay_us, int clock)
|
|||
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;
|
||||
}
|
||||
|
@ -246,18 +214,13 @@ void timer_stop(int id)
|
|||
}
|
||||
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;
|
||||
|
||||
/* 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);
|
||||
set(T->TCOR, 0xffffffff);
|
||||
set(T->TCNT, 0xffffffff);
|
||||
set(T->TCR.UNF, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,10 +256,20 @@ void timer_spinwait(int id)
|
|||
}
|
||||
|
||||
//---
|
||||
// Predefined timer callbacks
|
||||
// Deprecated API
|
||||
//---
|
||||
|
||||
/* timer_timeout() - callback that sets a flag and halts the timer */
|
||||
#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;
|
||||
|
@ -308,7 +281,7 @@ int timer_timeout(void volatile *arg)
|
|||
// Driver initialization
|
||||
//---
|
||||
|
||||
/* Interrupt handlers for standard timers (4 gates) */
|
||||
/* Interrupt handlers for standard timers (3 gates) */
|
||||
extern void inth_tmu(void);
|
||||
/* Interrupt handlers for extra timers */
|
||||
extern void inth_etmu4(void);
|
||||
|
@ -329,65 +302,44 @@ 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;
|
||||
/* 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 every timer to avoid surprises */
|
||||
for(int id = 0; id < 3; id++)
|
||||
/* Clear all timers */
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
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;
|
||||
set(TMU[i].TCR.word, 0);
|
||||
TMU[i].TCOR = 0xffffffff;
|
||||
TMU[i].TCNT = 0xffffffff;
|
||||
}
|
||||
for(int id = 0; id < timer_count()-3; id++)
|
||||
for(int i = 3; i < timer_count(); i++)
|
||||
{
|
||||
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. */
|
||||
etmu_t *T = &ETMU[i-3];
|
||||
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);
|
||||
set(T->TCOR, 0xffffffff);
|
||||
set(T->TCNT, 0xffffffff);
|
||||
set(T->TCR.byte, 0);
|
||||
}
|
||||
|
||||
/* Install the extra timers. On SH3, only ETMU0 is available */
|
||||
/* 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);
|
||||
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;
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
@ -448,16 +400,10 @@ static void hrestore(tmu_state_t const *s)
|
|||
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);
|
||||
|
||||
set(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);
|
||||
set(T->TCNT, c->TCNT);
|
||||
set(T->TCR.byte, c->TCR);
|
||||
}
|
||||
|
||||
*TSTR = s->TSTR;
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
** gint:usb:inth - Interrupt handler for the USB function module
|
||||
*/
|
||||
|
||||
.global _inth_usb
|
||||
|
||||
.section .gint.blocks, "ax"
|
||||
.align 4
|
||||
|
||||
/* USB INTERRUPT HANDLER */
|
||||
|
||||
_inth_usb:
|
||||
/* Call back into driver code, there is way too much work that needs to
|
||||
be done to fit in here */
|
||||
sts.l pr, @-r15
|
||||
mov.l 1f, r0
|
||||
mov.l @r0, r0
|
||||
mov.l 2f, r4
|
||||
jsr @r0
|
||||
nop
|
||||
|
||||
lds.l @r15+, pr
|
||||
rts
|
||||
nop
|
||||
|
||||
.zero 6
|
||||
|
||||
1: .long _gint_inth_callback
|
||||
2: .long _usb_interrupt_handler
|
|
@ -10,8 +10,7 @@
|
|||
|
||||
#define USB SH7305_USB
|
||||
|
||||
/* Interrupt handler */
|
||||
extern void inth_usb(void);
|
||||
static void usb_interrupt_handler(void);
|
||||
|
||||
/* Shorthand to clear a bit in INTSTS0 */
|
||||
#define INTSTS0_clear(field_name) { \
|
||||
|
@ -165,7 +164,7 @@ int usb_open(usb_interface_t const **interfaces, gint_call_t callback)
|
|||
USB.NRDYENB.word = 0x0000;
|
||||
USB.BEMPENB.word = 0x0000;
|
||||
|
||||
intc_handler(0xa20, inth_usb, 32);
|
||||
intc_handler_function(0xa20, usb_interrupt_handler);
|
||||
intc_priority(INTC_USB, 15);
|
||||
|
||||
usb_open_status = true;
|
||||
|
@ -191,7 +190,7 @@ void usb_close(void)
|
|||
// Userspace interrupt handler
|
||||
//---
|
||||
|
||||
void usb_interrupt_handler(void)
|
||||
static void usb_interrupt_handler(void)
|
||||
{
|
||||
static char const * const device_st[] = {
|
||||
"powered", "default", "address", "configured",
|
||||
|
|
|
@ -205,7 +205,8 @@ void usb_pipe_init_transfers(void);
|
|||
/* usb_while(): A while loop with a timeout */
|
||||
#define usb_while(condition) ({ \
|
||||
volatile int __f = 0; \
|
||||
int __t = timer_setup(TIMER_ANY, 100000 /*µs*/, timer_timeout, &__f); \
|
||||
int __t = timer_configure(TIMER_ANY, 100000 /*µs*/, \
|
||||
GINT_CALL_SET_STOP(&__f)); \
|
||||
if(__t >= 0) timer_start(__t); \
|
||||
while((condition) && __f == 0) {} \
|
||||
if(__f) usb_log("%s: %d: (" #condition ") holds\n", \
|
||||
|
|
Loading…
Reference in a new issue