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