More timers, RTC, basic overclock-resistant keyboard, CPG, PFC, driver levels.

This commit is contained in:
lephe 2018-08-19 17:11:37 +02:00
parent 2f0e049c33
commit 298338f633
26 changed files with 1326 additions and 149 deletions

View file

@ -19,9 +19,9 @@ MEMORY
/* This is mapped to RAM; 8k on SH3, apparently 32k on SH4 */ /* This is mapped to RAM; 8k on SH3, apparently 32k on SH4 */
ram (rw): o = 0x08100000, l = 8k ram (rw): o = 0x08100000, l = 8k
/* gint's VBR space, mentioned here for completeness */ /* 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 */ /* 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 SECTIONS
@ -76,7 +76,13 @@ SECTIONS
driver, even if the symbols are not referenced */ driver, even if the symbols are not referenced */
.gint.drivers : { .gint.drivers : {
_bdrv = . ; _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 = . ; _edrv = . ;
} > rom } > rom

View file

@ -18,9 +18,9 @@ MEMORY
The first 0x2000 bytes are reserved by gint, see below */ The first 0x2000 bytes are reserved by gint, see below */
ram (rw): o = 0x08102000, l = 512k ram (rw): o = 0x08102000, l = 512k
/* gint's VBR space, mentioned here for completeness */ /* 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 */ /* 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 SECTIONS
@ -68,7 +68,13 @@ SECTIONS
driver, even if the symbols are not referenced */ driver, even if the symbols are not referenced */
.gint.drivers : { .gint.drivers : {
_bdrv = . ; _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 = . ; _edrv = . ;
} > rom } > rom

80
include/core/cpg.h Normal file
View file

@ -0,0 +1,80 @@
//---
// gint:core:cpg - Clock Pulse Generator
//---
#ifndef GINT_CORE_CPG
#define GINT_CORE_CPG
#include <defs/types.h>
//---
// 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 */

View file

@ -3,13 +3,10 @@
// //
// This component detects the architecture and MPU type of the underlying // This component detects the architecture and MPU type of the underlying
// hardware by relying on version registers and/or side-information. It // hardware by relying on version registers and/or side-information. It
// provides macros isSH3() and isSH4(), but the best way of performing // provides macros isSH3() and isSH4() for MPU-dependent jobs
// MPU-dependent jobs is to use mpuSwitch():
// //
// mpuSwitch( // if(isSH3()) print("SH3 code");
// print("SH3 code"), // else print("SH4 code");
// print("SH4 code")
// );
//--- //---
#ifndef GINT_CORE_MPU #ifndef GINT_CORE_MPU
@ -40,8 +37,6 @@ typedef enum
#define isSH3() (mpu_id() & 1) #define isSH3() (mpu_id() & 1)
#define isSH4() (!isSH3()) #define isSH4() (!isSH3())
#define mpuSwitch(a, b) do { if(isSH3()) { a; } else { b; } } while(0)
/* mpu_init() - probe the MPU type /* mpu_init() - probe the MPU type
This function must be executed before mpu_id() can be used. */ This function must be executed before mpu_id() can be used. */
void mpu_init(void); void mpu_init(void);
@ -53,8 +48,6 @@ typedef enum
#define isSH3() 0 #define isSH3() 0
#define isSH4() 1 #define isSH4() 1
#define mpuSwitch(a, b) do { b; } while(0)
#endif /* FX9860G */ #endif /* FX9860G */
#endif /* GINT_CORE_MPU */ #endif /* GINT_CORE_MPU */

77
include/core/pfc.h Normal file
View file

@ -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 <defs/types.h>
//---
// 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 */

View file

@ -30,6 +30,9 @@
#define CTOR(x) __attribute__((constructor ((x) + 101))) PRETEXT #define CTOR(x) __attribute__((constructor ((x) + 101))) PRETEXT
#define DTOR(x) __attribute__((destructor ((x) + 101))) #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, /* 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 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 */ access sizes silently fail - honestly you don't want this to happen */

View file

@ -1,8 +1,82 @@
//--- //---
// gint:clock - Clock signals // gint:clock - Clock signals, overclock, and standby modes
//--- //---
#ifndef GINT_CLOCK #ifndef GINT_CLOCK
#define GINT_CLOCK #define GINT_CLOCK
#include <defs/types.h>
//---
// Clock signals
//---
/* clock_frequency_t
A dump of the Clock Pulse Generator's (CPG) configuration. Use the MPU
detection functions from <gint/mpu.h> 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 */ #endif /* GINT_CLOCK */

View file

@ -39,6 +39,11 @@ typedef struct
may be set to NULL */ may be set to NULL */
void (*init)(void); 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 */ /* Size of a context object for the driver */
uint ctx_size; uint ctx_size;
@ -63,11 +68,17 @@ typedef struct
} PACKED(4) gint_driver_t; } PACKED(4) gint_driver_t;
/* GINT_DECLARE_DRIVER() - make a driver visible to gint /* 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 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, structure. This macro moves the structure to the .gint.drivers.* sections,
which is automatically traversed at startup */ which are automatically traversed at startup.
#define GINT_DECLARE_DRIVER(name) \
SECTION(".gint.drivers") extern gint_driver_t name; 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 /* GINT_DRIVER_SH3() - declare a function for SH3-rectification
This macros allows the argument function to not exist on fxcg50. */ This macros allows the argument function to not exist on fxcg50. */

View file

@ -92,23 +92,27 @@ int gint_intlevel(int intid, int level);
When an interrupt request is accepted, the hardware jumps to a specific When an interrupt request is accepted, the hardware jumps to a specific
interrupt handler at an address that depends on the interrupt source. interrupt handler at an address that depends on the interrupt source.
Each interrupt handler should only refer to data within its own block For safety, interrupt handlers should avoir referring to data from other
because the relative displacement between blocks is MPU-dependent. There are blocks because the arrangement of blocks at runtime depends on event codes.
a few exceptions to this, such as timer handlers, which are contiguous on The assembler program will assume that consecutive blocks in the source code
all currently-used platforms. Be careful. 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 This function allows anyone to replace any interrupt handler so make sure
you're not interfering with usual interrupt assignments. you're not interfering with usual interrupt assignments.
The first parameter 'event_code' represents the event_code associated with The first parameter 'event_code' represents the event_code associated with
the interrupt. These codes are normally platform-dependent, but gint always the interrupt. The codes are normally platform-dependent, but gint always
uses the SH7305 codes: SH3 platforms have a translation table. See the uses SH7305 codes: SH3 platforms have a translation table. See the
documentation for a list of event codes and their associated interrupts. documentation for a list of event codes and their associated interrupts.
The handler function must be an interrupt handler: it should not raise The handler function must be an interrupt handler: it must not raise
exceptions, must end with 'rte', uses the kernel register bank... and it exceptions, must end with 'rte', and it will use the kernel register bank.
must fit within 32 bytes. If it's not written in assembler, then you're For convenience I allow any block size to be loaded as an interrupt handler,
likely doing something wrong. 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 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 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 @event_code Identifier of the interrupt block
@handler Address of handler function @handler Address of handler function
@size How many bytes to copy
Returns the VBR address where the handlers was installed. */ 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 */ #endif /* GINT_GINT */

67
include/gint/keyboard.h Normal file
View file

@ -0,0 +1,67 @@
//---
// gint:keyboard - Keyboard input
//---
#ifndef GINT_KEYBOARD
#define GINT_KEYBOARD
#include <defs/types.h>
/* 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 */

82
include/gint/rtc.h Normal file
View file

@ -0,0 +1,82 @@
//---
// gint:rtc - Real-Time Clock
//---
#ifndef GINT_RTC
#define GINT_RTC
#include <defs/types.h>
//---
// 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 <gint/clock.h>) 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 (<gint/timer.h>);
[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 */

View file

@ -29,13 +29,7 @@
what you intended. */ what you intended. */
/* timer_count() - tells how many timers are available on the platform */ /* timer_count() - tells how many timers are available on the platform */
HDRFUNC int timer_count(void) #define timer_count() (isSH3() ? 4 : 9)
{
mpuSwitch(
return 4, /* SH3-based */
return 9 /* SH4-based */
);
}
/* Clock input /* Clock input
@ -123,21 +117,15 @@ uint32_t timer_delay(int timer, int delay_us);
@timer Timer id, as returned by timer_setup() */ @timer Timer id, as returned by timer_setup() */
void timer_start(int timer); 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 Changes the delay constant of the given timer. Nothing will happen until the
start the timer if it stopped. The timer will start counting down from the next callback; then the timer will update its delay to reflect the new
new delay when it is started (or immediately if it was running), no matter constant. The new delay can be calculated by the timer_delay() function.
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.
@timer Timer id, as returned by timer_setup() @timer Timer id, as returned by timer_setup()
@delay New delay (unit depends on the clock source) @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);
void timer_reload(int timer, uint32_t delay, timer_input_t clock);
/* timer_pause() - stop a running timer /* timer_pause() - stop a running timer
The specified timer will be paused; its counter will not be reset. A stopped 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() */ @timer Timer id, as returned by timer_setup() */
void timer_pause(int timer); 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 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(). be used anymore until it is returned by a further call to timer_setup().
@timer Timer id, as returned by timer_setup() */ @timer Timer id, as returned by timer_setup() */
void timer_free(int timer); void timer_stop(int timer);
#endif /* GINT_TIMER */ #endif /* GINT_TIMER */

76
include/mod/rtc.h Normal file
View file

@ -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 */

View file

@ -0,0 +1,136 @@
//---
// gint:core:freq - Clock frequency management
//---
#include <gint/drivers.h>
#include <gint/clock.h>
#include <defs/types.h>
#include <core/mpu.h>
#include <core/cpg.h>
//---
// 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);

0
src/clock/sleep.c Normal file
View file

View file

@ -84,8 +84,8 @@ void bootlog_kernel(void)
{ {
print(15, 1, " Kernel"); print(15, 1, " Kernel");
mpuSwitch({ if(isSH3())
/* SH3-based */ {
print(1, 5, "ABCD"); print(1, 5, "ABCD");
print_hex( 6, 5, INTC3._.IPRA->word, 4); print_hex( 6, 5, INTC3._.IPRA->word, 4);
print_hex(10, 5, INTC3._.IPRB->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(10, 6, INTC3._.IPRF->word, 4);
print_hex(14, 6, INTC3._.IPRG->word, 4); print_hex(14, 6, INTC3._.IPRG->word, 4);
print_hex(18, 6, INTC3._.IPRH->word, 4); print_hex(18, 6, INTC3._.IPRH->word, 4);
},{ }
/* SH4-based */ else
{
print(1, 5, "ACFG"); print(1, 5, "ACFG");
print_hex( 6, 5, INTC4._->IPRA.word, 4); print_hex( 6, 5, INTC4._->IPRA.word, 4);
print_hex(10, 5, INTC4._->IPRC.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(10, 6, INTC4._->IPRJ.word, 4);
print_hex(14, 6, INTC4._->IPRK.word, 4); print_hex(14, 6, INTC4._->IPRK.word, 4);
print_hex(18, 6, INTC4._->IPRL.word, 4); print_hex(18, 6, INTC4._->IPRL.word, 4);
}); }
Bdisp_PutDisp_DD(); Bdisp_PutDisp_DD();
} }

View file

@ -36,10 +36,9 @@ int gint_intlevel(int intid, int level)
volatile uint16_t *ipr; volatile uint16_t *ipr;
level &= 0xf; level &= 0xf;
mpuSwitch( ipr = isSH3()
ipr = INTC3.IPRS[intid >> 2], /* SH3-based */ ? INTC3.IPRS[intid >> 2] /* SH3-based */
ipr = &INTC4.IPRS[2 * (intid >> 2)] /* SH4-based */ : &INTC4.IPRS[2 * (intid >> 2)]; /* SH4-based */
);
int oldlevel = (*ipr >> shift) & 0xf; int oldlevel = (*ipr >> shift) & 0xf;
*ipr = (*ipr & ~(0xf << shift)) | (level << shift); *ipr = (*ipr & ~(0xf << shift)) | (level << shift);
@ -48,7 +47,7 @@ int gint_intlevel(int intid, int level)
} }
/* gint_inthandler() - configure interrupt handlers */ /* 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; extern char gint_vbr;
@ -60,5 +59,5 @@ void *gint_inthandler(int event_code, const void *handler)
if(event_code < 0) return NULL; if(event_code < 0) return NULL;
void *dest = (void *)&gint_vbr + event_code + 0x620; void *dest = (void *)&gint_vbr + event_code + 0x620;
return memcpy(dest, handler, 32); return memcpy(dest, handler, size);
} }

View file

@ -30,24 +30,28 @@ typedef struct
@arg ctx gint core context object */ @arg ctx gint core context object */
static void gint_ctx_save(gint_core_ctx *ctx) static void gint_ctx_save(gint_core_ctx *ctx)
{ {
mpuSwitch( if(isSH3())
/* SH3-based */ {
for(int i = 0; i < 8; i++) ctx->iprs[i] = *(INTC3.IPRS[i]), 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] else
); {
for(int i = 0; i < 12; i++) ctx->iprs[i] = INTC4.IPRS[2 * i];
}
} }
/* gint_ctx_restore() - restore interrupt controller configuration /* gint_ctx_restore() - restore interrupt controller configuration
@arg ctx gint core context object */ @arg ctx gint core context object */
static void gint_ctx_restore(gint_core_ctx *ctx) static void gint_ctx_restore(gint_core_ctx *ctx)
{ {
mpuSwitch( if(isSH3())
/* SH3-based */ {
for(int i = 0; i < 8; i++) *(INTC3.IPRS[i]) = ctx->iprs[i], 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] 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) static void lock(void)
{ {
/* Just disable everything, drivers will enable what they support */ /* Just disable everything, drivers will enable what they support */
mpuSwitch( if(isSH3())
/* SH3-based */ {
for(int i = 0; i < 8; i++) *(INTC3.IPRS[i]) = 0x0000, 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 else
); {
for(int i = 0; i < 12; i++) INTC4.IPRS[2 * i] = 0x0000;
}
} }
/* gint_install() - install and start gint */ /* gint_install() - install and start gint */
@ -87,19 +93,20 @@ void gint_install(void)
/* Load the event handler entry points into memory */ /* Load the event handler entry points into memory */
/* TODO: Load an exception handler and a TLB miss handler */ /* TODO: Load an exception handler and a TLB miss handler */
mpuSwitch({ if(isSH3())
/* SH3-based */ {
extern void exch_entry_7705(void); extern void exch_entry_7705(void);
extern void inth_entry_7705(void); extern void inth_entry_7705(void);
exch_entry = exch_entry_7705; exch_entry = exch_entry_7705;
inth_entry = inth_entry_7705; inth_entry = inth_entry_7705;
},{ }
/* SH4-based */ else
{
extern void exch_entry_7305(void); extern void exch_entry_7305(void);
extern void inth_entry_7305(void); extern void inth_entry_7305(void);
exch_entry = exch_entry_7305; exch_entry = exch_entry_7305;
inth_entry = inth_entry_7305; inth_entry = inth_entry_7305;
}); }
memcpy((void *)(vbr + 0x100), exch_entry, 32); memcpy((void *)(vbr + 0x100), exch_entry, 32);
memcpy((void *)(vbr + 0x600), inth_entry, 32); memcpy((void *)(vbr + 0x600), inth_entry, 32);
@ -113,10 +120,12 @@ static void unlock(void)
{ {
gint_ctx_restore(&sys_ctx); gint_ctx_restore(&sys_ctx);
/* Restore all driver settings */ /* Restore all driver settings, but do it in reverse order of loading
for(gint_driver_t *drv = &bdrv; drv < &edrv; drv++) 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);
} }
} }

View file

@ -133,10 +133,8 @@ int start(int isappli, int optnum)
/* Count how much memory got mapped from this process */ /* Count how much memory got mapped from this process */
uint32_t rom, ram; uint32_t rom, ram;
mpuSwitch( isSH3() ? tlb_mapped_memory(&rom, &ram)
tlb_mapped_memory(&rom, &ram), /* SH3-based */ : utlb_mapped_memory(&rom, &ram);
utlb_mapped_memory(&rom, &ram) /* SH4-based */
);
#ifdef GINT_BOOT_LOG #ifdef GINT_BOOT_LOG
bootlog_mapped(rom, ram); bootlog_mapped(rom, ram);
@ -155,7 +153,8 @@ int start(int isappli, int optnum)
print(1, 3, "<"); print(1, 3, "<");
print_hex(1, 4, (uint32_t)&srom, 8); print_hex(1, 4, (uint32_t)&srom, 8);
Bdisp_PutDisp_DD(); Bdisp_PutDisp_DD();
delay(20); while(1);
// delay(20);
return 1; return 1;
} }
@ -178,7 +177,7 @@ int start(int isappli, int optnum)
for(drv = &bdrv; drv < &edrv; drv++) for(drv = &bdrv; drv < &edrv; drv++)
{ {
if(isSH3() && drv->driver_sh3) drv->driver_sh3(); 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(); 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 /* Before leaving the application, we need to clean up our mess. We
have changed many OS settings while accessing the peripheral have changed many OS settings while accessing the peripheral
modules. The OS is bound to be confused (and crash) if we don't modules. The OS is bound to be confused (and hang, or crash, or any
restore them */ other kind of giving up) if we don't restore them */
/* Unload gint and give back control to the system. Driver settings /* Unload gint and give back control to the system. Driver settings
will be restored while interrupts are disabled */ will be restored while interrupts are disabled */

114
src/keysc/iokbd.c Normal file
View file

@ -0,0 +1,114 @@
//---
// gint:keysc:iokbd - I/O-based keyboard input
//---
#include <defs/types.h>
#include <core/pfc.h>
/* 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 */

188
src/keysc/keysc.c Normal file
View file

@ -0,0 +1,188 @@
//---
// gint:keysc - The SH7305 and I/O Key Scan Interfaces
//---
#include <gint/drivers.h>
#include <gint/gint.h>
#include <gint/timer.h>
#include <gint/keyboard.h>
#include <core/mpu.h>
#include <defs/attributes.h>
//---
// 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);

View file

@ -309,6 +309,6 @@ gint_driver_t drv_r61524 = {
.ctx_restore = ctx_restore, .ctx_restore = ctx_restore,
}; };
GINT_DECLARE_DRIVER(drv_r61524); GINT_DECLARE_DRIVER(5, drv_r61524);
#endif /* FXCG50 */ #endif /* FXCG50 */

60
src/rtc/inth.s Normal file
View file

@ -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

191
src/rtc/rtc.c Normal file
View file

@ -0,0 +1,191 @@
//---
// gint:rtc - Real-Time Clock
//---
#include <gint/rtc.h>
#include <gint/drivers.h>
#include <gint/gint.h>
#include <mod/rtc.h>
#include <core/mpu.h>
#include <defs/types.h>
//---
// 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);

View file

@ -140,15 +140,16 @@ void t6k11_backlight(int setting)
/* This setting is mapped to an I/O port: /* This setting is mapped to an I/O port:
- On SH3, bit 7 of port G - On SH3, bit 7 of port G
- On SH4, bit 4 of port N */ - On SH4, bit 4 of port N */
mpuSwitch({ if(isSH3())
/* SH3-based */ {
port = (void *)0xa400012c; port = (void *)0xa400012c;
mask = 0x80; mask = 0x80;
},{ }
/* SH4-based */ else
{
port = (void *)0xa4050138; port = (void *)0xa4050138;
mask = 0x10; mask = 0x10;
}); }
if(!setting) *port &= ~mask; if(!setting) *port &= ~mask;
if(setting > 0) *port |= mask; if(setting > 0) *port |= mask;
@ -204,6 +205,6 @@ gint_driver_t drv_t6k11 = {
.ctx_restore = ctx_restore, .ctx_restore = ctx_restore,
}; };
GINT_DECLARE_DRIVER(drv_t6k11); GINT_DECLARE_DRIVER(5, drv_t6k11);
#endif /* FX9860G */ #endif /* FX9860G */

View file

@ -5,6 +5,7 @@
#include <gint/timer.h> #include <gint/timer.h>
#include <gint/drivers.h> #include <gint/drivers.h>
#include <gint/gint.h> #include <gint/gint.h>
#include <gint/clock.h>
#include <core/intc.h> #include <core/intc.h>
@ -66,7 +67,6 @@ typedef struct
uint16_t event; /* Interrupt event code */ uint16_t event; /* Interrupt event code */
} timer_t; } timer_t;
//--- //---
// Driver storage // Driver storage
//--- //---
@ -133,9 +133,12 @@ int timer_setup(int tid, uint32_t delay, timer_input_t clock,
t->TCR.UNIE = 1; t->TCR.UNIE = 1;
} }
/* Register the callback and its argument */ /* Register the callback and its argument (TMU-owned timers only) */
timers[tid].data->callback = callback; if(timers[tid].data)
timers[tid].data->arg = arg; {
timers[tid].data->callback = callback;
timers[tid].data->arg = arg;
}
/* Return the timer id, since configuration was successful */ /* Return the timer id, since configuration was successful */
return tid; 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) uint32_t timer_delay(int tid, int delay_us)
{ {
/* TODO: Proper timer_delay() */ /* TODO: Proper timer_delay() */
#ifdef FX9860G const clock_frequency_t *clock = clock_freq();
uint64_t freq = 14750000 >> 2; /* 14.75 MHz / 4 */ uint64_t freq;
#else
uint64_t freq = 29020000 >> 2; /* 29.49 MHz / 4 */ freq = isSH3() ? 14750000 >> 2 : clock->Pphi_f >> 2;
#endif
/* fxcg50: Calculated = 29491200 but it's too low */
// uint64_t freq = 29020000 >> 2;
if(tid >= 3) freq = 32768; /* 32768 Hz */ if(tid >= 3) freq = 32768; /* 32768 Hz */
uint64_t product = freq * (uint64_t)delay_us; uint64_t product = freq * (uint64_t)delay_us;
@ -180,8 +186,8 @@ void timer_start(int tid)
timer_control(tid, 0); timer_control(tid, 0);
} }
/* timer_reload() - change a timer's constant register for next interrupts */ /* timer_reload() - change a timer's delay constant for next interrupts */
void timer_reload(int tid, uint32_t delay, timer_input_t clock) void timer_reload(int tid, uint32_t delay)
{ {
if(tid < 3) ((tmu_t *)timers[tid].tmu)->TCOR = delay; if(tid < 3) ((tmu_t *)timers[tid].tmu)->TCOR = delay;
else ((tmu_extra_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_control(tid, 1);
} }
/* timer_free() - free a timer */ /* timer_stp() - stop and free a timer */
void timer_free(int tid) void timer_stop(int tid)
{ {
return;
/* Stop the timer and disable UNIE to indicate that it's free */ /* Stop the timer and disable UNIE to indicate that it's free */
timer_pause(tid); 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_help(void);
extern void inth_tmu_extra_others(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) static void init(void)
{ {
/* Install the standard's TMU interrupt handlers. By chance TMU gates /* Install the standard's TMU interrupt handlers. By chance TMU gates
use the same event codes on SH7705 and SH7305 */ use the same event codes on SH7705 and SH7305 */
UNUSED void *h0, *h1, *h2, *hs; UNUSED void *h0, *h1, *h2, *hs;
h0 = gint_inthandler(0x400, inth_tmu_0); h0 = gint_inthandler(0x400, inth_tmu_0, 32);
h1 = gint_inthandler(0x420, inth_tmu_1); h1 = gint_inthandler(0x420, inth_tmu_1, 32);
h2 = gint_inthandler(0x440, inth_tmu_2); h2 = gint_inthandler(0x440, inth_tmu_2, 32);
hs = gint_inthandler(0x460, inth_tmu_storage); hs = gint_inthandler(0x460, inth_tmu_storage, 32);
/* User information in interrupt handlers */ /* User information in interrupt handlers */
timers[0].data = h2 + 20; timers[0].data = h2 + 20;
@ -296,14 +318,14 @@ static void init(void)
void *handler = (i == 5) void *handler = (i == 5)
? inth_tmu_extra2 ? inth_tmu_extra2
: inth_tmu_extra_others; : 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 = h + 20;
timers[i].data->structure = timers[i].tmu; timers[i].data->structure = timers[i].tmu;
} }
/* Also install the helper handler */ /* 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 */ /* Enable TMU0 at level 13, TMU1 at level 11, TMU2 at level 9 */
gint_intlevel(0, 13); gint_intlevel(0, 13);
@ -311,11 +333,12 @@ static void init(void)
gint_intlevel(2, 9); gint_intlevel(2, 9);
/* Enable the extra TMUs at level 7 */ /* Enable the extra TMUs at level 7 */
mpuSwitch({ if(isSH3())
/* SH3-based */ {
gint_intlevel(23, 7); gint_intlevel(23, 7);
},{ }
/* SH4-based */ else
{
gint_intlevel(36, 7); gint_intlevel(36, 7);
gint_intlevel(25, 7); gint_intlevel(25, 7);
gint_intlevel(26, 7); gint_intlevel(26, 7);
@ -323,12 +346,12 @@ static void init(void)
gint_intlevel(32, 7); gint_intlevel(32, 7);
gint_intlevel(44, 7); gint_intlevel(44, 7);
/* Unmask the interrupts */ /* Unmask the extra timers' interrupts */
INTC4.MSKCLR->IMR2 = 0x01; INTC4.MSKCLR->IMR2 = 0x01;
INTC4.MSKCLR->IMR5 = 0x06; INTC4.MSKCLR->IMR5 = 0x06;
INTC4.MSKCLR->IMR6 = 0x18; INTC4.MSKCLR->IMR6 = 0x18;
INTC4.MSKCLR->IMR8 = 0x02; INTC4.MSKCLR->IMR8 = 0x02;
}); }
} }
//--- //---
@ -346,20 +369,6 @@ typedef struct
/* Allocate a system buffer in gint's BSS area */ /* Allocate a system buffer in gint's BSS area */
GBSS static ctx_t sys_ctx; 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) static void ctx_save(void *buf)
{ {
ctx_t *ctx = buf; ctx_t *ctx = buf;
@ -401,10 +410,12 @@ static void ctx_restore(void *buf)
for(int i = 0; i < timer_count() - 3; i++) for(int i = 0; i < timer_count() - 3; i++)
{ {
tmu_extra_t *t = timers[i + 3].tmu; 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->TCNT = ctx->extra[i].TCNT;
t->TCOR = ctx->extra[i].TCOR; t->TCOR = ctx->extra[i].TCOR;
t->TCR.byte = ctx->extra[i].TCR.byte; t->TCR.byte = ctx->extra[i].TCR.byte;
@ -418,12 +429,12 @@ static void ctx_restore(void *buf)
gint_driver_t drv_tmu = { gint_driver_t drv_tmu = {
.name = "Timer Unit", .name = "Timer Unit",
.driver_sh3 = GINT_DRIVER_SH3(driver_sh3),
.init = init, .init = init,
.ctx_size = sizeof(ctx_t), .ctx_size = sizeof(ctx_t),
.sys_ctx = &sys_ctx, .sys_ctx = &sys_ctx,
.driver_sh3 = GINT_DRIVER_SH3(driver_sh3),
.ctx_save = ctx_save, .ctx_save = ctx_save,
.ctx_restore = ctx_restore, .ctx_restore = ctx_restore,
}; };
GINT_DECLARE_DRIVER(drv_tmu); GINT_DECLARE_DRIVER(2, drv_tmu);