From 298338f633c45f04a00117a435566006d8439119 Mon Sep 17 00:00:00 2001 From: lephe Date: Sun, 19 Aug 2018 17:11:37 +0200 Subject: [PATCH] More timers, RTC, basic overclock-resistant keyboard, CPG, PFC, driver levels. --- fx9860g.ld | 12 ++- fxcg50.ld | 12 ++- include/core/cpg.h | 80 ++++++++++++++++ include/core/mpu.h | 13 +-- include/core/pfc.h | 77 +++++++++++++++ include/defs/attributes.h | 3 + include/gint/clock.h | 76 ++++++++++++++- include/gint/drivers.h | 19 +++- include/gint/gint.h | 27 +++--- include/gint/keyboard.h | 67 +++++++++++++ include/gint/rtc.h | 82 ++++++++++++++++ include/gint/timer.h | 30 ++---- include/mod/rtc.h | 76 +++++++++++++++ src/clock/freq.c | 136 +++++++++++++++++++++++++++ src/clock/sleep.c | 0 src/core/bootlog.c | 11 ++- src/core/gint.c | 11 +-- src/core/setup.c | 61 ++++++------ src/core/start.c | 15 ++- src/keysc/iokbd.c | 114 +++++++++++++++++++++++ src/keysc/keysc.c | 188 +++++++++++++++++++++++++++++++++++++ src/r61524/r61524.c | 2 +- src/rtc/inth.s | 60 ++++++++++++ src/rtc/rtc.c | 191 ++++++++++++++++++++++++++++++++++++++ src/t6k11/t6k11.c | 13 +-- src/tmu/tmu.c | 99 +++++++++++--------- 26 files changed, 1326 insertions(+), 149 deletions(-) create mode 100644 include/core/cpg.h create mode 100644 include/core/pfc.h create mode 100644 include/gint/keyboard.h create mode 100644 include/gint/rtc.h create mode 100644 include/mod/rtc.h create mode 100644 src/clock/sleep.c create mode 100644 src/keysc/iokbd.c create mode 100644 src/keysc/keysc.c create mode 100644 src/rtc/inth.s create mode 100644 src/rtc/rtc.c diff --git a/fx9860g.ld b/fx9860g.ld index dba5111..3a45257 100644 --- a/fx9860g.ld +++ b/fx9860g.ld @@ -19,9 +19,9 @@ MEMORY /* This is mapped to RAM; 8k on SH3, apparently 32k on SH4 */ ram (rw): o = 0x08100000, l = 8k /* gint's VBR space, mentioned here for completeness */ - vbr (rwx): o = 0x8800e000, l = 4k + vbr (rwx): o = 0x8800e000, l = 5k /* Some RAM region from P1 area; gint's data will reside here */ - rram (rwx): o = 0x8800f000, l = 4k + rram (rwx): o = 0x8800f400, l = 3k } SECTIONS @@ -76,7 +76,13 @@ SECTIONS driver, even if the symbols are not referenced */ .gint.drivers : { _bdrv = . ; - KEEP(*(.gint.drivers)); + KEEP(*(.gint.drivers.0)); + KEEP(*(.gint.drivers.1)); + KEEP(*(.gint.drivers.2)); + KEEP(*(.gint.drivers.3)); + KEEP(*(.gint.drivers.4)); + KEEP(*(.gint.drivers.5)); + KEEP(*(.gint.drivers.6)); _edrv = . ; } > rom diff --git a/fxcg50.ld b/fxcg50.ld index c35132d..251cff0 100644 --- a/fxcg50.ld +++ b/fxcg50.ld @@ -18,9 +18,9 @@ MEMORY The first 0x2000 bytes are reserved by gint, see below */ ram (rw): o = 0x08102000, l = 512k /* gint's VBR space, mentioned here for completeness */ - vbr (rwx): o = 0x8c160000, l = 4k + vbr (rwx): o = 0x8c160000, l = 5k /* Some RAM region from P1 area; gint's data will reside here */ - rram (rwx): o = 0x8c161000, l = 4k + rram (rwx): o = 0x8c161400, l = 3k } SECTIONS @@ -68,7 +68,13 @@ SECTIONS driver, even if the symbols are not referenced */ .gint.drivers : { _bdrv = . ; - KEEP(*(.gint.drivers)); + KEEP(*(.gint.drivers.0)); + KEEP(*(.gint.drivers.1)); + KEEP(*(.gint.drivers.2)); + KEEP(*(.gint.drivers.3)); + KEEP(*(.gint.drivers.4)); + KEEP(*(.gint.drivers.5)); + KEEP(*(.gint.drivers.6)); _edrv = . ; } > rom diff --git a/include/core/cpg.h b/include/core/cpg.h new file mode 100644 index 0000000..07d5244 --- /dev/null +++ b/include/core/cpg.h @@ -0,0 +1,80 @@ +//--- +// gint:core:cpg - Clock Pulse Generator +//--- + +#ifndef GINT_CORE_CPG +#define GINT_CORE_CPG + +#include + +//--- +// SH7705 Clock Pulse Generator. Refer to: +// "Renesas SH7705 Group Hardware Manual" +// Section 9: "Interrupt Controller (INTC)" +//--- + +/* sh7705_cpg_t - Clock Pulse Generator registers */ +typedef volatile struct +{ + word_union(FRQCR, + uint16_t :3; + uint16_t CKOEN :1; /* Clock Output Enable */ + uint16_t :2; + uint16_t STC :2; /* PLL multiplication ratio */ + uint16_t :2; + uint16_t IFC :2; /* Internal clock divider */ + uint16_t :2; + uint16_t PFC :2; /* Peripheral clock divider */ + ); + +} PACKED(4) sh7705_cpg_t; + +#define SH7705_CPG (*((sh7705_cpg_t *)0xffffff80)) + +//--- +// SH7305 Clock Pulse Generator. Refer to: +// "Renesas SH7724 User's Manual: Hardware" +// Section 17: "Clock Pulse Generator (CPG)" +//--- + +/* sh7305_cpg_t - Clock Pulse Generator registers + Fields marked with [*] don't have the meaning described in the SH7724 + documentation. */ +typedef volatile struct +{ + lword_union(FRQCRA, + uint32_t KICK :1; /* Flush FRQCRA modifications */ + uint32_t :1; + uint32_t STC :6; /* PLL multiplication [*] */ + uint32_t IFC :4; /* Iphi divider 1 [*] */ + uint32_t :4; + uint32_t SFC :4; /* Sphi divider 1 [*] */ + uint32_t BFC :4; /* Bphi divider 1 [*] */ + uint32_t :4; + uint32_t P1FC :4; /* Pphi divider 1 [*] */ + ); + pad(0x20); + + lword_union(PLLCR, + uint32_t :17; + uint32_t PLLE :1; /* PLL Enable */ + uint32_t :1; + uint32_t FLLE :1; /* FLL Enable */ + uint32_t :10; + uint32_t CKOFF :1; /* CKO Output Stop */ + uint32_t :1; + ); + pad(0x28); + + lword_union(FLLFRQ, + uint32_t :16; + uint32_t SELXM :2; /* FLL output division */ + uint32_t :3; + uint32_t FLF :11; /* FLL Multiplication Ratio */ + ); + +} PACKED(4) sh7305_cpg_t; + +#define SH7305_CPG (*((sh7305_cpg_t *)0xa4150000)) + +#endif /* GINT_CORE_CPG */ diff --git a/include/core/mpu.h b/include/core/mpu.h index 95486e3..c963eb6 100644 --- a/include/core/mpu.h +++ b/include/core/mpu.h @@ -3,13 +3,10 @@ // // This component detects the architecture and MPU type of the underlying // hardware by relying on version registers and/or side-information. It -// provides macros isSH3() and isSH4(), but the best way of performing -// MPU-dependent jobs is to use mpuSwitch(): +// provides macros isSH3() and isSH4() for MPU-dependent jobs // -// mpuSwitch( -// print("SH3 code"), -// print("SH4 code") -// ); +// if(isSH3()) print("SH3 code"); +// else print("SH4 code"); //--- #ifndef GINT_CORE_MPU @@ -40,8 +37,6 @@ typedef enum #define isSH3() (mpu_id() & 1) #define isSH4() (!isSH3()) - #define mpuSwitch(a, b) do { if(isSH3()) { a; } else { b; } } while(0) - /* mpu_init() - probe the MPU type This function must be executed before mpu_id() can be used. */ void mpu_init(void); @@ -53,8 +48,6 @@ typedef enum #define isSH3() 0 #define isSH4() 1 - #define mpuSwitch(a, b) do { b; } while(0) - #endif /* FX9860G */ #endif /* GINT_CORE_MPU */ diff --git a/include/core/pfc.h b/include/core/pfc.h new file mode 100644 index 0000000..bcee78b --- /dev/null +++ b/include/core/pfc.h @@ -0,0 +1,77 @@ +//--- +// gint:core:pfc - Pin Function Controller +// +// The Pin Function Controller has a simple register interface, the main +// difficulty is still understanding the role of its pins. +//--- + +#ifndef GINT_CORE_PFC +#define GINT_CORE_PFC + +#include + +//--- +// SH7705 Pin Function Controller. Refer to: +// "Renesas SH7705 Group Hardware Manual" +// Section 19: "Pin Function Controller" +//--- + +typedef volatile struct +{ + /* Control registers */ + uint16_t PACR; + uint16_t PBCR; + uint16_t PCCR; + uint16_t PDCR; + uint16_t PECR; + uint16_t PFCR; + uint16_t PGCR; + uint16_t PHCR; + uint16_t PJCR; + uint16_t PKCR; + uint16_t PLCR; + uint16_t SCPCR; /* Port SC control register */ + uint16_t PMCR; + uint16_t PNCR; + + pad(4); + + /* Data registers */ + uint8_t PADR; + pad(1); + uint8_t PBDR; + pad(1); + uint8_t PCDR; + pad(1); + uint8_t PDDR; + pad(1); + uint8_t PEDR; + pad(1); + uint8_t PFDR; + pad(1); + uint8_t PGDR; + pad(1); + uint8_t PHDR; + pad(1); + uint8_t PJDR; + pad(1); + uint8_t PKDR; + pad(1); + uint8_t PLDR; + pad(1); + uint8_t SCPDR; /* Port SC data register */ + pad(1); + uint8_t PMDR; + pad(1); + uint8_t PNDR; + pad(1); + +} PACKED(4) sh7705_pfc_t; + +#define SH7705_PFC (*((sh7705_pfc_t *)0xa4000100)) + +//--- +// TODO: Document the SH7305 Pin Function Controller +//--- + +#endif /* GINT_CORE_PFC */ diff --git a/include/defs/attributes.h b/include/defs/attributes.h index 67d45a2..9986936 100644 --- a/include/defs/attributes.h +++ b/include/defs/attributes.h @@ -30,6 +30,9 @@ #define CTOR(x) __attribute__((constructor ((x) + 101))) PRETEXT #define DTOR(x) __attribute__((destructor ((x) + 101))) +/* Aligned variables */ +#define ALIGNED(x) __attribute__((aligned(x))) + /* Packed structures. I require explicit alignment because if it's unspecified, GCC cannot optimize access size, and reads to memory-mapped I/O with invalid access sizes silently fail - honestly you don't want this to happen */ diff --git a/include/gint/clock.h b/include/gint/clock.h index 959c76a..eeb3f00 100644 --- a/include/gint/clock.h +++ b/include/gint/clock.h @@ -1,8 +1,82 @@ //--- -// gint:clock - Clock signals +// gint:clock - Clock signals, overclock, and standby modes //--- #ifndef GINT_CLOCK #define GINT_CLOCK +#include + +//--- +// Clock signals +//--- + +/* clock_frequency_t + A dump of the Clock Pulse Generator's (CPG) configuration. Use the MPU + detection functions from to use the correct fields. */ +typedef struct +{ + union { + int PLL1; + int FLL; + }; + union { + int PLL2; + int PLL; + }; + + int Bphi_div; + int Iphi_div; + int Pphi_div; + + union { + int CKIO_f; + int RTCCLK_f; + }; + + int Bphi_f; + int Iphi_f; + int Pphi_f; + +} clock_frequency_t; + +/* clock_freq() - get the frequency of the main clocks + This function returns the address of a static object which is used by the + module; this address never changes. */ +const clock_frequency_t *clock_freq(void); + +//--- +// Overclock +//--- + +/* TODO: All overclock */ + +//--- +// Sleep functions +//--- + +/* sleep() - halt the processor until an event occurs + The function stops the processor until an interrupt is accepted; the + duration is not known in advance. This function should be used when the + add-in is idle, for instance while waiting for keyboard input. */ +void sleep(void); + +/* sleep_us() - sleep for a definite duration in microseconds + + Stops the processor until [delay_us] microseconds have elapsed. Interrupts + may occur during that time (especially timers firing), in which case the + events will be treated as usual. The processor will resume sleeping after + handling them. + + The user may choose the timer used to time out the sleep. Remember that only + timers 0 to 2 have microsecond-level resolution; other timers count in units + of about 30 us. + + @timer Which timer to use to time out the sleep + @us_delay How long to sleep (in microseconds) */ +void sleep_us(int timer, int us_delay); + +/* sleep_ms() - sleep for a definite duration in milliseconds */ +#define sleep_ms(timer, ms_delay) sleep_us(timer, (ms_delay) * 1000) + #endif /* GINT_CLOCK */ diff --git a/include/gint/drivers.h b/include/gint/drivers.h index d52e088..475378e 100644 --- a/include/gint/drivers.h +++ b/include/gint/drivers.h @@ -39,6 +39,11 @@ typedef struct may be set to NULL */ void (*init)(void); + /* unload() - unitialize the driver + This function is called before ctx_restore() when gint is unloaded. + If there is no unload function, the field may be set to NULL */ + void (*unload)(void); + /* Size of a context object for the driver */ uint ctx_size; @@ -63,11 +68,17 @@ typedef struct } PACKED(4) gint_driver_t; /* GINT_DECLARE_DRIVER() - make a driver visible to gint + Use this macro to expose a driver by passing it the name of a gint_driver_t - structure. This macro moves the structure to the .gint.drivers section, - which is automatically traversed at startup */ -#define GINT_DECLARE_DRIVER(name) \ - SECTION(".gint.drivers") extern gint_driver_t name; + structure. This macro moves the structure to the .gint.drivers.* sections, + which are automatically traversed at startup. + + The @level argument represents the priority level: lower numbers mean that + drivers will be loaded sooner. This numbering allows a primitive form of + dependency for drivers. You need to specifiy a level which is strictly + higher than the level of all the drivers you depend on. */ +#define GINT_DECLARE_DRIVER(level, name) \ + SECTION(".gint.drivers." #level) extern gint_driver_t name; /* GINT_DRIVER_SH3() - declare a function for SH3-rectification This macros allows the argument function to not exist on fxcg50. */ diff --git a/include/gint/gint.h b/include/gint/gint.h index a9906f3..14f11e9 100644 --- a/include/gint/gint.h +++ b/include/gint/gint.h @@ -92,23 +92,27 @@ int gint_intlevel(int intid, int level); When an interrupt request is accepted, the hardware jumps to a specific interrupt handler at an address that depends on the interrupt source. - Each interrupt handler should only refer to data within its own block - because the relative displacement between blocks is MPU-dependent. There are - a few exceptions to this, such as timer handlers, which are contiguous on - all currently-used platforms. Be careful. + For safety, interrupt handlers should avoir referring to data from other + blocks because the arrangement of blocks at runtime depends on event codes. + The assembler program will assume that consecutive blocks in the source code + will be consecutive in memory, which is not always true. Avoiding cross- + references is a practical rule to avoid problems. (gint breaks this rule + very often but does it carefully... I guess?) This function allows anyone to replace any interrupt handler so make sure you're not interfering with usual interrupt assignments. The first parameter 'event_code' represents the event_code associated with - the interrupt. These codes are normally platform-dependent, but gint always - uses the SH7305 codes: SH3 platforms have a translation table. See the + the interrupt. The codes are normally platform-dependent, but gint always + uses SH7305 codes: SH3 platforms have a translation table. See the documentation for a list of event codes and their associated interrupts. - The handler function must be an interrupt handler: it should not raise - exceptions, must end with 'rte', uses the kernel register bank... and it - must fit within 32 bytes. If it's not written in assembler, then you're - likely doing something wrong. + The handler function must be an interrupt handler: it must not raise + exceptions, must end with 'rte', and it will use the kernel register bank. + For convenience I allow any block size to be loaded as an interrupt handler, + but it should really be a multiple of 32 bytes and not override other + handlers. If it's not written in assembler, then you're likely doing + something wrong. It is common for interrupt handlers to have a few bytes of data, such as the address of a callback function. gint often stores this data in the last @@ -117,7 +121,8 @@ int gint_intlevel(int intid, int level); @event_code Identifier of the interrupt block @handler Address of handler function + @size How many bytes to copy Returns the VBR address where the handlers was installed. */ -void *gint_inthandler(int event_code, const void *handler); +void *gint_inthandler(int event_code, const void *handler, size_t size); #endif /* GINT_GINT */ diff --git a/include/gint/keyboard.h b/include/gint/keyboard.h new file mode 100644 index 0000000..12696dd --- /dev/null +++ b/include/gint/keyboard.h @@ -0,0 +1,67 @@ +//--- +// gint:keyboard - Keyboard input +//--- + +#ifndef GINT_KEYBOARD +#define GINT_KEYBOARD + +#include + +/* key_event_t - any keyboard event + This structure represents an event that occurs on the keyboard. This is a + low-level structure that is produced by the keyboard scanner. It reports key + presses, key releases, and key repetitions. + + These events are detected and reported each time the keyboard is scanned, + which is 16 Hz by default, so you'll get 16 repeat events by second if a key + is kept pressed. We can filter the events to emit one only every second, for + example, but it's difficult to do it for all keys at the same time. Thus the + control of repetition delays is restricted to getkey(). + + When mod = 1, shift and alpha indicate whether the key has been modified. + This is only possible for key press events returned by getkey(). Note that + you can't have key = shift and mod = 1 at the same time. + + The time attribute indicates when the event occurred. This value increases + at each keyboard scan and *it wraps around every 4 minutes* (at 16 Hz). + I expect this attribute to be useful to analyze combo sequences in games. + Make sure you are aware of the two nitpicks: + - Don't keep the time values for too long because the wrap-around effect. + - 0xfff is just before 0x000, not long after. */ +typedef struct +{ + uint time :12; /* Time of event, unique over short periods */ + + uint :7; /* Reserved for future use */ + + uint mod :1; /* Whether modifiers are used */ + uint shift :1; /* If mod=1, whether SHIFT was pressed */ + uint alpha :1; /* If mod=1, whether ALPHA was pressed */ + + uint type :2; /* Type of key event */ + uint key :8; /* Hit key */ + +} PACKED(4) key_event_t; + +/* Keyboard event types, as in the type field of key_event_t */ +enum +{ + KEYEV_NONE = 0, /* No event available (poll() only) */ + KEYEV_DOWN = 1, /* Key was pressed */ + KEYEV_UP = 2, /* Key was released */ + KEYEV_HOLD = 3, /* A key that was pressed has been held down */ +}; + +/* Size of the buffer event queue, can be customized using gint's configure + script before compiling the library. Better be a power of 2. */ +#ifndef KEYBOARD_QUEUE_SIZE +#define KEYBOARD_QUEUE_SIZE 64 +#endif + +//--- +// Keyboard functions +//--- + +key_event_t key_poll(void); + +#endif /* GINT_KEYBOARD */ diff --git a/include/gint/rtc.h b/include/gint/rtc.h new file mode 100644 index 0000000..fff6c5c --- /dev/null +++ b/include/gint/rtc.h @@ -0,0 +1,82 @@ +//--- +// gint:rtc - Real-Time Clock +//--- + +#ifndef GINT_RTC +#define GINT_RTC + +#include + +//--- +// Time management +//--- + +/* rtc_time_t - a point in time, representable in the RTC registers */ +typedef struct +{ + uint16_t year; /* Years (exact value, e.g. 2018) */ + uint8_t week_day; /* Day of week, (0=Sunday, 6=Saturday) */ + uint8_t month; /* Month (0..11) */ + uint8_t month_day; /* Day of month (1..31) */ + uint8_t hours; /* Hour (0..23) */ + uint8_t minutes; /* Minute (0..59) */ + uint8_t seconds; // Second (0..59) */ + +} rtc_time_t; + +/* rtc_get_time() - read the current time from the RTC + @time Pointer to rtc_time_t structure (needs not be initialized) */ +void rtc_get_time(rtc_time_t *time); + +/* rtc_set_time() - write a new current time to the RTC + If [time->week_day] is not in the valid range, it is set to 0. Other fields + are not checked. + @time Pointer to new time */ +void rtc_set_time(const rtc_time_t *time); + +//--- +// RTC timer +// 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 ) are properly detected, by +// comparing the detected frequencies with the RTC. +//--- + +/* rtc_frequency_t - possible frequency settings for the RTC's interrupt */ +typedef enum +{ + rtc_500mHz = 7, + rtc_1Hz = 6, + rtc_2Hz = 5, + rtc_4Hz = 4, + rtc_16Hz = 3, + rtc_64Hz = 2, + rtc_256Hz = 1, + rtc_none = 0, + +} rtc_frequency_t; + +/* rtc_start_timer() - configure the RTC timer + + This function sets up the RTC timer to invoke the provided callback function + with its argument regularly. This works like normal timers (); + [callback] is passed [arg] as argument and the RTC timer is stopped if it + returns non-zero. + + This function will replace any existing callback! + + Note that, as opposed to timers, it is not possible to know how much time + will elapse before the callback will first be called. For instance, setting + up a 1 Hz-callback when the current time ends in 950 ms will result in the + callback being called after 50 ms, then every second. This is not a problem + for most uses. */ +void rtc_start_timer(rtc_frequency_t freq, int (*callback)(void *arg), + void *arg); + +/* 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 non-zero. */ +void rtc_stop_timer(void); + +#endif /* GINT_RTC */ diff --git a/include/gint/timer.h b/include/gint/timer.h index 82d9e47..7f325e7 100644 --- a/include/gint/timer.h +++ b/include/gint/timer.h @@ -29,13 +29,7 @@ what you intended. */ /* timer_count() - tells how many timers are available on the platform */ -HDRFUNC int timer_count(void) -{ - mpuSwitch( - return 4, /* SH3-based */ - return 9 /* SH4-based */ - ); -} +#define timer_count() (isSH3() ? 4 : 9) /* Clock input @@ -123,21 +117,15 @@ uint32_t timer_delay(int timer, int delay_us); @timer Timer id, as returned by timer_setup() */ void timer_start(int timer); -/* timer_reload() - change a timer's delay and source +/* timer_reload() - change a timer's delay constant for next interrupts - Changes the delay and source of the given timer. This function does not - start the timer if it stopped. The timer will start counting down from the - new delay when it is started (or immediately if it was running), no matter - how much time has elapsed since it last fired. Accumulated events are not - dropped. - - In cases where choosing a delay and input clock is difficult (such as when - using overclocking), use the timer_delay() function. + Changes the delay constant of the given timer. Nothing will happen until the + next callback; then the timer will update its delay to reflect the new + constant. The new delay can be calculated by the timer_delay() function. @timer Timer id, as returned by timer_setup() - @delay New delay (unit depends on the clock source) - @clock New clock source to be used by the timer */ -void timer_reload(int timer, uint32_t delay, timer_input_t clock); + @delay New delay (unit depends on the clock source) */ +void timer_reload(int timer, uint32_t delay); /* timer_pause() - stop a running timer The specified timer will be paused; its counter will not be reset. A stopped @@ -147,11 +135,11 @@ void timer_reload(int timer, uint32_t delay, timer_input_t clock); @timer Timer id, as returned by timer_setup() */ void timer_pause(int timer); -/* timer_free() - free a timer +/* timer_stop() - stop and free a timer Stops and destroys a timer, making its id free for re-use. The id must not be used anymore until it is returned by a further call to timer_setup(). @timer Timer id, as returned by timer_setup() */ -void timer_free(int timer); +void timer_stop(int timer); #endif /* GINT_TIMER */ diff --git a/include/mod/rtc.h b/include/mod/rtc.h new file mode 100644 index 0000000..b44215b --- /dev/null +++ b/include/mod/rtc.h @@ -0,0 +1,76 @@ +//--- +// gint:mod:rtc - Real-Time Clock +//--- + +#ifndef GINT_CORE_RTC +#define GINT_CORE_RTC + +//--- +// Hybrid SH7705-SH7305 Real-Time Clock. Refer to: +// "Renesas SH7705 Group Hardware Manual" +// Section 15: "Real-Time Clock (RTC)" +// "Renesas SH7724 User's Manual: Hardware" +// Section 28: "Real-Time Clock (RTC)" +//--- + +/* rtc_BCD2_t - a 2-digit BCD counter with a 1-byte gap */ +typedef struct +{ + byte_union(, + uint8_t TENS :4; + uint8_t ONES :4; + ); + pad(1); + +} PACKED(2) rtc_BCD2_t; + +/* sh7705_rtc_t, sh7305_rtc_t - Date and time access, RTC control */ +typedef volatile struct +{ + uint8_t const R64CNT; /* A 64-Hz counter */ + pad(1); + + rtc_BCD2_t RSECCNT; /* Second count */ + rtc_BCD2_t RMINCNT; /* Minute count */ + rtc_BCD2_t RHRCNT; /* Hour count */ + + uint8_t RWKCNT; /* Day of week, must be in [0..6] */ + pad(1); + + rtc_BCD2_t RDAYCNT; /* Day count */ + rtc_BCD2_t RMONCNT; /* Month count */ + + word_union(RYRCNT, /* Year count */ + uint THOUSANDS :4; + uint HUNDREDS :4; + uint TENS :4; + uint ONES :4; + ); + pad(12); /* Alarm registers... */ + + byte_union(RCR1, + uint8_t CF :1; /* Carry flag */ + uint8_t :2; + uint8_t CIE :1; /* Carry interrupt enable */ + uint8_t AIE :1; /* Alarm interrupt enable */ + uint8_t :2; + uint8_t AF :1; /* Alarm flag */ + ); + pad(1); + + byte_union(RCR2, + uint8_t PEF :1; /* Periodic interrupt flag */ + uint8_t PES :3; /* Periodic interrupt interval */ + uint8_t :1; + uint8_t ADJ :1; /* 30-second adjustment */ + uint8_t RESET :1; /* Reset trigger */ + uint8_t START :1; /* Start bit */ + ); + pad(1); + +} PACKED(4) rtc_t; + +#define SH7705_RTC (*((rtc_t *)0xfffffec0)) +#define SH7305_RTC (*((rtc_t *)0xa413fec0)) + +#endif /* GINT_CORE_RTC */ diff --git a/src/clock/freq.c b/src/clock/freq.c index e69de29..4ff270f 100644 --- a/src/clock/freq.c +++ b/src/clock/freq.c @@ -0,0 +1,136 @@ +//--- +// gint:core:freq - Clock frequency management +//--- + +#include +#include + +#include +#include +#include + +//--- +// Driver storage +//--- + +/* Local copy of the CPG settings */ +GBSS static clock_frequency_t freq; + +/* clock_freq() - get the frequency of the main clocks */ +const clock_frequency_t *clock_freq(void) +{ + return &freq; +} + +//--- +// SH7705 Clock signals +//--- + +#ifdef FX9860G +#define CPG SH7705_CPG + +static void sh7705_probe(void) +{ + /* According to Sentaro21 in the sources of Ftune 1.0.1, the clock mode + is thought to be 5, which means that: + - CPG input is XTAL (14.745'600 MHz) + - PLL2 is active and *2 (29.491'200 MHz) + - CKIO is output from PLL2 (29.491'200 MHz) */ + int xtal = 14745600; + int pll2 = 2; + int ckio = xtal * pll2; + + /* This signal is multiplied by the PLL1 circuit */ + int pll1 = CPG.FRQCR.STC + 1; + + /* Iphi and Pphi have dividers (Bphi is always equal to CKIO) */ + int idiv = CPG.FRQCR.IFC; + int pdiv = CPG.FRQCR.PFC; + + /* Fill in the setting structure */ + freq.PLL1 = pll1; + freq.PLL2 = pll2; + freq.Bphi_div = 1; + freq.Iphi_div = idiv + 1; + freq.Pphi_div = pdiv + 1; + + /* Deduce the frequency of the main clocks. The following piece of code + hardcodes ckio / 3 and avoids using the division operator */ + int ckio_3 = 9830400; + + /* Exchange the setting values 2 and 3 */ + idiv = idiv ^ (idiv >> 1); + pdiv = pdiv ^ (pdiv >> 1); + + freq.CKIO_f = ckio; + freq.Bphi_f = ckio; + freq.Iphi_f = (idiv == 3) ? ckio_3 : ckio >> idiv; + freq.Pphi_f = (pdiv == 3) ? ckio_3 : ckio >> pdiv; +} + +#undef CPG +#endif /* FX9860G */ + +//--- +// SH7305 clock signals +//--- + +#define CPG SH7305_CPG + +static void sh7305_probe(void) +{ + /* The meaning of the PLL setting on SH7305 differs from the + documentation of SH7224; the setting must not be doubled. */ + int pll = CPG.FRQCRA.STC + 1; + freq.PLL = pll; + + /* The FLL ratio is the value of the setting, possibly halved */ + int fll = CPG.FLLFRQ.FLF; + if(CPG.FLLFRQ.SELXM == 1) fll >>= 1; + freq.FLL = fll; + + /* On SH7724, the divider ratio is given by 1 / (setting + 1), but here + it's actually 1 / (2^setting + 1). */ + + int divb = CPG.FRQCRA.BFC; + int divi = CPG.FRQCRA.IFC; + int divp = CPG.FRQCRA.P1FC; + + freq.Bphi_div = 1 << (divb + 1); + freq.Iphi_div = 1 << (divi + 1); + freq.Pphi_div = 1 << (divp + 1); + + /* Deduce the input frequency of divider 1 */ + int base = 32768; + if(CPG.PLLCR.FLLE) base *= fll; + if(CPG.PLLCR.PLLE) base *= pll; + + /* And the frequency of all other input clocks */ + freq.RTCCLK_f = 32768; + freq.Bphi_f = base >> (divb + 1); + freq.Iphi_f = base >> (divi + 1); + freq.Pphi_f = base >> (divp + 1); +} + +#undef CPG + +//--- +// Other driver stuff +//--- + +static void init(void) +{ + isSH3() ? sh7705_probe() + : sh7305_probe(); +} + +gint_driver_t drv_cpg = { + .name = "Clock Pulse Generator", + .init = init, + .ctx_size = 0, + .sys_ctx = NULL, + .ctx_save = NULL, + .ctx_restore = NULL, +}; + +GINT_DECLARE_DRIVER(1, drv_cpg); diff --git a/src/clock/sleep.c b/src/clock/sleep.c new file mode 100644 index 0000000..e69de29 diff --git a/src/core/bootlog.c b/src/core/bootlog.c index 635bf56..d0877aa 100644 --- a/src/core/bootlog.c +++ b/src/core/bootlog.c @@ -84,8 +84,8 @@ void bootlog_kernel(void) { print(15, 1, " Kernel"); - mpuSwitch({ - /* SH3-based */ + if(isSH3()) + { print(1, 5, "ABCD"); print_hex( 6, 5, INTC3._.IPRA->word, 4); print_hex(10, 5, INTC3._.IPRB->word, 4); @@ -96,8 +96,9 @@ void bootlog_kernel(void) print_hex(10, 6, INTC3._.IPRF->word, 4); print_hex(14, 6, INTC3._.IPRG->word, 4); print_hex(18, 6, INTC3._.IPRH->word, 4); - },{ - /* SH4-based */ + } + else + { print(1, 5, "ACFG"); print_hex( 6, 5, INTC4._->IPRA.word, 4); print_hex(10, 5, INTC4._->IPRC.word, 4); @@ -108,7 +109,7 @@ void bootlog_kernel(void) print_hex(10, 6, INTC4._->IPRJ.word, 4); print_hex(14, 6, INTC4._->IPRK.word, 4); print_hex(18, 6, INTC4._->IPRL.word, 4); - }); + } Bdisp_PutDisp_DD(); } diff --git a/src/core/gint.c b/src/core/gint.c index 278557d..8be9115 100644 --- a/src/core/gint.c +++ b/src/core/gint.c @@ -36,10 +36,9 @@ int gint_intlevel(int intid, int level) volatile uint16_t *ipr; level &= 0xf; - mpuSwitch( - ipr = INTC3.IPRS[intid >> 2], /* SH3-based */ - ipr = &INTC4.IPRS[2 * (intid >> 2)] /* SH4-based */ - ); + ipr = isSH3() + ? INTC3.IPRS[intid >> 2] /* SH3-based */ + : &INTC4.IPRS[2 * (intid >> 2)]; /* SH4-based */ int oldlevel = (*ipr >> shift) & 0xf; *ipr = (*ipr & ~(0xf << shift)) | (level << shift); @@ -48,7 +47,7 @@ int gint_intlevel(int intid, int level) } /* gint_inthandler() - configure interrupt handlers */ -void *gint_inthandler(int event_code, const void *handler) +void *gint_inthandler(int event_code, const void *handler, size_t size) { extern char gint_vbr; @@ -60,5 +59,5 @@ void *gint_inthandler(int event_code, const void *handler) if(event_code < 0) return NULL; void *dest = (void *)&gint_vbr + event_code + 0x620; - return memcpy(dest, handler, 32); + return memcpy(dest, handler, size); } diff --git a/src/core/setup.c b/src/core/setup.c index c307ae3..b830239 100644 --- a/src/core/setup.c +++ b/src/core/setup.c @@ -30,24 +30,28 @@ typedef struct @arg ctx gint core context object */ static void gint_ctx_save(gint_core_ctx *ctx) { - mpuSwitch( - /* SH3-based */ - for(int i = 0; i < 8; i++) ctx->iprs[i] = *(INTC3.IPRS[i]), - /* SH4-based */ - for(int i = 0; i < 12; i++) ctx->iprs[i] = INTC4.IPRS[2 * i] - ); + if(isSH3()) + { + for(int i = 0; i < 8; i++) ctx->iprs[i] = *(INTC3.IPRS[i]); + } + else + { + for(int i = 0; i < 12; i++) ctx->iprs[i] = INTC4.IPRS[2 * i]; + } } /* gint_ctx_restore() - restore interrupt controller configuration @arg ctx gint core context object */ static void gint_ctx_restore(gint_core_ctx *ctx) { - mpuSwitch( - /* SH3-based */ - for(int i = 0; i < 8; i++) *(INTC3.IPRS[i]) = ctx->iprs[i], - /* SH4-based */ - for(int i = 0; i < 12; i++) INTC4.IPRS[2 * i] = ctx->iprs[i] - ); + if(isSH3()) + { + for(int i = 0; i < 8; i++) *(INTC3.IPRS[i]) = ctx->iprs[i]; + } + else + { + for(int i = 0; i < 12; i++) INTC4.IPRS[2 * i] = ctx->iprs[i]; + } } //--- @@ -61,12 +65,14 @@ GBSS static gint_core_ctx sys_ctx; static void lock(void) { /* Just disable everything, drivers will enable what they support */ - mpuSwitch( - /* SH3-based */ - for(int i = 0; i < 8; i++) *(INTC3.IPRS[i]) = 0x0000, - /* SH4-based */ - for(int i = 0; i < 12; i++) INTC4.IPRS[2 * i] = 0x0000 - ); + if(isSH3()) + { + for(int i = 0; i < 8; i++) *(INTC3.IPRS[i]) = 0x0000; + } + else + { + for(int i = 0; i < 12; i++) INTC4.IPRS[2 * i] = 0x0000; + } } /* gint_install() - install and start gint */ @@ -87,19 +93,20 @@ void gint_install(void) /* Load the event handler entry points into memory */ /* TODO: Load an exception handler and a TLB miss handler */ - mpuSwitch({ - /* SH3-based */ + if(isSH3()) + { extern void exch_entry_7705(void); extern void inth_entry_7705(void); exch_entry = exch_entry_7705; inth_entry = inth_entry_7705; - },{ - /* SH4-based */ + } + else + { extern void exch_entry_7305(void); extern void inth_entry_7305(void); exch_entry = exch_entry_7305; inth_entry = inth_entry_7305; - }); + } memcpy((void *)(vbr + 0x100), exch_entry, 32); memcpy((void *)(vbr + 0x600), inth_entry, 32); @@ -113,10 +120,12 @@ static void unlock(void) { gint_ctx_restore(&sys_ctx); - /* Restore all driver settings */ - for(gint_driver_t *drv = &bdrv; drv < &edrv; drv++) + /* Restore all driver settings, but do it in reverse order of loading + to honor the dependency system */ + for(gint_driver_t *drv = &edrv; (--drv) >= &edrv;) { - drv->ctx_restore(drv->sys_ctx); + if(drv->unload) drv->unload(); + if(drv->ctx_restore) drv->ctx_restore(drv->sys_ctx); } } diff --git a/src/core/start.c b/src/core/start.c index 7b8bf3d..13925b5 100644 --- a/src/core/start.c +++ b/src/core/start.c @@ -133,10 +133,8 @@ int start(int isappli, int optnum) /* Count how much memory got mapped from this process */ uint32_t rom, ram; - mpuSwitch( - tlb_mapped_memory(&rom, &ram), /* SH3-based */ - utlb_mapped_memory(&rom, &ram) /* SH4-based */ - ); + isSH3() ? tlb_mapped_memory(&rom, &ram) + : utlb_mapped_memory(&rom, &ram); #ifdef GINT_BOOT_LOG bootlog_mapped(rom, ram); @@ -155,7 +153,8 @@ int start(int isappli, int optnum) print(1, 3, "<"); print_hex(1, 4, (uint32_t)&srom, 8); Bdisp_PutDisp_DD(); - delay(20); + while(1); +// delay(20); return 1; } @@ -178,7 +177,7 @@ int start(int isappli, int optnum) for(drv = &bdrv; drv < &edrv; drv++) { if(isSH3() && drv->driver_sh3) drv->driver_sh3(); - drv->ctx_save(drv->sys_ctx); + if(drv->ctx_save) drv->ctx_save(drv->sys_ctx); if(drv->init) drv->init(); } @@ -197,8 +196,8 @@ int start(int isappli, int optnum) /* Before leaving the application, we need to clean up our mess. We have changed many OS settings while accessing the peripheral - modules. The OS is bound to be confused (and crash) if we don't - restore them */ + modules. The OS is bound to be confused (and hang, or crash, or any + other kind of giving up) if we don't restore them */ /* Unload gint and give back control to the system. Driver settings will be restored while interrupts are disabled */ diff --git a/src/keysc/iokbd.c b/src/keysc/iokbd.c new file mode 100644 index 0000000..dd0f8fb --- /dev/null +++ b/src/keysc/iokbd.c @@ -0,0 +1,114 @@ +//--- +// gint:keysc:iokbd - I/O-based keyboard input +//--- + +#include +#include + +/* This file is SH7705-only. */ +#ifdef FX9860G +#define PFC SH7705_PFC + +/* iokbd_delay() - wait a bit so that I/O can keep up + May use the watchdog timer, but the keyboard driver will need to save it. */ +static void iokbd_delay(void) +{ + __asm__( + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + ); + +#if 0 + /* Watchdog delay version */ + const int delay = 0xf4; + + /* Disable the watchdog timer interrupt and reset configuration */ + INTC.IPRB.BIT._WDT = 0; + WDT.WTCSR.WRITE = 0xa500; + + /* Set the delay, input on Pphi / 256 and start counting */ + WDT.WTCNT.WRITE = 0x5a00 | (delay & 0xff); + WDT.WTCSR.WRITE = 0xa505; + WDT.WTCSR.WRITE = 0xa585; + + /* Actively wait for overflow, then clear the interrupt flag */ + while((WDT.WTCSR.READ.BYTE & 0x08) == 0); + WDT.WTCSR.WRITE = 0xa500 | (WDT.WTCSR.READ.BYTE & 0xf7); + + /* Reset configuration, counter, and re-enabled interrupt */ + WDT.WTCSR.WRITE = 0xa500; + WDT.WTCSR.WRITE = 0x5a00; + INTC.IPRB.BIT._WDT = GINT_INTP_WDT; +#endif +} + +/* iokbd_row() - acquire hit status of a single row from the I/O ports + @row Requested row number, 0..9 + Returns 8 bits of key state. */ +uint8_t iokbd_row(int row) +{ + if((unsigned)row > 9) return 0x00; + row ^= 1; + + /* This will enable output (01) on @row, input (10) everywhere else */ + uint16_t ctrl_mask = 0x0003 << ((row & 7) * 2); + /* Enable output (0) on @row, input (1) everywhere else */ + uint8_t data_mask = ~(1 << (row & 7)); + + /* When row < 8, the associated bits are in port B */ + if(row < 8) + { + /* Set @row as output in port B; port M is unused */ + PFC.PBCR = 0xaaaa ^ ctrl_mask; + PFC.PMCR = (PFC.PMCR & 0xff00) | 0x00aa; + iokbd_delay(); + + /* Set @row to 0, everything else to 1 */ + PFC.PBDR = data_mask; + PFC.PMDR = (PFC.PMDR & 0xf0) | 0x0f; + iokbd_delay(); + } + /* When row >= 8, the associated bits are in port M */ + else + { + /* Set @row as output in port M; port B is unused */ + PFC.PBCR = 0xaaaa; + PFC.PMCR = (PFC.PMCR & 0xff00) | (0x00aa ^ ctrl_mask); + iokbd_delay(); + + /* Set @row to 0, everything else to 1 */ + PFC.PBDR = 0xff; + PFC.PMDR = PFC.PMDR & data_mask; + } + + /* Now read the input data from the keyboard! */ + uint8_t input = ~PFC.PADR; + iokbd_delay(); + + /* Reset the port configuration. I don't know why the intermediate step + is necessary; refer to SimLo's documentation. */ + PFC.PBCR = 0xaaaa; + PFC.PMCR = (PFC.PMCR & 0xff00) | 0x00aa; + iokbd_delay(); + PFC.PBCR = 0x5555; + PFC.PMCR = (PFC.PMCR & 0xff00) | 0x0055; + iokbd_delay(); + + /* Now also reset the data registers. This was forgotten from SimLo's + CheckKeyRow() and blows up everything. */ + PFC.PBDR = 0x00; + PFC.PMDR = PFC.PMDR & 0xf0; + + return input; +} + +void iokbd_scan(uint8_t *scan) +{ + /* Scan each row independently; the gain from scanning them altogether + is probably not worth it */ + for(int i = 0; i < 12; i++) scan[i] = iokbd_row(i); +} + +#endif /* FX9860G */ diff --git a/src/keysc/keysc.c b/src/keysc/keysc.c new file mode 100644 index 0000000..f4e8d76 --- /dev/null +++ b/src/keysc/keysc.c @@ -0,0 +1,188 @@ +//--- +// gint:keysc - The SH7305 and I/O Key Scan Interfaces +//--- + +#include +#include +#include +#include + +#include +#include + +//--- +// Keyboard buffer +//--- + +/* The driver's internal state. At each step of time, this file compares the + internal state with the hardware state and generates events accordingly. + Events can be seen as a delta-encoding of the state of the keyboard over + time, and this buffer must be the sum of all events. This means that if an + event cannot be generated, f.i. because the buffer is full, this state must + *not* be updated. */ +GDATA volatile uint8_t state[12] = { 0 }; + +/* A driver event, which is a change in a full row, not a key. */ +typedef struct +{ + uint time :12; /* Locally unique time identifier */ + uint row :4; /* Row number */ + uint old :8; /* Key status for the old row */ + uint new :8; /* Key status for the new row */ + +} driver_event_t; + +/* The keyboard event buffer. This is a circular list defined by [buffer_start] + and [buffer_end]. To avoid an ambiguity when start = end, the buffer is not + allowed to be full (at least one free cell must be remaining). */ +GBSS static driver_event_t buffer[KEYBOARD_QUEUE_SIZE]; +/* Buffer bounds */ +GDATA static int buffer_start = 0; +GDATA static int buffer_end = 0; + +/* Current time, in keyboard-scanning ticks */ +GDATA int time = 0; +GDATA int full_release = 2; + +/* buffer_push() - add an event in the keyboard buffer + Returns non-zero if the event cannot be pushed. */ +static int buffer_push(driver_event_t ev) +{ + int next = (buffer_end + 1) % KEYBOARD_QUEUE_SIZE; + if(next == buffer_start) return 1; + + buffer[buffer_end] = ev; + buffer_end = next; + return 0; +} + +/* buffer_poll() - generate key events from the buffer + Sets [ev] and returns zero on success, otherwise non-zero. */ +static int buffer_poll(driver_event_t *ev) +{ + if(buffer_start == buffer_end) return 1; + + *ev = buffer[buffer_start]; + buffer_start = (buffer_start + 1) % KEYBOARD_QUEUE_SIZE; + return 0; +} + + +/* keysc_frame() - generate a round of interrupts for the current frame */ +void keysc_frame(void) +{ + ALIGNED(2) uint8_t scan[12] = { 0 }; + time++; + + /* First scan the key matrix: from I/O ports on SH3, KEYSC on SH4 */ + if(isSH3()) iokbd_scan(&scan); + else + { + volatile uint16_t *KEYSC = (void *)0xa44b0000; + uint16_t *array = (void *)&scan; + + for(int i = 0; i < 6; i++) array[i] = KEYSC[i]; + } + + for(int row = 0; row < 12; row++) + { + /* Compare new data with the internal state. */ + int old = state[row]; + int new = scan[row]; + if(old == new && !new) continue; + + driver_event_t ev = { + .time = time, + .row = row, + .old = old, + .new = new, + }; + + /* Update internal status if the event could be pushed */ + if(!buffer_push(ev)) state[row] = new; + } +} + +/* key_poll() - poll the next keyboard event from the buffer */ +key_event_t key_poll(void) +{ + /* Every time a driver event is unqueued, its key events are generated + and put in this small buffer */ + static key_event_t events[8]; + /* Number of pending events in the previous buffer */ + static int events_pending = 0; + + /* If there are pending events, use them */ + if(events_pending > 0) return events[--events_pending]; + + /* Unqueue something from the driver buffer, if possible */ + driver_event_t ev; + if(buffer_poll(&ev)) return (key_event_t){ .type = KEYEV_NONE }; + + /* Generate new key events */ + int old = ev.old << 1; + int new = ev.new; + + for(int code = ((ev.row ^ 1) << 4) | 0x7; code & 0x7; code--) + { + int kind = (old & 2) | (new & 1); + old >>= 1; + new >>= 1; + + if(!kind) continue; + + key_event_t keyev = { + .time = ev.time, + .type = kind, + .key = code, + }; + events[events_pending++] = keyev; + } + + /* Return the first of these generated events */ + return events[--events_pending]; +} + +//--- +// Driver initialization +//--- + +static int callback(UNUSED void *arg) +{ + keysc_frame(); + return 0; +} + +/* init() - setup the support timer */ +static void init(void) +{ + int tid = isSH3() ? 4 : 7; + + /* 32768 Hz divided by 16 is 2048 */ + /* TODO: Use I/O port scanning on SH3 */ + timer_setup(tid, 2048, 0, callback, NULL); + timer_start(tid); +} + +/* unload() - stop the support timer */ +static void unload(void) +{ + int tid = isSH3() ? 4 : 7; + timer_stop(tid); +} + +//--- +// Driver structure definition +//--- + +gint_driver_t drv_keysc = { + .name = "KEYSC", + .init = init, + .unload = unload, + .ctx_size = 0, + .sys_ctx = NULL, + .ctx_save = NULL, + .ctx_restore = NULL, +}; + +GINT_DECLARE_DRIVER(4, drv_keysc); diff --git a/src/r61524/r61524.c b/src/r61524/r61524.c index eb2c6d5..cf5960d 100644 --- a/src/r61524/r61524.c +++ b/src/r61524/r61524.c @@ -309,6 +309,6 @@ gint_driver_t drv_r61524 = { .ctx_restore = ctx_restore, }; -GINT_DECLARE_DRIVER(drv_r61524); +GINT_DECLARE_DRIVER(5, drv_r61524); #endif /* FXCG50 */ diff --git a/src/rtc/inth.s b/src/rtc/inth.s new file mode 100644 index 0000000..1cbc37a --- /dev/null +++ b/src/rtc/inth.s @@ -0,0 +1,60 @@ +/* +** 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 1f, r0 + mov.l 2f, r4 + jsr @r0 + nop + + /* Save the return value */ + mov r0, r3 + + /* Prepare to clear the interrupt flag */ + mov.l 3f, r1 + + /* Jump to another gate to finish the work: + - 0xc is the size of storage below + - 0x20 is the size of the next gate (alarm interrupt) */ + mov #0x2c, r2 + braf r2 + nop + +1: .long 0 /* Callback function: edited dynamically */ +2: .long 0 /* Argument to callback function */ +3: .long 0xa413fede /* RCR2 address, edited at startup on SH3 */ + +_inth_rtc_pri_helper: + +.clear: + /* Clear the interrupt flag */ + 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 + rte + nop + + .zero 8 diff --git a/src/rtc/rtc.c b/src/rtc/rtc.c new file mode 100644 index 0000000..7b08671 --- /dev/null +++ b/src/rtc/rtc.c @@ -0,0 +1,191 @@ +//--- +// gint:rtc - Real-Time Clock +//--- + +#include +#include +#include +#include + +#include +#include + +//--- +// Real-Time Clock peripheral registers +//--- + +/* RTC address on SH7305, adjusted at startup on SH7337 and SH7355 */ +GDATA static rtc_t *RTC = &SH7305_RTC; +/* Address of interrupt handler parameters */ +GBSS struct { + int (*callback)(void *arg); + void *arg; + volatile uint8_t *RCR2; +} PACKED(4) *params; + +//--- +// Time management +//--- + +/* int8(), int16() + Converts BCD values to integers */ +static int int8(uint8_t bcd) +{ + return (bcd & 0x0f) + 10 * (bcd >> 4); +} +static int int16(uint16_t bcd) +{ + return (bcd & 0xf) + 10 * ((bcd >> 4) & 0xf) + 100 * ((bcd >> 8) & 0xf) + + 1000 * (bcd >> 12); +} + +/* bcd8(), bcd16() + Converts integer values to BCD + TODO: Use some kind of qdiv() for bcd8() and bcd16()? */ +static uint8_t bcd8(int integer) +{ + integer %= 100; + return ((integer / 10) << 4) | (integer % 10); +} +static uint16_t bcd16(int integer) +{ + integer %= 10000; + return (bcd8(integer / 100) << 8) | bcd8(integer % 100); +} + +/* rtc_get_time() - read the current time from the RTC */ +void rtc_get_time(rtc_time_t *time) +{ + if(!time) return; + + do { + RTC->RCR1.CF = 0; + + time->seconds = int8(RTC->RSECCNT.byte); + time->minutes = int8(RTC->RMINCNT.byte); + time->hours = int8(RTC->RHRCNT.byte); + time->month_day = int8(RTC->RDAYCNT.byte); + time->month = int8(RTC->RMONCNT.byte); + time->year = int16(RTC->RYRCNT.word); + time->week_day = RTC->RWKCNT; + + } while(RTC->RCR1.CF != 0); +} + +/* rtc_set_time() - write a new current time to the RTC */ +void rtc_set_time(const rtc_time_t *time) +{ + if(!time) return; + int wday = (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); +} + +//--- +// RTC timer +//--- + +/* rtc_start_timer() - configure the RTC timer */ +void rtc_start_timer(rtc_frequency_t freq, int (*callback)(void*arg), void*arg) +{ + /* Set up the callback */ + params->callback = callback; + params->arg = arg; + + /* Clear the interrupt flag */ + do RTC->RCR2.PEF = 0; + while(RTC->RCR2.PEF); + + /* Enable the interrupt */ + RTC->RCR2.PES = freq; +} + +/* rtc_stop_timer() - stop the RTC timer */ +void rtc_stop_timer(void) +{ + RTC->RCR2.PES = rtc_none; +} + +//--- +// Driver initialization +//--- + +#ifdef FX9860G +static void driver_sh3(void) +{ + /* Adjust the address of the RTC */ + RTC = &SH7705_RTC; +} +#endif + +static void init(void) +{ + /* 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 */ + UNUSED void *h0, *h1; + h0 = gint_inthandler(0xaa0, inth_rtc_pri, 32); + h1 = gint_inthandler(0xae0, inth_rtc_pri_helper, 32); + + params = h0 + 20; + params->RCR2 = &RTC->RCR2.byte; + + /* Disable the periodic interrupt for now, but give it priority 5 */ + RTC->RCR2.PES = rtc_none; + gint_intlevel(isSH3() ? 3 : 40, 5); +} + +//--- +// Context system for this driver +//--- + +typedef struct +{ + uint8_t RCR1; + uint8_t RCR2; +} ctx_t; + +GBSS static ctx_t sys_ctx; + +static void ctx_save(void *buf) +{ + ctx_t *ctx = buf; + ctx->RCR1 = RTC->RCR1.byte; + ctx->RCR2 = RTC->RCR2.byte; +} + +static void ctx_restore(void *buf) +{ + ctx_t *ctx = buf; + ctx->RCR1 = RTC->RCR1.byte; + ctx->RCR2 = RTC->RCR2.byte; +} + +//--- +// Driver structure definition +//--- + +gint_driver_t drv_rtc = { + .name = "Real-Time Clock", + .driver_sh3 = GINT_DRIVER_SH3(driver_sh3), + .init = init, + .ctx_size = sizeof(ctx_t), + .sys_ctx = &sys_ctx, + .ctx_save = ctx_save, + .ctx_restore = ctx_restore, +}; + +GINT_DECLARE_DRIVER(2, drv_rtc); diff --git a/src/t6k11/t6k11.c b/src/t6k11/t6k11.c index e58a026..f5d0eca 100644 --- a/src/t6k11/t6k11.c +++ b/src/t6k11/t6k11.c @@ -140,15 +140,16 @@ void t6k11_backlight(int setting) /* This setting is mapped to an I/O port: - On SH3, bit 7 of port G - On SH4, bit 4 of port N */ - mpuSwitch({ - /* SH3-based */ + if(isSH3()) + { port = (void *)0xa400012c; mask = 0x80; - },{ - /* SH4-based */ + } + else + { port = (void *)0xa4050138; mask = 0x10; - }); + } if(!setting) *port &= ~mask; if(setting > 0) *port |= mask; @@ -204,6 +205,6 @@ gint_driver_t drv_t6k11 = { .ctx_restore = ctx_restore, }; -GINT_DECLARE_DRIVER(drv_t6k11); +GINT_DECLARE_DRIVER(5, drv_t6k11); #endif /* FX9860G */ diff --git a/src/tmu/tmu.c b/src/tmu/tmu.c index dd7a95d..faa8d4d 100644 --- a/src/tmu/tmu.c +++ b/src/tmu/tmu.c @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -66,7 +67,6 @@ typedef struct uint16_t event; /* Interrupt event code */ } timer_t; - //--- // Driver storage //--- @@ -133,9 +133,12 @@ int timer_setup(int tid, uint32_t delay, timer_input_t clock, t->TCR.UNIE = 1; } - /* Register the callback and its argument */ - timers[tid].data->callback = callback; - timers[tid].data->arg = arg; + /* Register the callback and its argument (TMU-owned timers only) */ + if(timers[tid].data) + { + timers[tid].data->callback = callback; + timers[tid].data->arg = arg; + } /* Return the timer id, since configuration was successful */ return tid; @@ -145,11 +148,14 @@ int timer_setup(int tid, uint32_t delay, timer_input_t clock, uint32_t timer_delay(int tid, int delay_us) { /* TODO: Proper timer_delay() */ -#ifdef FX9860G - uint64_t freq = 14750000 >> 2; /* 14.75 MHz / 4 */ -#else - uint64_t freq = 29020000 >> 2; /* 29.49 MHz / 4 */ -#endif + const clock_frequency_t *clock = clock_freq(); + uint64_t freq; + + freq = isSH3() ? 14750000 >> 2 : clock->Pphi_f >> 2; + + /* fxcg50: Calculated = 29491200 but it's too low */ + // uint64_t freq = 29020000 >> 2; + if(tid >= 3) freq = 32768; /* 32768 Hz */ uint64_t product = freq * (uint64_t)delay_us; @@ -180,8 +186,8 @@ void timer_start(int tid) timer_control(tid, 0); } -/* timer_reload() - change a timer's constant register for next interrupts */ -void timer_reload(int tid, uint32_t delay, timer_input_t clock) +/* timer_reload() - change a timer's delay constant for next interrupts */ +void timer_reload(int tid, uint32_t delay) { if(tid < 3) ((tmu_t *)timers[tid].tmu)->TCOR = delay; else ((tmu_extra_t *)timers[tid].tmu)->TCOR = delay; @@ -193,9 +199,11 @@ void timer_pause(int tid) timer_control(tid, 1); } -/* timer_free() - free a timer */ -void timer_free(int tid) +/* timer_stp() - stop and free a timer */ +void timer_stop(int tid) { + return; + /* Stop the timer and disable UNIE to indicate that it's free */ timer_pause(tid); @@ -234,15 +242,29 @@ extern void inth_tmu_extra2(void); extern void inth_tmu_extra_help(void); extern void inth_tmu_extra_others(void); +#ifdef FX9860G +static void driver_sh3(void) +{ + timers[0].tmu = (void *)0xfffffe94; + timers[1].tmu = (void *)0xfffffea0; + timers[2].tmu = (void *)0xfffffeac; + /* We don't need to change the event code of ETMU0 since it's + translated to the SH4 code by the interrupt handler */ + timers[3].tmu = (void *)0xa44c0030; + + TSTR = (void *)0xfffffe92; +} +#endif + static void init(void) { /* Install the standard's TMU interrupt handlers. By chance TMU gates use the same event codes on SH7705 and SH7305 */ UNUSED void *h0, *h1, *h2, *hs; - h0 = gint_inthandler(0x400, inth_tmu_0); - h1 = gint_inthandler(0x420, inth_tmu_1); - h2 = gint_inthandler(0x440, inth_tmu_2); - hs = gint_inthandler(0x460, inth_tmu_storage); + h0 = gint_inthandler(0x400, inth_tmu_0, 32); + h1 = gint_inthandler(0x420, inth_tmu_1, 32); + h2 = gint_inthandler(0x440, inth_tmu_2, 32); + hs = gint_inthandler(0x460, inth_tmu_storage, 32); /* User information in interrupt handlers */ timers[0].data = h2 + 20; @@ -296,14 +318,14 @@ static void init(void) void *handler = (i == 5) ? inth_tmu_extra2 : inth_tmu_extra_others; - void *h = gint_inthandler(timers[i].event, handler); + void *h = gint_inthandler(timers[i].event, handler, 32); timers[i].data = h + 20; timers[i].data->structure = timers[i].tmu; } /* Also install the helper handler */ - gint_inthandler(0xc60, inth_tmu_extra_help); + gint_inthandler(0xc60, inth_tmu_extra_help, 32); /* Enable TMU0 at level 13, TMU1 at level 11, TMU2 at level 9 */ gint_intlevel(0, 13); @@ -311,11 +333,12 @@ static void init(void) gint_intlevel(2, 9); /* Enable the extra TMUs at level 7 */ - mpuSwitch({ - /* SH3-based */ + if(isSH3()) + { gint_intlevel(23, 7); - },{ - /* SH4-based */ + } + else + { gint_intlevel(36, 7); gint_intlevel(25, 7); gint_intlevel(26, 7); @@ -323,12 +346,12 @@ static void init(void) gint_intlevel(32, 7); gint_intlevel(44, 7); - /* Unmask the interrupts */ + /* Unmask the extra timers' interrupts */ INTC4.MSKCLR->IMR2 = 0x01; INTC4.MSKCLR->IMR5 = 0x06; INTC4.MSKCLR->IMR6 = 0x18; INTC4.MSKCLR->IMR8 = 0x02; - }); + } } //--- @@ -346,20 +369,6 @@ typedef struct /* Allocate a system buffer in gint's BSS area */ GBSS static ctx_t sys_ctx; -#ifdef FX9860G -static void driver_sh3(void) -{ - timers[0].tmu = (void *)0xfffffe94; - timers[1].tmu = (void *)0xfffffea0; - timers[2].tmu = (void *)0xfffffeac; - /* We don't need to change the event code of ETMU0 since it's - translated to the SH4 code by the interrupt handler */ - timers[3].tmu = (void *)0xa44c0030; - - TSTR = (void *)0xfffffe92; -} -#endif - static void ctx_save(void *buf) { ctx_t *ctx = buf; @@ -401,10 +410,12 @@ static void ctx_restore(void *buf) for(int i = 0; i < timer_count() - 3; i++) { tmu_extra_t *t = timers[i + 3].tmu; - /* Avoid some interrupts that occur when TCNT = 0 */ - t->TSTR = 0; - t->TCNT = 0xffffffff; + /* This thing is being unloaded while interrupts are disabled, + so we don't have to heed for ctx->extra[i].TCNT = 0, which + can generate interrupts. I tried to do t->TCNT = 0xffffffff + then restore ctx->extra[i], but it causes hangs on SH3 when + the overclock level is too high. */ t->TCNT = ctx->extra[i].TCNT; t->TCOR = ctx->extra[i].TCOR; t->TCR.byte = ctx->extra[i].TCR.byte; @@ -418,12 +429,12 @@ static void ctx_restore(void *buf) gint_driver_t drv_tmu = { .name = "Timer Unit", + .driver_sh3 = GINT_DRIVER_SH3(driver_sh3), .init = init, .ctx_size = sizeof(ctx_t), .sys_ctx = &sys_ctx, - .driver_sh3 = GINT_DRIVER_SH3(driver_sh3), .ctx_save = ctx_save, .ctx_restore = ctx_restore, }; -GINT_DECLARE_DRIVER(drv_tmu); +GINT_DECLARE_DRIVER(2, drv_tmu);