nkeynes@23 | 1 | /**
|
nkeynes@53 | 2 | * $Id: timer.c,v 1.3 2005-12-29 12:52:29 nkeynes Exp $
|
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@23 | 20 | #include "dream.h"
|
nkeynes@23 | 21 | #include "mem.h"
|
nkeynes@23 | 22 | #include "clock.h"
|
nkeynes@23 | 23 | #include "sh4core.h"
|
nkeynes@23 | 24 | #include "sh4mmio.h"
|
nkeynes@53 | 25 | #include "intc.h"
|
nkeynes@23 | 26 |
|
nkeynes@23 | 27 | /********************************* CPG *************************************/
|
nkeynes@53 | 28 | /* This is the base clock from which all other clocks are derived */
|
nkeynes@53 | 29 | uint32_t sh4_input_freq = SH4_BASE_RATE;
|
nkeynes@53 | 30 |
|
nkeynes@53 | 31 | uint32_t sh4_cpu_freq = SH4_BASE_RATE;
|
nkeynes@53 | 32 | uint32_t sh4_bus_freq = SH4_BASE_RATE;
|
nkeynes@53 | 33 | uint32_t sh4_peripheral_freq = SH4_BASE_RATE / 2;
|
nkeynes@53 | 34 |
|
nkeynes@53 | 35 | uint32_t sh4_cpu_period = 1000 / SH4_BASE_RATE; /* in nanoseconds */
|
nkeynes@53 | 36 | uint32_t sh4_bus_period = 1000 / SH4_BASE_RATE;
|
nkeynes@53 | 37 | uint32_t sh4_peripheral_period = 2000 / SH4_BASE_RATE;
|
nkeynes@23 | 38 |
|
nkeynes@23 | 39 | int32_t mmio_region_CPG_read( uint32_t reg )
|
nkeynes@23 | 40 | {
|
nkeynes@23 | 41 | return MMIO_READ( CPG, reg );
|
nkeynes@23 | 42 | }
|
nkeynes@23 | 43 |
|
nkeynes@53 | 44 | /* CPU + bus dividers (note officially only the first 6 values are valid) */
|
nkeynes@53 | 45 | int ifc_divider[8] = { 1, 2, 3, 4, 5, 8, 8, 8 };
|
nkeynes@53 | 46 | /* Peripheral clock dividers (only first 5 are officially valid) */
|
nkeynes@53 | 47 | int pfc_divider[8] = { 2, 3, 4, 6, 8, 8, 8, 8 };
|
nkeynes@53 | 48 |
|
nkeynes@23 | 49 | void mmio_region_CPG_write( uint32_t reg, uint32_t val )
|
nkeynes@23 | 50 | {
|
nkeynes@53 | 51 | uint32_t div;
|
nkeynes@53 | 52 | switch( reg ) {
|
nkeynes@53 | 53 | case FRQCR: /* Frequency control */
|
nkeynes@53 | 54 | div = ifc_divider[(val >> 6) & 0x07];
|
nkeynes@53 | 55 | sh4_cpu_freq = sh4_input_freq / div;
|
nkeynes@53 | 56 | sh4_cpu_period = 1000 * div / sh4_input_freq;
|
nkeynes@53 | 57 | div = ifc_divider[(val >> 3) & 0x07];
|
nkeynes@53 | 58 | sh4_bus_freq = sh4_input_freq / div;
|
nkeynes@53 | 59 | sh4_bus_period = 1000 * div / sh4_input_freq;
|
nkeynes@53 | 60 | div = pfc_divider[val & 0x07];
|
nkeynes@53 | 61 | sh4_peripheral_freq = sh4_input_freq / div;
|
nkeynes@53 | 62 | sh4_peripheral_period = 1000 * div / sh4_input_freq;
|
nkeynes@53 | 63 |
|
nkeynes@53 | 64 | /* Update everything that depends on the peripheral frequency */
|
nkeynes@53 | 65 | SCIF_update_line_speed();
|
nkeynes@53 | 66 | break;
|
nkeynes@53 | 67 | case WTCSR: /* Watchdog timer */
|
nkeynes@53 | 68 | break;
|
nkeynes@53 | 69 | }
|
nkeynes@53 | 70 |
|
nkeynes@23 | 71 | MMIO_WRITE( CPG, reg, val );
|
nkeynes@23 | 72 | }
|
nkeynes@23 | 73 |
|
nkeynes@23 | 74 | /********************************** RTC *************************************/
|
nkeynes@23 | 75 |
|
nkeynes@53 | 76 | uint32_t rtc_output_period;
|
nkeynes@53 | 77 |
|
nkeynes@23 | 78 | int32_t mmio_region_RTC_read( uint32_t reg )
|
nkeynes@23 | 79 | {
|
nkeynes@23 | 80 | return MMIO_READ( RTC, reg );
|
nkeynes@23 | 81 | }
|
nkeynes@23 | 82 |
|
nkeynes@23 | 83 | void mmio_region_RTC_write( uint32_t reg, uint32_t val )
|
nkeynes@23 | 84 | {
|
nkeynes@23 | 85 | MMIO_WRITE( RTC, reg, val );
|
nkeynes@23 | 86 | }
|
nkeynes@23 | 87 |
|
nkeynes@23 | 88 | /********************************** TMU *************************************/
|
nkeynes@23 | 89 |
|
nkeynes@53 | 90 | #define TCR_ICPF 0x0200
|
nkeynes@53 | 91 | #define TCR_UNF 0x0100
|
nkeynes@53 | 92 | #define TCR_UNIE 0x0020
|
nkeynes@53 | 93 |
|
nkeynes@53 | 94 | struct TMU_timer {
|
nkeynes@53 | 95 | uint32_t timer_period;
|
nkeynes@53 | 96 | uint32_t timer_remainder; /* left-over cycles from last count */
|
nkeynes@53 | 97 | uint32_t timer_run; /* cycles already run from this slice */
|
nkeynes@53 | 98 | };
|
nkeynes@53 | 99 |
|
nkeynes@53 | 100 | struct TMU_timer TMU_timers[3];
|
nkeynes@23 | 101 |
|
nkeynes@23 | 102 | int32_t mmio_region_TMU_read( uint32_t reg )
|
nkeynes@23 | 103 | {
|
nkeynes@23 | 104 | return MMIO_READ( TMU, reg );
|
nkeynes@23 | 105 | }
|
nkeynes@23 | 106 |
|
nkeynes@53 | 107 | void TMU_set_timer_period( int timer, int tcr )
|
nkeynes@53 | 108 | {
|
nkeynes@53 | 109 | uint32_t period = 1;
|
nkeynes@53 | 110 | switch( tcr & 0x07 ) {
|
nkeynes@53 | 111 | case 0:
|
nkeynes@53 | 112 | period = sh4_peripheral_period << 2 ;
|
nkeynes@53 | 113 | break;
|
nkeynes@53 | 114 | case 1:
|
nkeynes@53 | 115 | period = sh4_peripheral_period << 4;
|
nkeynes@53 | 116 | break;
|
nkeynes@53 | 117 | case 2:
|
nkeynes@53 | 118 | period = sh4_peripheral_period << 6;
|
nkeynes@53 | 119 | break;
|
nkeynes@53 | 120 | case 3:
|
nkeynes@53 | 121 | period = sh4_peripheral_period << 8;
|
nkeynes@53 | 122 | break;
|
nkeynes@53 | 123 | case 4:
|
nkeynes@53 | 124 | period = sh4_peripheral_period << 10;
|
nkeynes@53 | 125 | break;
|
nkeynes@53 | 126 | case 5:
|
nkeynes@53 | 127 | /* Illegal value. */
|
nkeynes@53 | 128 | ERROR( "TMU %d period set to illegal value (5)", timer );
|
nkeynes@53 | 129 | period = sh4_peripheral_period << 12; /* for something to do */
|
nkeynes@53 | 130 | break;
|
nkeynes@53 | 131 | case 6:
|
nkeynes@53 | 132 | period = rtc_output_period;
|
nkeynes@53 | 133 | break;
|
nkeynes@53 | 134 | case 7:
|
nkeynes@53 | 135 | /* External clock... Hrm? */
|
nkeynes@53 | 136 | period = sh4_peripheral_period; /* I dunno... */
|
nkeynes@53 | 137 | break;
|
nkeynes@53 | 138 | }
|
nkeynes@53 | 139 | TMU_timers[timer].timer_period = period;
|
nkeynes@53 | 140 | }
|
nkeynes@23 | 141 |
|
nkeynes@53 | 142 | void TMU_start( int timer )
|
nkeynes@23 | 143 | {
|
nkeynes@53 | 144 | TMU_timers[timer].timer_run = 0;
|
nkeynes@53 | 145 | TMU_timers[timer].timer_remainder = 0;
|
nkeynes@53 | 146 | }
|
nkeynes@53 | 147 |
|
nkeynes@53 | 148 | void TMU_stop( int timer )
|
nkeynes@53 | 149 | {
|
nkeynes@53 | 150 |
|
nkeynes@53 | 151 | }
|
nkeynes@53 | 152 |
|
nkeynes@53 | 153 | /**
|
nkeynes@53 | 154 | * Count the specified timer for a given number of nanoseconds.
|
nkeynes@53 | 155 | */
|
nkeynes@53 | 156 | uint32_t TMU_count( int timer, uint32_t nanosecs )
|
nkeynes@53 | 157 | {
|
nkeynes@53 | 158 | nanosecs = nanosecs + TMU_timers[timer].timer_remainder -
|
nkeynes@53 | 159 | TMU_timers[timer].timer_run;
|
nkeynes@53 | 160 | TMU_timers[timer].timer_remainder =
|
nkeynes@53 | 161 | nanosecs % TMU_timers[timer].timer_period;
|
nkeynes@53 | 162 | uint32_t count = nanosecs / TMU_timers[timer].timer_period;
|
nkeynes@53 | 163 | uint32_t value = MMIO_READ( TMU, TCNT0 + 12*timer );
|
nkeynes@53 | 164 | uint32_t reset = MMIO_READ( TMU, TCOR0 + 12*timer );
|
nkeynes@53 | 165 | if( count > value ) {
|
nkeynes@53 | 166 | uint32_t tcr = MMIO_READ( TMU, TCR0 + 12*timer );
|
nkeynes@53 | 167 | tcr |= TCR_UNF;
|
nkeynes@53 | 168 | count -= value;
|
nkeynes@53 | 169 | value = reset - (count % reset);
|
nkeynes@53 | 170 | MMIO_WRITE( TMU, TCR0 + 12*timer, tcr );
|
nkeynes@53 | 171 | if( tcr & TCR_UNIE )
|
nkeynes@53 | 172 | intc_raise_interrupt( INT_TMU_TUNI0 + timer );
|
nkeynes@53 | 173 | } else {
|
nkeynes@53 | 174 | value -= count;
|
nkeynes@23 | 175 | }
|
nkeynes@53 | 176 | MMIO_WRITE( TMU, TCNT0 + 12*timer, value );
|
nkeynes@53 | 177 | return value;
|
nkeynes@23 | 178 | }
|
nkeynes@23 | 179 |
|
nkeynes@23 | 180 | void mmio_region_TMU_write( uint32_t reg, uint32_t val )
|
nkeynes@23 | 181 | {
|
nkeynes@53 | 182 | uint32_t oldval;
|
nkeynes@53 | 183 | int i;
|
nkeynes@23 | 184 | switch( reg ) {
|
nkeynes@53 | 185 | case TSTR:
|
nkeynes@53 | 186 | oldval = MMIO_READ( TMU, TSTR );
|
nkeynes@53 | 187 | for( i=0; i<3; i++ ) {
|
nkeynes@53 | 188 | uint32_t tmp = 1<<i;
|
nkeynes@53 | 189 | if( (oldval & tmp) == 1 && (val&tmp) == 0 )
|
nkeynes@53 | 190 | TMU_stop(i);
|
nkeynes@53 | 191 | else if( (oldval&tmp) == 0 && (val&tmp) == 1 )
|
nkeynes@53 | 192 | TMU_start(i);
|
nkeynes@53 | 193 | }
|
nkeynes@53 | 194 | break;
|
nkeynes@53 | 195 | case TCR0:
|
nkeynes@53 | 196 | TMU_set_timer_period( 0, val );
|
nkeynes@53 | 197 | break;
|
nkeynes@53 | 198 | case TCR1:
|
nkeynes@53 | 199 | TMU_set_timer_period( 1, val );
|
nkeynes@53 | 200 | break;
|
nkeynes@53 | 201 | case TCR2:
|
nkeynes@53 | 202 | TMU_set_timer_period( 2, val );
|
nkeynes@53 | 203 | break;
|
nkeynes@23 | 204 | }
|
nkeynes@23 | 205 | MMIO_WRITE( TMU, reg, val );
|
nkeynes@23 | 206 | }
|
nkeynes@23 | 207 |
|
nkeynes@30 | 208 | void TMU_run_slice( uint32_t nanosecs )
|
nkeynes@23 | 209 | {
|
nkeynes@23 | 210 | int tcr = MMIO_READ( TMU, TSTR );
|
nkeynes@23 | 211 | if( tcr & 0x01 ) {
|
nkeynes@53 | 212 | TMU_count( 0, nanosecs );
|
nkeynes@53 | 213 | TMU_timers[0].timer_run = 0;
|
nkeynes@23 | 214 | }
|
nkeynes@23 | 215 | if( tcr & 0x02 ) {
|
nkeynes@53 | 216 | TMU_count( 1, nanosecs );
|
nkeynes@53 | 217 | TMU_timers[1].timer_run = 0;
|
nkeynes@23 | 218 | }
|
nkeynes@23 | 219 | if( tcr & 0x04 ) {
|
nkeynes@53 | 220 | TMU_count( 2, nanosecs );
|
nkeynes@53 | 221 | TMU_timers[2].timer_run = 0;
|
nkeynes@23 | 222 | }
|
nkeynes@23 | 223 | }
|
nkeynes@53 | 224 |
|
nkeynes@53 | 225 | void TMU_update_clocks()
|
nkeynes@53 | 226 | {
|
nkeynes@53 | 227 | TMU_set_timer_period( 0, MMIO_READ( TMU, TCR0 ) );
|
nkeynes@53 | 228 | TMU_set_timer_period( 1, MMIO_READ( TMU, TCR1 ) );
|
nkeynes@53 | 229 | TMU_set_timer_period( 2, MMIO_READ( TMU, TCR2 ) );
|
nkeynes@53 | 230 | }
|
nkeynes@53 | 231 |
|
nkeynes@53 | 232 | void TMU_reset( )
|
nkeynes@53 | 233 | {
|
nkeynes@53 | 234 | TMU_timers[0].timer_remainder = 0;
|
nkeynes@53 | 235 | TMU_timers[0].timer_run = 0;
|
nkeynes@53 | 236 | TMU_timers[1].timer_remainder = 0;
|
nkeynes@53 | 237 | TMU_timers[1].timer_run = 0;
|
nkeynes@53 | 238 | TMU_timers[2].timer_remainder = 0;
|
nkeynes@53 | 239 | TMU_timers[2].timer_run = 0;
|
nkeynes@53 | 240 | TMU_update_clocks();
|
nkeynes@53 | 241 | }
|
nkeynes@53 | 242 |
|
nkeynes@53 | 243 | void TMU_save_state( FILE *f ) {
|
nkeynes@53 | 244 | fwrite( &TMU_timers, sizeof(TMU_timers), 1, f );
|
nkeynes@53 | 245 | }
|
nkeynes@53 | 246 |
|
nkeynes@53 | 247 | int TMU_load_state( FILE *f )
|
nkeynes@53 | 248 | {
|
nkeynes@53 | 249 | fread( &TMU_timers, sizeof(TMU_timers), 1, f );
|
nkeynes@53 | 250 | return 0;
|
nkeynes@53 | 251 | }
|