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
This commit is contained in:
Yann MAGNIN 2022-06-13 21:15:36 +02:00
parent dca07d7f06
commit cb4af4f7bb
20 changed files with 1325 additions and 42 deletions

View File

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

View File

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

View File

@ -0,0 +1,136 @@
#ifndef __VHEX_MPU_SH7305_CPG__
# define __VHEX_MPU_SH7305_CPG__
#include <vhex/defs/attributes.h>
#include <vhex/defs/types.h>
//---
// 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__ */

View File

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

View File

@ -0,0 +1,85 @@
#ifndef __VHEX_MPU_SH7305_TMU__
# define __VHEX_MPU_SH7305_TMU__
#include <vhex/defs/attributes.h>
#include <vhex/defs/types.h>
/* 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__ */

View File

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

56
include/vhex/timer.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef __VHEX_TIMER__
#define __VHEX_TIMER__
#include <vhex/timer/call.h>
#include <vhex/timer/types.h>
/* 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__ */

20
include/vhex/timer/call.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef __VHEX_TIMER_CALL__
# define __VHEX_TIMER_CALL__
#include <vhex/defs/types.h>
/* 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__ */

View File

@ -0,0 +1,18 @@
#ifndef __VHEX_TIMER_INTERFACE__
# define __VHEX_TIMER_INTERFACE__
#include <vhex/timer/call.h>
#include <vhex/timer/types.h>
/* 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__ */

View File

@ -0,0 +1,10 @@
#ifndef __VHEX_TIMER_TYPES__
# define __VHEX_TIMER_TYPES__
/* force uint64_t */
#include <stdint.h>
/* timer ID */
typedef int tid_t;
#endif /* __VHEX_TIMER_TYPES__ */

View File

@ -0,0 +1,78 @@
#include <vhex/driver/mpu/sh/sh7305/cpg.h>
#include <vhex/driver.h>
#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);

View File

@ -0,0 +1,22 @@
#include <vhex/driver/cpu.h>
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();
}

View File

@ -3,11 +3,11 @@
#include <string.h>
/* 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

View File

@ -0,0 +1,101 @@
#define ASM_SOURCE
#include <vhex/hardware.h>
#include <board/fxcg50/hardware.h>
.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

View File

@ -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(<id>) */
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 <driver/mpu/sh/sh7305/tmu/tmu.c>)
! <> 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 */

View File

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

View File

@ -0,0 +1,402 @@
#include <vhex/driver/mpu/sh/sh7305/tmu.h>
#include <vhex/driver/mpu/sh/sh7305/cpu.h>
#include <vhex/driver/mpu/sh/sh7305/cpg.h>
#include <vhex/driver/mpu/sh/sh7305/intc.h>
#include <vhex/driver/cpu.h>
#include <vhex/driver.h>
#include <vhex/timer.h>
#include <vhex/timer/interface.h>
/* 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

View File

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

View File

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

112
src/modules/timer/timer.c Normal file
View File

@ -0,0 +1,112 @@
#include <vhex/timer/interface.h>
#include <vhex/driver.h>
#include <vhex/module.h>
#include <vhex/timer.h>
#include <string.h>
/* 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,
};