Search
lxdream.org :: lxdream/src/sh4/timer.c
lxdream 0.9.1
released Jun 29
Download Now
filename src/sh4/timer.c
changeset 619:0800a0137472
prev561:533f6b478071
next736:a02d1475ccfd
author nkeynes
date Wed Jan 30 09:38:24 2008 +0000 (16 years ago)
permissions -rw-r--r--
last change Deliver TMU interrupts precisely rather than only at end of time slice
file annotate diff log raw
nkeynes@23
     1
/**
nkeynes@561
     2
 * $Id$
nkeynes@23
     3
 * 
nkeynes@23
     4
 * SH4 Timer/Clock peripheral modules (CPG, TMU, RTC), combined together to
nkeynes@23
     5
 * keep things simple (they intertwine a bit).
nkeynes@23
     6
 *
nkeynes@23
     7
 * Copyright (c) 2005 Nathan Keynes.
nkeynes@23
     8
 *
nkeynes@23
     9
 * This program is free software; you can redistribute it and/or modify
nkeynes@23
    10
 * it under the terms of the GNU General Public License as published by
nkeynes@23
    11
 * the Free Software Foundation; either version 2 of the License, or
nkeynes@23
    12
 * (at your option) any later version.
nkeynes@23
    13
 *
nkeynes@23
    14
 * This program is distributed in the hope that it will be useful,
nkeynes@23
    15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
nkeynes@23
    16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
nkeynes@23
    17
 * GNU General Public License for more details.
nkeynes@23
    18
 */
nkeynes@23
    19
nkeynes@619
    20
#include <assert.h>
nkeynes@619
    21
#include "lxdream.h"
nkeynes@23
    22
#include "mem.h"
nkeynes@23
    23
#include "clock.h"
nkeynes@619
    24
#include "eventq.h"
nkeynes@619
    25
#include "sh4/sh4core.h"
nkeynes@619
    26
#include "sh4/sh4mmio.h"
nkeynes@619
    27
#include "sh4/intc.h"
nkeynes@23
    28
nkeynes@23
    29
/********************************* CPG *************************************/
nkeynes@53
    30
/* This is the base clock from which all other clocks are derived */
nkeynes@53
    31
uint32_t sh4_input_freq = SH4_BASE_RATE;
nkeynes@53
    32
nkeynes@414
    33
uint32_t sh4_cpu_multiplier = 2000; /* = 0.5 * frequency */
nkeynes@414
    34
nkeynes@53
    35
uint32_t sh4_cpu_freq = SH4_BASE_RATE;
nkeynes@53
    36
uint32_t sh4_bus_freq = SH4_BASE_RATE;
nkeynes@53
    37
uint32_t sh4_peripheral_freq = SH4_BASE_RATE / 2;
nkeynes@53
    38
nkeynes@53
    39
uint32_t sh4_cpu_period = 1000 / SH4_BASE_RATE; /* in nanoseconds */
nkeynes@53
    40
uint32_t sh4_bus_period = 1000 / SH4_BASE_RATE;
nkeynes@53
    41
uint32_t sh4_peripheral_period = 2000 / SH4_BASE_RATE;
nkeynes@23
    42
nkeynes@23
    43
int32_t mmio_region_CPG_read( uint32_t reg )
nkeynes@23
    44
{
nkeynes@23
    45
    return MMIO_READ( CPG, reg );
nkeynes@23
    46
}
nkeynes@23
    47
nkeynes@53
    48
/* CPU + bus dividers (note officially only the first 6 values are valid) */
nkeynes@53
    49
int ifc_divider[8] = { 1, 2, 3, 4, 5, 8, 8, 8 };
nkeynes@53
    50
/* Peripheral clock dividers (only first 5 are officially valid) */
nkeynes@53
    51
int pfc_divider[8] = { 2, 3, 4, 6, 8, 8, 8, 8 };
nkeynes@53
    52
nkeynes@23
    53
void mmio_region_CPG_write( uint32_t reg, uint32_t val )
nkeynes@23
    54
{
nkeynes@53
    55
    uint32_t div;
nkeynes@53
    56
    switch( reg ) {
nkeynes@53
    57
    case FRQCR: /* Frequency control */
nkeynes@53
    58
	div = ifc_divider[(val >> 6) & 0x07];
nkeynes@53
    59
	sh4_cpu_freq = sh4_input_freq / div;
nkeynes@414
    60
	sh4_cpu_period = sh4_cpu_multiplier * div / sh4_input_freq;
nkeynes@53
    61
	div = ifc_divider[(val >> 3) & 0x07];
nkeynes@53
    62
	sh4_bus_freq = sh4_input_freq / div;
nkeynes@53
    63
	sh4_bus_period = 1000 * div / sh4_input_freq;
nkeynes@53
    64
	div = pfc_divider[val & 0x07];
nkeynes@53
    65
	sh4_peripheral_freq = sh4_input_freq / div;
nkeynes@53
    66
	sh4_peripheral_period = 1000 * div / sh4_input_freq;
nkeynes@53
    67
nkeynes@53
    68
	/* Update everything that depends on the peripheral frequency */
nkeynes@53
    69
	SCIF_update_line_speed();
nkeynes@53
    70
	break;
nkeynes@53
    71
    case WTCSR: /* Watchdog timer */
nkeynes@53
    72
	break;
nkeynes@53
    73
    }
nkeynes@53
    74
	
nkeynes@23
    75
    MMIO_WRITE( CPG, reg, val );
nkeynes@23
    76
}
nkeynes@23
    77
nkeynes@260
    78
/**
nkeynes@260
    79
 * We don't really know what the default reset value is as it's determined
nkeynes@260
    80
 * by the mode select pins. This is the standard value that the BIOS sets,
nkeynes@260
    81
 * however, so it works for now.
nkeynes@260
    82
 */
nkeynes@260
    83
void CPG_reset( )
nkeynes@260
    84
{
nkeynes@260
    85
    mmio_region_CPG_write( FRQCR, 0x0E0A );
nkeynes@260
    86
}
nkeynes@260
    87
nkeynes@260
    88
nkeynes@23
    89
/********************************** RTC *************************************/
nkeynes@23
    90
nkeynes@53
    91
uint32_t rtc_output_period;
nkeynes@53
    92
nkeynes@23
    93
int32_t mmio_region_RTC_read( uint32_t reg )
nkeynes@23
    94
{
nkeynes@23
    95
    return MMIO_READ( RTC, reg );
nkeynes@23
    96
}
nkeynes@23
    97
nkeynes@23
    98
void mmio_region_RTC_write( uint32_t reg, uint32_t val )
nkeynes@23
    99
{
nkeynes@23
   100
    MMIO_WRITE( RTC, reg, val );
nkeynes@23
   101
}
nkeynes@23
   102
nkeynes@23
   103
/********************************** TMU *************************************/
nkeynes@23
   104
nkeynes@619
   105
#define TMU_IS_RUNNING(timer)  (MMIO_READ(TMU,TSTR) & (1<<timer))
nkeynes@619
   106
nkeynes@260
   107
uint32_t TMU_count( int timer, uint32_t nanosecs );
nkeynes@260
   108
nkeynes@619
   109
void TMU_event_callback( int eventid )
nkeynes@619
   110
{
nkeynes@619
   111
    TMU_count( eventid - EVENT_TMU0, sh4r.slice_cycle );
nkeynes@619
   112
}
nkeynes@619
   113
nkeynes@619
   114
void TMU_init(void)
nkeynes@619
   115
{
nkeynes@619
   116
    register_event_callback( EVENT_TMU0, TMU_event_callback );
nkeynes@619
   117
    register_event_callback( EVENT_TMU1, TMU_event_callback );
nkeynes@619
   118
    register_event_callback( EVENT_TMU2, TMU_event_callback );
nkeynes@619
   119
}    
nkeynes@260
   120
nkeynes@53
   121
#define TCR_ICPF 0x0200
nkeynes@53
   122
#define TCR_UNF  0x0100
nkeynes@53
   123
#define TCR_UNIE 0x0020
nkeynes@53
   124
nkeynes@115
   125
#define TCR_IRQ_ACTIVE (TCR_UNF|TCR_UNIE)
nkeynes@115
   126
nkeynes@53
   127
struct TMU_timer {
nkeynes@53
   128
    uint32_t timer_period;
nkeynes@53
   129
    uint32_t timer_remainder; /* left-over cycles from last count */
nkeynes@53
   130
    uint32_t timer_run; /* cycles already run from this slice */
nkeynes@53
   131
};
nkeynes@53
   132
nkeynes@619
   133
static struct TMU_timer TMU_timers[3];
nkeynes@23
   134
nkeynes@23
   135
int32_t mmio_region_TMU_read( uint32_t reg )
nkeynes@23
   136
{
nkeynes@260
   137
    switch( reg ) {
nkeynes@260
   138
    case TCNT0:
nkeynes@260
   139
	TMU_count( 0, sh4r.slice_cycle );
nkeynes@260
   140
	break;
nkeynes@260
   141
    case TCNT1:
nkeynes@260
   142
	TMU_count( 1, sh4r.slice_cycle );
nkeynes@260
   143
	break;
nkeynes@260
   144
    case TCNT2:
nkeynes@260
   145
	TMU_count( 2, sh4r.slice_cycle );
nkeynes@260
   146
	break;
nkeynes@260
   147
    }
nkeynes@23
   148
    return MMIO_READ( TMU, reg );
nkeynes@23
   149
}
nkeynes@23
   150
nkeynes@115
   151
void TMU_set_timer_control( int timer,  int tcr )
nkeynes@53
   152
{
nkeynes@53
   153
    uint32_t period = 1;
nkeynes@115
   154
    uint32_t oldtcr = MMIO_READ( TMU, TCR0 + (12*timer) );
nkeynes@115
   155
nkeynes@115
   156
    if( (oldtcr & TCR_UNF) == 0 ) {
nkeynes@115
   157
	tcr = tcr & (~TCR_UNF);
nkeynes@115
   158
    } else {
nkeynes@422
   159
	if( ((oldtcr & TCR_UNIE) == 0) && 
nkeynes@115
   160
	    (tcr & TCR_IRQ_ACTIVE) == TCR_IRQ_ACTIVE ) {
nkeynes@115
   161
	    intc_raise_interrupt( INT_TMU_TUNI0 + timer );
nkeynes@115
   162
	} else if( (oldtcr & TCR_UNIE) != 0 && 
nkeynes@115
   163
		   (tcr & TCR_IRQ_ACTIVE) != TCR_IRQ_ACTIVE ) {
nkeynes@115
   164
	    intc_clear_interrupt( INT_TMU_TUNI0 + timer );
nkeynes@115
   165
	}
nkeynes@115
   166
    }
nkeynes@115
   167
nkeynes@53
   168
    switch( tcr & 0x07 ) {
nkeynes@53
   169
    case 0:
nkeynes@53
   170
	period = sh4_peripheral_period << 2 ;
nkeynes@53
   171
	break;
nkeynes@53
   172
    case 1: 
nkeynes@53
   173
	period = sh4_peripheral_period << 4;
nkeynes@53
   174
	break;
nkeynes@53
   175
    case 2:
nkeynes@53
   176
	period = sh4_peripheral_period << 6;
nkeynes@53
   177
	break;
nkeynes@53
   178
    case 3: 
nkeynes@53
   179
	period = sh4_peripheral_period << 8;
nkeynes@53
   180
	break;
nkeynes@53
   181
    case 4:
nkeynes@53
   182
	period = sh4_peripheral_period << 10;
nkeynes@53
   183
	break;
nkeynes@53
   184
    case 5:
nkeynes@53
   185
	/* Illegal value. */
nkeynes@53
   186
	ERROR( "TMU %d period set to illegal value (5)", timer );
nkeynes@53
   187
	period = sh4_peripheral_period << 12; /* for something to do */
nkeynes@53
   188
	break;
nkeynes@53
   189
    case 6:
nkeynes@53
   190
	period = rtc_output_period;
nkeynes@53
   191
	break;
nkeynes@53
   192
    case 7:
nkeynes@53
   193
	/* External clock... Hrm? */
nkeynes@53
   194
	period = sh4_peripheral_period; /* I dunno... */
nkeynes@53
   195
	break;
nkeynes@53
   196
    }
nkeynes@53
   197
    TMU_timers[timer].timer_period = period;
nkeynes@115
   198
nkeynes@115
   199
    MMIO_WRITE( TMU, TCR0 + (12*timer), tcr );
nkeynes@53
   200
}
nkeynes@23
   201
nkeynes@619
   202
void TMU_schedule_timer( int timer )
nkeynes@619
   203
{
nkeynes@619
   204
    uint64_t duration = (uint64_t)((uint32_t)(MMIO_READ( TMU, TCNT0 + 12*timer )+1)) * 
nkeynes@619
   205
	(uint64_t)TMU_timers[timer].timer_period - TMU_timers[timer].timer_remainder;
nkeynes@619
   206
    event_schedule_long( EVENT_TMU0+timer, (uint32_t)(duration / 1000000000), 
nkeynes@619
   207
			 (uint32_t)(duration % 1000000000) );
nkeynes@619
   208
}
nkeynes@619
   209
nkeynes@53
   210
void TMU_start( int timer )
nkeynes@23
   211
{
nkeynes@260
   212
    TMU_timers[timer].timer_run = sh4r.slice_cycle;
nkeynes@53
   213
    TMU_timers[timer].timer_remainder = 0;
nkeynes@619
   214
    TMU_schedule_timer( timer );
nkeynes@53
   215
}
nkeynes@53
   216
nkeynes@264
   217
/**
nkeynes@264
   218
 * Stop the given timer. Run it up to the current time and leave it there.
nkeynes@264
   219
 */
nkeynes@53
   220
void TMU_stop( int timer )
nkeynes@53
   221
{
nkeynes@264
   222
    TMU_count( timer, sh4r.slice_cycle );
nkeynes@619
   223
    event_cancel( EVENT_TMU0+timer );
nkeynes@53
   224
}
nkeynes@53
   225
nkeynes@53
   226
/**
nkeynes@53
   227
 * Count the specified timer for a given number of nanoseconds.
nkeynes@53
   228
 */
nkeynes@53
   229
uint32_t TMU_count( int timer, uint32_t nanosecs ) 
nkeynes@53
   230
{
nkeynes@619
   231
    uint32_t run_ns = nanosecs + TMU_timers[timer].timer_remainder -
nkeynes@53
   232
	TMU_timers[timer].timer_run;
nkeynes@53
   233
    TMU_timers[timer].timer_remainder = 
nkeynes@619
   234
	run_ns % TMU_timers[timer].timer_period;
nkeynes@619
   235
    TMU_timers[timer].timer_run = nanosecs;
nkeynes@619
   236
    uint32_t count = run_ns / TMU_timers[timer].timer_period;
nkeynes@53
   237
    uint32_t value = MMIO_READ( TMU, TCNT0 + 12*timer );
nkeynes@53
   238
    uint32_t reset = MMIO_READ( TMU, TCOR0 + 12*timer );
nkeynes@53
   239
    if( count > value ) {
nkeynes@53
   240
	uint32_t tcr = MMIO_READ( TMU, TCR0 + 12*timer );
nkeynes@53
   241
	tcr |= TCR_UNF;
nkeynes@53
   242
	count -= value;
nkeynes@619
   243
        value = reset - (count % reset) + 1;
nkeynes@53
   244
	MMIO_WRITE( TMU, TCR0 + 12*timer, tcr );
nkeynes@53
   245
	if( tcr & TCR_UNIE ) 
nkeynes@53
   246
	    intc_raise_interrupt( INT_TMU_TUNI0 + timer );
nkeynes@619
   247
	MMIO_WRITE( TMU, TCNT0 + 12*timer, value );
nkeynes@619
   248
	TMU_schedule_timer(timer);
nkeynes@53
   249
    } else {
nkeynes@53
   250
	value -= count;
nkeynes@619
   251
	MMIO_WRITE( TMU, TCNT0 + 12*timer, value );
nkeynes@23
   252
    }
nkeynes@53
   253
    return value;
nkeynes@23
   254
}
nkeynes@23
   255
nkeynes@23
   256
void mmio_region_TMU_write( uint32_t reg, uint32_t val )
nkeynes@23
   257
{
nkeynes@53
   258
    uint32_t oldval;
nkeynes@53
   259
    int i;
nkeynes@23
   260
    switch( reg ) {
nkeynes@53
   261
    case TSTR:
nkeynes@53
   262
	oldval = MMIO_READ( TMU, TSTR );
nkeynes@53
   263
	for( i=0; i<3; i++ ) {
nkeynes@53
   264
	    uint32_t tmp = 1<<i;
nkeynes@264
   265
	    if( (oldval & tmp) != 0 && (val&tmp) == 0  )
nkeynes@53
   266
		TMU_stop(i);
nkeynes@264
   267
	    else if( (oldval&tmp) == 0 && (val&tmp) != 0 )
nkeynes@53
   268
		TMU_start(i);
nkeynes@53
   269
	}
nkeynes@53
   270
	break;
nkeynes@53
   271
    case TCR0:
nkeynes@115
   272
	TMU_set_timer_control( 0, val );
nkeynes@115
   273
	return;
nkeynes@53
   274
    case TCR1:
nkeynes@115
   275
	TMU_set_timer_control( 1, val );
nkeynes@115
   276
	return;
nkeynes@53
   277
    case TCR2:
nkeynes@115
   278
	TMU_set_timer_control( 2, val );
nkeynes@115
   279
	return;
nkeynes@619
   280
    case TCNT0:
nkeynes@619
   281
	MMIO_WRITE( TMU, reg, val );
nkeynes@619
   282
	if( TMU_IS_RUNNING(0) ) { // reschedule
nkeynes@619
   283
	    TMU_timers[0].timer_run = sh4r.slice_cycle;
nkeynes@619
   284
	    TMU_schedule_timer( 0 );
nkeynes@619
   285
	}
nkeynes@619
   286
	return;
nkeynes@619
   287
    case TCNT1:
nkeynes@619
   288
	MMIO_WRITE( TMU, reg, val );
nkeynes@619
   289
	if( TMU_IS_RUNNING(1) ) { // reschedule
nkeynes@619
   290
	    TMU_timers[1].timer_run = sh4r.slice_cycle;
nkeynes@619
   291
	    TMU_schedule_timer( 1 );
nkeynes@619
   292
	}
nkeynes@619
   293
	return;
nkeynes@619
   294
    case TCNT2:
nkeynes@619
   295
	MMIO_WRITE( TMU, reg, val );
nkeynes@619
   296
	if( TMU_IS_RUNNING(2) ) { // reschedule
nkeynes@619
   297
	    TMU_timers[2].timer_run = sh4r.slice_cycle;
nkeynes@619
   298
	    TMU_schedule_timer( 2 );
nkeynes@619
   299
	}
nkeynes@619
   300
	return;
nkeynes@23
   301
    }
nkeynes@23
   302
    MMIO_WRITE( TMU, reg, val );
nkeynes@23
   303
}
nkeynes@23
   304
nkeynes@619
   305
void TMU_count_all( uint32_t nanosecs )
nkeynes@23
   306
{
nkeynes@23
   307
    int tcr = MMIO_READ( TMU, TSTR );
nkeynes@23
   308
    if( tcr & 0x01 ) {
nkeynes@53
   309
	TMU_count( 0, nanosecs );
nkeynes@23
   310
    }
nkeynes@23
   311
    if( tcr & 0x02 ) {
nkeynes@53
   312
	TMU_count( 1, nanosecs );
nkeynes@23
   313
    }
nkeynes@23
   314
    if( tcr & 0x04 ) {
nkeynes@53
   315
	TMU_count( 2, nanosecs );
nkeynes@23
   316
    }
nkeynes@23
   317
}
nkeynes@53
   318
nkeynes@619
   319
void TMU_run_slice( uint32_t nanosecs )
nkeynes@619
   320
{
nkeynes@619
   321
    TMU_count_all( nanosecs );
nkeynes@619
   322
    TMU_timers[0].timer_run = 0;
nkeynes@619
   323
    TMU_timers[1].timer_run = 0;
nkeynes@619
   324
    TMU_timers[2].timer_run = 0;
nkeynes@619
   325
}
nkeynes@619
   326
nkeynes@53
   327
void TMU_update_clocks()
nkeynes@53
   328
{
nkeynes@115
   329
    TMU_set_timer_control( 0, MMIO_READ( TMU, TCR0 ) );
nkeynes@115
   330
    TMU_set_timer_control( 1, MMIO_READ( TMU, TCR1 ) );
nkeynes@115
   331
    TMU_set_timer_control( 2, MMIO_READ( TMU, TCR2 ) );
nkeynes@53
   332
}
nkeynes@53
   333
nkeynes@53
   334
void TMU_reset( )
nkeynes@53
   335
{
nkeynes@53
   336
    TMU_timers[0].timer_remainder = 0;
nkeynes@53
   337
    TMU_timers[0].timer_run = 0;
nkeynes@53
   338
    TMU_timers[1].timer_remainder = 0;
nkeynes@53
   339
    TMU_timers[1].timer_run = 0;
nkeynes@53
   340
    TMU_timers[2].timer_remainder = 0;
nkeynes@53
   341
    TMU_timers[2].timer_run = 0;
nkeynes@53
   342
    TMU_update_clocks();
nkeynes@53
   343
}
nkeynes@53
   344
nkeynes@53
   345
void TMU_save_state( FILE *f ) {
nkeynes@53
   346
    fwrite( &TMU_timers, sizeof(TMU_timers), 1, f );
nkeynes@53
   347
}
nkeynes@53
   348
nkeynes@53
   349
int TMU_load_state( FILE *f ) 
nkeynes@53
   350
{
nkeynes@53
   351
    fread( &TMU_timers, sizeof(TMU_timers), 1, f );
nkeynes@53
   352
    return 0;
nkeynes@53
   353
}
.