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:
parent
dca07d7f06
commit
cb4af4f7bb
|
@ -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;
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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__ */
|
|
@ -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__ */
|
||||
|
|
|
@ -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__ */
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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__ */
|
|
@ -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__ */
|
|
@ -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__ */
|
|
@ -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__ */
|
|
@ -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);
|
|
@ -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();
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
@ -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 */
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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,
|
||||
};
|
Loading…
Reference in New Issue