diff --git a/Makefile b/Makefile index 907b636..9e84bf3 100755 --- a/Makefile +++ b/Makefile @@ -99,7 +99,7 @@ obj-std = $(foreach mod,$(modules-libc),$(mod-$(mod)-obj)) $(obj-std-spec) obj-lib = $(foreach mod,$(modules-gint),$(mod-$(mod)-obj)) $(obj-lib-spec) # Dependencies. -hdr-dep = $(wildcard include/*.h include/internals/*.h) +hdr-dep = $(wildcard include/*.h include/*/*.h) @@ -249,9 +249,9 @@ ifdef config_ext endif @ printf '\e[32;1mmsg \u00bb\e[0m All installed!\n' -install-demo: +install-demo: all p7 send -f $(target-g1a) -install-debug: +install-debug: all p7 send -f $(target-dbg) .PHONY: all-lib all help diff --git a/TODO b/TODO index 5be8293..45638bd 100644 --- a/TODO +++ b/TODO @@ -12,7 +12,8 @@ Simple improvements: - string: Use cmp/str to implement memchr() (assembler examples) - string: Do some tests for memcmp() - core: Register more interrupts (and understand their parameters) -- rtc: Take care of carry when reading time +- project: Clean headers that have some internal definitions +- project: Check size of all library structures Larger improvements: - errno: Introduce errno and use it more or less everywhere - bopti: Monochrome bitmaps blending modes @@ -24,6 +25,7 @@ Larger improvements: - usb: Implement a driver - esper: Cleaner playback, synthetizing - clock: Handle overclocking (relaunch clocks when overclocking) +- project: Unify this hellish mess of register access! Things to investigate: - Packed bit fields alignment diff --git a/configure b/configure index 5bd34db..5ff00f3 100755 --- a/configure +++ b/configure @@ -13,7 +13,7 @@ conf[GINT_EXTENDED_LIBC]= # Size limits conf[ATEXIT_MAX]=16 -conf[RTC_CB_ARRAY_SIZE]=5 +conf[TIMER_SLOTS]=16 conf[EVENTS_QUEUE_SIZE]=64 # Output files @@ -50,8 +50,8 @@ Options that affect the behavior of the library: Options that customize size limits: $Cr--atexit-max$C0=$Cy$Cg [default:$Cp 16$Cg]$C0 Number of exit handlers that can be registered by atexit(). - $Cr--rtc-callbacks$C0=$Cy$Cg [default:$Cp 5$Cg]$C0 - Number of RTC callbacks that can be registered. + $Cr--timer-slots$C0=$Cy$Cg [default:$Cp 16$Cg]$C0 + Number of virtual timers that may be registered at the same time. $Cr--events-queue-size$C0=$Cy$Cg [default:$Cp 64$Cg]$C0 Number of events simultaneously stored in the event queue. EOF @@ -76,11 +76,11 @@ for arg; do case "$arg" in conf[ATEXIT_MAX]=$size else echo -e "$error --atexit-max expects an integer value" fail=true; fi;; - --rtc-callbacks=*) + --timer-slots=*) size=${arg#*=} if [[ $size == +([0-9]) ]]; then - conf[RTC_CB_ARRAY_SIZE]=$size - else echo -e "$error --rtc-callbacks expects an integer value" + conf[TIMER_SLOTS]=$size + else echo -e "$error --timer-slots expects an integer value" fail=true; fi;; --events-queue-size=*) size=${arg#*=} @@ -90,7 +90,7 @@ for arg; do case "$arg" in "value" fail=true; fi;; - --atexit-max | --rtc-callbacks | --events-queue-size) + --atexit-max | --timer-slots | --events-queue-size) echo -e "$error syntax for $arg is $arg=";; *) @@ -108,7 +108,7 @@ output_config_gcc() [ "${conf[GINT_EXTENDED_LIBC]}" != "" ] && echo "-D GINT_EXTENDED_LIBC" echo "-D ATEXIT_MAX=${conf[ATEXIT_MAX]}" - echo "-D RTC_CB_ARRAY_SIZE=${conf[RTC_CB_ARRAY_SIZE]}" + echo "-D TIMER_SLOTS=${conf[TIMER_SLOTS]}" echo "-D EVENTS_QUEUE_SIZE=${conf[EVENTS_QUEUE_SIZE]}" } diff --git a/demo/gintdemo.c b/demo/gintdemo.c index a65dad6..bbd1785 100644 --- a/demo/gintdemo.c +++ b/demo/gintdemo.c @@ -521,7 +521,6 @@ int main(void) return 0; } -/* void crash(void) { __asm__( @@ -530,4 +529,3 @@ void crash(void) "trapa #1 " ); } -*/ diff --git a/demo/test_keyboard.c b/demo/test_keyboard.c index 0ab410d..87e3ead 100644 --- a/demo/test_keyboard.c +++ b/demo/test_keyboard.c @@ -102,7 +102,7 @@ static void draw_events(enhanced_event_t *history, int size) "0", ".", "\x08", "(-)", "EXE", NULL }; const char *event_names[] = { - "None ", "User ", "Press", "Rept.", "Rel. " + "None ", "User ", "Press", "Rept.", "Rel. ", "Timer" }; for(int i = 0; i < size && history[i].type != ET_None; i++) diff --git a/demo/test_rtc.c b/demo/test_rtc.c index 2264f00..053e218 100644 --- a/demo/test_rtc.c +++ b/demo/test_rtc.c @@ -16,7 +16,7 @@ #include #include -static void draw(struct RTCTime time) +static void draw(rtc_time_t time) { extern Image res_rtc_segments; @@ -53,8 +53,7 @@ static void draw(struct RTCTime time) static void callback(void) { extern Image res_opt_rtc; - struct RTCTime time = rtc_getTime(); - time.year += 1900; + rtc_time_t time = rtc_getTime(); dclear(); draw(time); @@ -62,7 +61,7 @@ static void callback(void) dupdate(); } -static void set_region(struct RTCTime *time, int region, int value) +static void set_region(rtc_time_t *time, int region, int value) { switch(region) { @@ -129,12 +128,10 @@ static void set(void) { 72, 39, 7, 9 }, { 84, 39, 7, 9 }, { 90, 39, 7, 9 }, { 96, 39, 7, 9 }, { 102, 39, 7, 9 }, }; - struct RTCTime time = rtc_getTime(); + rtc_time_t time = rtc_getTime(); int region_count = 14; int n = 0, slide = 0, key, leave; - time.year += 1900; - while(1) { dclear(); @@ -157,11 +154,9 @@ static void set(void) else if(key == KEY_F1 || key == KEY_EXE) { - n++; slide = 0; - if(n == region_count) + if(++n == region_count) { - time.year -= 1900; rtc_setTime(time); return; } @@ -187,7 +182,7 @@ static void set(void) else if(n == 6 && (slide != 1 || k != 5)) { - int day = k + 4 * slide - 1; + int day = (k + 4 * slide - 1) % 7; set_region(&time, n, day); n++; slide = 0; @@ -221,7 +216,6 @@ static void set(void) n++; if(n == region_count) { - time.year -= 1900; rtc_setTime(time); return; } diff --git a/demo/test_timer.c b/demo/test_timer.c index 53e8bd0..a830677 100644 --- a/demo/test_timer.c +++ b/demo/test_timer.c @@ -22,6 +22,7 @@ static clock_config_t conf; static volatile int elapsed_timer = -1; static volatile int elapsed_rtc = -1; static int cb_id = -1; +static timer_t *htimer = NULL; static void timing_rtc(void) { @@ -35,7 +36,11 @@ static void timing_timer(void) static void timing_start(void) { - timer_start(TIMER_USER, 64, Clock_Hz, timing_timer, NULL, 0); + uint32_t delay = clock_setting(64, Clock_Hz); + htimer = htimer_setup(timer_user, delay, timer_Po_4, 0); + timer_attach(htimer, timing_timer, NULL); + timer_start(htimer); + rtc_cb_edit(cb_id, RTCFreq_64Hz, timing_rtc); elapsed_timer = 0; @@ -253,8 +258,8 @@ void test_timer(void) switch(getkey_opt(Getkey_NoOption, 1)) { case KEY_EXIT: - timer_stop(TIMER_USER); rtc_cb_end(cb_id); + timer_stop(htimer); return; case KEY_F1: diff --git a/include/clock.h b/include/clock.h index 65ed96b..00822e8 100644 --- a/include/clock.h +++ b/include/clock.h @@ -10,6 +10,8 @@ #ifndef _CLOCK_H #define _CLOCK_H +#include + //--- // Sleep functions. //--- @@ -21,12 +23,16 @@ void sleep(void); /* - sleep_ms(), sleep_us() - Sleeps for the given number of milliseconds / microseconds using the - TIMER_USER timer. The actual sleep time will always be slightly less - than requested. + sleep_ms() + Sleeps for the given number of milliseconds using a virtual timer. */ void sleep_ms(int ms_delay); + +/* + sleep_us() + Sleeps for the given number of microseconds using the hardware timer + timer_user. +*/ void sleep_us(int us_delay); @@ -83,7 +89,7 @@ typedef struct Normally you need not use this function when setting up timers because timer_start() handles this conversion for you. */ -int clock_setting(int duration, enum ClockUnit unit); +uint32_t clock_setting(int duration, enum ClockUnit unit); /* clock_config() diff --git a/include/events.h b/include/events.h index b1832a4..8c1f285 100644 --- a/include/events.h +++ b/include/events.h @@ -9,26 +9,31 @@ #ifndef _EVENTS_H #define _EVENTS_H +#include + /* event_type_t Something user programs will surely use most often. */ typedef enum { - EventType_None = 0, - ET_None = EventType_None, + EventType_None = 0, + ET_None = EventType_None, - EventType_User = 1, - ET_User = EventType_User, + EventType_User = 1, + ET_User = EventType_User, - EventType_KeyPressed = 2, - ET_KeyPress = EventType_KeyPressed, + EventType_KeyPressed = 2, + ET_KeyPress = EventType_KeyPressed, - EventType_KeyRepeated = 3, - ET_KeyRepeat = EventType_KeyRepeated, + EventType_KeyRepeated = 3, + ET_KeyRepeat = EventType_KeyRepeated, - EventType_KeyReleased = 4, - ET_KeyRel = EventType_KeyReleased, + EventType_KeyReleased = 4, + ET_KeyRel = EventType_KeyReleased, + + EventType_TimerUnderflow = 5, + ET_Timer = EventType_TimerUnderflow, } event_type_t; @@ -47,6 +52,8 @@ typedef struct void *data; // For ET_KeyPress, ET_KeyRepeat and ET_KeyRel. int key; + // For ET_Timer. + timer_t *timer; }; } event_t; diff --git a/include/internals/modules.h b/include/internals/modules.h new file mode 100644 index 0000000..c96e770 --- /dev/null +++ b/include/internals/modules.h @@ -0,0 +1,12 @@ +#ifndef _INTERNALS_MODULES_H +#define _INTERNALS_MODULES_H + +/* + mod_init() + Initializes the module data to make register access cross-platform. The + MPU needs to have been detected or this function will yield wrong + results. +*/ +void mod_init(void); + +#endif // _INTERNALS_MODULES_H diff --git a/include/internals/rtc.h b/include/internals/rtc.h index 228ce59..4f3e348 100644 --- a/include/internals/rtc.h +++ b/include/internals/rtc.h @@ -14,7 +14,7 @@ */ struct rtc_cb { - enum RTCFrequency freq; + rtc_frequency_t freq; int id; void (*callback)(void); @@ -24,6 +24,19 @@ struct rtc_cb // The callback array. struct rtc_cb cb_array[RTC_CB_ARRAY_SIZE]; +/* + rtc_perodic_interrupt() + Handles periodic interrupts and calls the callbacks. +*/ +void rtc_periodic_interrupt(void); + +/* + rtc_cb_interrupt() + Calls the RTC callbacks if necessary, and updates the repeat counts. + Should only be called when RTC periodic interrupts occur. +*/ +void rtc_cb_interrupt(void); + /* struct mod_rtc This structure describes the arrangement of RTC register in the memory. diff --git a/include/internals/timer.h b/include/internals/timer.h index 19b4a4e..c169a6e 100644 --- a/include/internals/timer.h +++ b/include/internals/timer.h @@ -1,49 +1,76 @@ #ifndef _INTERNALS_TIMER_H -#define _INTERNALS_TIMER_H 1 +#define _INTERNALS_TIMER_H + +#include +#include /* - struct Timer - This structure holds information for a running timer. + timer_t + A virtual or hardware timer. We need to declare the struct timer_t name + so that we can forward-reference it. */ -struct Timer +typedef struct timer_t { + // Current delay, how much time elapsed since last interrupt occurred, + // and how many repeats are left. + int ms_delay; + int ms_elapsed; + int repeats_left; + + // Is the virtual slot free? Is the virtual timer active? + uint8_t used :1; + uint8_t active :1; + // Is this a virtual timer? Is this the virtual timer support? + uint8_t virtual :1; + uint8_t vsupport :1; + // How many events do I have received but not executed? + uint8_t events :4; + + // Callback function (NULL for event-firing timers) and its argument. void *callback; - void *data; - int repeats; -}; + void *argument; -extern struct Timer timers[3]; +} timer_t; + +// Hardware timers. +extern timer_t htimers[3]; +// Virtual timers. +extern timer_t vtimers[TIMER_SLOTS]; /* - struct mod_tmu - This structure holds information about the timer unit (peripheral - module) registers. + timer_interrupt() + Handles the interrupt for the given timer channel. */ -struct mod_tmu -{ - unsigned int TCOR; // Timer constant register. - unsigned int TCNT; // Timer counter. - - union - { - unsigned short WORD; - struct - { - unsigned :7; - unsigned UNF :1; // Underflow flag. - unsigned :2; - unsigned UNIE :1; // Underflow interrupt enable. - unsigned CKEG :2; // Clock edge (SH7705 only). - unsigned TPSC :3; // Timer prescaler. - }; - } TCR; // Timer control register. -}; +void timer_interrupt(int channel); /* - timer_get() - Returns the timer and TSTR register addresses. + timer_callback_event() + Executes the callback of a timer, or pushes a new timer event depending + on the timer configuration. Also reduces the amount of repeats left and + clears the active flag (or stops the hardware timer) if this number + falls from one to zero. */ -void timer_get(int timer, volatile struct mod_tmu **tmu, - volatile unsigned char **tstr); +void timer_callback_event(timer_t *timer); + +/* + vtimer_interrupt() + Interrupt handling subsystem for the virtual timers. +*/ +void vtimer_interrupt(void); + +/* + vtimer_updateOne() + Update the virtual timer hardware support timer, knowing that a virtual + timer with the given delay has been started. +*/ +void vtimer_updateOne(int additional_delay_ms); + +/* + vtimer_updateAll() + Updates the virtual timer hardware support after computing the GCD of + all virtual timers delays. This is rather long so avoid calling this + when possible. +*/ +void vtimer_updateAll(void); #endif // _INTERNALS_TIMER_H diff --git a/include/modules/macros.h b/include/modules/macros.h new file mode 100644 index 0000000..5f4403b --- /dev/null +++ b/include/modules/macros.h @@ -0,0 +1,48 @@ +#ifndef _MODULES_MACROS_H +#define _MODULES_MACROS_H + +#include + +// Fixed-width types for bit field are totally meaningless. +typedef unsigned uint; + +/* + byte_union() + Union between an uint8_t 'byte' attribute and a provided bit-field + structure that describe the contents of the byte. +*/ +#define byte_union(name, fields) \ + union \ + { \ + uint8_t byte; \ + struct { fields } \ + __attribute__((packed)); \ + } __attribute__((packed)) name + +/* + word_union() + Union between an uint16_t 'word' attribute and a provided bit-field + structure that describe the contents of the word. +*/ +#define word_union(name, fields) \ + union \ + { \ + uint16_t word; \ + struct { fields } \ + __attribute__((packed)); \ + } __attribute__((packed)) name + +/* + lword_union() + Union between an uint32_t 'lword' attribute and a provided bit-field + structure that describe the contents of the longword. +*/ +#define lword_union(name, fields) \ + union \ + { \ + uint32_t lword; \ + struct { fields } \ + __attribute__((packed)); \ + } __attribute__((packed)) name + +#endif // _MODULES_MACROS_H diff --git a/include/modules/timer.h b/include/modules/timer.h new file mode 100644 index 0000000..3a8b187 --- /dev/null +++ b/include/modules/timer.h @@ -0,0 +1,64 @@ +#ifndef _MODULES_TIMER_H +#define _MODULES_TIMER_H + +#include +#include + +/* + mod_tmu_timer_t + Registers of a single timer. +*/ +typedef struct +{ + uint32_t TCOR; /* Timer constant register */ + uint32_t TCNT; /* Timer counter */ + + word_union(TCR, + uint :7; + uint UNF :1; /* Underflow flag */ + uint :2; + uint UNIE :1; /* Underflow interrupt enable */ + uint CKEG :2; /* Clock edge (SH7705) */ + uint TPSC :3; /* Timer prescaler */ + ); + + uint16_t :16; + +} __attribute__((packed, aligned(4))) mod_tmu_timer_t; + +/* + mod_tmu_tstr_t + The timer start register. +*/ +typedef struct +{ + byte_union(, + uint :5; + uint STR2 :1; /* Counter start 2 */ + uint STR1 :1; /* Counter start 1 */ + uint STR0 :1; /* Counter start 0 */ + ); + +} __attribute__((packed)) mod_tmu_tstr_t; + +/* + mod_tmu_t (resides into gint memory) + Basically three timer units and an additional register to control who's + running. +*/ +typedef struct +{ + /* Timer configurations */ + volatile mod_tmu_timer_t *timers[3]; + /* Timer start register */ + volatile mod_tmu_tstr_t *TSTR; + /* Timer input capture 2 (SH7705) */ + volatile const uint32_t *TCPR2; + +} mod_tmu_t; + +// The actual thing is there. It's initialized by gint at startup and contains +// addresses and information suitable for the current platform. +extern volatile mod_tmu_t TMU; + +#endif // _MODULES_TIMER diff --git a/include/rtc.h b/include/rtc.h index 48f72e9..e5ca263 100644 --- a/include/rtc.h +++ b/include/rtc.h @@ -9,38 +9,41 @@ #ifndef _RTC_H #define _RTC_H 1 +#include + //--- // Time access. //--- /* - struct RTCTime + rtc_time_t Defines a point in time. */ -struct RTCTime +typedef struct { - int seconds; // Seconds in range 0-59 - int minutes; // Minutes in range 0-59 - int hours; // Hours in range 0-23 - int month_day; // Day of month in range 1-31 - int month; // Month in range 0-11 - int year; // Years (full value) - int week_day; // Day of week in range 0(Sunday)-6(Saturday). -}; + uint8_t seconds; // Seconds in range 0-59 + uint8_t minutes; // Minutes in range 0-59 + uint8_t hours; // Hours in range 0-23 + uint8_t month_day; // Day of month in range 1-31 + uint8_t month; // Month in range 0-11 + uint8_t week_day; // Day of week in range 0(Sunday)-6(Saturday). + uint16_t year; // Years (around 2000) + +} rtc_time_t; /* rtc_getTime() Reads the current time from the RTC. There is no guarantee that the week day is correct (use the time API for that). */ -struct RTCTime rtc_getTime(void); +rtc_time_t rtc_getTime(void); /* rtc_setTime() Sets the time in the RTC registers. The week day is set to 0 if greater than 6. Other fields are not checked. */ -void rtc_setTime(struct RTCTime time); +void rtc_setTime(rtc_time_t time); @@ -49,11 +52,11 @@ void rtc_setTime(struct RTCTime time); //--- /* - enum RTCFrequency + rtc_frequency_t Describes the possible frequencies available for the real-time clock interrupt. */ -enum RTCFrequency +typedef enum { RTCFreq_500mHz = 7, RTCFreq_1Hz = 6, @@ -63,7 +66,8 @@ enum RTCFrequency RTCFreq_64Hz = 2, RTCFreq_256Hz = 1, RTCFreq_None = 0, -}; + +} rtc_frequency_t; /* rtc_cb_add() @@ -74,7 +78,7 @@ enum RTCFrequency The number of repeats may be set to 0, in which case the callback is called indefinitely unless the user calls rtc_cb_end(). */ -int rtc_cb_add(enum RTCFrequency freq, void (*function)(void), int repeats); +int rtc_cb_add(rtc_frequency_t freq, void (*function)(void), int repeats); /* rtc_cb_end() @@ -93,29 +97,6 @@ void rtc_cb_end(int id); can set the function to NULL or the frequency to RTCFreq_None to temporarily disable the callback. */ -int rtc_cb_edit(int id, enum RTCFrequency new_freq, - void (*new_function)(void)); - - - -//--- -// Internal API. -// Referenced here for documentation purposes only. Do not call. -//--- - -/* - rtc_interrupt() - Handles an RTC interrupt by calling the callback. -*/ -void rtc_interrupt(void); -void rtc_interrupt_7705(void); -void rtc_interrupt_7305(void); - -/* - rtc_cb_interrupt() - Handles an RTC interrupt. Calls the RTC callbacks if necessary, and - updates the repeat counts. -*/ -void rtc_cb_interrupt(void); +int rtc_cb_edit(int id, rtc_frequency_t new_freq, void (*new_function)(void)); #endif // _RTC_H diff --git a/include/timer.h b/include/timer.h index e231cf8..3028e62 100644 --- a/include/timer.h +++ b/include/timer.h @@ -11,106 +11,157 @@ #define _TIMER_H 1 #include +#include +#include + +// The timer object is manipulated by the module; the user needs not access it +// directly. Its contents are defined in . +struct timer_t; +typedef struct timer_t timer_t; //--- -// Constants. +// Virtual timer API +// Gint allows users to create virtual timers with 1-ms precision to +// handle an important amount of timed events. The amount of virtual +// timers available is gapped by the TIMER_SLOTS parameter, but this +// parameter can be customized when building the library; thus, the +// amount of usable virtual timers is not limited. //--- -// Timer identifiers. -#define TIMER_0 0 -#define TIMER_TMU0 TIMER_0 -#define TIMER_1 1 -#define TIMER_TMU1 TIMER_1 -#define TIMER_2 2 -#define TIMER_TMU2 TIMER_2 -// Timer function identifiers. -#define TIMER_KEYBOARD TIMER_TMU0 -#define TIMER_GRAY TIMER_TMU1 -#define TIMER_USER TIMER_TMU2 +#ifndef TIMER_SLOTS +#define TIMER_SLOTS 16 +#endif -// Timer prescalers. -#define TIMER_Po_4 0 -#define TIMER_Po_16 1 -#define TIMER_Po_64 2 -#define TIMER_Po_256 3 -#define TIMER_TCLK 5 +/* + timer_create() + Basic timer configuration. This function creates a virtual timer and + configures its delay and repetition count. It returns a virtual timer + object that will be used by other functions. At most TIMER_SLOTS + virtual timers can be used at the same time: this function returns NULL + if there is no new slot available. + By default a virtual timer configured by timer_configure() will fire + ET_Timer events, which the user will need to catch. The user should + then execute some action. + There is another option: the user may call timer_attach() to attach a + callback to a virtual timer. A virtual timer which has a callback + attached will not fire any ET_Timer event and will call the callback + automatically instead. +*/ +timer_t *timer_create(int delay_ms, int repeats); + +/* + timer_destroy() + Destroys a virtual timer. This virtual timer pointer becomes invalid + and should not be used anymore. +*/ +void timer_destroy(timer_t *virtual_timer); //--- -// Public API. +// Hardware timer API +// Gint provides access to hardware timer. These timer offer more or less +// microsecond-level control. However, the user should ensure, when using +// hardware timers, that they are not overriding the configuration of +// timers that are already running -- gint won't check. //--- +/* + timer_hard_t + Available hardware timers. The user can use timer_user freely, but + timer_gray and timer_virtual should not be used as long as the gray + engine or virtual timers are running (respectively). +*/ +typedef enum +{ + timer_tmu0 = 0, + timer_virtual = timer_tmu0, + + timer_tmu1 = 1, + timer_gray = timer_tmu1, + + timer_tmu2 = 2, + timer_user = timer_tmu2, + +} timer_hard_t; + +/* + timer_input_t + Available input clocks for the hardware timer: + - Po/4 Peripheral clock (frequency divided by 4) + - Po/16 Peripheral clock (frequency divided by 16) + - Po/64 Peripheral clock (frequency divided by 64) + - Po/256 Peripheral clock (frequency divided by 256) + - TCLK External clock + I'm not totally sure there is any signal on the external clock, so + don't use it unless you know what you are doing. +*/ +typedef enum +{ + timer_Po_4 = 0, + timer_Po_16 = 1, + timer_Po_64 = 2, + timer_Po_256 = 3, + timer_tclk = 5, + +} timer_input_t; + +/* + htimer_setup() + Configures a hardware timer. By default hardware timers generates + ET_Timer events but catching them may take some time, especially if + there are other events waiting in the queue. For increased timing, and + for fast timers, the user should consider using callbacks instead. + Returns a hardware timer object. + Returns the correct timer structure if the requested timer is free and + NULL otherwise. +*/ +timer_t *htimer_setup(timer_hard_t id, uint32_t constant, timer_input_t input, + int repeats); + +/* + htimer_reload() + Reloads a hardware timer without starting or stopping it. +*/ +void htimer_reload(timer_hard_t id, uint32_t new_constant); + + + +//--- +// Common API +// The following functions work with both virtual and hardware timers. +//--- + +/* + timer_attach() + This function attaches a callback to a virtual or hardware timer. A + timer with a callback attached will stop firing ET_Timer events and + will call the callback function directly instead. + The type signature of the function should be as follows: + - void callback(void) if argument == NULL + - void callback(void *argument) if argument is non-NULL + In the latter case, the argument pointer will be passed to the callback + function. +*/ +void timer_attach(timer_t *timer, void *callback, void *argument); + /* timer_start() - Configures and starts a timer. The timer argument expects a timer name. - You can use TIMER_USER anytime. You may also use TIMER_GRAY if you're - not running the gray engine. - Unit names are defined in the clock.h header and must be one of the - following: - - Clock_us (microseconds) - - Clock_ms (milliseconds) - - Clock_s (seconds) - - Clock_Hz (hertz) - - Clock_kHz (kilohertz) - - Clock_MHz (megahertz) - The number of repeats may to set to 0. In this case, the timer will not - stop until timer_stop() is explicitly called. - The callback is expected to be a function of the following type: - - void callback(void) if data == NULL - - void callback(void *data) if data is non-NULL - In the latter case, the data pointer will be passed as argument to the - callback function. + Starts a virtual or hardware timer. If the timer has a callback + attached, then the callback function will start being called regularly; + otherwise, the timer will start pushing ET_Timer events to the event + queue. It is advised, not to change a timer's configuration while it's + running. */ -void timer_start(int timer, int delay_or_frequency, enum ClockUnit unit, - void *callback, void *data, int repeats); - -/* - timer_start2() - Basically the same as timer_start(), but uses a clock-count delay and a - prescaler. The possible values for the prescaler are dividers of the - peripheral clock frequency Po: - - TIMER_Po_4 - - TIMER_Po_16 - - TIMER_Po_64 - - TIMER_Po_256 - - TIMER_TCLK -*/ -void timer_start2(int timer, int delay, int prescaler, void *callback, - void *data, int repeats); +void timer_start(timer_t *timer); /* timer_stop() - Stops the given timer. This function may be called even if the timer is - not running. + Stops a timer. If the argument is virtual timer, it is paused and can + be started again later. + If it's a hardware timer, it is freed and made accessible to other + uses. It should be set up again before being started. */ -void timer_stop(int timer); - -/* - timer_reload() - Reloads the given timer with the supplied constant. Starts the timer if - it was stopped. -*/ -void timer_reload(int timer, int new_delay_or_frequency, enum ClockUnit unit); - -/* - timer_reload2() - Same as timer_reload(), but again uses the native clock count. The - prescaler may not be changed. -*/ -void timer_reload2(int timer, int new_delay); - - - -//--- -// Internal API. -// Referenced for documentation purposes only. Do not call. -//--- - -/* - timer_interrupt() - Handles the interrupt for the given timer. -*/ -void timer_interrupt(int timer); +void timer_stop(timer_t *timer); #endif // _TIMER_H diff --git a/src/clock/clock.c b/src/clock/clock.c index 06426eb..8a14274 100644 --- a/src/clock/clock.c +++ b/src/clock/clock.c @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include @@ -19,9 +19,9 @@ static clock_config_t conf = { Several units can be used. Be aware that the result is approximate, and very high frequencies or very short delays will yield important errors. */ -int clock_setting(int duration, enum ClockUnit unit) +uint32_t clock_setting(int duration, enum ClockUnit unit) { - if(conf.Pphi_f <= 0) return -1; + if(conf.Pphi_f <= 0) return 0xffffffff; uint64_t f = conf.Pphi_f >> 2; uint64_t result; @@ -74,30 +74,42 @@ void sleep(void) ); } -/* - sleep_ms(), sleep_us() - Sleeps for the given number of milliseconds / microseconds using the - TIMER_USER timer. The actual sleep time will always be slightly less - than requested. -*/ - -static void sleep_callback(void *flag) +static void sleep_callback(void *arg) { - *((int *)flag) = 1; + int *flag = arg; + *flag = 1; } +/* + sleep_ms() + Sleeps for the given number of milliseconds using a virtual timer. +*/ void sleep_ms(int ms_delay) { - sleep_us(1000 * ms_delay); + volatile int sleep_done = 0; + + timer_t *timer = timer_create(ms_delay, 1); + timer_attach(timer, sleep_callback, (void *)&sleep_done); + timer_start(timer); + + while(!sleep_done) sleep(); } + +/* + sleep_us() + Sleeps for the given number of microseconds using the hardware timer + timer_user. +*/ void sleep_us(int us_delay) { volatile int sleep_done = 0; - timer_start(TIMER_USER, us_delay, Clock_us, sleep_callback, - (void *)&sleep_done, 1); + const uint32_t constant = clock_setting(us_delay, Clock_us); - do sleep(); - while(!sleep_done); + timer_t *timer = htimer_setup(timer_user, constant, timer_Po_4, 1); + timer_attach(timer, sleep_callback, (void *)&sleep_done); + timer_start(timer); + + while(!sleep_done) sleep(); } @@ -109,6 +121,7 @@ void sleep_us(int us_delay) // Indicates whether the measurements are finished. static volatile int clock_measure_done = 0; // Once again SH7705 and SH7305 need different methods... +static timer_t *htimer_7705 = NULL; static int cb_id_7705 = -1; static void clock_measure_7705(void); static void clock_compute_7305(void); @@ -128,25 +141,8 @@ void clock_measure(void) // P_phi using a timer/RTC combination, and we deduce CKIO. if(isSH3()) { - // We prepare the timer manually, without starting it, so that - // we only have to push the running bit to start it when the - // measurements begin. This might look of little effect but it - // makes the precision jump from ~97% to more than 99%. - volatile struct mod_tmu *tmu; - timer_get(TIMER_USER, &tmu, NULL); - - tmu->TCOR = 0xffffffff; - tmu->TCNT = tmu->TCOR; - tmu->TCR.TPSC = TIMER_Po_4; - - tmu->TCR.UNF = 0; - tmu->TCR.UNIE = 1; - tmu->TCR.CKEG = 0; - - timers[TIMER_USER].callback = NULL; - timers[TIMER_USER].data = NULL; - timers[TIMER_USER].repeats = 0; - + htimer_7705 = htimer_setup(timer_user, 0xffffffff, timer_Po_4, + 1); cb_id_7705 = rtc_cb_add(RTCFreq_256Hz, clock_measure_7705, 0); } @@ -226,7 +222,7 @@ static void clock_compute_7305(void) Given the number of P_phi / 4 timer ticks elapsed between two RTC 256 Hz interrupts, determines the clock configuration. */ -static void clock_measure_7705_finalize(int elapsed) +static void clock_measure_7705_finalize(uint32_t elapsed) { volatile unsigned int *FRQCR = (void *)0xffffff80; @@ -254,13 +250,10 @@ static void clock_measure_7705_finalize(int elapsed) */ static void clock_measure_7705_callback(void) { - timer_stop(TIMER_USER); + timer_stop(htimer_7705); rtc_cb_end(cb_id_7705); - volatile struct mod_tmu *tmu; - timer_get(TIMER_USER, &tmu, NULL); - int elapsed = 0xffffffff - tmu->TCNT; - + uint32_t elapsed = 0xffffffff - TMU.timers[timer_user]->TCNT; clock_measure_7705_finalize(elapsed); clock_measure_done = 1; } @@ -274,9 +267,6 @@ static void clock_measure_7705_callback(void) */ static void clock_measure_7705(void) { - volatile unsigned char *tstr; - timer_get(TIMER_USER, NULL, &tstr); - - *tstr |= (1 << TIMER_USER); + timer_start(htimer_7705); rtc_cb_edit(cb_id_7705, RTCFreq_256Hz, clock_measure_7705_callback); } diff --git a/src/core/crt0.c b/src/core/crt0.c index 19cf44d..a343a98 100644 --- a/src/core/crt0.c +++ b/src/core/crt0.c @@ -8,6 +8,7 @@ #include #include #include +#include int main(void); @@ -58,7 +59,7 @@ __attribute__((section(".pretext.entry"))) int start(void) dg->stage = stage_startup; // Exception records: what kind of exceptions / TLB faults / interrupts - // occured last to get some trace in case of crash. + // occurred last to get some trace in case of crash. dg->excepts = 0; for(size_t i = 0; i < sizeof dg->except_vect; i++) dg->except_vect[i] = 0; @@ -67,7 +68,7 @@ __attribute__((section(".pretext.entry"))) int start(void) dg->expevt = 0x00000000; dg->tea = 0x00000000; - // Memory map: provides information about alignement and the relative + // Memory map: provides information about alignment and the relative // size and position of each section, as well as read-only / read-write // types of addresses. dg->section_text.address = (uint32_t)&btext; @@ -107,7 +108,10 @@ __attribute__((section(".pretext.entry"))) int start(void) dg->stage = stage_mmu; #endif - // Initializing gint. + // Detecting the MPU type, initializing the register module addresses, + // and initializing gint. + mpu_init(); + mod_init(); gint_init(); #ifdef GINT_DIAGNOSTICS diff --git a/src/core/init_quit.c b/src/core/init_quit.c index 8ecea6b..ac16dd0 100644 --- a/src/core/init_quit.c +++ b/src/core/init_quit.c @@ -29,11 +29,6 @@ void gint_init(void) uint32_t *ptr = &bgint; uint32_t *src = &gint_data; - // This initialization routine is usually called before any - // constructor. We want to ensure that the MPU type is detected, but - // mpu_init() hasn't been called yet. - mpu_init(); - // Loading the interrupt handler into the memory. while(ptr < &egint) *ptr++ = *src++; diff --git a/src/core/interrupts.c b/src/core/interrupts.c index d056774..eff3d94 100644 --- a/src/core/interrupts.c +++ b/src/core/interrupts.c @@ -1,6 +1,6 @@ #include -#include -#include +#include +#include /* inth_timer_underflow() @@ -17,5 +17,5 @@ void inth_timer_underflow(uint32_t channel) */ void inth_rtc_periodic(void) { - rtc_interrupt(); + rtc_periodic_interrupt(); } diff --git a/src/core/modules.c b/src/core/modules.c new file mode 100644 index 0000000..aad3491 --- /dev/null +++ b/src/core/modules.c @@ -0,0 +1,49 @@ +#include +#include +#include + +//--- +// Structure information +// Here resides most of the platform-dependent register configuration. +// Module structures are arranged to mask as much as possible hardware +// differences to the user. When it becomes impossible to do so at +// compile-time, gint provides functions to ensure that the user does not +// confront to the hardware directly. +//--- + +volatile mod_tmu_t TMU; + + + +//--- +// Initializer +//--- + +static void mod_init_7705(void) +{ + TMU.timers[0] = (void *)0xfffffe94; + TMU.timers[1] = (void *)0xfffffea0; + TMU.timers[2] = (void *)0xfffffeac; + TMU.TSTR = (void *)0xfffffe92; + TMU.TCPR2 = (void *)0xfffffeb8; +} + +static void mod_init_7305(void) +{ + TMU.timers[0] = (void *)0xa4490008; + TMU.timers[1] = (void *)0xa4490014; + TMU.timers[2] = (void *)0xa4490020; + TMU.TSTR = (void *)0xa4490004; + TMU.TCPR2 = NULL; +} + +/* + mod_init() + Initializes the module data to make register access cross-platform. The + MPU needs to have been detected or this function will yield wrong + results. +*/ +void mod_init(void) +{ + isSH3() ? mod_init_7705() : mod_init_7305(); +} diff --git a/src/gray/gray_engine.c b/src/gray/gray_engine.c index fab319e..2e843f3 100644 --- a/src/gray/gray_engine.c +++ b/src/gray/gray_engine.c @@ -19,7 +19,7 @@ static int delays[2]; static int runs = 0; -#define GRAY_PRESCALER TIMER_Po_64 +static timer_t *gray_timer = NULL; @@ -33,7 +33,8 @@ static int runs = 0; */ void gray_interrupt(void) { - timer_reload2(TIMER_GRAY, delays[(~current) & 1]); + htimer_reload(timer_gray, delays[(~current) & 1]); + screen_display(vrams[current]); current ^= 1; } @@ -68,8 +69,10 @@ void gray_start(void) { if(runs) return; - timer_start2(TIMER_GRAY, delays[0], GRAY_PRESCALER, gray_interrupt, - NULL, 0); + gray_timer = htimer_setup(timer_gray, delays[0], timer_Po_64, 0); + timer_attach(gray_timer, gray_interrupt, NULL); + timer_start(gray_timer); + current &= 1; runs = 1; } @@ -81,9 +84,7 @@ void gray_start(void) */ void gray_stop(void) { - if(!runs) return; - - timer_stop(TIMER_GRAY); + timer_stop(gray_timer); runs = 0; display_useVRAM(display_getLocalVRAM()); diff --git a/src/rtc/rtc_callback.c b/src/rtc/rtc_callback.c index 635eafc..488d0ce 100644 --- a/src/rtc/rtc_callback.c +++ b/src/rtc/rtc_callback.c @@ -7,7 +7,7 @@ struct rtc_cb cb_array[RTC_CB_ARRAY_SIZE] = { 0 }; // Callback identifier (unique). static int unique_id = 1; // Current RTC interrupt frequency. -static enum RTCFrequency rtc_freq = RTCFreq_None; +static rtc_frequency_t rtc_freq = RTCFreq_None; // 256-Hz tick count. This counter is stopped when no callback is registered. static unsigned elapsed256 = 0; @@ -21,7 +21,7 @@ static unsigned elapsed256 = 0; */ static void rtc_cb_update(void) { - enum RTCFrequency max = RTCFreq_None; + rtc_frequency_t max = RTCFreq_None; int n; for(n = 0; n < RTC_CB_ARRAY_SIZE; n++) if(cb_array[n].id) @@ -46,7 +46,7 @@ static void rtc_cb_update(void) The number of repeats may be set to 0, in which case the callback is called indefinitely unless the user calls rtc_cb_end(). */ -int rtc_cb_add(enum RTCFrequency freq, void (*function)(void), int repeats) +int rtc_cb_add(rtc_frequency_t freq, void (*function)(void), int repeats) { int n = 0; if(freq == RTCFreq_None || !function || repeats < 0) return -2; @@ -92,7 +92,7 @@ void rtc_cb_end(int id) -2 Invalid parameters This function never removes a callback. Call rtc_cb_end() for this. */ -int rtc_cb_edit(int id, enum RTCFrequency new_freq, +int rtc_cb_edit(int id, rtc_frequency_t new_freq, void (*new_function)(void)) { if(new_freq < 0 || new_freq > 7) return -2; diff --git a/src/rtc/rtc_getTime.c b/src/rtc/rtc_getTime.c index 5e9f653..5a460e9 100644 --- a/src/rtc/rtc_getTime.c +++ b/src/rtc/rtc_getTime.c @@ -21,18 +21,24 @@ static int integer16(int bcd) Reads the current time from the RTC. There is no guarantee that the week day is correct (use the time API for that). */ -struct RTCTime rtc_getTime(void) +rtc_time_t rtc_getTime(void) { volatile struct mod_rtc *rtc = isSH3() ? RTC_SH7705 : RTC_SH7305; - struct RTCTime time; + rtc_time_t time; - time.seconds = integer8(rtc->RSECCNT.BYTE); - time.minutes = integer8(rtc->RMINCNT.BYTE); - time.hours = integer8(rtc->RHRCNT.BYTE); - time.month_day = integer8(rtc->RDAYCNT.BYTE); - time.month = integer8(rtc->RMONCNT.BYTE); - time.year = integer16(rtc->RYRCNT.WORD); - time.week_day = rtc->RWKCNT; + do + { + rtc->RCR1.CF = 0; + + time.seconds = integer8(rtc->RSECCNT.BYTE); + time.minutes = integer8(rtc->RMINCNT.BYTE); + time.hours = integer8(rtc->RHRCNT.BYTE); + time.month_day = integer8(rtc->RDAYCNT.BYTE); + time.month = integer8(rtc->RMONCNT.BYTE); + time.year = integer16(rtc->RYRCNT.WORD); + time.week_day = rtc->RWKCNT; + } + while(rtc->RCR1.CF != 0); return time; } diff --git a/src/rtc/rtc_interrupt.c b/src/rtc/rtc_interrupt.c index 775a220..32a2c73 100644 --- a/src/rtc/rtc_interrupt.c +++ b/src/rtc/rtc_interrupt.c @@ -2,11 +2,13 @@ #include #include +int rtc_carry_flag = 0; + /* - rtc_interrupt() + rtc_periodic_interrupt() Handles an RTC interrupt by calling the callback. */ -void rtc_interrupt(void) +void rtc_periodic_interrupt(void) { rtc_cb_interrupt(); diff --git a/src/rtc/rtc_setTime.c b/src/rtc/rtc_setTime.c index d343c51..4e71e7f 100644 --- a/src/rtc/rtc_setTime.c +++ b/src/rtc/rtc_setTime.c @@ -22,15 +22,22 @@ static int bcd16(int integer) Sets the time in the RTC registers. The week day is set to 0 if greater than 6. Other fields are not checked. */ -void rtc_setTime(struct RTCTime time) +void rtc_setTime(rtc_time_t time) { volatile struct mod_rtc *rtc = isSH3() ? RTC_SH7705 : RTC_SH7305; + int wday = (time.week_day < 7) ? (time.week_day) : (0); - rtc->RSECCNT.BYTE = bcd8(time.seconds); - rtc->RMINCNT.BYTE = bcd8(time.minutes); - rtc->RHRCNT.BYTE = bcd8(time.hours); - rtc->RDAYCNT.BYTE = bcd8(time.month_day); - rtc->RMONCNT.BYTE = bcd8(time.month); - rtc->RYRCNT.WORD = bcd16(time.year); - rtc->RWKCNT = time.week_day < 7 ? time.week_day : 0; + do + { + rtc->RCR1.CF = 0; + + rtc->RSECCNT.BYTE = bcd8(time.seconds); + rtc->RMINCNT.BYTE = bcd8(time.minutes); + rtc->RHRCNT.BYTE = bcd8(time.hours); + rtc->RDAYCNT.BYTE = bcd8(time.month_day); + rtc->RMONCNT.BYTE = bcd8(time.month); + rtc->RYRCNT.WORD = bcd16(time.year); + rtc->RWKCNT = wday; + } + while(rtc->RCR1.CF != 0); } diff --git a/src/time/time.c b/src/time/time.c index 16189d4..e6a6727 100644 --- a/src/time/time.c +++ b/src/time/time.c @@ -12,7 +12,7 @@ */ time_t time(time_t *timeptr) { - struct RTCTime rtc = rtc_getTime(); + rtc_time_t rtc = rtc_getTime(); struct tm tm; time_t calendar; diff --git a/src/timer/common_api.c b/src/timer/common_api.c new file mode 100644 index 0000000..46b39fa --- /dev/null +++ b/src/timer/common_api.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include + +/* + timer_attach() + Attaches a callback to a virtual or hardware timer. +*/ +void timer_attach(timer_t *timer, void *callback, void *argument) +{ + if(!timer) return; + + timer->callback = callback; + timer->argument = argument; +} + +/* + timer_start() + Starts a virtual or hardware timer. If the timer has a callback + attached, then the callback function will start being called regularly; + otherwise, the timer will start pushing ET_Timer events to the event + queue. It is advised, not to change a timer's configuration while it's + running. +*/ +void timer_start(timer_t *timer) +{ + if(!timer) return; + timer->active = 1; + + if(timer->virtual) vtimer_updateOne(timer->ms_delay); + else TMU.TSTR->byte |= (1 << (timer - htimers)); +} + +/* + timer_stop() + Pauses a virtual or hardware timer. The timer stops counting and can be + started again later. +*/ +void timer_stop(timer_t *timer) +{ + if(!timer) return; + timer->active = 0; + + if(timer->virtual) vtimer_updateAll(); + else + { + TMU.TSTR->byte &= ~(1 << (timer - htimers)); + timer->used = 0; + } +} + +/* + timer_interrupt() + Handles the interrupt for the given timer channel. +*/ +void timer_interrupt(int channel) +{ + // Is this the virtual timer support? + if(htimers[channel].vsupport) vtimer_interrupt(); + else timer_callback_event(&htimers[channel]); + + // Clearing the interrupt flag. + TMU.timers[channel]->TCR.UNF = 0; +} + +/* + timer_callback_event() + Executes the callback of a timer, or pushes a new timer event depending + on the timer configuration. Also reduces the amount of repeats left and + clears the active flag (or stops the hardware timer) if this number + falls from one to zero. +*/ +void timer_callback_event(timer_t *timer) +{ + if(!timer) return; + + // Callback-type timers. + if(timer->callback) + { + if(!timer->argument) + { + void (*fun)(void) = timer->callback; + fun(); + } + else + { + void (*fun)(void *arg) = timer->callback; + fun(timer->argument); + } + } + + // Event-type timers. + else + { + event_t event = { + .type = ET_Timer, + .timer = timer + }; + event_push(event); + } + + // Reducing the number of repeats and stopping the timer if required. + if(timer->repeats_left > 0) + { + if(!--timer->repeats_left) + { + if(timer->virtual) timer->active = 0; + else timer_stop(timer); + } + } +} diff --git a/src/timer/hardware_timers.c b/src/timer/hardware_timers.c new file mode 100644 index 0000000..57e247f --- /dev/null +++ b/src/timer/hardware_timers.c @@ -0,0 +1,63 @@ +#include +#include +#include + +//--- +// Public API +//--- + +timer_t htimers[3] = { 0 }; + +/* + htimer_setup() + Configures a hardware timer. +*/ +timer_t *htimer_setup(timer_hard_t id, uint32_t constant, timer_input_t input, + int repeats) +{ + if(id < 0 || id >= 3 || htimers[id].used) return NULL; + timer_t *timer = &htimers[id]; + + // We don't care about this. + timer->ms_delay = 0; + timer->ms_elapsed = 0; + + timer->repeats_left = repeats; + + timer->used = 1; + timer->active = 0; + timer->virtual = 0; + timer->vsupport = 0; + timer->events = 0; + + timer->callback = NULL; + timer->argument = NULL; + + // Time to set up the real thing. + volatile mod_tmu_timer_t *tmu = TMU.timers[id]; + + tmu->TCOR = constant; + tmu->TCNT = constant; + tmu->TCR.TPSC = input; + + tmu->TCR.UNF = 0; // Clear the interrupt flag. + tmu->TCR.UNIE = 1; // Enable underflow interrupt. + tmu->TCR.CKEG = 0; // Count on rising edge (SH7705). + + return timer; +} + +/* + htimer_reload() + Reloads a hardware timer without starting or stopping it. +*/ +void htimer_reload(timer_hard_t id, uint32_t new_constant) +{ + volatile mod_tmu_timer_t *tmu = TMU.timers[id]; + + // This is not much but we want to make the transition as swift as + // possible and I can prove that at least one cycle will be lost in + // processor operation. Thus... + tmu->TCNT = new_constant - 1; + tmu->TCOR = new_constant; +} diff --git a/src/timer/timer_get.c b/src/timer/timer_get.c deleted file mode 100644 index 9174164..0000000 --- a/src/timer/timer_get.c +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include - -/* - timer_get() - Returns the timer and TSTR register addresses. -*/ -void timer_get(int timer, volatile struct mod_tmu **tmu, - volatile unsigned char **tstr) -{ - // Using SH7705 information for SH-3-based MPUs. - if(isSH3()) - { - if(tstr) *tstr = (volatile unsigned char *)0xfffffe92; - if(tmu) *tmu = (volatile struct mod_tmu *) - (0xfffffe94 + 12 * timer); - } - // Assuming SH7305 by default. - else - { - if(tstr) *tstr = (volatile unsigned char *)0xa4490004; - if(tmu) *tmu = (volatile struct mod_tmu *) - (0xa4490008 + 12 * timer); - } -} diff --git a/src/timer/timer_interrupt.c b/src/timer/timer_interrupt.c deleted file mode 100644 index 5b183f4..0000000 --- a/src/timer/timer_interrupt.c +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include - -#include - -struct Timer timers[3] = { - { .callback = NULL, .data = NULL, .repeats = 0 }, - { .callback = NULL, .data = NULL, .repeats = 0 }, - { .callback = NULL, .data = NULL, .repeats = 0 }, -}; - -/* - timer_interrupt() - Handles the interrupt for the given timer. -*/ -void timer_interrupt(int timer) -{ - volatile struct mod_tmu *tmu; - timer_get(timer, &tmu, NULL); - - tmu->TCR.UNF = 0; - - if(timers[timer].callback) - { - if(timers[timer].data) - { - void (*fun)(void *data) = timers[timer].callback; - fun(timers[timer].data); - } - else - { - void (*fun)(void) = timers[timer].callback; - fun(); - } - } - - // Reducing the number of repetitions left, if not infinite. - if(!timers[timer].repeats) return; - if(timers[timer].repeats == 1) timer_stop(timer); - else timers[timer].repeats--; -} diff --git a/src/timer/timer_reload.c b/src/timer/timer_reload.c deleted file mode 100644 index 7d6bbe8..0000000 --- a/src/timer/timer_reload.c +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include - -/* - timer_reload() - Reloads the given timer with the supplied constant. Starts the timer if - it was stopped. -*/ -void timer_reload(int timer, int new_delay_or_frequency, enum ClockUnit unit) -{ - timer_reload2(timer, clock_setting(new_delay_or_frequency, unit)); -} - -/* - timer_reload2() - Same as timer_reload(), but again uses the native clock count. The - prescaler may not be changed. -*/ -void timer_reload2(int timer, int new_delay) -{ - volatile struct mod_tmu *tmu; - volatile unsigned char *tstr; - int byte = (1 << timer); - timer_get(timer, &tmu, &tstr); - - // Setting the constant and the delay. - tmu->TCOR = new_delay; - tmu->TCNT = new_delay; - - // Starting the timer. - *tstr |= byte; -} diff --git a/src/timer/timer_start.c b/src/timer/timer_start.c deleted file mode 100644 index 0792702..0000000 --- a/src/timer/timer_start.c +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include - -/* - timer_start2() - Configures and starts a time using a clock count and a prescaler. -*/ -void timer_start2(int timer, int delay, int prescaler, void *callback, - void *data, int repeats) -{ - volatile struct mod_tmu *tmu; - volatile unsigned char *tstr; - int byte = (1 << timer); - - timer_get(timer, &tmu, &tstr); - - // Loading the counter, the constant and the prescaler/ - tmu->TCOR = delay; - tmu->TCNT = delay; - tmu->TCR.TPSC = prescaler; - - // Resetting underflow flag and enabling interruptions. - tmu->TCR.UNF = 0; - tmu->TCR.UNIE = 1; - - // Counting on rising edge (ignored on SH7305). - tmu->TCR.CKEG = 0; - - // Loading the structure information. - timers[timer].callback = callback; - timers[timer].data = data; - timers[timer].repeats = repeats; - - // Starting the timer. - *tstr |= byte; -} - -/* - timer_start() - Configures and starts a timer using a delay, or a frequency, and the - associated unit. -*/ -void timer_start(int timer, int delay, enum ClockUnit unit, void *callback, - void *data, int repeats) -{ - timer_start2(timer, clock_setting(delay, unit), TIMER_Po_4, callback, - data, repeats); -} diff --git a/src/timer/timer_stop.c b/src/timer/timer_stop.c deleted file mode 100644 index 083c4a4..0000000 --- a/src/timer/timer_stop.c +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include - -#include - -/* - timer_stop() - Stops the given timer. This function may be called even if the timer is - not running. -*/ -void timer_stop(int timer) -{ - volatile unsigned char *tstr; - int byte = (1 << timer); - - timer_get(timer, NULL, &tstr); - *tstr &= ~byte; -} diff --git a/src/timer/virtual_timers.c b/src/timer/virtual_timers.c new file mode 100644 index 0000000..518c5fd --- /dev/null +++ b/src/timer/virtual_timers.c @@ -0,0 +1,223 @@ +#include +#include +#include +#include + +//--- +// Public API +//--- + +timer_t vtimers[TIMER_SLOTS] = { 0 }; + +/* + timer_create() + Creates a virtual timer and configures its delay and repetition count. +*/ +timer_t *timer_create(int ms_delay, int repeats) +{ + if(ms_delay <= 0) return NULL; + timer_t *timer = vtimers; + + // Finding an available virtual slot. + while(timer < vtimers + TIMER_SLOTS && timer->used) timer++; + if(timer >= vtimers + TIMER_SLOTS) return NULL; + + timer->ms_delay = ms_delay; + timer->ms_elapsed = 0; + timer->repeats_left = repeats; + + timer->used = 1; + timer->active = 0; + timer->virtual = 1; + timer->vsupport = 0; + timer->events = 0; + + timer->callback = NULL; + timer->argument = NULL; + + return timer; +} + +/* + timer_destroy() + Destroys a virtual timer. This virtual timer pointer becomes invalid + and should not be used anymore. +*/ +void timer_destroy(timer_t *timer) +{ + timer->events = 0; + timer->active = 0; + timer->used = 0; + + vtimer_updateAll(); +} + + + +//--- +// Virtual timers management +//--- + +static int current_delay = 0; +static uint32_t current_constant = 0; + +/* + vtimer_interrupt() + Interrupt handling subsystem for the virtual timers. +*/ +void vtimer_interrupt(void) +{ + // Do we need to recompute the hardware support frequency? + int recalc = 0; + + // No timer is running. + if(!current_delay) return; + + // Update them all and call the required callbacks. Stop the ones that + // have been running for long enough. + for(timer_t *timer = vtimers; timer < vtimers + TIMER_SLOTS; timer++) + if(timer->used) + { + timer->ms_elapsed += current_delay; + + // This should happen only once but in case there is a problem, + // this loop will ensure that at least the correct amount of + // callbacks is executed. + // We could divide but it would be slower. + while(timer->ms_elapsed >= timer->ms_delay) + { + // We don't call the callbacks now because we may need + // to update the timer frequency later and we want to + // get there asap. + timer->events++; + timer->ms_elapsed -= timer->ms_delay; + } + + // We would need to stop one virtual timer. + if(timer->repeats_left > 0 && timer->repeats_left <= + timer->events) recalc = 1; + } + + if(recalc) vtimer_updateAll(); + + for(timer_t *timer = vtimers; timer < vtimers + TIMER_SLOTS; timer++) + if(timer->used) + { + while(timer->active && timer->events) + { + timer_callback_event(timer); + timer->events--; + } + timer->used = timer->active; + } +} + +/* + vtimer_update() + Swiftly updates the hardware support counter and constant to keep up + with new kinds of timers being added while disturbing the counting flow + as little as possible. +*/ +static void vtimer_update(int new_delay) +{ + volatile mod_tmu_timer_t *tmu = TMU.timers[timer_virtual]; + timer_t *timer = &htimers[timer_virtual]; + + if(new_delay == current_delay) return; + + if(!new_delay) + { + current_delay = 0; + current_constant = 0; + + // At this point the virtual support timer was running and has + // been stopped, thus the hardware timer structure is filled + // properly so we can use this function. + timer_stop(timer); + return; + } + uint32_t new_constant = clock_setting(new_delay, Clock_ms); + + // The transition needs to be as smooth as possible. We have probably + // spent a lot of time calculating this new GCD delay so we want to + // take into consideration the current value of the timer counter. + // The -1 is here to take into account the time between reading and + // writing the new constant (~10 I_phi cycles thus usually 5 P_phi + // cycles; given this timer runs on P_phi / 4 this makes 1 unit). + // Here we suppose that the new TCNT is positive, i.e that the update + // happened in less than 1 ms. This is reasonable as long as this + // happens *before* calling callbacks. + tmu->TCNT = new_constant - (current_constant - tmu->TCNT - 1); + tmu->TCOR = new_constant; + + if(!current_delay) + { + // Ups, we've been using a random counter; the timer's not + // running. + tmu->TCNT = tmu->TCOR; + // Set it up then. + tmu->TCR.TPSC = timer_Po_4; + tmu->TCR.UNF = 0; + tmu->TCR.UNIE = 1; + tmu->TCR.CKEG = 0; + // Tell them that the virtual timer support is running there. + timer->used = 1; + timer->active = 0; + timer->virtual = 0; + timer->vsupport = 1; + timer->events = 0; + // And let's roll! + timer_start(timer); + } + + current_delay = new_delay; + current_constant = new_constant; +} + +/* + gcd() + Well, the Euclidean algorithm. That's a O(ln(a)) & O(ln(b)) FWIW. +*/ +static int gcd(int a, int b) +{ + while(b) + { + int r = a % b; + a = b; + b = r; + } + + return a; +} + +/* + vtimer_updateOne() + Update the virtual timer hardware support timer, knowing that a virtual + timer with the given delay has been started. +*/ +void vtimer_updateOne(int additional_delay) +{ + vtimer_update(gcd(current_delay, additional_delay)); +} + +/* + vtimer_updateAll() + Updates the virtual timer hardware support after computing the GCD of + all virtual timers delays. This computation is rather long (especially + this modulo can afaik only be achieved using division on SuperH and + it's a 70-cycle operation) so it should be avoided when possible. +*/ + +void vtimer_updateAll(void) +{ + int gcd_delay = 0; + + for(timer_t *timer = vtimers; timer < vtimers + TIMER_SLOTS; timer++) + if(timer->used && timer->active && (timer->repeats_left <= 0 || + timer->repeats_left > timer->events)) + { + gcd_delay = gcd(gcd_delay, timer->ms_delay); + } + + vtimer_update(gcd_delay); +}