From 2f0e049c33e57b634710ecf43d31ae46bfe1cf5d Mon Sep 17 00:00:00 2001 From: lephe Date: Wed, 1 Aug 2018 20:41:36 +0200 Subject: [PATCH] More interrupt system, exceptions, timers. --- Makefile | 8 +- configure | 26 ++- fx9860g.ld | 3 + fxcg50.ld | 13 +- include/core/bootlog.h | 4 +- include/core/intc.h | 341 +++++++++++------------------- include/core/memory.h | 22 ++ include/core/mmu.h | 64 +++++- include/core/mpu.h | 14 +- include/defs/attributes.h | 3 + include/drivers/t6k11.h | 7 +- include/gint/clock.h | 8 + include/gint/display-fx.h | 105 ++++++++++ include/gint/drivers.h | 33 +++ include/gint/gint.h | 64 ++++++ include/gint/timer.h | 157 ++++++++++++++ src/clock/freq.c | 0 src/core/bootlog.c | 23 +- src/core/exch.S | 32 +++ src/core/gint.c | 64 ++++++ src/core/inth.S | 48 +++-- src/core/memory.c | 15 +- src/core/mmu.c | 55 ++++- src/core/setup.c | 98 ++++----- src/core/start.c | 34 ++- src/r61524/r61524.c | 65 ++---- src/t6k11/t6k11.c | 12 +- src/tmu/inth.s | 225 ++++++++++++++++++++ src/tmu/tmu.c | 429 ++++++++++++++++++++++++++++++++++++++ 29 files changed, 1568 insertions(+), 404 deletions(-) create mode 100644 include/core/memory.h create mode 100644 include/gint/clock.h create mode 100644 include/gint/timer.h create mode 100644 src/clock/freq.c create mode 100644 src/core/exch.S create mode 100644 src/core/gint.c create mode 100644 src/tmu/inth.s create mode 100644 src/tmu/tmu.c diff --git a/Makefile b/Makefile index ff5264b..ca1ac0b 100755 --- a/Makefile +++ b/Makefile @@ -16,12 +16,12 @@ cfg := config/Makefile.cfg cfgdep := $(if $(shell [ -f $(cfg) ] || echo n),CFGDEP,$(cfg)) cf-fx := -m3 -mb -ffreestanding -nostdlib -Wall -Wextra -std=c11 -Os \ - -I include $(cfg_macros) + -fstrict-volatile-bitfields -I include $(cfg_macros) cf-cg := -m4 -mb -ffreestanding -nostdlib -Wall -Wextra -std=c11 -Os \ - -I include $(cfg_macros) + -fstrict-volatile-bitfields -I include $(cfg_macros) -# On fx9860g, use the sh3eb-elf toolchain; on fxcg50, prefer sh4eb-elf -toolchain := $(if $(filter $(cfg_target),fx),sh3eb-elf,sh4eb-elf) +# On fx9860g, use the sh3eb-elf toolchain; on fxcg50, prefer sh4eb-nofpu-elf +toolchain := $(if $(filter $(cfg_target),fx),sh3eb-elf,sh4eb-nofpu-elf) # Compiler flags, assembler flags, dependency generation, archiving cflags := $(cf-$(cfg_target)) diff --git a/configure b/configure index b1be97f..d97157a 100755 --- a/configure +++ b/configure @@ -12,6 +12,7 @@ conf_target= # Behavior conf[GINT_BOOT_LOG]= conf[GINT_NO_SYSCALLS]= +cong[GINT_LAX]= conf[GINT_EXTENDED_LIBC]= conf[GINT_STATIC_GRAY]= @@ -45,28 +46,31 @@ Platform settings (specify exactly one): Target platform is fx-9860G II: all monochrome models that support add-ins or can be flashed to support them. ${Cg}-fxcg50$C0 - Target platform is fx-CG50. + Target platform is fx-CG 50; there is some compatibility with fx-CG 10/20. -Options that affect the behavior of the library: - $Cr-boot-log $C_[default:$Cp not specified$C_]$C0 +Options that affect the behavior of the library $C_[${Cp}default$C_]$C0: + $Cr-boot-log $C_[${Cp}disabled$C_]$C0 Enable an on-screen log at startup if a key is kept pressed while launching the add-in, allowing easy debug and crash diagnoses. - $Cr-no-syscalls $C_[default:$Cp not specified$C_]$C0 + $Cr-no-syscalls $C_[${Cp}disabled$C_]$C0 Never use syscalls. Expect trouble with malloc() and the gray engine. Do not trigger this switch unless you know what you are doing. - $Cr-extended-libc $C_[default:$Cp not specified$C_]$C0 + $Cr-lax $C_[${Cp}disabled$C_]$C0 + Make more assumptions about functions parameters. This disables coordinate + checks in drawing functions. Be careful! + $Cr-extended-libc $C_[${Cp}disabled$C_]$C0 Enable specific C99 headers/features that are normally not required by calculator programs. This may allow porting programs from other platforms. - $Cr-static-gray-engine $C_[default:$Cp not specified$C_]$C0 + $Cr-static-gray-engine $C_[${Cp}disabled$C_]$C0 Place the gray engine vram in static ram instead of using the heap. Always use this option when using both the gray engine and -no-syscalls. -Options that customize size limits: - $Cr-atexit-max$C0=${Cg}integer$C_ [default:$Cp 16$C_]$C0 +Options that customize size limits $C_[${Cp}default$C_]$C0: + $Cr-atexit-max$C0=${Cg}integer$C_ [${Cp}16$C_]$C0 Number of exit handlers that can be registered by atexit(). - $Cr-timer-slots$C0=${Cg}integer$C_ [default:$Cp 16$C_]$C0 + $Cr-timer-slots$C0=${Cg}integer$C_ [${Cp}16$C_]$C0 Number of virtual timers that may be registered at the same time. - $Cr-events-queue-size$C0=${Cg}integer$C_ [default:$Cp 64$C_]$C0 + $Cr-events-queue-size$C0=${Cg}integer$C_ [${Cp}64$C_]$C0 Number of events simultaneously stored in the event queue. EOF @@ -88,6 +92,7 @@ for arg; do case "$arg" in -boot-log) conf[GINT_BOOT_LOG]=true;; -no-syscalls) conf[GINT_NO_SYSCALLS]=true;; + -lax) conf[GINT_LAX]=true;; -extended-libc) conf[GINT_EXTENDED_LIBC]=true;; -static-gray-engine) conf[GINT_STATIC_GRAY]=true;; @@ -142,6 +147,7 @@ output_config() [ "${conf[GINT_BOOT_LOG]}" ] && echo -n " -DGINT_BOOT_LOG" [ "${conf[GINT_NO_SYSCALLS]}" ] && echo -n " -DGINT_NO_SYSCALLS" + [ "${conf[GINT_LAX]}" ] && echo -n " -DGINT_LAX" [ "${conf[GINT_EXTENDED_LIBC]}" ] && echo -n " -DGINT_EXTENDED_LIBC" [ "${conf[GINT_STATIC_GRAY]}" ] && echo -n " -DGINT_STATIC_GRAY" diff --git a/fx9860g.ld b/fx9860g.ld index dce9c53..dba5111 100644 --- a/fx9860g.ld +++ b/fx9860g.ld @@ -173,11 +173,14 @@ SECTIONS */ /* Unwanted sections going to meet Dave Null: + - Debug sections, often come out of libgcc - Java classes registration (why are there even here?) - Asynchronous unwind tables: no C++ exception handling for now ^^ - Comments or anything the compiler might put in its assembler - A stack section sometimes generated for build/version.o */ /DISCARD/ : { + *(.debug_info .debug_abbrev .debug_loc .debug_aranges + .debug_ranges .debug_line .debug_str) *(.jcr) *(.eh_frame_hdr) *(.eh_frame) diff --git a/fxcg50.ld b/fxcg50.ld index 9b13ad8..c35132d 100644 --- a/fxcg50.ld +++ b/fxcg50.ld @@ -118,15 +118,15 @@ SECTIONS /* ** gint-related sections - ** 88160000:4k VBR space - ** 88161000:4k .gint.data and .gint.bss + ** 8c160000:4k VBR space + ** 8c161000:4k .gint.data and .gint.bss */ /* VBR address: let's just start at the beginning of the RAM area. There's an unused 0x100-byte gap at the start of the VBR space. The VBR space is already a large block (> 2 kiB), so I'm cutting off the gap to spare some memory */ - _gint_vbr = 0x8815ff00; + _gint_vbr = ORIGIN(vbr) - 0x100; . = ORIGIN(rram); @@ -161,10 +161,17 @@ SECTIONS */ /* Unwanted sections going to meet Dave Null: + - SH3-only data sections + - Debug sections, often come out of libgcc - Java classes registration (why are there even here?) - Asynchronous unwind tables: no C++ exception handling for now ^^ - Comments or anything the compiler might put in its assembler */ /DISCARD/ : { + *(.gint.bss.sh3) + *(.gint.data.sh3) + + *(.debug_info .debug_abbrev .debug_loc .debug_aranges + .debug_ranges .debug_line .debug_str) *(.jcr) *(.eh_frame_hdr) *(.eh_frame) diff --git a/include/core/bootlog.h b/include/core/bootlog.h index 152bdea..8b65247 100644 --- a/include/core/bootlog.h +++ b/include/core/bootlog.h @@ -5,7 +5,7 @@ #ifndef GINT_CORE_BOOTLOG #define GINT_CORE_BOOTLOG -/* bootlog_loaded() - Section loading stage +/* bootlog_loaded() - section loading stage Called when RAM sections have been wiped and copied */ void bootlog_loaded(void); @@ -16,7 +16,7 @@ void bootlog_loaded(void); @ram Amount of mapped RAM, in bytes */ void bootlog_mapped(uint32_t rom, uint32_t ram); -/* bootlog_kernel() - Gint loading stage +/* bootlog_kernel() - gint loading stage Called when gint has been installed, the VBR switched and the interrupt handlers set up. */ void bootlog_kernel(void); diff --git a/include/core/intc.h b/include/core/intc.h index f660619..255f7f8 100644 --- a/include/core/intc.h +++ b/include/core/intc.h @@ -28,75 +28,75 @@ typedef struct { volatile word_union(*IPRA, - uint TMU0 :4; /* Timer 0 */ - uint TMU1 :4; /* Timer 1 */ - uint TMU2 :4; /* Timer 2 */ - uint RTC :4; /* Real-Time Clock */ + uint16_t TMU0 :4; /* Timer 0 */ + uint16_t TMU1 :4; /* Timer 1 */ + uint16_t TMU2 :4; /* Timer 2 */ + uint16_t RTC :4; /* Real-Time Clock */ ); volatile word_union(*IPRB, - uint WDT :4; /* Watchdog Timer */ - uint REF :4; /* BSC Refresh Request, SDRAM (?) */ - uint :4; - uint :4; + uint16_t WDT :4; /* Watchdog Timer */ + uint16_t REF :4; /* BSC Refresh Request, SDRAM (?) */ + uint16_t :4; + uint16_t :4; ); volatile word_union(*IPRC, - uint IRQ3 :4; /* Interrupt request 3 */ - uint IRQ2 :4; /* Interrupt request 2 */ - uint IRQ1 :4; /* Interrupt request 1 */ - uint IRQ0 :4; /* Interrupt request 0 */ + uint16_t IRQ3 :4; /* Interrupt request 3 */ + uint16_t IRQ2 :4; /* Interrupt request 2 */ + uint16_t IRQ1 :4; /* Interrupt request 1 */ + uint16_t IRQ0 :4; /* Interrupt request 0 */ ); volatile word_union(*IPRD, - uint PINT0_7 :4; /* External interrupt pins 0 to 7 */ - uint PINT8_15 :4; /* External interrupt pins 8 to 15 */ - uint IRQ5 :4; /* Interrupt request 5 */ - uint IRQ4 :4; /* Interrupt request 4 */ + uint16_t PINT0_7 :4; /* External interrupt pins 0 to 7 */ + uint16_t PINT8_15 :4; /* External interrupt pins 8 to 15 */ + uint16_t IRQ5 :4; /* Interrupt request 5 */ + uint16_t IRQ4 :4; /* Interrupt request 4 */ ); volatile word_union(*IPRE, - uint DMAC :4; /* Direct Memory Access Controller */ - uint SCIF0 :4; /* Serial Communication Interface 0 */ - uint SCIF2 :4; /* Serial Communication Interface 2 */ - uint ADC :4; /* Analog/Decimal Converter */ + uint16_t DMAC :4; /* Direct Memory Access Controller */ + uint16_t SCIF0 :4; /* Serial Communication Interface 0 */ + uint16_t SCIF2 :4; /* Serial Communication Interface 2 */ + uint16_t ADC :4; /* Analog/Decimal Converter */ ); volatile word_union(*IPRF, - uint :4; - uint :4; - uint USB :4; /* USB Controller */ - uint :4; + uint16_t :4; + uint16_t :4; + uint16_t USB :4; /* USB Controller */ + uint16_t :4; ); volatile word_union(*IPRG, - uint TPU0 :4; /* Timer Pulse Unit 0 */ - uint TPU1 :4; /* Timer Pulse Unit 1 */ - uint :4; - uint :4; + uint16_t TPU0 :4; /* Timer Pulse Unit 0 */ + uint16_t TPU1 :4; /* Timer Pulse Unit 1 */ + uint16_t :4; + uint16_t :4; ); volatile word_union(*IPRH, - uint TPU2 :4; /* Timer Pulse Unit 2 */ - uint TPU3 :4; /* Timer Pulse Unit 3 */ - uint :4; - uint :4; + uint16_t TPU2 :4; /* Timer Pulse Unit 2 */ + uint16_t TPU3 :4; /* Timer Pulse Unit 3 */ + uint16_t :4; + uint16_t :4; ); } PACKED(4) sh7705_intc_ipc_t; /* sh7705_intc_icr1_t - Interrupt Control Register 1 (general) */ -typedef word_union(sh7705_intc_icr1_t, - uint MAI :1; /* Mask All Interrupts */ - uint IRQLVL :1; /* Interrupt Request Level Detect */ - uint BLMSK :1; /* Enable NMI when BL is set */ - uint :1; - uint IRQ5E :2; /* IRQ 5 Edge Detection */ - uint IRQ4E :2; /* etc. */ - uint IRQ3E :2; - uint IRQ2E :2; - uint IRQ1E :2; - uint IRQ0E :2; +typedef volatile word_union(sh7705_intc_icr1_t, + uint16_t MAI :1; /* Mask All Interrupts */ + uint16_t IRQLVL :1; /* Interrupt Request Level Detect */ + uint16_t BLMSK :1; /* Enable NMI when BL is set */ + uint16_t :1; + uint16_t IRQ5E :2; /* IRQ 5 Edge Detection */ + uint16_t IRQ4E :2; /* etc. */ + uint16_t IRQ3E :2; + uint16_t IRQ2E :2; + uint16_t IRQ1E :2; + uint16_t IRQ0E :2; ); /* sh7705_intc_t - the SH7705 interrupt controller */ @@ -109,7 +109,7 @@ typedef struct } PACKED(4); /* Control registers */ - volatile sh7705_intc_icr1_t *ICR1; + sh7705_intc_icr1_t *ICR1; } PACKED(4) sh7705_intc_t; @@ -125,101 +125,101 @@ typedef struct Some of the fields have been left unnamed because they correspond to SH7724 peripheral modules that are *very* unlikely to even exist in the SH7305, let alone by of any use to us */ -typedef struct +typedef volatile struct { word_union(IPRA, - uint TMU0_0 :4; /* TMU0 Channel 0 */ - uint TMU0_1 :4; /* TMU0 Channel 1 */ - uint TMU0_2 :4; /* TMU0 Channel 2 */ - uint IrDA :4; /* Infrared Communication */ + uint16_t TMU0_0 :4; /* TMU0 Channel 0 */ + uint16_t TMU0_1 :4; /* TMU0 Channel 1 */ + uint16_t TMU0_2 :4; /* TMU0 Channel 2 */ + uint16_t IrDA :4; /* Infrared Communication */ ); pad(2); word_union(IPRB, - uint :4; /* JPEG Processing Unit */ - uint LCDC :4; /* LCD Controller */ - uint DMAC1A :4; /* Direct Memory Access Controller 1 */ - uint :4; /* Blending Engine Unit */ + uint16_t :4; + uint16_t LCDC :4; /* LCD Controller */ + uint16_t DMAC1A :4; /* Direct Memory Access Controller 1 */ + uint16_t :4; ); pad(2); word_union(IPRC, - uint TMU1_0 :4; /* TMU1 Channel 0 */ - uint TMU1_1 :4; /* TMU1 Channel 1 */ - uint TMU1_2 :4; /* TMU1 Channel 2 */ - uint :4; /* Sound Processing Unit */ + uint16_t TMU1_0 :4; /* TMU1 Channel 0 */ + uint16_t TMU1_1 :4; /* TMU1 Channel 1 */ + uint16_t TMU1_2 :4; /* TMU1 Channel 2 */ + uint16_t :4; ); pad(2); word_union(IPRD, - uint :4; - uint MMCIF :4; /* MultiMedia Card Interface */ - uint :4; - uint :4; /* ATAPI Interface */ + uint16_t :4; + uint16_t MMCIF :4; /* MultiMedia Card Interface */ + uint16_t :4; + uint16_t :4; ); pad(2); word_union(IPRE, - uint DMAC0A :4; /* Direct Memory Access Controller 0 */ - uint :4; /* Various graphical engines */ - uint SCIFA3 :4; /* SCIFA channel 3 interrupt */ - uint :4; /* Video Processing Unit */ + uint16_t DMAC0A :4; /* Direct Memory Access Controller 0 */ + uint16_t :4; + uint16_t ETMU3 :4; /* Extra TMU 3 */ + uint16_t :4; ); pad(2); word_union(IPRF, - uint KEYSC :4; /* Key Scan Interface */ - uint DMACOB :4; /* DMAC0 transfer/error info */ - uint USB0_1 :4; /* USB controller */ - uint CMT :4; /* Compare Match Timer */ + uint16_t KEYSC :4; /* Key Scan Interface */ + uint16_t DMACOB :4; /* DMAC0 transfer/error info */ + uint16_t USB0_1 :4; /* USB controller */ + uint16_t CMT :4; /* Compare Match Timer */ ); pad(2); word_union(IPRG, - uint SCIF0 :4; /* SCIF0 transfer/error info */ - uint SCIF1 :4; /* SCIF1 transfer/error info */ - uint SCIF2 :4; /* SCIF2 transfer/error info */ - uint :4; /* Video Engine Unit */ + uint16_t SCIF0 :4; /* SCIF0 transfer/error info */ + uint16_t ETMU1 :4; /* Extra TMU 1 */ + uint16_t ETMU2 :4; /* Extra TMU 2 */ + uint16_t :4; ); pad(2); word_union(IPRH, - uint MSIOF0 :4; /* Clock-synchronized SCIF channel 0 */ - uint MSIOF1 :4; /* Clock-synchronized SCIF channel 1 */ - uint :4; /* I2C Interface channel 0 */ - uint :4; /* I2C Interface channel 1 */ + uint16_t MSIOF0 :4; /* Clock-synchronized SCIF channel 0 */ + uint16_t MSIOF1 :4; /* Clock-synchronized SCIF channel 1 */ + uint16_t :4; + uint16_t :4; ); pad(2); word_union(IPRI, - uint SCIFA4 :4; /* SCIFA channel 4 interrupt */ - uint :4; /* MediaRAM InterConnected Buffers */ - uint :4; /* Transport Stream Interface */ - uint :4; /* 2D Graphics Accelerator & ICB */ + uint16_t ETMU4 :4; /* Extra TMU 4 */ + uint16_t :4; + uint16_t :4; + uint16_t :4; ); pad(2); word_union(IPRJ, - uint :4; /* Capture Engine Unit */ - uint :4; /* Ethernet Memory Access Controller */ - uint FSI :4; /* FIFO-Buffered Serial Interface */ - uint SDHI1 :4; /* SD Card Host Interface channel 1 */ + uint16_t ETMU0 :4; /* Extra TMU 0 */ + uint16_t :4; + uint16_t FSI :4; /* FIFO-Buffered Serial Interface */ + uint16_t SDHI1 :4; /* SD Card Host Interface channel 1 */ ); pad(2); word_union(IPRK, - uint RTC :4; /* Real-Time Clock */ - uint DMAC1B :4; /* DMAC1 transfer/error info */ - uint :4; /* MediaRAM InterConnected Buffers */ - uint SDHI0 :4; /* SD Card Host Interface channel 0 */ + uint16_t RTC :4; /* Real-Time Clock */ + uint16_t DMAC1B :4; /* DMAC1 transfer/error info */ + uint16_t :4; + uint16_t SDHI0 :4; /* SD Card Host Interface channel 0 */ ); pad(2); word_union(IPRL, - uint SCIFA5 :4; /* SCIFA channel 5 interrupt */ - uint :4; - uint TPU :4; /* Timer-Pulse Unit */ - uint :4; /* Image Extraction DMAC */ + uint16_t ETMU5 :4; /* Extra TMU 5 */ + uint16_t :4; + uint16_t TPU :4; /* Timer-Pulse Unit */ + uint16_t :4; ); pad(2); @@ -228,124 +228,25 @@ typedef struct /* sh7305_intc_masks_t - Interrupt mask management Writing 1 to IMR masks interrupts; writing 1 to IMCRs clears the masks. Writing 0 is ignored; reading from IMCRs yields undefined values */ -typedef struct +typedef volatile struct { - byte_union(IMR0, - uint :1; - uint TUNI1_2 :1; /* TMU1 overflow interrupts */ - uint TUNI1_1 :1; - uint TUNI1_0 :1; - uint SDHII3 :1; /* SD Card Host 1 interrupts */ - uint SDHII2 :1; - uint SDHII1 :1; - uint SDHII0 :1; - ); - pad(3); - - byte_union(IMR1, - uint :4; - uint DEI3 :1; /* DMAC0A interrupts */ - uint DEI2 :1; - uint DEI1 :1; - uint DEI0 :1; - ); - pad(3); - - byte_union(IMR2, - uint :7; - uint SCIFA0 :1; /* Asynchronous Serial interrupts */ - ); - pad(3); - - byte_union(IMR3, - uint DEI3 :1; /* DMAC1A interrupts */ - uint DEI2 :1; - uint DEI1 :1; - uint DEI0 :1; - uint :4; - ); - pad(3); - - byte_union(IMR4, - uint :1; - uint TUNI0_2 :1; /* TMU0 overflow interrupts */ - uint TUNI0_1 :1; - uint TUNI0_0 :1; - uint :3; - uint LCDCI :1; /* LCD Controller Interrupt */ - ); - pad(3); - - byte_union(IMR5, - uint KEYI :1; /* Key Interface */ - uint DADERR :1; /* DMAC0B interrupts */ - uint DEI5 :1; - uint DEI4 :1; - uint :1; - uint SCIF2 :1; /* Serial Communication Interface */ - uint SCIF1 :1; - uint SCIF0 :1; - ); - pad(3); - - byte_union(IMR6, - uint :2; - uint :1; - uint SCIFA4 :1; /* SCIFA4 interrupt */ - uint :1; - uint :1; - uint MSIOFI0 :1; /* Clock-synchronized SCIF channel 0 */ - uint MSIOFI1 :1; /* Clock-synchronized SCIF channel 1 */ - ); - pad(3); - - uint8_t IMR7; - pad(3); - - byte_union(IMR8, - uint SDHII3 :1; /* SD Card Host 0 interrupts */ - uint SDHII2 :1; - uint SDHII1 :1; - uint SDHII0 :1; - uint :2; - uint SCFIA5 :1; /* SCIFA5 interrupt */ - uint FSI :1; /* FIFO-Buffered Serial Interface */ - ); - pad(3); - - byte_union(IMR9, - uint :3; - uint CMTI :1; /* Compare Match Timer Interrupt */ - uint :1; - uint USI1 :1; /* USB1 */ - uint USI0 :1; /* USB0 */ - uint :1; - ); - pad(3); - - byte_union(IMR10, - uint :1; - uint DADERR :1; /* DMAC1B interrupts */ - uint DEI5 :1; - uint DEI4 :1; - uint :1; - uint ATI :1; /* RTC Alarm interrupt */ - uint PRI :1; /* RTC Periodic interrupt */ - uint CUI :1; /* RTC Carry interrupt */ - ); - pad(3); - - byte_union(IMR11, - uint :5; - uint TPUI :1; /* Timer-Pulse Unit */ - uint :2; - ); - pad(3); - + uint8_t IMR0; pad(3); + uint8_t IMR1; pad(3); + uint8_t IMR2; pad(3); + uint8_t IMR3; pad(3); + uint8_t IMR4; pad(3); + uint8_t IMR5; pad(3); + uint8_t IMR6; pad(3); + uint8_t IMR7; pad(3); + uint8_t IMR8; pad(3); + uint8_t IMR9; pad(3); + uint8_t IMR10; pad(3); + uint8_t IMR11; pad(3); uint8_t IMR12; } PACKED(4) sh7305_intc_masks_t; + /* sh7305_intc_userimask_t - User Interrupt Mask Sets the minimum required level for interrupts to be accepted. @@ -362,11 +263,11 @@ typedef struct *(INTC._7305.USERIMASK) = mask; } */ -typedef lword_union(sh7305_intc_userimask_t, - uint _0xa5 :8; /* Always set to 0xa5 before writing */ - uint :16; - uint UIMASK :4; /* User Interrupt Mask Level */ - uint :4; +typedef volatile lword_union(sh7305_intc_userimask_t, + uint32_t _0xa5 :8; /* Always set to 0xa5 before writing */ + uint32_t :16; + uint32_t UIMASK :4; /* User Interrupt Mask Level */ + uint32_t :4; ); /* sh7305_intc_t - the SH7305 interrupt controller */ @@ -374,17 +275,25 @@ typedef struct { /* Interrupt priority registers */ union { - volatile sh7305_intc_ipc_t *_; - volatile uint16_t *IPRS; + sh7305_intc_ipc_t *_; + volatile uint16_t *IPRS; }; /* Interrupt mask & mask clear registers */ - volatile sh7305_intc_masks_t *MSK; - volatile sh7305_intc_masks_t *MSKCLR; + sh7305_intc_masks_t *MSK; + sh7305_intc_masks_t *MSKCLR; /* Other registers */ - volatile sh7305_intc_userimask_t *USERIMASK; + sh7305_intc_userimask_t *USERIMASK; } PACKED(4) sh7305_intc_t; +//--- +// Forward definitions +//--- + +/* Provided by core/gint.c */ +extern sh7705_intc_t INTC3; +extern sh7305_intc_t INTC4; + #endif /* GINT_CORE_INTC */ diff --git a/include/core/memory.h b/include/core/memory.h new file mode 100644 index 0000000..49c02aa --- /dev/null +++ b/include/core/memory.h @@ -0,0 +1,22 @@ +//--- +// gint:core:memory - Core memory functions +// +// These are the basic standard memory functions required by GCC. +//--- + +#ifndef GINT_CORE_MEMORY +#define GINT_CORE_MEMORY + +/* memcpy() - copy a chunk of memory to a non-overlapping destination */ +void *memcpy(void * restrict dest, const void * restrict src, size_t n); + +/* memmove() - copy a chunk of memory to any destination */ +void *memmove(void *dest, const void *src, size_t n); + +/* memcmp() - compare two chunks of memory of the same size */ +int memcmp(const void *src1, const void *src2, size_t n); + +/* memset() - fill a chunk of memory with a single byte */ +void *memset(void *dest, int byte, size_t n); + +#endif /* GINT_CORE_MEMORY */ diff --git a/include/core/mmu.h b/include/core/mmu.h index 140ebfa..d92d3ee 100644 --- a/include/core/mmu.h +++ b/include/core/mmu.h @@ -12,6 +12,68 @@ #include #include +//--- +// SH7705 TLB +//--- + +#ifdef FX9860G + +/* tlb_addr_t - address part of a TLB entry */ +typedef struct +{ + uint VPN :22; + uint :1; + uint V :1; + uint ASID :8; + +} PACKED(4) tlb_addr_t; + +/* tlb_data_t - data part of a TLB entry */ +typedef struct +{ + uint :3; + uint PPN :19; + uint :1; + uint V :1; + uint :1; + uint PR :2; + uint SZ :1; + uint C :1; + uint D :1; + uint SH :1; + uint :1; + +} PACKED(4) tlb_data_t; + +/* tlb_addr() - get the P4 address of a TLB address entry + @way TLB way (0..3) + @E Entry number (0..31) + Returns a pointer to the entry. */ +const tlb_addr_t *tlb_addr(uint way, uint E); + +/* tlb_data() - get the P4 address of a TLB data entry + @way TLB way (0..3) + @E Entry number (0..31) + Returns a pointer to the entry. */ +const tlb_data_t *tlb_data(uint way, uint E); + +/* tlb_mapped_memort() - count amount of mapped memory + This function returns the amount of mapped text and data segment memory, in + bytes. The ranges are defined as follows: + ROM 00300000:512k + RAM 08100000:512k + Other mappings are ignored. Both pointers may be NULL. + + @rom Pointer to amount of mapped ROM + @ram Pointer to amount of mapped RAM */ +void tlb_mapped_memory(uint32_t *p_rom, uint32_t *p_ram); + +#endif + +//--- +// SH7305 Unified TLB +//--- + /* utlb_addr_t - address part of a UTLB entry */ typedef struct { @@ -52,7 +114,7 @@ const utlb_data_t *utlb_data(uint E); /* utlb_mapped_memory() - count amount of mapped memory This function returns the amount of mapped text and data segment memory, in bytes. The ranges are defined as follows: - ROM 00300000:512k + ROM 00300000:4M RAM 08100000:512k Other mappings are ignored. Both pointers may be NULL. diff --git a/include/core/mpu.h b/include/core/mpu.h index 91164d9..95486e3 100644 --- a/include/core/mpu.h +++ b/include/core/mpu.h @@ -3,9 +3,13 @@ // // 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() to perform MPU-dependent jobs: +// provides macros isSH3() and isSH4(), but the best way of performing +// MPU-dependent jobs is to use mpuSwitch(): // -// if(isSH3()) { ... } else { ... } +// mpuSwitch( +// print("SH3 code"), +// print("SH4 code") +// ); //--- #ifndef GINT_CORE_MPU @@ -13,7 +17,7 @@ #include -/* mpu_t - Supported MPUs */ +/* mpu_t - supported MPUs */ typedef enum { mpu_unknown = 0, @@ -36,6 +40,8 @@ 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); @@ -47,6 +53,8 @@ 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/defs/attributes.h b/include/defs/attributes.h index 64a38a7..67d45a2 100644 --- a/include/defs/attributes.h +++ b/include/defs/attributes.h @@ -13,6 +13,9 @@ /* Objects from the .gint.data and .gint.bss sections */ #define GDATA __attribute__((section(".gint.data"))) #define GBSS __attribute__((section(".gint.bss"))) +/* Additional sections that are only needed on SH3 */ +#define GDATA3 __attribute__((section(".gint.data.sh3"))) +#define GBSS3 __attribute__((section(".gint.bss.sh3"))) /* Initialization functions */ #define PRETEXT __attribute__((section(".pretext"))) diff --git a/include/drivers/t6k11.h b/include/drivers/t6k11.h index 5b89f40..a4bc7e1 100644 --- a/include/drivers/t6k11.h +++ b/include/drivers/t6k11.h @@ -40,12 +40,13 @@ void t6k11_contrast(int contrast); /* t6k11_backlight() - manage the screen backlight - Changes the backlight setting depending on the value of "setting": - - If setting = 0, turns the backlight off. + Changes the backlight status depending on the value of the argument: - If setting > 0, turns the backlight on. + - If setting = 0, turns the backlight off. - If setting < 0, toggles backlight. - This function has no effect on models that do not support the backlight. + This function has no effect on models that do not support the backlight, + although gint does not provide any facility for detecting them. @setting Requested backlight setting */ void t6k11_backlight(int setting); diff --git a/include/gint/clock.h b/include/gint/clock.h new file mode 100644 index 0000000..959c76a --- /dev/null +++ b/include/gint/clock.h @@ -0,0 +1,8 @@ +//--- +// gint:clock - Clock signals +//--- + +#ifndef GINT_CLOCK +#define GINT_CLOCK + +#endif /* GINT_CLOCK */ diff --git a/include/gint/display-fx.h b/include/gint/display-fx.h index fb54785..c7448c1 100644 --- a/include/gint/display-fx.h +++ b/include/gint/display-fx.h @@ -1,8 +1,113 @@ //--- // gint:display-fx - fx9860g drawing functions +// +// This module is in charge of all monochrome rendering. The gray engine +// has its own functions, but often relies on this module (because the +// gray effect is created through two monochrome buffers). //--- #ifndef GINT_DISPLAY_FX #define GINT_DISPLAY_FX +#ifdef FX9860G + +/* Screen dimensions on fx9860g */ +#define DWIDTH 128 +#define DHEIGHT 64 + +/* color_t - colors available for drawing + The following colors are defined by the library: + + OPAQUE COLORS (override existing pixels) + white, black - the usual thing + light, dark - additional colors used by the gray engine + + OPERATORS (combine with existing pixels) + none - leaves unchanged + reverse - inverts white <-> black, light <-> dark + lighten - shifts black -> dark -> light -> white -> white + darken - shifts white -> light -> dark -> black -> black + + Not all colors can be used with all functions. To avoid ambiguities, all + functions explicitly indicate compatible colors. */ +typedef enum +{ + /* Opaque colors */ + color_white = 0, + color_light = 1, + color_dark = 2, + color_black = 3, + + /* Monochrome operators */ + color_none = 4, + color_reverse = 5, + + /* Gray operators */ + color_lighten = 6, + color_darken = 7, + +} color_t; + +//--- +// Area drawing functions +//--- + +/* dupdate() - pushes the video RAM to the display driver + This function makes the contents of the VRAM visible on the screen. It is + the direct equivalent of Bdisp_PutDisp_DD() */ +void dupdate(void); + +/* dclear() - fill the screen with a single color + This function clears the screen by replacing all the pixels with a single + color. This function is optimized for opaque drawing. If you wish to apply + operators, use drect(). + + @color Allowed colors: white, black */ +void dclear(color_t color); + +/* drect() - fill a rectangle of the screen + This functions applies a color or an operator to a rectangle defined by two + points (x1 y1) and (x2 y2). Both are included in the rectangle. + + If GINT_LAX is defined, this function makes the following assumptions: + 0 <= x1 < x2 <= 127 + 0 <= y1 < y2 <= 63 + + @x1 @y1 @x2 @y2 Bounding rectangle (drawn area). + @color Allowed colors: white, black, none, reverse */ +void drect(int x1, int y1, int x2, int y2, color_t color); + +//--- +// Point drawing functions +//--- + +/* dpixel() - change a pixel's color + If the requested color is an operator, the result will depend on the current + color of the pixel. + + If GINT_LAX is defined, this function makes the following assumptions: + 0 <= x <= 127 + 0 <= y <= 63 + + @x @y Coordinates of the pixel to repaint + @color Allowed colors: white, black, none, reverse */ +void dpixel(int x, int y, color_t color); + +/* dline() - render a straight line + This function draws a line using a Bresenham-style algorithm. Please note + that the affected pixels may not be exactly the same when using dline() and + Bdisp_DrawLineVRAM(). + + dline() has optimization facilities for horizontal and vertical lines. + + If GINT_LAX is defined, this function makes the following assumptions: + 0 <= x1 <= x2 <= 127 + 0 <= y1, y2 <= 63 + + @x1 @y1 @x2 @y2 End points of theline (both included). + @color Allowed colors: white, black, none, reverse */ +void dline(int x1, int y1, int x2, int y2, color_t color); + +#endif /* FX9860G */ + #endif /* GINT_DISPLAY_FX */ diff --git a/include/gint/drivers.h b/include/gint/drivers.h index b1ebf95..d52e088 100644 --- a/include/gint/drivers.h +++ b/include/gint/drivers.h @@ -8,12 +8,37 @@ #include #include +//--- +// Driver initialization +// +// Drivers are initialized in linking order. For each driver, the +// following functions are called: +// - driver_sh3() [if running on an SH3-based machine] +// - ctx_save() +// - init() +// +// When the driver is unloaded, the following functions are called: +// - ctx_restore() +//--- + /* gint_driver_t - driver meta-information used by gint */ typedef struct { /* Driver name */ const char *name; + /* driver_sh3() - rectify driver initialization on SH3 platforms + This function is called during driver initialization on SH3. It may + be NULL. */ + void (*driver_sh3)(void); + + /* init() - initialize the driver + This function is called after ctx_save() and needs not save the + system setting. It is typically used to initialize registers to + suitable values on startup. If there is no init function, this field + may be set to NULL */ + void (*init)(void); + /* Size of a context object for the driver */ uint ctx_size; @@ -44,4 +69,12 @@ typedef struct #define GINT_DECLARE_DRIVER(name) \ SECTION(".gint.drivers") 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. */ +#ifdef FX9860G + #define GINT_DRIVER_SH3(name) name +#else + #define GINT_DRIVER_SH3(name) NULL +#endif + #endif /* GINT_DRIVERS */ diff --git a/include/gint/gint.h b/include/gint/gint.h index f081a5d..a9906f3 100644 --- a/include/gint/gint.h +++ b/include/gint/gint.h @@ -56,4 +56,68 @@ void gint_unload(void); getkey() call. */ void gint_pause(void); +//--- +// Public functions +//--- + +/* gint_intlevel() - configure the level of interrupts + + This function changes the interrupt level of the requested interrupt. Make + sure you are aware of interrupt assignments to avoid breaking other code. + This function is mainly used by drivers to enable the interrupts that they + support. + + The first parameter 'intid' identifies an interrupt by its position in the + sequence: + + IPRA & 0xf000 ; IPRA & 0x0f00 .. IPRA & 0x000f ; IPRB & 0xf000 .. + + For instance ID 7 refers to the low nibble of IPRB. These IDs and the range + for which there are valid is heavily platform-dependent and any call to this + function should be wrapped inside an MPU type check. This function will + crash if the provided interrupt ID is invalid. + + The interrupt level should be in the range 0 (disabled) .. 15 (highest + priority). + + @intid Interrupt ID of the targeted interrupt + @level Requested interrupt level + Returns the interrupt level that was assigned before the call. */ +int gint_intlevel(int intid, int level); + +/* gint_inthandler() - configure interrupt handlers + + This function installs (copies) interrupt handlers in the VBR space of the + application. Each handler is a 32-byte block aligned on a 32-byte boundary. + 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. + + 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 + 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. + + 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 + bytes of the block. This function returns the VBR address of the block to + allow the caller to edit the parameters. + + @event_code Identifier of the interrupt block + @handler Address of handler function + Returns the VBR address where the handlers was installed. */ +void *gint_inthandler(int event_code, const void *handler); + #endif /* GINT_GINT */ diff --git a/include/gint/timer.h b/include/gint/timer.h new file mode 100644 index 0000000..82d9e47 --- /dev/null +++ b/include/gint/timer.h @@ -0,0 +1,157 @@ +//--- +// gint:timer - Timer operation +//--- + +#ifndef GINT_TIMER +#define GINT_TIMER + +#include +#include +#include + +/* Timer identifiers + + Hardware timers are numbered with integers starting from 0. You can freely + access all the available timers by using their number once you have + configured them with timer_setup(). The number of timers depends on the MPU: + + SH3-based: 4 timers, ids 0..3 [SH7355, SH7337] + SH4-based: 9 timers, ids 0..8 [SH7305] + + You should be aware that some of these timers are used by default by gint: + - Timer 1 is used by the gray engine on fx9860g. + - Timer 3 is used by the keyboard, unless GINT_RTC_KEYBOARD is defined. This + macro is controlled by the -rtc-keyboard switch when gint is compiled. + + timer_setup() will fail if you try to use a timer that's already running. + Always check the return value of timer_setup()! Using a timer id that has + not been validated by timer_setup() will work, but do *something else* than + 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 */ + ); +} + +/* Clock input + + Timers count down when their input clock ticks, and fire when their counter + reach 0. The choice of the input clock influences the resolution of the + timer, but if the clock is too fast, the 32-bit counter might not be able to + represent long delays. + + Several input clocks are available. The peripheral clock (Po) can be divided + by 4, 16, 64 or 256; as an alternative the external clock TCLK can be used + for counting. I suspect TCLK runs at a fixed frequency of 32768 Hz, but this + has yet to be verified. + + You don't really need to choose an input clock unless you are doing + something very specific. In most practical cases you can use timer_default + which is 0. See the timer_delay() function for more information. */ +typedef enum +{ + timer_Po_4 = 0, + timer_Po_16 = 1, + timer_Po_64 = 2, + timer_Po_256 = 3, + timer_TCLK = 5, + + timer_default = timer_Po_4, + +} timer_input_t; + +//--- +// Timer functions +//--- + +/* timer_setup() - set up a timer + + This function configures the requested timer without starting it. On + success, it returns the first argument "timer", which is used as a timer + identifier in all other timer functions. If the requested timer is already + in use, this function fails and returns a negative number. + + This function sets the timer delay, the clock source, and registers a + callback function to be called when the timer fires. An argument can be + supplied to the callback function in the form of a pointer. + + When the timer fires, the callback function is called with the provided + argument pointer. The callback decides whether the timer should continue + running (by returning 0) or stop (by returning nonzero). In the latter case, + events accumulated while the callback was running are dropped. + + It is sometimes difficult to choose a timer constant and a clock source + given a wished delay in seconds, especially when overclock is used. The + timer_delay() function is provided for this purpose. + + @timer Requested timer id + @delay Delay between each event (the unit depends on the clock source) + @clock Clock source used by the timer for counting down + @callback Callback function (called when the timer fires) + @arg Passed as argument to the callback function */ +int timer_setup(int timer, uint32_t delay, timer_input_t clock, + int (*callback)(void *arg), void *arg); + +/* timer_delay() - compute a delay constant from a duration in seconds + + This function can used as a facility to calculate the [delay] argument to + the timer_setup() function. It takes a microsecond delay as an argument and + returns the corresponding timer constant. A typical use to start a timer + with a 25 ms interval would be: + + timer_setup(0, timer_delay(0, 25 * 1000), 0, callback, arg); + + WARNING: Only timers 0 to 2 can count microseconds! Other timers have a + resolution of around 30 us. Counting in ms is safe for all timers, though. + + For standard timers (0 to 2) it uses Po / 4 as clock input, which is very + precise and can represent up to 3 minutes' time; for extra timers (3 and + above) the clock is fixed to 32768 Hz. + + @timer The timer you are planning to use + @delay_us Requested delay in microseconds */ +uint32_t timer_delay(int timer, int delay_us); + +/* timer_start() - start a configured timer + The specified timer will start counting down and fire callbacks at regular + intervals. + + @timer Timer id, as returned by timer_setup() */ +void timer_start(int timer); + +/* timer_reload() - change a timer's delay and source + + 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. + + @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); + +/* timer_pause() - stop a running timer + The specified timer will be paused; its counter will not be reset. A stopped + timer can be resumed anytime by calling timer_start(). If you want to also + reset the counter, use timer_reload(). + + @timer Timer id, as returned by timer_setup() */ +void timer_pause(int timer); + +/* timer_free() - 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); + +#endif /* GINT_TIMER */ diff --git a/src/clock/freq.c b/src/clock/freq.c new file mode 100644 index 0000000..e69de29 diff --git a/src/core/bootlog.c b/src/core/bootlog.c index ed73906..635bf56 100644 --- a/src/core/bootlog.c +++ b/src/core/bootlog.c @@ -14,7 +14,7 @@ extern char sgdata, sgbss, sdata, sbss, btors, mtors, etors; -/* bootlog_loaded() - Section loading stage */ +/* bootlog_loaded() - section loading stage */ void bootlog_loaded(void) { /* Version string - the string constant resides in ROM */ @@ -61,6 +61,7 @@ void bootlog_loaded(void) print_dec(11, 3, gint_size, 4); print_dec(17, 3, &mtors - &btors, 2); print_dec(20, 3, &etors - &mtors, 2); + Bdisp_PutDisp_DD(); } @@ -74,17 +75,17 @@ void bootlog_mapped(int rom, int ram) print(1, 4, "MMU ROM: k RAM: k"); (rom < 0) ? print(9, 4, "???") : print_dec(9, 4, rom, 3); (ram < 0) ? print(18, 4, "???") : print_dec(18, 4, ram, 3); + Bdisp_PutDisp_DD(); } -/* bootlog_kernel() - Gint loading stage */ +/* bootlog_kernel() - gint loading stage */ void bootlog_kernel(void) { - if(isSH3()) - { - #ifdef FX9860G - extern sh7705_intc_t INTC3; + print(15, 1, " Kernel"); + mpuSwitch({ + /* SH3-based */ print(1, 5, "ABCD"); print_hex( 6, 5, INTC3._.IPRA->word, 4); print_hex(10, 5, INTC3._.IPRB->word, 4); @@ -95,12 +96,8 @@ 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); - #endif - } - else - { - extern sh7305_intc_t INTC4; - + },{ + /* SH4-based */ print(1, 5, "ACFG"); print_hex( 6, 5, INTC4._->IPRA.word, 4); print_hex(10, 5, INTC4._->IPRC.word, 4); @@ -111,7 +108,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/exch.S b/src/core/exch.S new file mode 100644 index 0000000..a7840cb --- /dev/null +++ b/src/core/exch.S @@ -0,0 +1,32 @@ +/* +** gint:core:exch - Exception handlers +*/ + + .global _exch_entry_7305 + +#ifdef FX9860G + .global _exch_entry_7705 +#endif + + .section .gint.blocks, "ax" + .align 4 + +/* SH7305-TYPE DEBUG EXCEPTION HANDLER - 26 BYTES */ + +_exch_entry_7705: +_exch_entry_7305: + mov.l 1f, r0 + mov.l @r0, r4 + + sts.l pr, @-r15 + mov.l 2f, r0 + jsr @r0 + nop + lds.l @r15+, pr + rte + nop + + .zero 6 + +2: .long _debug_exc +1: .long 0xff000024 diff --git a/src/core/gint.c b/src/core/gint.c new file mode 100644 index 0000000..278557d --- /dev/null +++ b/src/core/gint.c @@ -0,0 +1,64 @@ +//--- +// gint:core:gint - Library functions +//--- + +#include +#include +#include +#include + +/* Interrupt controllers */ + +GDATA3 sh7705_intc_t INTC3 = { + .IPRS = { + (void *)0xfffffee2, (void *)0xfffffee4, + (void *)0xa4000016, (void *)0xa4000018, (void *)0xa400001a, + (void *)0xa4080000, (void *)0xa4080002, (void *)0xa4080004, + }, + .ICR1 = (void *)0xa4000010, +}; + +GDATA sh7305_intc_t INTC4 = { + .IPRS = (void *)0xa4080000, + .MSK = (void *)0xa4080080, + .MSKCLR = (void *)0xa40800c0, + .USERIMASK = (void *)0xa4700000, +}; + +//--- +// Library functions +//--- + +/* gint_intlevel() - configure the level of interrupts */ +int gint_intlevel(int intid, int level) +{ + int shift = (~intid & 0x3) << 2; + volatile uint16_t *ipr; + level &= 0xf; + + mpuSwitch( + ipr = INTC3.IPRS[intid >> 2], /* SH3-based */ + ipr = &INTC4.IPRS[2 * (intid >> 2)] /* SH4-based */ + ); + + int oldlevel = (*ipr >> shift) & 0xf; + *ipr = (*ipr & ~(0xf << shift)) | (level << shift); + + return oldlevel; +} + +/* gint_inthandler() - configure interrupt handlers */ +void *gint_inthandler(int event_code, const void *handler) +{ + extern char gint_vbr; + + /* Normalize the event code */ + event_code -= 0x400; + event_code &= ~0x1f; + + /* Prevent overriding the entry gate */ + if(event_code < 0) return NULL; + + void *dest = (void *)&gint_vbr + event_code + 0x620; + return memcpy(dest, handler, 32); +} diff --git a/src/core/inth.S b/src/core/inth.S index ef79caf..102ae52 100644 --- a/src/core/inth.S +++ b/src/core/inth.S @@ -5,11 +5,10 @@ ** blocks depending on its configuration. */ - - .global _inth_entry_7305 + .global _inth_entry_7305 #ifdef FX9860G - .global _inth_entry_7705 + .global _inth_entry_7705 #endif .section .gint.blocks, "ax" @@ -34,20 +33,13 @@ references unless their relative order is fully known. This happens with the timer driver, and it works swimmingly; just be careful. */ -/* SH7305-TYPE INTERRUPT HANDLER ENTRY - 20 BYTES */ +/* SH7305-TYPE DEBUG INTERRUPT HANDLER - 26 BYTES */ +#if 0 _inth_entry_7305: - /* Get the event code from the INTEVT register */ mov.l 1f, r0 - /* TODO: mov.l @r0, r0 */ mov.l @r0, r4 - /* Interrupt codes start at 0x400 */ - mov #4, r1 - shll8 r1 - sub r1, r0 - - /* Jump to a C routine (TODO: Remove this) */ sts.l pr, @-r15 mov.l 2f, r0 jsr @r0 @@ -56,13 +48,31 @@ _inth_entry_7305: rte nop - /* Add the distance between nop and the first entry, and jump - add #16, r0 - braf r0 - nop */ + .zero 6 2: .long _debug 1: .long 0xff000028 +#endif + +/* SH7305-TYPE INTERRUPT HANDLER ENTRY - 20 BYTES */ + +_inth_entry_7305: + /* Get the event code from the INTEVT register */ + mov.l 1f, r0 + mov.l @r0, r0 + + /* Interrupt codes start at 0x400 */ + mov #4, r1 + shll8 r1 + sub r1, r0 + + /* Add the distance to the first entry and jump */ + add #16, r0 + braf r0 + nop + + .zero 12 +1: .long 0xff000028 #ifdef FX9860G @@ -90,11 +100,10 @@ _inth_entry_7705: 1: .long 0xa4000000 /* INTEVT2 register */ 2: .long _inth_remap - .section .gint.data .align 4 -/* EVENT CODE TRANSLATION TABLE - 72 BYTES */ +/* EVENT CODE TRANSLATION TABLE - 96 BYTES */ _inth_remap: .byte 0x00, 0x01, 0x02, 0xfe, 0x34, 0x35, 0x36, 0xff @@ -106,5 +115,8 @@ _inth_remap: .byte 0xff, 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff .byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff .byte 0x2d, 0x2d, 0xff, 0xff, 0x2d, 0x2d, 0xff, 0xff + .byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + .byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + .byte 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff #endif diff --git a/src/core/memory.c b/src/core/memory.c index 029dd68..3322120 100644 --- a/src/core/memory.c +++ b/src/core/memory.c @@ -85,23 +85,24 @@ void *memcpy(void * restrict dst, const void * restrict src, size_t n) return dst; } -void *_memmove(void *dst, const void *src, size_t n) +void *_memmove(UNUSED void *dst, UNUSED const void *src, UNUSED size_t n) { // (same as memcpy, but heed for direction if areas overlap) - // more complicated - // allocate a buffer aligned with destination (source would be ok too) - // read unaligned from source to buffer - // copy aligned from buffer to destination + // copy by increasing addresses if dst < src + // copy by decreasing addresses if dst > src return dst; } -int _memcmp(const void *s1, const void *s2, size_t n) +int _memcmp(UNUSED const void *s1, UNUSED const void *s2, UNUSED size_t n) { return 0; } -void *_memset(void *s, int byte, size_t n) +void *memset(void *s, int byte, size_t n) { + /* TODO: Do it efficiently */ + char *dst = s; + while(n--) *dst++ = byte; return s; } diff --git a/src/core/mmu.c b/src/core/mmu.c index 291b831..c942c6e 100644 --- a/src/core/mmu.c +++ b/src/core/mmu.c @@ -4,6 +4,58 @@ #include +//--- +// SH7705 TLB +//--- + +#ifdef FX9860G + +/* tlb_addr() - get the P4 address of a TLB address entry */ +INLINE const tlb_addr_t *tlb_addr(uint way, uint E) +{ + uint32_t addr = 0xf2000000 | (E << 12) | (way << 8); + return (void *)addr; +} + +/* tlb_data() - get the P4 address of a TLB data entry */ +INLINE const tlb_data_t *tlb_data(uint way, uint E) +{ + uint32_t addr = 0xf3000000 | (E << 12) | (way << 8); + return (void *)addr; +} + +/* tlb_mapped_memort() - count amount of mapped memory */ +void tlb_mapped_memory(uint32_t *p_rom, uint32_t *p_ram) +{ + uint32_t rom = 0, ram = 0; + + for(int way = 0; way < 4; way++) + for(int E = 0; E < 32; E++) + { + const tlb_addr_t *addr = tlb_addr(way, E); + const tlb_data_t *data = tlb_data(way, E); + if(!addr->V || !data->V) continue; + + int size = data->SZ ? 4096 : 1024; + uint32_t src; + + if(data->SZ) src = (((addr->VPN >> 2) | E) << 12); + else src = (addr->VPN | (E << 2)) << 10; + + if(src >= 0x00300000 && src < 0x00380000) rom += size; + if(src >= 0x08100000 && src < 0x08180000) ram += size; + } + + if(p_rom) *p_rom = rom; + if(p_ram) *p_ram = ram; +} + +#endif + +//--- +// SH7305 Unified TLB +//--- + /* utlb_addr() - get the P4 address of a UTLB address entry */ INLINE const utlb_addr_t *utlb_addr(uint E) { @@ -29,11 +81,12 @@ void utlb_mapped_memory(uint32_t *p_rom, uint32_t *p_ram) const utlb_data_t *data = utlb_data(E); if(!addr->V || !data->V) continue; + /* Magic formula to get the size without using an array */ int sz = ((data->SZ1 << 1) | data->SZ2) << 3; int size = 1 << ((0x14100c0a >> sz) & 0xff); uint32_t src = addr->VPN << 10; - if(src >= 0x00300000 && src < 0x00380000) rom += size; + if(src >= 0x00300000 && src < 0x00700000) rom += size; if(src >= 0x08100000 && src < 0x08180000) ram += size; } diff --git a/src/core/setup.c b/src/core/setup.c index 1335a63..c307ae3 100644 --- a/src/core/setup.c +++ b/src/core/setup.c @@ -3,34 +3,18 @@ //--- #include +#include +#include #include #include #include -/* Interrupt controllers */ - -#ifdef FX9860G -GDATA sh7705_intc_t INTC3 = { - .IPRS = { - (void *)0xfffffee2, (void *)0xfffffee4, - (void *)0xa4000016, (void *)0xa4000018, (void *)0xa400001a, - (void *)0xa4080000, (void *)0xa4080002, (void *)0xa4080004, - }, - .ICR1 = (void *)0xa4000010, -}; -#endif - -GDATA sh7305_intc_t INTC4 = { - .IPRS = (void *)0xa4080000, - .MSK = (void *)0xa4080080, - .MSKCLR = (void *)0xa40800c0, - .USERIMASK = (void *)0xa4700000, -}; - /* VBR address, from the linker script */ extern char gint_vbr; /* System's VBR address */ GBSS static uint32_t system_vbr; +/* Driver table */ +extern gint_driver_t bdrv, edrv; //--- // Context system for gint's core @@ -46,32 +30,24 @@ typedef struct @arg ctx gint core context object */ static void gint_ctx_save(gint_core_ctx *ctx) { - if(isSH3()) - { - #ifdef FX9860G - for(int i = 0; i < 8; i++) ctx->iprs[i] = *(INTC3.IPRS[i]); - #endif - } - else - { - for(int i = 0; i < 12; i++) ctx->iprs[i] = INTC4.IPRS[2 * i]; - } + 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] + ); } /* gint_ctx_restore() - restore interrupt controller configuration @arg ctx gint core context object */ static void gint_ctx_restore(gint_core_ctx *ctx) { - if(isSH3()) - { - #ifdef FX9860G - for(int i = 0; i < 8; i++) *(INTC3.IPRS[i]) = ctx->iprs[i]; - #endif - } - else - { - for(int i = 0; i < 12; i++) INTC4.IPRS[2 * i] = ctx->iprs[i]; - } + 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] + ); } //--- @@ -85,16 +61,12 @@ GBSS static gint_core_ctx sys_ctx; static void lock(void) { /* Just disable everything, drivers will enable what they support */ - if(isSH3()) - { - #ifdef FX9860G - for(int i = 0; i < 8; i++) *(INTC3.IPRS[i]) = 0x0000; - #endif - } - else - { - for(int i = 0; i < 12; i++) INTC4.IPRS[2 * i] = 0x0000; - } + 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 + ); } /* gint_install() - install and start gint */ @@ -104,8 +76,8 @@ void gint_install(void) uint32_t vbr = (uint32_t)&gint_vbr; /* Event handler entry points */ - extern void inth_entry_7705(void); - extern void inth_entry_7305(void); + void *exch_entry; + void *inth_entry; /* First save the hardware configuration. This step is crucial because we don't want the system to find out about us directly manipulating @@ -115,7 +87,21 @@ void gint_install(void) /* Load the event handler entry points into memory */ /* TODO: Load an exception handler and a TLB miss handler */ - void *inth_entry = isSH3() ? inth_entry_7705 : inth_entry_7305; + mpuSwitch({ + /* SH3-based */ + extern void exch_entry_7705(void); + extern void inth_entry_7705(void); + exch_entry = exch_entry_7705; + inth_entry = inth_entry_7705; + },{ + /* SH4-based */ + 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); /* Time to switch VBR and roll! */ @@ -126,6 +112,12 @@ void gint_install(void) static void unlock(void) { gint_ctx_restore(&sys_ctx); + + /* Restore all driver settings */ + for(gint_driver_t *drv = &bdrv; drv < &edrv; drv++) + { + drv->ctx_restore(drv->sys_ctx); + } } /* gint_unload() - unload gint and give back control to the system */ diff --git a/src/core/start.c b/src/core/start.c index 48c0445..7b8bf3d 100644 --- a/src/core/start.c +++ b/src/core/start.c @@ -118,7 +118,6 @@ int start(int isappli, int optnum) mpu_init(); #endif - /* Load data sections and wipe the bss section. This has to be done first for static and global variables to be initialized */ regcpy(lgdata, sgdata, rgdata); @@ -129,23 +128,25 @@ int start(int isappli, int optnum) bootlog_loaded(); #endif - /* TODO: Do the TLB investigation on SH3 (UTLB things are SH4 only) */ - /* Traverse all ROM pages */ explore(brom, srom); + /* Count how much memory got mapped from this process */ uint32_t rom, ram; - utlb_mapped_memory(&rom, &ram); + mpuSwitch( + tlb_mapped_memory(&rom, &ram), /* SH3-based */ + utlb_mapped_memory(&rom, &ram) /* SH4-based */ + ); + + #ifdef GINT_BOOT_LOG + bootlog_mapped(rom, ram); + #endif //--- - #ifdef GINT_BOOT_LOG - isSH3() ? bootlog_mapped(-1, -1) - : bootlog_mapped(rom, ram); - #endif - /* Cancel add-in execution if not all pages are mapped - TODO: Resort to better graphical display */ + TODO: Resort to better graphical display, but still fxlib since + add-in is not mapped yet */ if(rom < (uint32_t)&srom) { Bdisp_AllClr_VRAM(); @@ -176,7 +177,9 @@ int start(int isappli, int optnum) /* Initialize all drivers by saving the system settings */ for(drv = &bdrv; drv < &edrv; drv++) { + if(isSH3() && drv->driver_sh3) drv->driver_sh3(); drv->ctx_save(drv->sys_ctx); + if(drv->init) drv->init(); } /* With gint fully initialized, we are ready to start the hosted user @@ -195,15 +198,10 @@ 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 - resoture them. */ + restore them */ - /* Restore all driver settings */ - for(drv = &drv; drv < &edrv; drv++) - { - drv->ctx_restore(drv->sys_ctx); - } - - /* Finally, unload gint and give back control to the system */ + /* Unload gint and give back control to the system. Driver settings + will be restored while interrupts are disabled */ gint_unload(); /* TODO: Invoke main menu instead of returning? */ diff --git a/src/r61524/r61524.c b/src/r61524/r61524.c index 461e66e..eb2c6d5 100644 --- a/src/r61524/r61524.c +++ b/src/r61524/r61524.c @@ -66,8 +66,8 @@ INLINE static void select(uint16_t reg) /* Set RS back. We don't do this in read()/write() because the display driver is optimized for consecutive GRAM access. LCD-transfers will - be faster when executing select() followed by many write(). - (Most applications should use DMA though.) */ + be faster when executing select() followed by several calls to + write(). (Although most applications should use the DMA instead.) */ *PRDR |= 0x10; synco(); } @@ -126,7 +126,7 @@ void r61524_test(void) { print(1, 2, "Aborting."); Bdisp_PutDisp_DD(); - delay(20); + delay(40); return; } @@ -194,7 +194,7 @@ void r61524_test(void) print_hex(12, 6, COL, 1); Bdisp_PutDisp_DD(); - delay(10); + delay(50); //--- @@ -211,7 +211,12 @@ void r61524_test(void) print_hex(14, 3, VEA, 3); Bdisp_PutDisp_DD(); - delay(20); + delay(50); + + // + + Bdisp_AllClr_VRAM(); + Bdisp_PutDisp_DD(); //--- @@ -227,57 +232,24 @@ void r61524_test(void) //--- -// Bdisp_AllClr_VRAM(); -// Bdisp_PutDisp_DD(); - select(ram_address_horizontal); write(HEA); select(ram_address_vertical); write(VSA); select(write_data); - uint16_t color = 0xf3e4; - for(int v = 0; v < 396; v++) - for(int h = 0; h < 224; h++) + uint16_t color; + for(int v = 0; v < 224; v++) + for(int h = 0; h < 396; h++) { - color = (v << 7) ^ h ^ 0x3754; +// int offset = 396 * v + h; +// uint8_t *src = gimp_image.pixel_data + 2 * offset; + color = 0x3eb7; // (src[1] << 8) | src[0]; + write(color); } - delay(60); - - //--- - - /* Shift by 9, not 8, because of horizontal/vertical range inversion */ - select(ram_address_horizontal); - AD = read(); - select(ram_address_vertical); - AD |= read() << 9; - - select(horizontal_ram_start); - HSA = read(); - select(horizontal_ram_end); - HEA = read(); - - select(vertical_ram_start); - VSA = read(); - select(vertical_ram_end); - VEA = read(); - - Bdisp_AllClr_VRAM(); - - print(1, 1, "Address=?????"); - print_hex(9, 1, AD, 5); - - print(1, 2, "HSA=??? HEA=???"); - print_hex(5, 2, HSA, 3); - print_hex(14, 2, HEA, 3); - print(1, 3, "VSA=??? VEA=???"); - print_hex(5, 3, VSA, 3); - print_hex(14, 3, VEA, 3); - - Bdisp_PutDisp_DD(); - delay(10); + delay(200); } //--- @@ -330,6 +302,7 @@ static void ctx_restore(void *buf) gint_driver_t drv_r61524 = { .name = "Renesas R61524", + .init = NULL, .ctx_size = sizeof(ctx_t), .sys_ctx = &sys_ctx, .ctx_save = ctx_save, diff --git a/src/t6k11/t6k11.c b/src/t6k11/t6k11.c index 97207bc..e58a026 100644 --- a/src/t6k11/t6k11.c +++ b/src/t6k11/t6k11.c @@ -140,16 +140,15 @@ 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 */ - if(isSH3()) - { + mpuSwitch({ + /* SH3-based */ port = (void *)0xa400012c; mask = 0x80; - } - else - { + },{ + /* SH4-based */ port = (void *)0xa4050138; mask = 0x10; - } + }); if(!setting) *port &= ~mask; if(setting > 0) *port |= mask; @@ -198,6 +197,7 @@ static void ctx_restore(void *buf) gint_driver_t drv_t6k11 = { .name = "Toshiba T6K11", + .init = NULL, .ctx_size = sizeof(ctx_t), .sys_ctx = &sys_ctx, .ctx_save = ctx_save, diff --git a/src/tmu/inth.s b/src/tmu/inth.s new file mode 100644 index 0000000..1bc7a40 --- /dev/null +++ b/src/tmu/inth.s @@ -0,0 +1,225 @@ +/* +** gint:tmu:inth - Interrupt handlers for the timer units +** Perhaps the most technical of my interrupt handlers. They implement a +** simple kind of interrupt handler communication by letting the control flow +** from each interrupt handler to the next. +*/ + + /* Gates for the standard Timer Unit (TMU) */ + .global _inth_tmu_0 + .global _inth_tmu_1 + .global _inth_tmu_2 + .global _inth_tmu_storage + + /* Gates for the extra timers (informally called ETMU) */ + .global _inth_tmu_extra2 + .global _inth_tmu_extra_help + .global _inth_tmu_extra_others + + .section .gint.blocks, "ax" + .align 4 + +/* TMU INTERRUPT HANDLERS - 128 BYTES + Unfortunately I did not manage to write a handler that cleared the interrupt + flag and invoked a callback in less than 34 bytes data included. So I + decided to make several gates operate as a whole and add a bit more features + in them. Basically, these handlers: + - Clear the interrupt flag + - Invoke a callback function and pass it a user-provided argument + - Stop the timer if the callback returns non-zero + - Host their own callback and arguments + + It is important to notice that the code of the following gates looks like + they are contiguous in memory. The assembler will make that assumption, and + turn any address reference between two gates into a *relative displacement*. + If the gates don't have the same relative location at runtime, the code will + crash because we will have broken the references. This is why we can only do + it with handlers that are mapped to consecutive event codes. */ + +/* FIRST GATE - TMU0 entry, clear underflow flag and call back */ +_inth_tmu_0: + mova .storage0, r0 + mov #-2, r1 + + /*** This is the first shared section ***/ + +.clearflag: + mov.l r1, @-r15 + + /* Load the TCR address and clear the interrupt flag */ + mov.l .mask, r3 + mov.l @(8, r0), r1 + mov.w @r1, r2 + and r3, r2 + mov.w r2, @r1 + + /* Invoke the callback function and pass the argument */ + sts.l pr, @-r15 + mov.l @r0, r1 + jsr @r1 + mov.l @(4, r0), r4 + lds.l @r15+, pr + + /* Prepare stopping the timer */ + mov.l .tstr, r5 + bra .stoptimer + mov.l @r15+, r1 + +/* SECOND GATE - TMU1 entry and stop timer */ +_inth_tmu_1: + mova .storage1, r0 + bra .clearflag + mov #-3, r1 + + /*** This is the second shared section ***/ + +.stoptimer: + /* Stop the timer if the return value is not zero */ + tst r0, r0 + bt .end + mov.b @r5, r2 + and r1, r2 + mov.b r2, @r5 + +.end: + rte + nop + + .zero 12 + +/* THIRD GATE - TMU2 entry and storage for TMU0 */ +_inth_tmu_2: + mova .storage2, r0 + bra .clearflag + mov #-5, r1 + + .zero 14 + +.storage0: + .long 0 /* Callback: Configured dynamically */ + .long 0 /* Argument: Configured dynamically */ + .long 0xa4490010 /* TCR0: Overridden at startup on SH3 */ + +/* FOURTH GATE - Storage for TMU1, TMU2 and other values */ +_inth_tmu_storage: + +.mask: .long 0x0000feff +.tstr: .long 0xa4490004 /* TSTR: Overridden at startup on SH3 */ + +.storage1: + .long 0 /* Callback: Configured dynamically */ + .long 0 /* Argument: Configured dynamically */ + .long 0xa449001c /* TCR1: Overridden at startup on SH3 */ + +.storage2: + .long 0 /* Callback: Configured dynamically */ + .long 0 /* Argument: Configured dynamically */ + .long 0xa4490028 /* TCR2: Overridden at startup on SH3 */ + + + +/* EXTRA TMU INTERRUPT HANDLERS - 96 BYTES + To implement the same functionalities as the standard timers, several blocks + are once again needed. But the handlers for the extra timers are not located + in adjacent gates, except for ETMU1 and ETMU2 which have event codes 0xc20 + and 0xc40. Since handler 0xc60 is free on both SH3 and SH4, I'm using it to + build a three-handler block and achieve the same result as above. + + On SH4 this means that an extra gate has to be installed, but no interrupt + pointed here. On SH3 this means that four gates are used for the only extra + timer, but the incurred cost is minimal (96 bytes on the binary file) + because the size of the VBR area can hardly be shrunk anyway. + + It *is* possible to do generalized communication between interrupt handlers + that do not reside in consecutive gates. The general way of performing a + jump or data access between two interrupt handlers would be to store at + runtime the address of the target resource in a reserved longword in the + source handler. But longwords are costly in 32-byte areas. Even if the event + codes of the interrupt handlers are known at development time, the best I + can think of is hardcoding the relative displacements, and one would need to + use the unnatural and unmaintainable @(disp, pc) addressing modes. */ + +/* FIRST GATE - ETMU2 entry, clear flag and prepare callback */ +_inth_tmu_extra2: + /* Warning: the size of this section (2 bytes) is hardcoded in another + interrupt handler, _inth_tmu_extra_others */ + mova .storage_extra_1, r0 + +.extra_callback: + mov.l r8, @-r15 + mov r0, r8 + stc.l gbr, @-r15 + + /* Invoke the callback function */ + sts.l pr, @-r15 + mov.l @r8, r1 + jsr @r1 + mov.l @(4, r8), r4 + + bra .extra_clearflag + lds.l @r15+, pr + +.storage_extra_1: + .long 0 /* Callback: Configured dynamically */ + .long 0 /* Argument: Configured dynamically */ + .long 0 /* Structure address: Edited at startup */ + +/* SECOND GATE - Helper entry, invoke callback and stop timer if requested */ +_inth_tmu_extra_help: + +.extra_clearflag: + mov r0, r5 + + /* Load struture address */ + mov.l @(8, r8), r0 + ldc r0, gbr + mov #12, r0 + +.extra_loopclear: + /* Aggressively clear the interrupt flag. The loop is required because + clearing takes "some time". The system does it. Not doing it will + cause the interrupt to be triggered again until the flag is cleared, + which can be ~20 interrupts if the handler is fast! */ + and.b #0xfd, @(r0, gbr) + tst.b #0x02, @(r0, gbr) + bf .extra_loopclear + + /* Check whether to stop the timer */ + tst r5, r5 + bt .extra_end + +.extra_stoptimer: + mov.b @(0, gbr), r0 + and #0xfe, r0 + mov.b r0, @(0, gbr) + +.extra_end: + ldc.l @r15+, gbr + mov.l @r15+, r8 + rte + nop + +/* FOURTH GATE - All other ETMU entries, deferred to the previous ones */ +_inth_tmu_extra_others: + /* Dynamically compute the target of the jump */ + stc vbr, r1 + mov.l 1f, r2 + add r2, r1 + + mova .storage_extra_others, r0 + jmp @r1 + nop + + /* Offset from VBR where extra timer 2 is located: + - 0x600 to reach the interrupt handlers + - 0x020 to jump over the entry gate + - 0x840 to reach the handler of extra timer 2 + - 0x002 to jump over the first part of its code */ +1: .long 0xe62 + + .zero 4 + +.storage_extra_others: + .long 0 /* Callback: Configured dynamically */ + .long 0 /* Argument: Configured dynamically */ + .long 0 /* Structure address: Edited at startup */ diff --git a/src/tmu/tmu.c b/src/tmu/tmu.c new file mode 100644 index 0000000..dd7a95d --- /dev/null +++ b/src/tmu/tmu.c @@ -0,0 +1,429 @@ +//--- +// gint:tmu - Timer operation +//--- + +#include +#include +#include + +#include + +#include +#include + +//--- +// Timer structures +//--- + +/* tmu_t - a single timer from a standard timer unit */ +typedef volatile struct +{ + uint32_t TCOR; /* Constant register */ + uint32_t TCNT; /* Counter register, counts down */ + + word_union(TCR, + uint16_t :7; + uint16_t UNF :1; /* Underflow flag */ + uint16_t :2; + uint16_t UNIE :1; /* Underflow interrupt enable */ + uint16_t CKEG :2; /* Input clock edge */ + uint16_t TPSC :3; /* Timer prescaler (input clock) */ + ); + +} PACKED(4) tmu_t; + +/* tmu_extra_t - extra timers on sh7337, sh7355 and sh7305 */ +typedef volatile struct +{ + uint8_t TSTR; /* Only bit 0 is used */ + pad(3); + + uint32_t TCOR; /* Constant register */ + uint32_t TCNT; /* Counter register */ + + byte_union(TCR, + uint8_t :6; + uint8_t UNF :1; /* Underflow flag */ + uint8_t UNIE :1; /* Underflow interrupt enable */ + ); + +} PACKED(4) tmu_extra_t; + +/* inth_data_t - data storage inside interrupt handlers */ +typedef struct +{ + int (*callback)(void *arg); /* User-provided callback function */ + void *arg; /* Argument for [callback] */ + volatile void *structure; /* Either TCR or timer address */ + +} PACKED(4) inth_data_t; + +/* timer_t - all data required to run a single timer */ +typedef struct +{ + void *tmu; /* Address of timer structure */ + inth_data_t *data; /* Interrupt handler data */ + uint16_t event; /* Interrupt event code */ +} timer_t; + + +//--- +// Driver storage +//--- + +/* This is the description of the structure on SH4. SH3-based fx9860g models, + which are already very rare, will adapt the values in init functions */ +GDATA static timer_t timers[9] = { + { .tmu = (void *)0xa4490008, .event = 0x400 }, + { .tmu = (void *)0xa4490014, .event = 0x420 }, + { .tmu = (void *)0xa4490020, .event = 0x440 }, + { .tmu = (void *)0xa44d0030, .event = 0x9e0 }, + { .tmu = (void *)0xa44d0050, .event = 0xc20 }, + { .tmu = (void *)0xa44d0070, .event = 0xc40 }, + { .tmu = (void *)0xa44d0090, .event = 0x900 }, + { .tmu = (void *)0xa44d00b0, .event = 0xd00 }, + { .tmu = (void *)0xa44d00d0, .event = 0xfa0 }, +}; + +/* TSTR register for standard timers */ +GDATA static volatile uint8_t *TSTR = (void *)0xa4490004; + +//--- +// Timer API +//--- + +/* timer_setup() - set up a timer */ +int timer_setup(int tid, uint32_t delay, timer_input_t clock, + int (*callback)(void *arg), void *arg) +{ + /* We need to distinguish normal and extra timers */ + if(tid < 3) + { + /* Refuse to setup timers that are already in use */ + tmu_t *t = timers[tid].tmu; + if(t->TCR.UNIE) return -1; + + /* Configure the registers of the target timer */ + t->TCOR = delay; + t->TCNT = delay; + t->TCR.TPSC = clock; + + /* Clear the interrupt flag */ + do t->TCR.UNF = 0; + while(t->TCR.UNF); + + t->TCR.UNIE = 1; /* Enable interrupt on underflow */ + t->TCR.CKEG = 0; /* Count on rising edge (SH7705) */ + } + + /* Extra timers have a simpler structure */ + else + { + tmu_extra_t *t = timers[tid].tmu; + if(t->TCR.UNIE) return -1; + + /* There is no clock input and no clock edge settings */ + t->TCOR = delay; + t->TCNT = delay; + + /* Clear the interrupt flag */ + do t->TCR.UNF = 0; + while(t->TCR.UNF); + + t->TCR.UNIE = 1; + } + + /* Register the callback and its argument */ + timers[tid].data->callback = callback; + timers[tid].data->arg = arg; + + /* Return the timer id, since configuration was successful */ + return tid; +} + +/* timer_delay() - compute a delay constant from a duration in seconds */ +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 + if(tid >= 3) freq = 32768; /* 32768 Hz */ + + uint64_t product = freq * (uint64_t)delay_us; + return product / 1000000; +} + +/* timer_control() - start or stop a timer + @timer Timer ID to configure + @state 0 to start the timer, 1 to stop it (nothing else!) */ +static void timer_control(int tid, int state) +{ + if(tid < 3) + { + /* For standard timers, use the MPU's TSTR register */ + *TSTR = (*TSTR | (1 << tid)) ^ (state << tid); + } + else + { + /* Extra timers all have their own TSTR register */ + tmu_extra_t *t = timers[tid].tmu; + t->TSTR = (t->TSTR | 1) ^ state; + } +} + +/* timer_start() - start a configured timer */ +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) +{ + if(tid < 3) ((tmu_t *)timers[tid].tmu)->TCOR = delay; + else ((tmu_extra_t *)timers[tid].tmu)->TCOR = delay; +} + +/* timer_pause() - stop a running timer */ +void timer_pause(int tid) +{ + timer_control(tid, 1); +} + +/* timer_free() - free a timer */ +void timer_free(int tid) +{ + /* Stop the timer and disable UNIE to indicate that it's free */ + timer_pause(tid); + + if(tid < 3) + { + tmu_t *t = timers[tid].tmu; + t->TCR.UNIE = 0; + } + else + { + tmu_extra_t *t = timers[tid].tmu; + t->TCR.UNIE = 0; + + /* Also clear TCOR and TCNT to avoid spurious interrupts */ + t->TCOR = 0xffffffff; + t->TCNT = 0xffffffff; + + do t->TCR.UNF = 0; + while(t->TCR.UNF); + } +} + +//--- +// Driver initialization +//--- + +/* Interrupt handlers provided by tmu/inth.s for standard timers */ +extern void inth_tmu_0(void); +extern void inth_tmu_1(void); +extern void inth_tmu_2(void); +extern void inth_tmu_storage(void); + +/* Interrupt handlers provided by tmu/inth.s for extra timers */ +extern void inth_tmu_extra1(void); +extern void inth_tmu_extra2(void); +extern void inth_tmu_extra_help(void); +extern void inth_tmu_extra_others(void); + +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); + + /* User information in interrupt handlers */ + timers[0].data = h2 + 20; + timers[1].data = hs + 8; + timers[2].data = hs + 20; + + /* SH3: Override the address of TSTR in the interrupt handler helper */ + if(isSH3()) *(volatile uint8_t **)(hs + 4) = TSTR; + + /* Stop all timers */ + *TSTR = 0; + + /* This driver uses the UNIE (UNderflow Interrupt Enable) bit of the + TCR register to indicate which timers are being used; reset them */ + for(int i = 0; i < 3; i++) + { + tmu_t *t = timers[i].tmu; + t->TCR.UNIE = 0; + + do t->TCR.UNF = 0; + while(t->TCR.UNF); + + /* Standard timers: TCR is provided to the interrupt handler */ + timers[i].data->structure = &t->TCR; + } + + /* Clear the extra timers */ + for(int i = 3; i < timer_count(); i++) + { + tmu_extra_t *t = timers[i].tmu; + + /* This is *extremely important*: extra timers will generate + interrupts as long as TCNT = 0 *regardless of TSTR*! */ + t->TCOR = 0xffffffff; + t->TCNT = 0xffffffff; + + t->TCR.UNIE = 0; + t->TSTR = 0; + + do t->TCR.UNF = 0; + while(t->TCR.UNF); + } + + /* Install the extra timers. We need three extra timers for the + interrupt handlers to work, so install 3 on SH3, even if only one + actually exists */ + int limit = isSH3() ? 6 : 9; + + for(int i = 3; i < limit; i++) + { + void *handler = (i == 5) + ? inth_tmu_extra2 + : inth_tmu_extra_others; + void *h = gint_inthandler(timers[i].event, handler); + + timers[i].data = h + 20; + timers[i].data->structure = timers[i].tmu; + } + + /* Also install the helper handler */ + gint_inthandler(0xc60, inth_tmu_extra_help); + + /* Enable TMU0 at level 13, TMU1 at level 11, TMU2 at level 9 */ + gint_intlevel(0, 13); + gint_intlevel(1, 11); + gint_intlevel(2, 9); + + /* Enable the extra TMUs at level 7 */ + mpuSwitch({ + /* SH3-based */ + gint_intlevel(23, 7); + },{ + /* SH4-based */ + gint_intlevel(36, 7); + gint_intlevel(25, 7); + gint_intlevel(26, 7); + gint_intlevel(18, 7); + gint_intlevel(32, 7); + gint_intlevel(44, 7); + + /* Unmask the interrupts */ + INTC4.MSKCLR->IMR2 = 0x01; + INTC4.MSKCLR->IMR5 = 0x06; + INTC4.MSKCLR->IMR6 = 0x18; + INTC4.MSKCLR->IMR8 = 0x02; + }); +} + +//--- +// Context system for this driver +//--- + +typedef struct +{ + tmu_t std[3]; + tmu_extra_t extra[6]; + uint8_t TSTR; + +} PACKED(4) ctx_t; + +/* 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; + + for(int i = 0; i < 3; i++) + { + tmu_t *t = timers[i].tmu; + ctx->std[i].TCOR = t->TCOR; + ctx->std[i].TCNT = t->TCNT; + ctx->std[i].TCR.word = t->TCR.word; + } + + ctx->TSTR = *TSTR; + + for(int i = 0; i < timer_count() - 3; i++) + { + tmu_extra_t *t = timers[i + 3].tmu; + ctx->extra[i].TCOR = t->TCOR; + ctx->extra[i].TCNT = t->TCNT; + ctx->extra[i].TCR.byte = t->TCR.byte; + ctx->extra[i].TSTR = t->TSTR; + } +} + +static void ctx_restore(void *buf) +{ + ctx_t *ctx = buf; + + for(int i = 0; i < 3; i++) + { + tmu_t *t = timers[i].tmu; + t->TCNT = ctx->std[i].TCNT; + t->TCOR = ctx->std[i].TCOR; + t->TCR.word = ctx->std[i].TCR.word; + } + + *TSTR = ctx->TSTR; + + 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; + + t->TCNT = ctx->extra[i].TCNT; + t->TCOR = ctx->extra[i].TCOR; + t->TCR.byte = ctx->extra[i].TCR.byte; + t->TSTR = ctx->extra[i].TSTR; + } +} + +//--- +// Driver structure definition +//--- + +gint_driver_t drv_tmu = { + .name = "Timer Unit", + .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);