From cb4af4f7bbb85337b6229e4b260f00472abb6aad Mon Sep 17 00:00:00 2001 From: Yann MAGNIN Date: Mon, 13 Jun 2022 21:15:36 +0200 Subject: [PATCH] VxKernel 0.6.0-5 : Clock driver + TMU/ETMU driver (WIP) + timer API @add <> include/vhex/driver/mpu/sh/sh7305/cpg | add hardware description | add kernel-level API to fetch clocks information <> include/vhex/driver/mpu/sh/sh7305/tmu | add hardware description <> include/vhex/timer | add timer API | add timer callback definition | add timer types information <> src/drivers/mpu/sh/sh7305/cpg | add CPG driver declaration | add CPG driver kernel-API | prepare "real" driver installation hook <> src/drivers/mpu/sh/sh7305/cpu/sleep | add CPU sleep primitives <> src/drivers/mpu/sh/sh7305/intc/inth_callback | add special interrupt handler callback manager <> src/drivers/mpu/sh/sh7305/tmu | add TMU interrupt handlers | add ETMU interrupt handlers | add TMU/ETMU driver-level API code <> src/modules/timer | add timer API code (simply a binding to the TMU/ETMU driver) @update <> include/vhex/driver | add TIMER flags information <> include/vhex/driver/cpu | add sleep primitive <> include/vhex/driver/mpu/sh/sh7305/intc | return the address where the handle has been installed <> src/driver/screen/r61524 | perform the "sprite" VRAM merging before sending all pixel on screen @fix <> include/vhex/hardware | add workaround to allow this header to be included in ASM source file (.S) <> src/modules/display/text/dfont | fix line discipline with \n | fix string size when a bad character is detected --- include/vhex/driver.h | 2 +- include/vhex/driver/cpu.h | 15 + include/vhex/driver/mpu/sh/sh7305/cpg.h | 136 ++++++ include/vhex/driver/mpu/sh/sh7305/intc.h | 2 +- include/vhex/driver/mpu/sh/sh7305/tmu.h | 85 ++++ include/vhex/hardware.h | 6 + include/vhex/timer.h | 56 +++ include/vhex/timer/call.h | 20 + include/vhex/timer/interface.h | 18 + include/vhex/timer/types.h | 10 + src/drivers/mpu/sh/sh7305/cpg/cpg.c | 78 ++++ src/drivers/mpu/sh/sh7305/cpu/sleep.c | 22 + src/drivers/mpu/sh/sh7305/intc/install.c | 4 +- .../mpu/sh/sh7305/intc/inth_callback.S | 101 +++++ src/drivers/mpu/sh/sh7305/tmu/inth-etmu.s | 105 +++++ src/drivers/mpu/sh/sh7305/tmu/inth-tmu.s | 106 +++++ src/drivers/mpu/sh/sh7305/tmu/tmu.c | 402 ++++++++++++++++++ src/drivers/screen/R61524/r61524.c | 45 +- src/modules/display/text/dfont.c | 42 +- src/modules/timer/timer.c | 112 +++++ 20 files changed, 1325 insertions(+), 42 deletions(-) create mode 100644 include/vhex/driver/mpu/sh/sh7305/cpg.h create mode 100644 include/vhex/driver/mpu/sh/sh7305/tmu.h create mode 100644 include/vhex/timer.h create mode 100644 include/vhex/timer/call.h create mode 100644 include/vhex/timer/interface.h create mode 100644 include/vhex/timer/types.h create mode 100644 src/drivers/mpu/sh/sh7305/cpg/cpg.c create mode 100644 src/drivers/mpu/sh/sh7305/cpu/sleep.c create mode 100644 src/drivers/mpu/sh/sh7305/intc/inth_callback.S create mode 100644 src/drivers/mpu/sh/sh7305/tmu/inth-etmu.s create mode 100644 src/drivers/mpu/sh/sh7305/tmu/inth-tmu.s create mode 100644 src/drivers/mpu/sh/sh7305/tmu/tmu.c create mode 100644 src/modules/timer/timer.c diff --git a/include/vhex/driver.h b/include/vhex/driver.h index a6a318a..0c4d153 100644 --- a/include/vhex/driver.h +++ b/include/vhex/driver.h @@ -45,7 +45,7 @@ struct vhex_driver byte_union(flags, uint8_t :1; uint8_t :1; - uint8_t :1; + uint8_t TIMER :1; uint8_t DISPLAY :1; uint8_t :1; diff --git a/include/vhex/driver/cpu.h b/include/vhex/driver/cpu.h index abe1aa3..96c91c5 100644 --- a/include/vhex/driver/cpu.h +++ b/include/vhex/driver/cpu.h @@ -30,4 +30,19 @@ extern void cpu_atomic_start(void); There should be exactly one cpu_atomic_end() for each cpu_atomic_start(). */ extern void cpu_atomic_end(void); + +/* sleep(): Put the processor to sleep + + This function uses the [sleep] instruction to put the processor in sleep + mode. This allows reduced energy consumption while waiting for interrupts. + + In some certain situations, sleeping would block the process that is being + waited for (generally when on-chip memory is involved). When this occurs, + this function will not sleep at all and instead return instantly. + + The indented use for sleep() is *always* in some sort of loop, as there is + no guarantee about time or interrupts elapsed before this function + returns. */ +extern void cpu_sleep(void); + #endif /* __VHEX_DRIVERS_CPU__ */ diff --git a/include/vhex/driver/mpu/sh/sh7305/cpg.h b/include/vhex/driver/mpu/sh/sh7305/cpg.h new file mode 100644 index 0000000..7a22302 --- /dev/null +++ b/include/vhex/driver/mpu/sh/sh7305/cpg.h @@ -0,0 +1,136 @@ +#ifndef __VHEX_MPU_SH7305_CPG__ +# define __VHEX_MPU_SH7305_CPG__ + +#include +#include + + +//--- +// SH7305 Clock Pulse Generator. Refer to: +// "Renesas SH7724 User's Manual: Hardware" +// Section 17: "Clock Pulse Generator (CPG)" +//--- + +/* sh7305_cpg - Clock Pulse Generator registers + Fields marked with [*] don't have the meaning described in the SH7724 + documentation. */ +struct sh7305_cpg +{ + lword_union(FRQCR, + 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(0x4); + + lword_union(FSICLKCR, + uint32_t :16; + uint32_t DIVB :6; /* Division ratio for port B */ + uint32_t :1; + uint32_t CLKSTP :1; /* Clock Stop */ + uint32_t SRC :2; /* Clock source select */ + uint32_t DIVA :6; /* Division ratio for port A */ + ); + pad(0x04); + + lword_union(DDCLKCR, + uint32_t :23; + uint32_t CLKSTP :1; /* Clock Stop */ + uint32_t SRC :1; /* Clock source select */ + uint32_t :1; + uint32_t DIV :6; + ); + + lword_union(USBCLKCR, + uint32_t :23; + uint32_t CLKSTP :1; /* Clock Stop */ + uint32_t :8; + ); + pad(0x0c); + + 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; + ); + + lword_union(PLL2CR, + uint32_t KICK :1; /* Flush ??? modification */ + uint32_t :1; + uint32_t MUL :6; /* ??? multiplication ration */ + uint32_t :24; + ); + pad(0x10); + + lword_union(SPUCLKCR, + uint32_t :23; + uint32_t CLKSTP :1; /* Clock Stop */ + uint32_t SRC :1; /* Clock source select */ + uint32_t :1; + uint32_t DIV :6; /* Division ratio */ + ); + pad(0x4); + + lword_union(SSCGCR, + uint32_t SSEN :1; /* Spread Spectrum Enable */ + uint32_t :31; + ); + pad(0x8); + + lword_union(FLLFRQ, + uint32_t :16; + uint32_t SELXM :2; /* FLL output division */ + uint32_t :3; + uint32_t FLF :11; /* FLL Multiplication Ratio */ + ); + pad(0x0c); + + uint32_t LSTATS; + +} VPACKED(4); + +#define SH7305_CPG (*((volatile struct sh7305_cpg *)0xa4150000)) + +//--- +// kernel-level API +//--- + +/* cpg_clock_frequency + A dump of the Clock Pulse Generator's (CPG) configuration.*/ +struct cpg_clock_frequency { + int PLL; + int FLL; + int base; + + int Bphi_div; + int Iphi_div; + int Sphi_div; + int Pphi_div; + + int RTCCLK_f; + + int Bphi_f; + int Iphi_f; + int Sphi_f; + int Pphi_f; + +}; + +/* cpg_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. */ +void cpg_clock_freq(struct cpg_clock_frequency *freq); + + + +#endif /* __VHEX_MPU_SH7305_CPG__ */ diff --git a/include/vhex/driver/mpu/sh/sh7305/intc.h b/include/vhex/driver/mpu/sh/sh7305/intc.h index 230fa60..7478eaf 100644 --- a/include/vhex/driver/mpu/sh/sh7305/intc.h +++ b/include/vhex/driver/mpu/sh/sh7305/intc.h @@ -415,6 +415,6 @@ extern struct sh7305_intc SH7305_INTC; /* provide hardware-specific primitive */ /* sh7305_intc_install_inth() : install interrupt gate */ -extern void sh7305_intc_install_inth(int blockid, void *gate, size_t size); +extern void *sh7305_intc_install_inth(int blockid, void *gate, size_t size); #endif /* __VHEX_ARCH_SH7305_INTC__ */ diff --git a/include/vhex/driver/mpu/sh/sh7305/tmu.h b/include/vhex/driver/mpu/sh/sh7305/tmu.h new file mode 100644 index 0000000..bf4ffa6 --- /dev/null +++ b/include/vhex/driver/mpu/sh/sh7305/tmu.h @@ -0,0 +1,85 @@ +#ifndef __VHEX_MPU_SH7305_TMU__ +# define __VHEX_MPU_SH7305_TMU__ + +#include +#include + +/* Clock input + + Standard TMU can count at different speeds. A fast speed offers more + precision but a slower speed offers longer delays. vhex automatically + selects suitable speeds by default. + + If you want something very particular, you can add (with + or |) a prescaler + value to a chosen ID in timer_configure() to request that specific value. + The default prescaler if the ID is fixed is TIMER_Pphi_4. */ +enum { + TIMER_Pphi_4 = 0x00, + TIMER_Pphi_16 = 0x10, + TIMER_Pphi_64 = 0x20, + TIMER_Pphi_256 = 0x30, +}; + +/* 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) */ + ); + +} VPACKED(4) tmu_t; + +/* etmu_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 */ + ); + pad(19); + +} VPACKED(4) etmu_t; + +//--- +// SH7305 Timer Unit. Refer to: +// "Renesas SH7724 User's Manual: Hardware" +// Section 20: "Timer Unit (TMU)" +//--- + +typedef volatile struct +{ + uint8_t TSTR; /* Timer Start Register */ + pad(3); + + tmu_t TMU[3]; + +} VPACKED(4) sh7305_tmu_t; + +#define SH7305_TMU (*((sh7305_tmu_t *)0xa4490004)) + +//--- +// SH7305 Extra Timer Unit. No official documentation exists. +//--- + +typedef volatile etmu_t sh7305_etmu_t[6]; +#define SH7305_ETMU (*(sh7305_etmu_t *)0xa44d0030) + + + + +#endif /* __VHEX_MPU_SH7305_TMU__ */ diff --git a/include/vhex/hardware.h b/include/vhex/hardware.h index 10032e4..e669e52 100644 --- a/include/vhex/hardware.h +++ b/include/vhex/hardware.h @@ -1,6 +1,10 @@ #ifndef __VHEX_HARDWARE_H__ # define __VHEX_HARDWARE_H__ + +/* For compatibility with ASM, include the following bits only in C code */ +#ifndef ASM_SOURCE + #include /* hw_detect(): Basic hardware detection */ @@ -12,6 +16,8 @@ extern void hw_detect(void); #define HW_KEYS 16 extern uintptr_t vhex[HW_KEYS]; +#endif /* ASM_SOURCE */ + /* ** Key list */ diff --git a/include/vhex/timer.h b/include/vhex/timer.h new file mode 100644 index 0000000..148bfa9 --- /dev/null +++ b/include/vhex/timer.h @@ -0,0 +1,56 @@ +#ifndef __VHEX_TIMER__ +#define __VHEX_TIMER__ + +#include +#include + +/* TIMER_DELAY_MS() : convert ms into us */ +#define TIMER_DELAY_MS(ms) (ms * 1000) +#define TIMER_DELAY_SEC(sec) (sec * 1000000) + +/* returned status from a callback */ +enum { + TIMER_STOP, + TIMER_CONTINUE +}; + +/* timer_configure(): Reserve and configure a timer + + This function finds and configures a timer (without starting it). On + success, it returns the ID of the configured timer, which is used in all + other timer functions. If no timer matching the requested settings is + available, this function returns -1. + + When started, the configured timer will run for the requested delay and call + the supplied callback function at the end of this delay. The callback + function can then decide whether to leave the timer running (and be called + again after the same delay) or stop the timer. */ +extern tid_t timer_configure(uint64_t delay_us, timer_call_t callback); + +/* timer_start(): Start a configured timer */ +extern int timer_start(tid_t timer); + +/* timer_pause(): Pause a timer without freeing it */ +extern int timer_pause(tid_t timer); + +/* timer_stop(): Stop and free a timer */ +extern int timer_stop(tid_t timer); + +/* timer_wait(): Wait for a timer to stop + Waits until the timer pauses or stops. If the timer is not running, returns + immediately. Even after timer_wait(), the timer may not be available since + it may have only paused. If the timer never stops, you're in trouble. */ +extern int timer_wait(tid_t timer); + +/* timer_spinwait(): Start a timer and actively wait + Waits until the timer has finished its countdown, without sleeping. This is + useful for delays in driver code that is run when interrupts are disabled. + Interrupt are disabled before starting the timer and waiting, so the callback + is never called. */ +extern int timer_spinwait(tid_t timer); + +/* timer_reload(): Change a timer's delay constant for next interrupts */ +extern int timer_reload(tid_t timer, uint64_t delay); + + +#endif /* __VHEX_TIMER__ */ diff --git a/include/vhex/timer/call.h b/include/vhex/timer/call.h new file mode 100644 index 0000000..ebcc960 --- /dev/null +++ b/include/vhex/timer/call.h @@ -0,0 +1,20 @@ +#ifndef __VHEX_TIMER_CALL__ +# define __VHEX_TIMER_CALL__ + +#include + +/* timer_call_t: Indirect call with up to 4 register arguments */ +struct timer_call { + void *function; + uint32_t args[4]; +}; +typedef struct timer_call timer_call_t; + +/* TIMER_CALL(): Build an callback from function and arguments */ +#define TIMER_CALL(fct, ...) \ + (timer_call_t){ \ + .function = (void*)fct, \ + .args = { __VA_ARGS__ } \ + } + +#endif /* __VHEX_TIMER_CALL__ */ diff --git a/include/vhex/timer/interface.h b/include/vhex/timer/interface.h new file mode 100644 index 0000000..c72d9e9 --- /dev/null +++ b/include/vhex/timer/interface.h @@ -0,0 +1,18 @@ +#ifndef __VHEX_TIMER_INTERFACE__ +# define __VHEX_TIMER_INTERFACE__ + +#include +#include + +/* timer_drv_interface - driver interface */ +struct timer_drv_interface { + tid_t (*timer_configure)(uint64_t delay_us, timer_call_t callback); + int (*timer_start)(tid_t timer); + int (*timer_pause)(tid_t timer); + int (*timer_stop)(tid_t timer); + int (*timer_wait)(tid_t timer); + int (*timer_spinwait)(tid_t timer); + int (*timer_reload)(tid_t timer, uint64_t delay); +}; + +#endif /* __VHEX_TIMER_INTERFACE__ */ diff --git a/include/vhex/timer/types.h b/include/vhex/timer/types.h new file mode 100644 index 0000000..fd62636 --- /dev/null +++ b/include/vhex/timer/types.h @@ -0,0 +1,10 @@ +#ifndef __VHEX_TIMER_TYPES__ +# define __VHEX_TIMER_TYPES__ + +/* force uint64_t */ +#include + +/* timer ID */ +typedef int tid_t; + +#endif /* __VHEX_TIMER_TYPES__ */ diff --git a/src/drivers/mpu/sh/sh7305/cpg/cpg.c b/src/drivers/mpu/sh/sh7305/cpg/cpg.c new file mode 100644 index 0000000..5d328a2 --- /dev/null +++ b/src/drivers/mpu/sh/sh7305/cpg/cpg.c @@ -0,0 +1,78 @@ +#include +#include + +#define CPG SH7305_CPG + +void cpg_clock_freq(struct cpg_clock_frequency *freq) +{ + /* The meaning of the PLL setting on SH7305 differs from the + documentation of SH7224; the value must not be doubled. */ + int pll = CPG.FRQCR.STC + 1; + freq->PLL = pll; + + /* The FLL ratio is the value of the setting, halved if SELXM=1 */ + 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 on + the SH7305 it is 1 / (2^setting + 1). */ + + int divb = CPG.FRQCR.BFC; + int divi = CPG.FRQCR.IFC; + int divs = CPG.FRQCR.SFC; + int divp = CPG.FRQCR.P1FC; + + freq->Bphi_div = 1 << (divb + 1); + freq->Iphi_div = 1 << (divi + 1); + freq->Sphi_div = 1 << (divs + 1); + freq->Pphi_div = 1 << (divp + 1); + + /* Deduce the input frequency of divider 1 */ + freq->base = 32768; + if(CPG.PLLCR.FLLE) freq->base *= fll; + if(CPG.PLLCR.PLLE) freq->base *= pll; + + /* And the frequency of all other input clocks */ + freq->RTCCLK_f = 32768; + freq->Bphi_f = freq->base >> (divb + 1); + freq->Iphi_f = freq->base >> (divi + 1); + freq->Sphi_f = freq->base >> (divs + 1); + freq->Pphi_f = freq->base >> (divp + 1); +} + + +//--- +// Define driver information +//--- + +struct cpg_ctx { + uint32_t SSCGCR; +}; + +/* __cpg_configure() : configure the CPG */ +static void __cpg_configure(struct cpg_ctx *state) +{ + (void)state; +} + +/* __cpg_hsave() : save hardware information */ +static void __cpg_hsave(struct cpg_ctx *state) +{ + (void)state; +} + +/* __cpg_hrestore() : restore hardware information */ +static void __cpg_hrestore(struct cpg_ctx *state) +{ + (void)state; +} + +struct vhex_driver drv_cpg = { + .name = "CPG", + .hsave = (void*)&__cpg_hsave, + .hrestore = (void*)&__cpg_hrestore, + .configure = (void*)&__cpg_configure, + .state_size = sizeof(struct cpg_ctx) +}; +VHEX_DECLARE_DRIVER(03, drv_cpu); diff --git a/src/drivers/mpu/sh/sh7305/cpu/sleep.c b/src/drivers/mpu/sh/sh7305/cpu/sleep.c new file mode 100644 index 0000000..efed0c9 --- /dev/null +++ b/src/drivers/mpu/sh/sh7305/cpu/sleep.c @@ -0,0 +1,22 @@ +#include + +volatile int cpu_sleep_block_counter = 0; + +void cpu_sleep(void) +{ + if(cpu_sleep_block_counter <= 0) __asm__("sleep"); +} + +void cpu_sleep_block(void) +{ + cpu_atomic_start(); + cpu_sleep_block_counter++; + cpu_atomic_end(); +} + +void cpu_sleep_unblock(void) +{ + cpu_atomic_start(); + cpu_sleep_block_counter--; + cpu_atomic_end(); +} diff --git a/src/drivers/mpu/sh/sh7305/intc/install.c b/src/drivers/mpu/sh/sh7305/intc/install.c index 0bd5b6e..856b7ec 100644 --- a/src/drivers/mpu/sh/sh7305/intc/install.c +++ b/src/drivers/mpu/sh/sh7305/intc/install.c @@ -3,11 +3,11 @@ #include /* sh7305_intc_install_inth() : install interrupt gate */ -void sh7305_intc_install_inth(int event_code, void *gate, size_t size) +void *sh7305_intc_install_inth(int event_code, void *gate, size_t size) { extern uintptr_t vhex_vbr; - memcpy( + return memcpy( (void*)((uintptr_t)&vhex_vbr + 0x600 + 0x40 + (event_code - 0x400)), gate, size diff --git a/src/drivers/mpu/sh/sh7305/intc/inth_callback.S b/src/drivers/mpu/sh/sh7305/intc/inth_callback.S new file mode 100644 index 0000000..bc47e00 --- /dev/null +++ b/src/drivers/mpu/sh/sh7305/intc/inth_callback.S @@ -0,0 +1,101 @@ +#define ASM_SOURCE +#include +#include +.text + +.global _sh7305_inth_callback + +/* CALLBACK HELPER + This function implements the callback with context saving. It is a general + function and does not need to reside in VBR space which is block-structured. + This function saves registers r0_bank...r7_bank, enables interrupts, + switches back to user bank and executes the callback. It does not save other + registers (pr/mach/macl/gbr) which are managed by the handler entry. */ + +/* sh7305_inth_callback: Indirect call from kernel space to userland + @r4 Address of callback function + -> Returns the return value of the callback (int). */ +_sh7305_inth_callback: + stc.l r0_bank, @-r15 + stc.l r1_bank, @-r15 + stc.l r2_bank, @-r15 + stc.l r3_bank, @-r15 + stc.l r4_bank, @-r15 + stc.l r5_bank, @-r15 + stc.l r6_bank, @-r15 + stc.l r7_bank, @-r15 + stc.l spc, @-r15 + stc.l ssr, @-r15 + stc.l sr, @-r15 + + /* Save some values to user bank; once we enable interrupts, the kernel + bank might be overwritten at any moment. */ + ldc r4, r0_bank + + /* Enable interrupts and go back to user bank. On SH4, SR.IMASK is set + to the level of the current interrupt, which makes sure we can only + be re-interrupted by something with a higher priority. */ + stc sr, r1 + mov.l .SR_clear_RB_BL, r0 + and r0, r1 + + /* On the fx-CG emulator, it is outright ignored. In these situations, + set IMASK to 15 to block interrupts while allowing TLB misses to be + handled. */ + mov.l .vhex, r2 + mov.l @(4*HWDEVICE,r2), r0 + cmp/eq #HWDEVICE_FXCG_MANAGER, r0 + bt .set_imask + + bra .load_sr + nop + +.set_imask: + mov.l .SR_set_IMASK, r0 + or r0, r1 + +.load_sr: + ldc r1, sr + + /* We are now in the user bank with r0 set. Perform the call. We want + to forward the return value to kernel bank, but this bank can be + changed at any moment since interrupts are enabled. */ + sts.l pr, @-r15 + mov.l @(4, r0), r4 + mov.l @(8, r0), r5 + mov.l @(12, r0), r6 + mov.l @(16, r0), r7 + mov.l @r0, r0 + jsr @r0 + nop + lds.l @r15+, pr + + /* Restore the previous status register and the registers of the + interrupted procedure. Restoring sr gets us back to system bank with + interrupts disabled. */ + ldc.l @r15+, sr + + /* We can now pull the return value since interrupts are disabled */ + stc r0_bank, r0 + + ldc.l @r15+, ssr + ldc.l @r15+, spc + ldc.l @r15+, r7_bank + ldc.l @r15+, r6_bank + ldc.l @r15+, r5_bank + ldc.l @r15+, r4_bank + ldc.l @r15+, r3_bank + ldc.l @r15+, r2_bank + ldc.l @r15+, r1_bank + rts + ldc.l @r15+, r0_bank + +.align 4 +.SR_clear_RB_BL: + .long ~((1 << 29) | (1 << 28)) +.SR_set_IMASK: + .long (0xf << 4) +.vhex: + .long _vhex + + diff --git a/src/drivers/mpu/sh/sh7305/tmu/inth-etmu.s b/src/drivers/mpu/sh/sh7305/tmu/inth-etmu.s new file mode 100644 index 0000000..c445fe5 --- /dev/null +++ b/src/drivers/mpu/sh/sh7305/tmu/inth-etmu.s @@ -0,0 +1,105 @@ +/* +** vhex:tmu:inth-etmu - Interrupt handlers for the RTC-bound timers +** +** This handler uses 3 consecutive blocks like the TMU handler. However this +** time 2 empty blocks after ETMU4 (0xd20, 0xd40) are used because blocks for +** ETMU are not consecutive in memory. +** +** It would be possible to communicate between any interrupt handlers in non- +** consecutive gates. A simple way is to store at runtime a pointer to the +** desired object in one handler. But that costs a lot of space. If the +** relative position of interrupt handlers is known, the best option left is +** the unnatural @(disp,pc) addressing mode, and it doesn't even work with the +** SH3's compact VBR scheme. +*/ + +/* Gates for the extra timers (informally called ETMU) */ +.global _sh7305_inth_etmu4 /* 96 bytes */ +.global _sh7305_inth_etmux /* 32 bytes */ + +.section .vhex.blocks, "ax" +.align 4 + +/* 3-block handler installed at the ETMU4 gate. */ +_sh7305_inth_etmu4: + mova .storage_etmu4, r0 + mov #7, r2 + +.shared: + mov.l r8, @-r15 + sts.l pr, @-r15 + + /* Prepare an indirect call to timer_stop() */ + add #-20, r15 + mov.l r2, @(4, r15) + + /* Clear interrupt flag in TCR using slow write */ + mov r0, r1 + mov.l @(4, r1), r3 +1: mov.b @r3, r0 + tst #0x02, r0 + and #0xfd, r0 + bf/s 1b + mov.b r0, @r3 + + /* Invoke callback */ + mov.l .sh7305_inth_callback, r8 + mov.l @r8, r8 + jsr @r8 + mov.l @r1, r4 + tst r0, r0 + bt 2f + + /* If return value is non-zero, stop the timer with another callback */ + mov.l .timer_stop, r0 + mov.l r0, @r15 + jsr @r8 + mov r15, r4 + +2: add #20, r15 + lds.l @r15+, pr + rts + mov.l @r15+, r8 + + .zero 26 + +.timer_stop: + .long _sh7305_tmu_stop +.sh7305_inth_callback: + .long _sh7305_inth_callback +.storage_etmu4: + .long _sh7305_tmu_callbacks + 140 + .long 0xa44d00bc /* RTCR4 */ + + +!--- +! Generic gate for all other ETMU handlers, falling back to ETMU4. +!--- +! Note +! <> all information needed in the storage_etmux is set on-the-fly when the +! tmu handler is installed (see ) +! <> the VBR computation is used to determine the address of the ETMU4 "real" +! address during runtime +! <> the timer ID is set during the installation of the handler +_sh7305_inth_etmux: + /* Dynamically compute the target of the jump */ + stc vbr, r3 + mov.l 1f, r2 + add r2, r3 + + mova .storage_etmux, r0 + mov.w .id_etmux, r2 + jmp @r3 + nop + nop + nop + +.id_etmux: + .word 0 /* Timer ID */ + + /* Offset from VBR where ETMU4 is located; set during configure */ +1: .long (.shared - _sh7305_inth_etmu4) + +.storage_etmux: + .long _sh7305_tmu_callbacks + .long 0 /* TCR address */ diff --git a/src/drivers/mpu/sh/sh7305/tmu/inth-tmu.s b/src/drivers/mpu/sh/sh7305/tmu/inth-tmu.s new file mode 100644 index 0000000..ab8a457 --- /dev/null +++ b/src/drivers/mpu/sh/sh7305/tmu/inth-tmu.s @@ -0,0 +1,106 @@ +/* +** vhex:tmu:inth-tmu - Interrupt handlers for the timer units +** +** This handler consists of 3 consecutive gates that operate as a block. It +** clears the interrupt flags, invokes a TIMER_CALL() in userspace, and stops +** the timer if the callback returns non-zero. +** +** It is important to notice that the code of the gates is continuous in this +** file and thus must be continuous in memory, as the assembler will use +** relative addressing methods. This "block operations" is only possible for +** handlers that are mapped to consecutive event codes. +*/ + +/* Gates for the standard Timer Unit (TMU) */ +.global _sh7305_inth_tmu /* 96 bytes */ + +.section .vhex.blocks, "ax" +.align 4 + +/* TMU0 entry and interrupt flag clearing. */ +_sh7305_inth_tmu: + mov #0, r5 + mov #0, r6 + mov #0, r7 + +! Note +! <> first part of the shared TMU interrupt control +! <> r5 = timerID +! <> r6 = offset for the TMUx TCR register +! <> r7 = offset for the TMUx callback table (each slot = 20 bytes) +.shared1: + mov.l .TCR0, r1 + add r6, r1 + + /* Save the timer ID on the stack */ + mov.l r8, @-r15 + sts.l pr, @-r15 + mov.l r5, @-r15 + + /* Clear the interrupt flag. Because r5 contains 0, 1 or 2 the 16 top + bits are 0 so we can compare without extending */ +1: mov.w @r1, r5 + extu.b r5, r3 + cmp/eq r5, r3 + bf/s 1b + mov.w r3, @r1 + + /* Prepare to run the callback */ + mov.l .sh7305_inth_callback, r8 + bra .shared2 + mov.l @r8, r8 + +/* TMU1 entry, callback and timer stop logic. */ +_inth_tmu_1: + mov #1, r5 + mov #12, r6 + bra .shared1 + mov #20, r7 + +! Note +! <> second part of the shared TMUx interrupt control +! <> r7 = offset for the TMUx callback table +! <> r8 = the routine address which will run the TIMER_CALL() callback +.shared2: + /* Invoke callback */ + mov.l .tmu_callbacks, r4 + jsr @r8 + add r7, r4 + tst r0, r0 + mov.l .timer_stop, r2 + bt/s .shared3 + mov.l r2, @-r15 + + /* Stop the timer if the return value is not zero. We use the top of + the stack as a vhex_call_t object; only the function and first + argument matter, timer_stop() will ignore the rest. */ + jsr @r8 + mov r15, r4 + bra .shared3 + nop + nop + +/* TMU2 entry, shared exit and storage. */ +_inth_tmu_2: + mov #2, r5 + mov #24, r6 + bra .shared1 + mov #40, r7 + +! Note +! <> last part of the shared TMUx interrupt handler +! <> return to the "general" interrupt handler +.shared3: + add #8, r15 + lds.l @r15+, pr + rts + mov.l @r15+, r8 + +.timer_stop: + .long _sh7305_tmu_stop +.sh7305_inth_callback: + .long _sh7305_inth_callback +.TCR0: + .long 0xa4490010 +.tmu_callbacks: + .long _sh7305_tmu_callbacks diff --git a/src/drivers/mpu/sh/sh7305/tmu/tmu.c b/src/drivers/mpu/sh/sh7305/tmu/tmu.c new file mode 100644 index 0000000..245f157 --- /dev/null +++ b/src/drivers/mpu/sh/sh7305/tmu/tmu.c @@ -0,0 +1,402 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Callbacks for all timers */ +timer_call_t sh7305_tmu_callbacks[9]; + +/* Arrays of standard and extra timers */ +static tmu_t *TMU = SH7305_TMU.TMU; +static etmu_t *ETMU = SH7305_ETMU; + +/* TSTR register for standard timers */ +static volatile uint8_t *TSTR = &SH7305_TMU.TSTR; + +/* Shortcut to set registers that are slow to update */ +#define set(lval, rval) do(lval = rval); while(rval != lval) + +/* define the private TimerUnit context structure */ +struct tmu_ctx { + struct tmu_state_stored_timer { + uint32_t TCOR; + uint32_t TCNT; + uint16_t TCR; + uint16_t TSTR; + } timer[9]; + uint8_t TSTR; +}; + +//--- +// Internal helpers +//--- + +/* conf(): Configure a fixed timer */ +static int conf(tid_t id, uint32_t delay, int clock, timer_call_t call) +{ + if(id < 3) { + /* Refuse to setup timers that are already in use */ + tmu_t *T = &TMU[id]; + if(T->TCR.UNIE || *TSTR & (1 << id)) + return (-1); + + /* Configure the counter, clear interrupt flag*/ + T->TCOR = delay; + T->TCNT = delay; + T->TCR.TPSC = clock; + set(T->TCR.UNF, 0); + + /* Enable interrupt and count on rising edge (SH7705) */ + T->TCR.UNIE = 1; + T->TCR.CKEG = 0; + } else { + etmu_t *T = &ETMU[id-3]; + if(T->TCR.UNIE) return (-1); + + /* No clock input and clock edge here */ + set(T->TCR.UNF, 0); + set(T->TCOR, delay); + set(T->TCNT, delay); + T->TCR.UNIE = 1; + } + + sh7305_tmu_callbacks[id] = call; + return (id); +} + +/* sh7305_tmu_control() - start or stop a timer + @id Timer ID to configure + @state 0 to start the timer, 1 to stop it (nothing else!) */ +static int sh7305_tmu_control(tid_t id, int state) +{ + if (id < 0 || id > 9) + return (-1); + + if(id < 3) { + *TSTR = (*TSTR | (1 << id)) ^ (state << id); + } else { + ETMU[id-3].TSTR = state ^ 1; + } + return (0); +} + +/* available(): Check if a timer is available (UNIE cleared, not running) */ +static int available(int id) +{ + if (id < 0 || id > 9) + return (-1); + + if(id < 3) { + tmu_t *T = &TMU[id]; + return !T->TCR.UNIE && !(*TSTR & (1 << id)); + } else { + etmu_t *T = &ETMU[id-3]; + return !T->TCR.UNIE && !T->TSTR; + } +} + +/* sh7305_tmu_delay() - compute a delay constant from a duration in seconds */ +static uint32_t sh7305_tmu_delay(int id, uint64_t delay_us, int clock) +{ + struct cpg_clock_frequency cpg_freq; + uint64_t freq; + + if(id < 3) { + cpg_clock_freq(&cpg_freq); + freq = cpg_freq.Pphi_f; + if(clock == TIMER_Pphi_4) freq >>= 2; + if(clock == TIMER_Pphi_16) freq >>= 4; + if(clock == TIMER_Pphi_64) freq >>= 6; + if(clock == TIMER_Pphi_256) freq >>= 8; + } else { + /* ETMU all run on TCLK at 32768 Hz */ + freq = 32768; + } + + uint64_t product = freq * delay_us; + return product / 1000000; +} + +/* stop_callback(): Empty callback that stops the timer */ +static int stop_callback(void) +{ + return TIMER_STOP; +} + +//-- +// Public timer-API +//--- + +int sh7305_tmu_configure(uint64_t delay, timer_call_t call) +{ + int clock = 0; + + /* Default behavior for the callback */ + if(!call.function) call = TIMER_CALL(stop_callback); + + /* Find a matching timer, starting from the slowest timers with the + smallest interrupt priorities all the way up to TMU0 */ + for(int id = 8; id >= 0; id--) + { + if(available(id) != 0) + continue; + + /* If ID is a TMU, choose a timer prescaler. Assuming the worst + running Pphi of ~48 MHz, select the finest resolution that + allows the requested delay to be represented. */ + if(id < 3) { + uint64_t sec = 1000000; + + /* Pphi/4 until 350 seconds */ + if(delay <= 350 * sec) clock = TIMER_Pphi_4; + /* Pphi/16 until 1430 seconds */ + else if(delay <= 1430 * sec) clock = TIMER_Pphi_16; + /* Pphi/64 until 5720 seconds */ + else if(delay <= 5720 * sec) clock = TIMER_Pphi_64; + /* Pphi/256 otherwise */ + else clock = TIMER_Pphi_256; + } + + /* Find the delay constant for that timer and clock */ + delay = sh7305_tmu_delay(id, delay, clock); + + return conf(id, delay, clock, call); + } + + return (-1); +} + +/* sh7305_tmu_start() - start a configured timer */ +int sh7305_tmu_start(tid_t id) +{ + return sh7305_tmu_control(id, 0); +} + +/* sh7305_tmu_reload() - change a timer's delay constant for next interrupts */ +int sh7305_tmu_reload(tid_t id, uint64_t delay) +{ + //FIXME + if (id < 0 || id > 9) + return (-1); + + if(id < 3) { + TMU[id].TCOR = delay; + } else { + ETMU[id-3].TCOR = delay; + } + return (0); +} + +/* sh7305_tmu_pause() - stop a running timer */ +int sh7305_tmu_pause(tid_t id) +{ + return sh7305_tmu_control(id, 1); +} + +/* sh7305_tmu_stop() - stop and free a timer */ +int sh7305_tmu_stop(tid_t id) +{ + if (id < 0 || id > 9) + return (-1); + + /* Stop the timer and disable UNIE to indicate that it's free */ + sh7305_tmu_pause(id); + + if(id < 3) { + TMU[id].TCR.UNIE = 0; + TMU[id].TCR.UNF = 0; + TMU[id].TCOR = 0xffffffff; + TMU[id].TCNT = 0xffffffff; + } else { + /* Extra timers generate interrupts when TCNT=0 even if TSTR=0. + We always keep TCOR/TCNT to non-zero values when idle. */ + etmu_t *T = &ETMU[id-3]; + T->TCR.UNIE = 0; + set(T->TCOR, 0xffffffff); + set(T->TCNT, 0xffffffff); + set(T->TCR.UNF, 0); + } + return (0); +} + +/* sh7305_tmu_wait(): Wait for a timer to stop */ +int sh7305_tmu_wait(tid_t id) +{ + if (id < 0 || id > 9) + return (-1); + + if(id < 3) { + tmu_t *T = &TMU[id]; + /* Sleep only if an interrupt will be there to wake us up */ + while(*TSTR & (1 << id)) if(T->TCR.UNIE) cpu_sleep(); + } else { + etmu_t *T = &ETMU[id-3]; + while(T->TSTR) if(T->TCR.UNIE) cpu_sleep(); + } +} + +/* sh7305_tmu_spinwait(): Start a timer and actively wait for UNF */ +int sh7305_tmu_spinwait(tid_t id) +{ + if (id < 0 || id > 9) + return (-1); + + if(id < 3) { + tmu_t *T = &TMU[id]; + T->TCR.UNIE = 0; + sh7305_tmu_start(id); + while(!T->TCR.UNF) {} + } else { + etmu_t *T = &ETMU[id-3]; + set(T->TCR.UNIE, 0); + sh7305_tmu_start(id); + while(!T->TCR.UNF) {} + } + return (0); +} + +//--- +// hardware primitives +//--- + +/* __tmu_configure() : configure the SH7305 TMU/ETMU module */ +static void __tmu_configure(struct tmu_ctx *s) +{ + /* prepare timers comtext */ + s->TSTR = 0; + for(int i = 0; i < 9; i++) + { + s->timer[i].TCOR = 0xffffffff; + s->timer[i].TCNT = 0xffffffff; + s->timer[i].TCR = 0; + s->timer[i].TSTR = 0; + } + + /* install the TMUs interupt handler */ + extern void *sh7305_inth_tmu; + sh7305_intc_install_inth(0x400, &sh7305_inth_tmu, 96); + + /* install all ETMUx interrupt handler + + The interruptions handling for the ETMU is more complexe that the + "classical" timer (TMU) because, instead of the TMU that each + interrupt vector is grouped (TMU0:0x400, TMU1:0x420, TMU2:0x440), each + ETMUx interrupt vector is not followed so we should relocalize and + update each interrut gate. + + We know that the ETMU4 (which have the interrupt vector 0xd00) is + followed by excatly 3 free gates (where not interrupt exist). We will + use this place to store all the ETMUx interrupt logic. */ + extern void *sh7305_inth_etmu4; + extern void *sh7305_inth_etmux; + void *h4; + void *h; + + uint16_t etmu_event[6] = { 0x9e0, 0xc20, 0xc40, 0x900, 0xd00, 0xfa0 }; + h4 = sh7305_intc_install_inth(etmu_event[4], sh7305_inth_etmu4, 96); + + for (int i = 0; i < 6; ++i) { + /* skip the core ETMUx core handler */ + if (i == 4) + continue; + + /* install the default interrupt handler */ + h = sh7305_intc_install_inth(etmu_event[i], sh7305_inth_etmux, 32); + + /* Distance from VBR handler to ETMU4, used to jump */ + *(uint32_t *)(h + 20) += (uintptr_t)h4 - (uintptr_t)cpu_get_vbr(); + /* Timer ID, used for sh7305_tmu_stop() after the callback */ + *(uint16_t *)(h + 18) = i; + /* Pointer to the callback */ + *(void **)(h + 24) += i; + /* TCR address to acknowledge the interrupt */ + *(void volatile **)(h + 28) = &ETMU[i].TCR; + } +} + +/* __tmu_hsave() : save hardware information */ +static void __tmu_hsave(struct tmu_ctx *s) +{ + struct tmu_state_stored_timer *c; + + s->TSTR = *TSTR; + + /* classic timer */ + for(int i = 0; i < 3; i++) { + s->timer[i].TCOR = TMU[i].TCOR; + s->timer[i].TCNT = TMU[i].TCNT; + s->timer[i].TCR = TMU[i].TCR.word; + } + + /* extra timer */ + c = &s->timer[3]; + for(int i = 0; i < 6; i++) { + /* Don't snapshot an interrupt state, because the timer state + is sometimes garbage protected by a masked interrupt. */ + c[i].TCOR = ETMU[i].TCOR ? ETMU[i].TCOR : 0xffffffff; + c[i].TCNT = ETMU[i].TCNT ? ETMU[i].TCNT : c->TCOR; + c[i].TCR = ETMU[i].TCR.byte & 0xd; + c[i].TSTR = ETMU[i].TSTR; + } +} + +/* __tmu_hrestore() : restore hadware information */ +static void __tmu_hrestore(struct tmu_ctx *s) +{ + struct tmu_state_stored_timer const *c; + + *TSTR = 0; + + /* classic timer */ + for(int i = 0; i < 3; i++) + { + TMU[i].TCOR = s->timer[i].TCOR; + TMU[i].TCNT = s->timer[i].TCNT; + TMU[i].TCR.word = s->timer[i].TCR; + } + + /* extra timer */ + c = &s->timer[3]; + for(int i = 0; i < 6; i++) + { + set(ETMU[i].TCOR, c[i].TCOR); + set(ETMU[i].TCNT, c[i].TCNT); + set(ETMU[i].TCR.byte, c[i].TCR); + set(ETMU[i].TSTR, c[i].TSTR); + } + + *TSTR = s->TSTR; +} + + + +/* declare the TMU driver */ + +#if 0 +struct vhex_driver drv_tmu = { + .name = "TMU", + .hsave = (void*)&__tmu_hsave, + .hrestore = (void*)&__tmu_hrestore, + .configure = (void*)&__tmu_configure, + .state_size = sizeof(struct tmu_ctx), + .flags = { + .TIMER = 1, + .SHARED = 0, + .UNUSED = 0 + }, + .module_data = &(struct timer_drv_interface){ + .timer_configure = &sh7305_tmu_configure, + .timer_start = &sh7305_tmu_start, + .timer_pause = &sh7305_tmu_pause, + .timer_stop = &sh7305_tmu_stop, + .timer_wait = &sh7305_tmu_wait, + .timer_spinwait = &sh7305_tmu_spinwait, + .timer_reload = &sh7305_tmu_reload + } +}; +VHEX_DECLARE_DRIVER(03, drv_tmu); +#endif diff --git a/src/drivers/screen/R61524/r61524.c b/src/drivers/screen/R61524/r61524.c index e57cca3..0103631 100644 --- a/src/drivers/screen/R61524/r61524.c +++ b/src/drivers/screen/R61524/r61524.c @@ -65,13 +65,27 @@ int r61524_frame_frag_next(struct dshader_surface *surface) surface->y -= 10; return (-1); } + //TODO: wipe only the YRAM + //TODO: move this in dclear() ? r61524_clear_surface(surface); return (0); } int r61524_frame_frag_send(struct dshader_surface *surface) { - //static int counter = 0; + uint16_t pixel; + uint16_t * restrict xram = surface->draw; + uint16_t * restrict yram = surface->frag; + int size = (surface->y == 220) ? 1584 : 3960; + + /* merge the two fragment */ + for (int i = 0; i < size; ++i) { + pixel = xram[i]; + if ((pixel & 0x0001) == 0) { + yram[i] = (pixel & 0xffc0) | ((pixel & 0x003e) >> 1); + } + } + //TODO: assembly //TODO: check cache behaviour: @@ -79,17 +93,8 @@ int r61524_frame_frag_send(struct dshader_surface *surface) // > xram and yram // > yram and xram // > restrict / non-restrict - uint16_t pixel; - uint16_t * restrict xram = surface->draw; - uint16_t * restrict yram = surface->frag; - int size = (surface->y == 220) ? 1584 : 3960; for (int i = 0; i < size; ++i) { - pixel = xram[i]; - if (pixel & 0x0001) { - r61524_write(yram[i]); - continue; - } - r61524_write((pixel & 0xffc0) | ((pixel & 0x003e) >> 1)); + r61524_write(yram[i]); } return (0); } @@ -147,15 +152,6 @@ static void __r61524_hrestore(struct r61524_ctx const *s) } -static struct dstack_drv_interface drv_r61524_dstack = { - .frame_start = &r61524_frame_start, - .frame_frag_next = &r61524_frame_frag_next, - .frame_frag_send = &r61524_frame_frag_send, - .frame_end = &r61524_frame_end, - .display_width = 396, - .display_height = 224 -}; - struct vhex_driver drv_r61524 = { .name = "R61524", .hsave = (void*)&__r61524_hsave, @@ -167,6 +163,13 @@ struct vhex_driver drv_r61524 = { .SHARED = 0, .UNUSED = 0, }, - .module_data = &drv_r61524_dstack + .module_data = &(struct dstack_drv_interface){ + .frame_start = &r61524_frame_start, + .frame_frag_next = &r61524_frame_frag_next, + .frame_frag_send = &r61524_frame_frag_send, + .frame_end = &r61524_frame_end, + .display_width = 396, + .display_height = 224 + } }; VHEX_DECLARE_DRIVER(16, drv_r61524); diff --git a/src/modules/display/text/dfont.c b/src/modules/display/text/dfont.c index 41c6e51..8ab30f4 100644 --- a/src/modules/display/text/dfont.c +++ b/src/modules/display/text/dfont.c @@ -179,6 +179,12 @@ void dfont_text_render(struct dshader_surface *surface, uint32_t *arg) while (++counter < (int)arg[7]) { code_point = dfont_utf8_next(&str); + if (code_point == '\n') { + arg[1] += font->glyph.height; + arg[0] = sx; + continue; + } + glyph_idx = dfont_glyph_index(font, code_point); glyph_width = font->glyph.mono.width; if (font->shape.prop == 1) @@ -187,10 +193,7 @@ void dfont_text_render(struct dshader_surface *surface, uint32_t *arg) dfont_char_render(surface, glyph_idx, arg); arg[0] += glyph_width + font->char_spacing; - if (code_point == '\n') { - arg[1] += font->glyph.height; - arg[0] = sx; - } + } arg[1] = sy; arg[0] = sx; @@ -219,12 +222,12 @@ int dfont_text_geometry( uint8_t const *str0 = (void *)str_char; uint8_t const *str1 = (void *)str_char; uint32_t code_point; - size_t char_width; size_t length = 0; size_t mx = 0; size_t x = 0; size_t y = 0; - int glyphidx; + size_t glyph_width; + int glyph_idx; int limit; /* handle special behaviour */ @@ -240,23 +243,28 @@ int dfont_text_geometry( if (code_point == 0 || (limit > 0 && str0 - str1 >= limit)) break; - char_width = font->glyph.mono.width; - if (font->shape.prop == 1) { - glyphidx = dfont_glyph_index(font, code_point); - if (glyphidx < 0) - return (-1); - char_width = font->glyph.prop[glyphidx].width; - } - /* line discipline */ - x += char_width + font->char_spacing; + length++; if (code_point == '\n') { y += font->glyph.height; x = 0; + continue; } - if (x > mx) mx = x; - length++; + glyph_width = font->glyph.mono.width; + if (font->shape.prop == 1) { + glyph_idx = dfont_glyph_index(font, code_point); + if (glyph_idx < 0) { + if (w != NULL) *w = 0; + if (h != NULL) *h = 0; + if (size != NULL) *size = length; + return (-1); + } + glyph_width = font->glyph.prop[glyph_idx].width; + } + + x += glyph_width + font->char_spacing; + if (x > mx) mx = x; } /* set geometry information */ diff --git a/src/modules/timer/timer.c b/src/modules/timer/timer.c new file mode 100644 index 0000000..df9070b --- /dev/null +++ b/src/modules/timer/timer.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include + +#include + + +/* internal timer information */ +struct { + struct timer_drv_interface driver; +} timer_info; + +//--- +// user-level API +//--- + +/* timer_configure(): Reserve and configure a timer */ +tid_t timer_configure(uint64_t delay_us, timer_call_t callback) +{ + if (timer_info.driver.timer_configure != NULL) + return (timer_info.driver.timer_configure(delay_us, callback)); + return (-1); +} + +/* timer_start(): Start a configured timer */ +int timer_start(tid_t timer) +{ + if (timer_info.driver.timer_start != NULL) + return (timer_info.driver.timer_start(timer)); + return (-1); +} + +/* timer_pause(): Pause a timer without freeing it */ +int timer_pause(tid_t timer) +{ + if (timer_info.driver.timer_pause != NULL) + return (timer_info.driver.timer_pause(timer)); + return (-1); +} + +/* timer_stop(): Stop and free a timer */ +int timer_stop(tid_t timer) +{ + if (timer_info.driver.timer_stop != NULL) + return (timer_info.driver.timer_stop(timer)); + return (-1); +} + +/* timer_wait(): Wait for a timer to stop */ +int timer_wait(tid_t timer) +{ + if (timer_info.driver.timer_wait != NULL) + return (timer_info.driver.timer_wait(timer)); + return (-1); +} + +/* timer_spinwait(): Start a timer and actively wait */ +int timer_spinwait(tid_t timer) +{ + if (timer_info.driver.timer_spinwait != NULL) + return (timer_info.driver.timer_spinwait(timer)); + return (-1); +} + +/* timer_reload(): Change a timer's delay constant for next interrupts */ +int timer_reload(tid_t timer, uint64_t delay_us) +{ + if (timer_info.driver.timer_reload != NULL) + return (timer_info.driver.timer_reload(timer, delay_us)); + return (-1); +} + + +//--- +// Kernel module information +//--- + + +/* __timer_init() : initialize the timer module */ +static void __timer_init(void) +{ + memset(&timer_info, 0x00, sizeof(timer_info)); + + struct vhex_driver *driver = vhex_driver_table(); + for (int i = 0; i < vhex_driver_count(); ++i) { + if (driver[i].flags.DISPLAY) { + memcpy( + &timer_info.driver, + driver[i].module_data, + sizeof(struct timer_drv_interface) + ); + break; + } + } +} + +/* __timer_quit() : uninitialize the timer module */ +static void __timer_quit(void) +{ + ; +} + + + +/* declare the timer module */ + +struct vhex_module mod_timer = { + .name = "timer", + .init = &__timer_init, + .quit = &__timer_quit, +};