Search
lxdream.org :: lxdream/src/sh4/timer.c
lxdream 0.9.1
released Jun 29
Download Now
filename src/sh4/timer.c
changeset 859:b941c703ccd6
prev736:a02d1475ccfd
next929:fd8cb0c82f5f
author nkeynes
date Thu Dec 11 23:26:03 2008 +0000 (15 years ago)
permissions -rw-r--r--
last change Disable the generational translation cache - I've got no evidence that it
actually helps performance, and it simplifies things to get rid of it (in
particular, translated code doesn't have to worry about being moved now).
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@859
    30
/* This is the base clock from which all other clocks are derived. 
nkeynes@859
    31
 * Note: The real clock runs at 33Mhz, which is multiplied by the PLL to
nkeynes@859
    32
 * run the instruction clock at 200Mhz. For sake of simplicity/precision,
nkeynes@859
    33
 * we instead use 200Mhz as the base rate and divide everything down instead.
nkeynes@859
    34
 **/
nkeynes@53
    35
uint32_t sh4_input_freq = SH4_BASE_RATE;
nkeynes@53
    36
nkeynes@414
    37
uint32_t sh4_cpu_multiplier = 2000; /* = 0.5 * frequency */
nkeynes@414
    38
nkeynes@53
    39
uint32_t sh4_cpu_freq = SH4_BASE_RATE;
nkeynes@859
    40
uint32_t sh4_bus_freq = SH4_BASE_RATE / 2;
nkeynes@859
    41
uint32_t sh4_peripheral_freq = SH4_BASE_RATE / 4;
nkeynes@53
    42
nkeynes@53
    43
uint32_t sh4_cpu_period = 1000 / SH4_BASE_RATE; /* in nanoseconds */
nkeynes@859
    44
uint32_t sh4_bus_period = 2* 1000 / SH4_BASE_RATE;
nkeynes@859
    45
uint32_t sh4_peripheral_period = 4 * 2000 / SH4_BASE_RATE;
nkeynes@23
    46
nkeynes@23
    47
int32_t mmio_region_CPG_read( uint32_t reg )
nkeynes@23
    48
{
nkeynes@23
    49
    return MMIO_READ( CPG, reg );
nkeynes@23
    50
}
nkeynes@23
    51
nkeynes@53
    52
/* CPU + bus dividers (note officially only the first 6 values are valid) */
nkeynes@53
    53
int ifc_divider[8] = { 1, 2, 3, 4, 5, 8, 8, 8 };
nkeynes@53
    54
/* Peripheral clock dividers (only first 5 are officially valid) */
nkeynes@53
    55
int pfc_divider[8] = { 2, 3, 4, 6, 8, 8, 8, 8 };
nkeynes@53
    56
nkeynes@23
    57
void mmio_region_CPG_write( uint32_t reg, uint32_t val )
nkeynes@23
    58
{
nkeynes@53
    59
    uint32_t div;
nkeynes@859
    60
    uint32_t primary_clock = sh4_input_freq;
nkeynes@859
    61
    
nkeynes@53
    62
    switch( reg ) {
nkeynes@53
    63
    case FRQCR: /* Frequency control */
nkeynes@859
    64
        if( (val & FRQCR_PLL1EN) == 0 )
nkeynes@859
    65
            primary_clock /= 6;
nkeynes@736
    66
        div = ifc_divider[(val >> 6) & 0x07];
nkeynes@859
    67
        sh4_cpu_freq = primary_clock / div;
nkeynes@736
    68
        sh4_cpu_period = sh4_cpu_multiplier * div / sh4_input_freq;
nkeynes@736
    69
        div = ifc_divider[(val >> 3) & 0x07];
nkeynes@859
    70
        sh4_bus_freq = primary_clock / div;
nkeynes@736
    71
        sh4_bus_period = 1000 * div / sh4_input_freq;
nkeynes@736
    72
        div = pfc_divider[val & 0x07];
nkeynes@859
    73
        sh4_peripheral_freq = primary_clock / div;
nkeynes@736
    74
        sh4_peripheral_period = 1000 * div / sh4_input_freq;
nkeynes@53
    75
nkeynes@736
    76
        /* Update everything that depends on the peripheral frequency */
nkeynes@736
    77
        SCIF_update_line_speed();
nkeynes@736
    78
        break;
nkeynes@53
    79
    case WTCSR: /* Watchdog timer */
nkeynes@736
    80
        break;
nkeynes@53
    81
    }
nkeynes@736
    82
nkeynes@23
    83
    MMIO_WRITE( CPG, reg, val );
nkeynes@23
    84
}
nkeynes@23
    85
nkeynes@260
    86
/**
nkeynes@260
    87
 * We don't really know what the default reset value is as it's determined
nkeynes@260
    88
 * by the mode select pins. This is the standard value that the BIOS sets,
nkeynes@260
    89
 * however, so it works for now.
nkeynes@260
    90
 */
nkeynes@260
    91
void CPG_reset( )
nkeynes@260
    92
{
nkeynes@260
    93
    mmio_region_CPG_write( FRQCR, 0x0E0A );
nkeynes@260
    94
}
nkeynes@260
    95
nkeynes@260
    96
nkeynes@23
    97
/********************************** RTC *************************************/
nkeynes@23
    98
nkeynes@53
    99
uint32_t rtc_output_period;
nkeynes@53
   100
nkeynes@23
   101
int32_t mmio_region_RTC_read( uint32_t reg )
nkeynes@23
   102
{
nkeynes@23
   103
    return MMIO_READ( RTC, reg );
nkeynes@23
   104
}
nkeynes@23
   105
nkeynes@23
   106
void mmio_region_RTC_write( uint32_t reg, uint32_t val )
nkeynes@23
   107
{
nkeynes@23
   108
    MMIO_WRITE( RTC, reg, val );
nkeynes@23
   109
}
nkeynes@23
   110
nkeynes@23
   111
/********************************** TMU *************************************/
nkeynes@23
   112
nkeynes@619
   113
#define TMU_IS_RUNNING(timer)  (MMIO_READ(TMU,TSTR) & (1<<timer))
nkeynes@619
   114
nkeynes@260
   115
uint32_t TMU_count( int timer, uint32_t nanosecs );
nkeynes@260
   116
nkeynes@619
   117
void TMU_event_callback( int eventid )
nkeynes@619
   118
{
nkeynes@619
   119
    TMU_count( eventid - EVENT_TMU0, sh4r.slice_cycle );
nkeynes@619
   120
}
nkeynes@619
   121
nkeynes@619
   122
void TMU_init(void)
nkeynes@619
   123
{
nkeynes@619
   124
    register_event_callback( EVENT_TMU0, TMU_event_callback );
nkeynes@619
   125
    register_event_callback( EVENT_TMU1, TMU_event_callback );
nkeynes@619
   126
    register_event_callback( EVENT_TMU2, TMU_event_callback );
nkeynes@619
   127
}    
nkeynes@260
   128
nkeynes@53
   129
#define TCR_ICPF 0x0200
nkeynes@53
   130
#define TCR_UNF  0x0100
nkeynes@53
   131
#define TCR_UNIE 0x0020
nkeynes@53
   132
nkeynes@115
   133
#define TCR_IRQ_ACTIVE (TCR_UNF|TCR_UNIE)
nkeynes@115
   134
nkeynes@53
   135
struct TMU_timer {
nkeynes@53
   136
    uint32_t timer_period;
nkeynes@53
   137
    uint32_t timer_remainder; /* left-over cycles from last count */
nkeynes@53
   138
    uint32_t timer_run; /* cycles already run from this slice */
nkeynes@53
   139
};
nkeynes@53
   140
nkeynes@619
   141
static struct TMU_timer TMU_timers[3];
nkeynes@23
   142
nkeynes@23
   143
int32_t mmio_region_TMU_read( uint32_t reg )
nkeynes@23
   144
{
nkeynes@260
   145
    switch( reg ) {
nkeynes@260
   146
    case TCNT0:
nkeynes@736
   147
        TMU_count( 0, sh4r.slice_cycle );
nkeynes@736
   148
        break;
nkeynes@260
   149
    case TCNT1:
nkeynes@736
   150
        TMU_count( 1, sh4r.slice_cycle );
nkeynes@736
   151
        break;
nkeynes@260
   152
    case TCNT2:
nkeynes@736
   153
        TMU_count( 2, sh4r.slice_cycle );
nkeynes@736
   154
        break;
nkeynes@260
   155
    }
nkeynes@23
   156
    return MMIO_READ( TMU, reg );
nkeynes@23
   157
}
nkeynes@23
   158
nkeynes@115
   159
void TMU_set_timer_control( int timer,  int tcr )
nkeynes@53
   160
{
nkeynes@53
   161
    uint32_t period = 1;
nkeynes@115
   162
    uint32_t oldtcr = MMIO_READ( TMU, TCR0 + (12*timer) );
nkeynes@115
   163
nkeynes@115
   164
    if( (oldtcr & TCR_UNF) == 0 ) {
nkeynes@736
   165
        tcr = tcr & (~TCR_UNF);
nkeynes@115
   166
    } else {
nkeynes@736
   167
        if( ((oldtcr & TCR_UNIE) == 0) && 
nkeynes@736
   168
                (tcr & TCR_IRQ_ACTIVE) == TCR_IRQ_ACTIVE ) {
nkeynes@736
   169
            intc_raise_interrupt( INT_TMU_TUNI0 + timer );
nkeynes@736
   170
        } else if( (oldtcr & TCR_UNIE) != 0 && 
nkeynes@736
   171
                (tcr & TCR_IRQ_ACTIVE) != TCR_IRQ_ACTIVE ) {
nkeynes@736
   172
            intc_clear_interrupt( INT_TMU_TUNI0 + timer );
nkeynes@736
   173
        }
nkeynes@115
   174
    }
nkeynes@115
   175
nkeynes@53
   176
    switch( tcr & 0x07 ) {
nkeynes@53
   177
    case 0:
nkeynes@736
   178
        period = sh4_peripheral_period << 2 ;
nkeynes@736
   179
        break;
nkeynes@53
   180
    case 1: 
nkeynes@736
   181
        period = sh4_peripheral_period << 4;
nkeynes@736
   182
        break;
nkeynes@53
   183
    case 2:
nkeynes@736
   184
        period = sh4_peripheral_period << 6;
nkeynes@736
   185
        break;
nkeynes@53
   186
    case 3: 
nkeynes@736
   187
        period = sh4_peripheral_period << 8;
nkeynes@736
   188
        break;
nkeynes@53
   189
    case 4:
nkeynes@736
   190
        period = sh4_peripheral_period << 10;
nkeynes@736
   191
        break;
nkeynes@53
   192
    case 5:
nkeynes@736
   193
        /* Illegal value. */
nkeynes@736
   194
        ERROR( "TMU %d period set to illegal value (5)", timer );
nkeynes@736
   195
        period = sh4_peripheral_period << 12; /* for something to do */
nkeynes@736
   196
        break;
nkeynes@53
   197
    case 6:
nkeynes@736
   198
        period = rtc_output_period;
nkeynes@736
   199
        break;
nkeynes@53
   200
    case 7:
nkeynes@736
   201
        /* External clock... Hrm? */
nkeynes@736
   202
        period = sh4_peripheral_period; /* I dunno... */
nkeynes@736
   203
        break;
nkeynes@53
   204
    }
nkeynes@53
   205
    TMU_timers[timer].timer_period = period;
nkeynes@115
   206
nkeynes@115
   207
    MMIO_WRITE( TMU, TCR0 + (12*timer), tcr );
nkeynes@53
   208
}
nkeynes@23
   209
nkeynes@619
   210
void TMU_schedule_timer( int timer )
nkeynes@619
   211
{
nkeynes@619
   212
    uint64_t duration = (uint64_t)((uint32_t)(MMIO_READ( TMU, TCNT0 + 12*timer )+1)) * 
nkeynes@736
   213
    (uint64_t)TMU_timers[timer].timer_period - TMU_timers[timer].timer_remainder;
nkeynes@619
   214
    event_schedule_long( EVENT_TMU0+timer, (uint32_t)(duration / 1000000000), 
nkeynes@736
   215
                         (uint32_t)(duration % 1000000000) );
nkeynes@619
   216
}
nkeynes@619
   217
nkeynes@53
   218
void TMU_start( int timer )
nkeynes@23
   219
{
nkeynes@260
   220
    TMU_timers[timer].timer_run = sh4r.slice_cycle;
nkeynes@53
   221
    TMU_timers[timer].timer_remainder = 0;
nkeynes@619
   222
    TMU_schedule_timer( timer );
nkeynes@53
   223
}
nkeynes@53
   224
nkeynes@264
   225
/**
nkeynes@264
   226
 * Stop the given timer. Run it up to the current time and leave it there.
nkeynes@264
   227
 */
nkeynes@53
   228
void TMU_stop( int timer )
nkeynes@53
   229
{
nkeynes@264
   230
    TMU_count( timer, sh4r.slice_cycle );
nkeynes@619
   231
    event_cancel( EVENT_TMU0+timer );
nkeynes@53
   232
}
nkeynes@53
   233
nkeynes@53
   234
/**
nkeynes@53
   235
 * Count the specified timer for a given number of nanoseconds.
nkeynes@53
   236
 */
nkeynes@53
   237
uint32_t TMU_count( int timer, uint32_t nanosecs ) 
nkeynes@53
   238
{
nkeynes@619
   239
    uint32_t run_ns = nanosecs + TMU_timers[timer].timer_remainder -
nkeynes@736
   240
    TMU_timers[timer].timer_run;
nkeynes@53
   241
    TMU_timers[timer].timer_remainder = 
nkeynes@736
   242
        run_ns % TMU_timers[timer].timer_period;
nkeynes@619
   243
    TMU_timers[timer].timer_run = nanosecs;
nkeynes@619
   244
    uint32_t count = run_ns / TMU_timers[timer].timer_period;
nkeynes@53
   245
    uint32_t value = MMIO_READ( TMU, TCNT0 + 12*timer );
nkeynes@53
   246
    uint32_t reset = MMIO_READ( TMU, TCOR0 + 12*timer );
nkeynes@53
   247
    if( count > value ) {
nkeynes@736
   248
        uint32_t tcr = MMIO_READ( TMU, TCR0 + 12*timer );
nkeynes@736
   249
        tcr |= TCR_UNF;
nkeynes@736
   250
        count -= value;
nkeynes@619
   251
        value = reset - (count % reset) + 1;
nkeynes@736
   252
        MMIO_WRITE( TMU, TCR0 + 12*timer, tcr );
nkeynes@736
   253
        if( tcr & TCR_UNIE ) 
nkeynes@736
   254
            intc_raise_interrupt( INT_TMU_TUNI0 + timer );
nkeynes@736
   255
        MMIO_WRITE( TMU, TCNT0 + 12*timer, value );
nkeynes@736
   256
        TMU_schedule_timer(timer);
nkeynes@53
   257
    } else {
nkeynes@736
   258
        value -= count;
nkeynes@736
   259
        MMIO_WRITE( TMU, TCNT0 + 12*timer, value );
nkeynes@23
   260
    }
nkeynes@53
   261
    return value;
nkeynes@23
   262
}
nkeynes@23
   263
nkeynes@23
   264
void mmio_region_TMU_write( uint32_t reg, uint32_t val )
nkeynes@23
   265
{
nkeynes@53
   266
    uint32_t oldval;
nkeynes@53
   267
    int i;
nkeynes@23
   268
    switch( reg ) {
nkeynes@53
   269
    case TSTR:
nkeynes@736
   270
        oldval = MMIO_READ( TMU, TSTR );
nkeynes@736
   271
        for( i=0; i<3; i++ ) {
nkeynes@736
   272
            uint32_t tmp = 1<<i;
nkeynes@736
   273
            if( (oldval & tmp) != 0 && (val&tmp) == 0  )
nkeynes@736
   274
                TMU_stop(i);
nkeynes@736
   275
            else if( (oldval&tmp) == 0 && (val&tmp) != 0 )
nkeynes@736
   276
                TMU_start(i);
nkeynes@736
   277
        }
nkeynes@736
   278
        break;
nkeynes@53
   279
    case TCR0:
nkeynes@736
   280
        TMU_set_timer_control( 0, val );
nkeynes@736
   281
        return;
nkeynes@53
   282
    case TCR1:
nkeynes@736
   283
        TMU_set_timer_control( 1, val );
nkeynes@736
   284
        return;
nkeynes@53
   285
    case TCR2:
nkeynes@736
   286
        TMU_set_timer_control( 2, val );
nkeynes@736
   287
        return;
nkeynes@619
   288
    case TCNT0:
nkeynes@736
   289
        MMIO_WRITE( TMU, reg, val );
nkeynes@736
   290
        if( TMU_IS_RUNNING(0) ) { // reschedule
nkeynes@736
   291
            TMU_timers[0].timer_run = sh4r.slice_cycle;
nkeynes@736
   292
            TMU_schedule_timer( 0 );
nkeynes@736
   293
        }
nkeynes@736
   294
        return;
nkeynes@619
   295
    case TCNT1:
nkeynes@736
   296
        MMIO_WRITE( TMU, reg, val );
nkeynes@736
   297
        if( TMU_IS_RUNNING(1) ) { // reschedule
nkeynes@736
   298
            TMU_timers[1].timer_run = sh4r.slice_cycle;
nkeynes@736
   299
            TMU_schedule_timer( 1 );
nkeynes@736
   300
        }
nkeynes@736
   301
        return;
nkeynes@619
   302
    case TCNT2:
nkeynes@736
   303
        MMIO_WRITE( TMU, reg, val );
nkeynes@736
   304
        if( TMU_IS_RUNNING(2) ) { // reschedule
nkeynes@736
   305
            TMU_timers[2].timer_run = sh4r.slice_cycle;
nkeynes@736
   306
            TMU_schedule_timer( 2 );
nkeynes@736
   307
        }
nkeynes@736
   308
        return;
nkeynes@23
   309
    }
nkeynes@23
   310
    MMIO_WRITE( TMU, reg, val );
nkeynes@23
   311
}
nkeynes@23
   312
nkeynes@619
   313
void TMU_count_all( uint32_t nanosecs )
nkeynes@23
   314
{
nkeynes@23
   315
    int tcr = MMIO_READ( TMU, TSTR );
nkeynes@23
   316
    if( tcr & 0x01 ) {
nkeynes@736
   317
        TMU_count( 0, nanosecs );
nkeynes@23
   318
    }
nkeynes@23
   319
    if( tcr & 0x02 ) {
nkeynes@736
   320
        TMU_count( 1, nanosecs );
nkeynes@23
   321
    }
nkeynes@23
   322
    if( tcr & 0x04 ) {
nkeynes@736
   323
        TMU_count( 2, nanosecs );
nkeynes@23
   324
    }
nkeynes@23
   325
}
nkeynes@53
   326
nkeynes@619
   327
void TMU_run_slice( uint32_t nanosecs )
nkeynes@619
   328
{
nkeynes@619
   329
    TMU_count_all( nanosecs );
nkeynes@619
   330
    TMU_timers[0].timer_run = 0;
nkeynes@619
   331
    TMU_timers[1].timer_run = 0;
nkeynes@619
   332
    TMU_timers[2].timer_run = 0;
nkeynes@619
   333
}
nkeynes@619
   334
nkeynes@53
   335
void TMU_update_clocks()
nkeynes@53
   336
{
nkeynes@115
   337
    TMU_set_timer_control( 0, MMIO_READ( TMU, TCR0 ) );
nkeynes@115
   338
    TMU_set_timer_control( 1, MMIO_READ( TMU, TCR1 ) );
nkeynes@115
   339
    TMU_set_timer_control( 2, MMIO_READ( TMU, TCR2 ) );
nkeynes@53
   340
}
nkeynes@53
   341
nkeynes@53
   342
void TMU_reset( )
nkeynes@53
   343
{
nkeynes@53
   344
    TMU_timers[0].timer_remainder = 0;
nkeynes@53
   345
    TMU_timers[0].timer_run = 0;
nkeynes@53
   346
    TMU_timers[1].timer_remainder = 0;
nkeynes@53
   347
    TMU_timers[1].timer_run = 0;
nkeynes@53
   348
    TMU_timers[2].timer_remainder = 0;
nkeynes@53
   349
    TMU_timers[2].timer_run = 0;
nkeynes@53
   350
    TMU_update_clocks();
nkeynes@53
   351
}
nkeynes@53
   352
nkeynes@53
   353
void TMU_save_state( FILE *f ) {
nkeynes@53
   354
    fwrite( &TMU_timers, sizeof(TMU_timers), 1, f );
nkeynes@53
   355
}
nkeynes@53
   356
nkeynes@53
   357
int TMU_load_state( FILE *f ) 
nkeynes@53
   358
{
nkeynes@53
   359
    fread( &TMU_timers, sizeof(TMU_timers), 1, f );
nkeynes@53
   360
    return 0;
nkeynes@53
   361
}
.