tmu: export definitions, clean stop, expose address

This commit introduces three timer driver changes:

* Export the definitions of the timer structures to a detailed header at
  <gint/mpu/tmu.h>, and re-use them in the driver.
  This integration is still limited as the driver keeps its own address
  definitions and event codes.

* Clean the timer stop routine that is used in the interrupt handler. Up
  until now the interrupt handler would only stop TSTR, which is not
  enough to cleanly leave the timer (need TCOR=TCNT=-1) and is not even
  sound with respect to gint's semantics as UNIE stays enabled so the
  timer is not made available again.

  The interrupt handler now calls into C code when the timer stop
  condition is met (callback returns non-zero) to keep this clean. This
  unsurprisingly solves problems that occurred in certain situations
  when a timer was used repeatedly.

* Expose timer addresses using a timer_address() function, compensating
  for the lack of address definitions in <gint/mpu/tmu.h>. This
  interface is likely to evolve in the future to better integrate the
  address in the MPU headers and move them out of the driver.
This commit is contained in:
lephe 2019-07-16 15:32:20 -04:00
parent 7e09e37938
commit 31ade70c42
5 changed files with 218 additions and 133 deletions

89
include/gint/mpu/tmu.h Normal file
View File

@ -0,0 +1,89 @@
//---
// gint:intc:tmu - Timer Unit
//
// The definitions in this file cover both the Timer Unit and the Extra
// Timers. The structures are related but not identical; the behaviour is
// subtly different.
//---
#ifndef GINT_MPU_TMU
#define GINT_MPU_TMU
#include <gint/defs/types.h>
//---
// Common structures
//---
/* tmu_t - a single timer from a standard timer unit */
typedef volatile struct
{
uint32_t TCOR; /* Constant register */
uint32_t TCNT; /* Counter register, counts down */
word_union(TCR,
uint16_t :7;
uint16_t UNF :1; /* Underflow flag */
uint16_t :2;
uint16_t UNIE :1; /* Underflow interrupt enable */
uint16_t CKEG :2; /* Input clock edge */
uint16_t TPSC :3; /* Timer prescaler (input clock) */
);
} GPACKED(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 */
);
} GPACKED(4) etmu_t;
//---
// SH7705 Timer Unit. Refer to:
// "Renesas SH7705 Group Hardware Manual"
// Section 12: "Timer Unit (TMU)"
//---
typedef volatile struct
{
pad(2);
uint8_t TSTR; /* Timer Start Register */
pad(1);
tmu_t TMU[3];
uint32_t TCPR2; /* Timer Input Capture Register 2 */
} GPACKED(4) sh7705_tmu_t;
#define SH7705_TMU (*((sh7705_tmu_t *)0xfffffe90))
//---
// 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];
} GPACKED(4) sh7305_tmu_t;
#define SH7305_TMU (*((sh7305_tmu_t *)0xa4490004))
#endif /* GINT_MPU_TMU */

View File

@ -6,6 +6,7 @@
#define GINT_TIMER
#include <gint/defs/types.h>
#include <gint/mpu/tmu.h>
#include <gint/hardware.h>
/* Timer identifiers
@ -151,4 +152,16 @@ void timer_stop(int timer);
int * and you must declare the variable as volatile int. */
int timer_timeout(volatile void *arg);
//---
// Low-level functions
//---
/* timer_address() - get the address of a timer structure
Returns a tmu_t if the id is 0, 1 or 2 and an etmu_t otherwise. The address
will be NULL if the requested timer does not exist.
@timer Requested timer
@TSTR If the requested timer is a TMU, set to the TSTR address */
void *timer_address(int timer, volatile uint8_t **TSTR);
#endif /* GINT_TIMER */

View File

@ -12,9 +12,9 @@
.global _inth_tmu_storage
/* Gates for the extra timers (informally called ETMU) */
.global _inth_tmu_extra2
.global _inth_tmu_extra_help
.global _inth_tmu_extra_others
.global _inth_etmu2
.global _inth_etmu_help
.global _inth_etmux
.section .gint.blocks, "ax"
.align 4
@ -39,11 +39,12 @@
/* FIRST GATE - TMU0 entry, clear underflow flag and call back */
_inth_tmu_0:
mova .storage0, r0
mov #-2, r1
mov #0, r1
/*** This is the first shared section ***/
.clearflag:
sts.l pr, @-r15
mov.l r1, @-r15
/* Load the TCR address and clear the interrupt flag */
@ -54,22 +55,21 @@ _inth_tmu_0:
mov.w r2, @r1
/* Invoke the callback function and pass the argument */
sts.l pr, @-r15
mov.l @r0, r1
jsr @r1
mov.l @(4, r0), r4
lds.l @r15+, pr
/* Prepare stopping the timer and jump to second section */
mov.l .tstr, r5
mov.l @r15+, r4
mov.l .timer_stop, r1
bra .stoptimer
mov.l @r15+, r1
nop
/* SECOND GATE - TMU1 entry and stop timer */
_inth_tmu_1:
mova .storage1, r0
bra .clearflag
mov #-3, r1
mov #1, r1
/*** This is the second shared section ***/
@ -77,11 +77,11 @@ _inth_tmu_1:
/* Stop the timer if the return value is not zero */
tst r0, r0
bt .end
mov.b @r5, r2
and r1, r2
mov.b r2, @r5
jsr @r1
nop
.end:
lds.l @r15+, pr
rte
nop
@ -91,7 +91,7 @@ _inth_tmu_1:
_inth_tmu_2:
mova .storage2, r0
bra .clearflag
mov #-5, r1
mov #2, r1
.zero 14
@ -103,8 +103,10 @@ _inth_tmu_2:
/* FOURTH GATE - Storage for TMU1, TMU2 and other values */
_inth_tmu_storage:
.mask: .long 0x0000feff
.tstr: .long 0xa4490004 /* TSTR: Overridden at startup on SH3 */
.mask:
.long 0x0000feff
.timer_stop:
.long _timer_stop /* gint's function from <gint/timer.h> */
.storage1:
.long 0 /* Callback: Configured dynamically */
@ -139,87 +141,72 @@ _inth_tmu_storage:
can think of is hardcoding the relative displacements, and one would need to
use the unnatural and unmaintainable @(disp, pc) addressing modes. */
/* FIRST GATE - ETMU2 entry, clear flag and prepare callback */
_inth_tmu_extra2:
/* Warning: the size of the following instruction (2 bytes) is
hardcoded in another interrupt handler, _inth_tmu_extra_others */
mova .storage_extra_1, r0
/* FIRST GATE - ETMU2 entry, invoke callback and prepare clear flag */
_inth_etmu2:
/* Warning: the size of the following section (4 bytes) is hardcoded in
the jump in _inth_etmux */
mova .storage_etmu2, r0
mov #5, r1
.extra_callback:
mov.l r8, @-r15
mov r0, r8
stc.l gbr, @-r15
sts.l pr, @-r15
mov.l r1, @-r15
/* Invoke the callback function */
sts.l pr, @-r15
mov.l @r8, r1
mov.l @r0, r1
jsr @r1
mov.l @(4, r8), r4
mov.l @(4, r0), r4
bra .extra_clearflag
lds.l @r15+, pr
.storage_extra_1:
.long 0 /* Callback: Configured dynamically */
.long 0 /* Argument: Configured dynamically */
.long 0 /* Structure address: Edited at startup */
/* SECOND GATE - Helper entry, invoke callback and stop timer if requested */
_inth_tmu_extra_help:
.extra_clearflag:
/* Load timer ID and forward the callback's return value */
mov.l .timer_clear, r1
mov.l @r15+, r4
bra _inth_etmu_help
mov r0, r5
/* Load struture address */
mov.l @(8, r8), r0
ldc r0, gbr
mov #12, r0
nop
.extra_loopclear:
/* Aggressively clear the interrupt flag. The loop is required because
clearing takes "some time". The system does it. Not doing it will
cause the interrupt to be triggered again until the flag is cleared,
which can be ~20 interrupts if the handler is fast! */
and.b #0xfd, @(r0, gbr)
tst.b #0x02, @(r0, gbr)
bf .extra_loopclear
.storage_etmu2:
.long 0 /* Callback: Configured dynamically */
.long 0 /* Argument: Configured dynamically */
/* Check whether to stop the timer */
tst r5, r5
bt .extra_end
/* SECOND GATE - Helper entry, invoke callback and stop timer if requested */
_inth_etmu_help:
.extra_stoptimer:
mov.b @(0, gbr), r0
and #0xfe, r0
mov.b r0, @(0, gbr)
/* Clear the flag and possibly stop the timer */
jsr @r1
nop
.extra_end:
ldc.l @r15+, gbr
mov.l @r15+, r8
lds.l @r15+, pr
rte
nop
/* FOURTH GATE - All other ETMU entries, deferred to the previous ones */
_inth_tmu_extra_others:
/* Dynamically compute the target of the jump */
stc vbr, r1
mov.l 1f, r2
add r2, r1
.zero 18
mova .storage_extra_others, r0
jmp @r1
.timer_clear:
.long _timer_clear /* gint's function from src/tmu/tmu.c */
/* THIRD GATE - All other ETMU entries, deferred to the previous ones */
_inth_etmux:
/* Dynamically compute the target of the jump */
stc vbr, r3
mov.l 1f, r2
add r2, r3
mova .storage_etmux, r0
mov.l .id_etmux, r1
jmp @r3
nop
nop
/* Offset from VBR where extra timer 2 is located:
- 0x600 to reach the interrupt handlers
- 0x020 to jump over the entry gate
- 0x840 to reach the handler of extra timer 2
- 0x002 to skip its first instruction (the size is hardcoded) */
1: .long 0xe62
- 0x840 to reach the handler of ETMU2
- 0x004 to skip its first instructions (the size is hardcoded) */
1: .long 0xe64
.zero 4
.storage_extra_others:
.storage_etmux:
.long 0 /* Callback: Configured dynamically */
.long 0 /* Argument: Configured dynamically */
.long 0 /* Structure address: Edited at startup */
.id_etmux:
.long 0 /* Timer ID */

View File

@ -10,8 +10,7 @@ void sleep_us(int tid, int us_delay)
{
volatile int flag = 0;
timer_setup(tid, timer_delay(tid, us_delay), 0, timer_timeout,
(void *)&flag);
timer_setup(tid, timer_delay(tid, us_delay), 0, timer_timeout, &flag);
timer_start(tid);
while(!flag) sleep();

View File

@ -8,6 +8,7 @@
#include <gint/clock.h>
#include <gint/mpu/intc.h>
#include <gint/mpu/tmu.h>
#include <gint/defs/attributes.h>
#include <gint/defs/types.h>
@ -16,46 +17,13 @@
// Timer structures
//---
/* tmu_t - a single timer from a standard timer unit */
typedef volatile struct
{
uint32_t TCOR; /* Constant register */
uint32_t TCNT; /* Counter register, counts down */
word_union(TCR,
uint16_t :7;
uint16_t UNF :1; /* Underflow flag */
uint16_t :2;
uint16_t UNIE :1; /* Underflow interrupt enable */
uint16_t CKEG :2; /* Input clock edge */
uint16_t TPSC :3; /* Timer prescaler (input clock) */
);
} GPACKED(4) tmu_t;
/* tmu_extra_t - extra timers on sh7337, sh7355 and sh7305 */
typedef volatile struct
{
uint8_t TSTR; /* Only bit 0 is used */
pad(3);
uint32_t TCOR; /* Constant register */
uint32_t TCNT; /* Counter register */
byte_union(TCR,
uint8_t :6;
uint8_t UNF :1; /* Underflow flag */
uint8_t UNIE :1; /* Underflow interrupt enable */
);
} GPACKED(4) tmu_extra_t;
/* inth_data_t - data storage inside interrupt handlers */
typedef struct
{
int (*cb)(volatile void *arg); /* User-provided callback */
volatile void *arg; /* Argument for [callback] */
volatile void *structure; /* Either TCR or timer address */
volatile void *TCR; /* TCR address for TMU */
} GPACKED(4) inth_data_t;
@ -119,7 +87,7 @@ int timer_setup(int tid, uint32_t delay, timer_input_t clock,
/* Extra timers have a simpler structure */
else
{
tmu_extra_t *t = timers[tid].tmu;
etmu_t *t = timers[tid].tmu;
if(t->TCR.UNIE) return -1;
/* Clear the interrupt flag */
@ -173,7 +141,7 @@ static void timer_control(int tid, int state)
/* For standard timers, use the MPU's TSTR register */
if(tid < 3) *TSTR = (*TSTR | (1 << tid)) ^ (state << tid);
/* Extra timers all have their own TSTR register */
else ((tmu_extra_t *)timers[tid].tmu)->TSTR = state ^ 1;
else ((etmu_t *)timers[tid].tmu)->TSTR = state ^ 1;
}
/* timer_start() - start a configured timer */
@ -186,7 +154,7 @@ void timer_start(int tid)
void timer_reload(int tid, uint32_t delay)
{
if(tid < 3) ((tmu_t *)timers[tid].tmu)->TCOR = delay;
else ((tmu_extra_t *)timers[tid].tmu)->TCOR = delay;
else ((etmu_t *)timers[tid].tmu)->TCOR = delay;
}
/* timer_pause() - stop a running timer */
@ -205,10 +173,14 @@ void timer_stop(int tid)
{
tmu_t *t = timers[tid].tmu;
t->TCR.UNIE = 0;
/* Clear TCOR and TCNT */
t->TCOR = 0xffffffff;
t->TCNT = 0xffffffff;
}
else
{
tmu_extra_t *t = timers[tid].tmu;
etmu_t *t = timers[tid].tmu;
t->TCR.UNIE = 0;
/* Also clear TCOR and TCNT to avoid spurious interrupts */
@ -232,7 +204,32 @@ int timer_timeout(volatile void *arg)
{
volatile int *x = arg;
(*x)++;
return 0;
/* Always stop after firing once */
return 1;
}
//---
// Low-level functions
//---
/* timer_address() - get the address of a timer structure */
void *timer_address(int timer, volatile uint8_t **TSTR_arg)
{
if((uint)timer < 2 && TSTR_arg) *TSTR_arg = TSTR;
return (uint)timer < timer_count() ? timers[timer].tmu : NULL;
}
/* timer_clear() - clear an ETMU flag and possibly stop the timer
@timer Timer ID, must be an ETMU
@stop Non-zero to stop the timer */
void timer_clear(int timer, int stop)
{
etmu_t *t = timers[timer].tmu;
do t->TCR.UNF = 0;
while(t->TCR.UNF);
if(stop) timer_stop(timer);
}
//---
@ -246,10 +243,9 @@ extern void inth_tmu_2(void);
extern void inth_tmu_storage(void);
/* Interrupt handlers provided by tmu/inth.s for extra timers */
extern void inth_tmu_extra1(void);
extern void inth_tmu_extra2(void);
extern void inth_tmu_extra_help(void);
extern void inth_tmu_extra_others(void);
extern void inth_etmu2(void);
extern void inth_etmu_help(void);
extern void inth_etmux(void);
#ifdef FX9860G
static void driver_sh3(void)
@ -297,13 +293,13 @@ static void init(void)
while(t->TCR.word);
/* Standard timers: TCR is provided to the interrupt handler */
timers[i].data->structure = &t->TCR;
timers[i].data->TCR = &t->TCR;
}
/* Clear the extra timers */
for(int i = 3; i < timer_count(); i++)
{
tmu_extra_t *t = timers[i].tmu;
etmu_t *t = timers[i].tmu;
/* Extra timers seem to generate interrupts as long as TCNT=0,
regardless of TSTR. I'm not entirely sure about this weird
@ -329,17 +325,18 @@ static void init(void)
for(int i = 3; i < limit; i++)
{
void *handler = (i == 5)
? inth_tmu_extra2
: inth_tmu_extra_others;
void *handler = (i == 5) ? inth_etmu2 : inth_etmux;
void *h = gint_inthandler(timers[i].event, handler, 32);
timers[i].data = h + 20;
timers[i].data->structure = timers[i].tmu;
timers[i].data = (i == 5) ? (h + 24) : (h + 20);
if(i == 5) continue;
uint32_t *data_id = (h + 28);
*data_id = i;
}
/* Also install the helper handler */
gint_inthandler(0xc60, inth_tmu_extra_help, 32);
gint_inthandler(0xc60, inth_etmu_help, 32);
/* Enable TMU0 at level 13, TMU1 at level 11, TMU2 at level 9 */
gint_intlevel(0, 13);
@ -374,7 +371,7 @@ static void init(void)
for(int i = 3; i < timer_count(); i++)
{
tmu_extra_t *t = timers[i].tmu;
etmu_t *t = timers[i].tmu;
int v = !(t->TCOR + 1)
&& !(t->TCNT + 1)
&& !(t->TSTR)
@ -412,7 +409,7 @@ static const char *tmu_status(void)
int j = 5;
for(int i = 3; i < timer_count(); i++)
{
tmu_extra_t *t = timers[i].tmu;
etmu_t *t = timers[i].tmu;
int v1 = (!(t->TCOR + 1))
| (!(t->TCNT + 1) << 1)
| (!(t->TSTR) << 2);
@ -435,7 +432,7 @@ static const char *tmu_status(void)
typedef struct
{
tmu_t std[3];
tmu_extra_t extra[6];
etmu_t extra[6];
uint8_t TSTR;
} GPACKED(4) ctx_t;
@ -459,7 +456,7 @@ static void ctx_save(void *buf)
for(int i = 0; i < timer_count() - 3; i++)
{
tmu_extra_t *t = timers[i + 3].tmu;
etmu_t *t = timers[i + 3].tmu;
ctx->extra[i].TCOR = t->TCOR;
ctx->extra[i].TCNT = t->TCNT;
ctx->extra[i].TCR.byte = t->TCR.byte;
@ -483,7 +480,7 @@ static void ctx_restore(void *buf)
for(int i = 0; i < timer_count() - 3; i++)
{
tmu_extra_t *t = timers[i + 3].tmu;
etmu_t *t = timers[i + 3].tmu;
/* This thing is being unloaded while interrupts are disabled,
so we don't have to heed for ctx->extra[i].TCNT = 0, which