Search
lxdream.org :: lxdream/src/sh4/timer.c
lxdream 0.9.1
released Jun 29
Download Now
filename src/sh4/timer.c
changeset 1124:aacaae9812ea
prev975:007bf7eb944f
next1127:4b8194e3974c
author nkeynes
date Sat Sep 11 09:58:22 2010 +1000 (13 years ago)
permissions -rw-r--r--
last change GTK: Force file extension when creating files
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@1124
    45
uint32_t sh4_peripheral_period = 4 * 1000 / 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@1124
   115
#define TCR_ICPF 0x0200
nkeynes@1124
   116
#define TCR_UNF  0x0100
nkeynes@1124
   117
#define TCR_UNIE 0x0020
nkeynes@1124
   118
nkeynes@1124
   119
#define TCR_IRQ_ACTIVE (TCR_UNF|TCR_UNIE)
nkeynes@1124
   120
nkeynes@619
   121
#define TMU_IS_RUNNING(timer)  (MMIO_READ(TMU,TSTR) & (1<<timer))
nkeynes@619
   122
nkeynes@1124
   123
struct TMU_timer {
nkeynes@1124
   124
    uint32_t timer_period;
nkeynes@1124
   125
    uint32_t timer_remainder; /* left-over cycles from last count */
nkeynes@1124
   126
    uint32_t timer_run; /* cycles already run from this slice */
nkeynes@1124
   127
};
nkeynes@1124
   128
nkeynes@1124
   129
static struct TMU_timer TMU_timers[3];
nkeynes@1124
   130
nkeynes@260
   131
uint32_t TMU_count( int timer, uint32_t nanosecs );
nkeynes@1124
   132
void TMU_schedule_timer( int timer );
nkeynes@260
   133
nkeynes@619
   134
void TMU_event_callback( int eventid )
nkeynes@619
   135
{
nkeynes@619
   136
    TMU_count( eventid - EVENT_TMU0, sh4r.slice_cycle );
nkeynes@1124
   137
    assert( MMIO_READ( TMU, TCR0 + (eventid - EVENT_TMU0)*12 ) & 0x100 );
nkeynes@619
   138
}
nkeynes@619
   139
nkeynes@619
   140
void TMU_init(void)
nkeynes@619
   141
{
nkeynes@619
   142
    register_event_callback( EVENT_TMU0, TMU_event_callback );
nkeynes@619
   143
    register_event_callback( EVENT_TMU1, TMU_event_callback );
nkeynes@619
   144
    register_event_callback( EVENT_TMU2, TMU_event_callback );
nkeynes@619
   145
}    
nkeynes@260
   146
nkeynes@1124
   147
void TMU_dump(unsigned timer)
nkeynes@1124
   148
{
nkeynes@1124
   149
    fprintf(stderr, "Timer %d: %s %08x/%08x %dns run: %08X - %08X\n",
nkeynes@1124
   150
            timer, TMU_IS_RUNNING(timer) ? "running" : "stopped",
nkeynes@1124
   151
            MMIO_READ(TMU, TCNT0 + (timer*12)), MMIO_READ(TMU, TCOR0 + (timer*12)),
nkeynes@1124
   152
            TMU_timers[timer].timer_period,
nkeynes@1124
   153
            TMU_timers[timer].timer_run,
nkeynes@1124
   154
            TMU_timers[timer].timer_remainder );
nkeynes@1124
   155
}
nkeynes@53
   156
nkeynes@23
   157
nkeynes@115
   158
void TMU_set_timer_control( int timer,  int tcr )
nkeynes@53
   159
{
nkeynes@53
   160
    uint32_t period = 1;
nkeynes@115
   161
    uint32_t oldtcr = MMIO_READ( TMU, TCR0 + (12*timer) );
nkeynes@115
   162
nkeynes@115
   163
    if( (oldtcr & TCR_UNF) == 0 ) {
nkeynes@736
   164
        tcr = tcr & (~TCR_UNF);
nkeynes@115
   165
    } else {
nkeynes@736
   166
        if( ((oldtcr & TCR_UNIE) == 0) && 
nkeynes@736
   167
                (tcr & TCR_IRQ_ACTIVE) == TCR_IRQ_ACTIVE ) {
nkeynes@736
   168
            intc_raise_interrupt( INT_TMU_TUNI0 + timer );
nkeynes@736
   169
        } else if( (oldtcr & TCR_UNIE) != 0 && 
nkeynes@736
   170
                (tcr & TCR_IRQ_ACTIVE) != TCR_IRQ_ACTIVE ) {
nkeynes@736
   171
            intc_clear_interrupt( INT_TMU_TUNI0 + timer );
nkeynes@736
   172
        }
nkeynes@115
   173
    }
nkeynes@115
   174
nkeynes@53
   175
    switch( tcr & 0x07 ) {
nkeynes@53
   176
    case 0:
nkeynes@736
   177
        period = sh4_peripheral_period << 2 ;
nkeynes@736
   178
        break;
nkeynes@53
   179
    case 1: 
nkeynes@736
   180
        period = sh4_peripheral_period << 4;
nkeynes@736
   181
        break;
nkeynes@53
   182
    case 2:
nkeynes@736
   183
        period = sh4_peripheral_period << 6;
nkeynes@736
   184
        break;
nkeynes@53
   185
    case 3: 
nkeynes@736
   186
        period = sh4_peripheral_period << 8;
nkeynes@736
   187
        break;
nkeynes@53
   188
    case 4:
nkeynes@736
   189
        period = sh4_peripheral_period << 10;
nkeynes@736
   190
        break;
nkeynes@53
   191
    case 5:
nkeynes@736
   192
        /* Illegal value. */
nkeynes@736
   193
        ERROR( "TMU %d period set to illegal value (5)", timer );
nkeynes@736
   194
        period = sh4_peripheral_period << 12; /* for something to do */
nkeynes@736
   195
        break;
nkeynes@53
   196
    case 6:
nkeynes@736
   197
        period = rtc_output_period;
nkeynes@736
   198
        break;
nkeynes@53
   199
    case 7:
nkeynes@736
   200
        /* External clock... Hrm? */
nkeynes@736
   201
        period = sh4_peripheral_period; /* I dunno... */
nkeynes@736
   202
        break;
nkeynes@53
   203
    }
nkeynes@1124
   204
nkeynes@1124
   205
    if( period != TMU_timers[timer].timer_period ) {
nkeynes@1124
   206
        if( TMU_IS_RUNNING(timer) ) {
nkeynes@1124
   207
            /* If we're changing clock speed while counting, sync up and reschedule */
nkeynes@1124
   208
            TMU_count(timer, sh4r.slice_cycle);
nkeynes@1124
   209
            TMU_timers[timer].timer_period = period;
nkeynes@1124
   210
            TMU_schedule_timer(timer);
nkeynes@1124
   211
        } else {
nkeynes@1124
   212
            TMU_timers[timer].timer_period = period;
nkeynes@1124
   213
        }
nkeynes@1124
   214
    }
nkeynes@115
   215
nkeynes@115
   216
    MMIO_WRITE( TMU, TCR0 + (12*timer), tcr );
nkeynes@53
   217
}
nkeynes@23
   218
nkeynes@619
   219
void TMU_schedule_timer( int timer )
nkeynes@619
   220
{
nkeynes@1124
   221
    uint64_t duration = ((uint64_t)((uint32_t)(MMIO_READ( TMU, TCNT0 + 12*timer )))+1) * 
nkeynes@736
   222
    (uint64_t)TMU_timers[timer].timer_period - TMU_timers[timer].timer_remainder;
nkeynes@619
   223
    event_schedule_long( EVENT_TMU0+timer, (uint32_t)(duration / 1000000000), 
nkeynes@736
   224
                         (uint32_t)(duration % 1000000000) );
nkeynes@1124
   225
//    if( timer == 2 ) {
nkeynes@1124
   226
//        WARN( "Schedule timer %d: %lldns", timer, duration );
nkeynes@1124
   227
//        TMU_dump(timer);
nkeynes@1124
   228
//    }
nkeynes@619
   229
}
nkeynes@619
   230
nkeynes@53
   231
void TMU_start( int timer )
nkeynes@23
   232
{
nkeynes@260
   233
    TMU_timers[timer].timer_run = sh4r.slice_cycle;
nkeynes@53
   234
    TMU_timers[timer].timer_remainder = 0;
nkeynes@619
   235
    TMU_schedule_timer( timer );
nkeynes@53
   236
}
nkeynes@53
   237
nkeynes@264
   238
/**
nkeynes@264
   239
 * Stop the given timer. Run it up to the current time and leave it there.
nkeynes@264
   240
 */
nkeynes@53
   241
void TMU_stop( int timer )
nkeynes@53
   242
{
nkeynes@264
   243
    TMU_count( timer, sh4r.slice_cycle );
nkeynes@619
   244
    event_cancel( EVENT_TMU0+timer );
nkeynes@53
   245
}
nkeynes@53
   246
nkeynes@53
   247
/**
nkeynes@53
   248
 * Count the specified timer for a given number of nanoseconds.
nkeynes@53
   249
 */
nkeynes@53
   250
uint32_t TMU_count( int timer, uint32_t nanosecs ) 
nkeynes@53
   251
{
nkeynes@619
   252
    uint32_t run_ns = nanosecs + TMU_timers[timer].timer_remainder -
nkeynes@736
   253
    TMU_timers[timer].timer_run;
nkeynes@53
   254
    TMU_timers[timer].timer_remainder = 
nkeynes@736
   255
        run_ns % TMU_timers[timer].timer_period;
nkeynes@619
   256
    TMU_timers[timer].timer_run = nanosecs;
nkeynes@619
   257
    uint32_t count = run_ns / TMU_timers[timer].timer_period;
nkeynes@53
   258
    uint32_t value = MMIO_READ( TMU, TCNT0 + 12*timer );
nkeynes@53
   259
    uint32_t reset = MMIO_READ( TMU, TCOR0 + 12*timer );
nkeynes@1124
   260
//    if( timer == 2 )
nkeynes@1124
   261
//        WARN( "Counting timer %d: %d ns, %d ticks", timer, run_ns, count );
nkeynes@53
   262
    if( count > value ) {
nkeynes@736
   263
        uint32_t tcr = MMIO_READ( TMU, TCR0 + 12*timer );
nkeynes@736
   264
        tcr |= TCR_UNF;
nkeynes@736
   265
        count -= value;
nkeynes@619
   266
        value = reset - (count % reset) + 1;
nkeynes@736
   267
        MMIO_WRITE( TMU, TCR0 + 12*timer, tcr );
nkeynes@736
   268
        if( tcr & TCR_UNIE ) 
nkeynes@736
   269
            intc_raise_interrupt( INT_TMU_TUNI0 + timer );
nkeynes@736
   270
        MMIO_WRITE( TMU, TCNT0 + 12*timer, value );
nkeynes@1124
   271
//        if( timer == 2 )
nkeynes@1124
   272
//            WARN( "Underflowed timer %d", timer );
nkeynes@736
   273
        TMU_schedule_timer(timer);
nkeynes@53
   274
    } else {
nkeynes@736
   275
        value -= count;
nkeynes@736
   276
        MMIO_WRITE( TMU, TCNT0 + 12*timer, value );
nkeynes@23
   277
    }
nkeynes@53
   278
    return value;
nkeynes@23
   279
}
nkeynes@23
   280
nkeynes@929
   281
MMIO_REGION_READ_FN( TMU, reg )
nkeynes@929
   282
{
nkeynes@929
   283
    reg &= 0xFFF;
nkeynes@929
   284
    switch( reg ) {
nkeynes@929
   285
    case TCNT0:
nkeynes@929
   286
        TMU_count( 0, sh4r.slice_cycle );
nkeynes@929
   287
        break;
nkeynes@929
   288
    case TCNT1:
nkeynes@929
   289
        TMU_count( 1, sh4r.slice_cycle );
nkeynes@929
   290
        break;
nkeynes@929
   291
    case TCNT2:
nkeynes@929
   292
        TMU_count( 2, sh4r.slice_cycle );
nkeynes@929
   293
        break;
nkeynes@929
   294
    }
nkeynes@929
   295
    return MMIO_READ( TMU, reg );
nkeynes@929
   296
}
nkeynes@975
   297
MMIO_REGION_READ_DEFSUBFNS(TMU)
nkeynes@975
   298
nkeynes@929
   299
nkeynes@929
   300
MMIO_REGION_WRITE_FN( TMU, reg, val )
nkeynes@23
   301
{
nkeynes@53
   302
    uint32_t oldval;
nkeynes@53
   303
    int i;
nkeynes@929
   304
    reg &= 0xFFF;
nkeynes@23
   305
    switch( reg ) {
nkeynes@53
   306
    case TSTR:
nkeynes@736
   307
        oldval = MMIO_READ( TMU, TSTR );
nkeynes@736
   308
        for( i=0; i<3; i++ ) {
nkeynes@736
   309
            uint32_t tmp = 1<<i;
nkeynes@736
   310
            if( (oldval & tmp) != 0 && (val&tmp) == 0  )
nkeynes@736
   311
                TMU_stop(i);
nkeynes@736
   312
            else if( (oldval&tmp) == 0 && (val&tmp) != 0 )
nkeynes@736
   313
                TMU_start(i);
nkeynes@736
   314
        }
nkeynes@736
   315
        break;
nkeynes@53
   316
    case TCR0:
nkeynes@736
   317
        TMU_set_timer_control( 0, val );
nkeynes@736
   318
        return;
nkeynes@53
   319
    case TCR1:
nkeynes@736
   320
        TMU_set_timer_control( 1, val );
nkeynes@736
   321
        return;
nkeynes@53
   322
    case TCR2:
nkeynes@736
   323
        TMU_set_timer_control( 2, val );
nkeynes@736
   324
        return;
nkeynes@619
   325
    case TCNT0:
nkeynes@736
   326
        MMIO_WRITE( TMU, reg, val );
nkeynes@736
   327
        if( TMU_IS_RUNNING(0) ) { // reschedule
nkeynes@736
   328
            TMU_timers[0].timer_run = sh4r.slice_cycle;
nkeynes@736
   329
            TMU_schedule_timer( 0 );
nkeynes@736
   330
        }
nkeynes@736
   331
        return;
nkeynes@619
   332
    case TCNT1:
nkeynes@736
   333
        MMIO_WRITE( TMU, reg, val );
nkeynes@736
   334
        if( TMU_IS_RUNNING(1) ) { // reschedule
nkeynes@736
   335
            TMU_timers[1].timer_run = sh4r.slice_cycle;
nkeynes@736
   336
            TMU_schedule_timer( 1 );
nkeynes@736
   337
        }
nkeynes@736
   338
        return;
nkeynes@619
   339
    case TCNT2:
nkeynes@736
   340
        MMIO_WRITE( TMU, reg, val );
nkeynes@736
   341
        if( TMU_IS_RUNNING(2) ) { // reschedule
nkeynes@736
   342
            TMU_timers[2].timer_run = sh4r.slice_cycle;
nkeynes@736
   343
            TMU_schedule_timer( 2 );
nkeynes@736
   344
        }
nkeynes@736
   345
        return;
nkeynes@23
   346
    }
nkeynes@23
   347
    MMIO_WRITE( TMU, reg, val );
nkeynes@23
   348
}
nkeynes@23
   349
nkeynes@619
   350
void TMU_count_all( uint32_t nanosecs )
nkeynes@23
   351
{
nkeynes@23
   352
    int tcr = MMIO_READ( TMU, TSTR );
nkeynes@23
   353
    if( tcr & 0x01 ) {
nkeynes@736
   354
        TMU_count( 0, nanosecs );
nkeynes@23
   355
    }
nkeynes@23
   356
    if( tcr & 0x02 ) {
nkeynes@736
   357
        TMU_count( 1, nanosecs );
nkeynes@23
   358
    }
nkeynes@23
   359
    if( tcr & 0x04 ) {
nkeynes@736
   360
        TMU_count( 2, nanosecs );
nkeynes@23
   361
    }
nkeynes@23
   362
}
nkeynes@53
   363
nkeynes@619
   364
void TMU_run_slice( uint32_t nanosecs )
nkeynes@619
   365
{
nkeynes@619
   366
    TMU_count_all( nanosecs );
nkeynes@619
   367
    TMU_timers[0].timer_run = 0;
nkeynes@619
   368
    TMU_timers[1].timer_run = 0;
nkeynes@619
   369
    TMU_timers[2].timer_run = 0;
nkeynes@619
   370
}
nkeynes@619
   371
nkeynes@53
   372
void TMU_update_clocks()
nkeynes@53
   373
{
nkeynes@115
   374
    TMU_set_timer_control( 0, MMIO_READ( TMU, TCR0 ) );
nkeynes@115
   375
    TMU_set_timer_control( 1, MMIO_READ( TMU, TCR1 ) );
nkeynes@115
   376
    TMU_set_timer_control( 2, MMIO_READ( TMU, TCR2 ) );
nkeynes@53
   377
}
nkeynes@53
   378
nkeynes@53
   379
void TMU_reset( )
nkeynes@53
   380
{
nkeynes@53
   381
    TMU_timers[0].timer_remainder = 0;
nkeynes@53
   382
    TMU_timers[0].timer_run = 0;
nkeynes@53
   383
    TMU_timers[1].timer_remainder = 0;
nkeynes@53
   384
    TMU_timers[1].timer_run = 0;
nkeynes@53
   385
    TMU_timers[2].timer_remainder = 0;
nkeynes@53
   386
    TMU_timers[2].timer_run = 0;
nkeynes@53
   387
    TMU_update_clocks();
nkeynes@53
   388
}
nkeynes@53
   389
nkeynes@53
   390
void TMU_save_state( FILE *f ) {
nkeynes@53
   391
    fwrite( &TMU_timers, sizeof(TMU_timers), 1, f );
nkeynes@53
   392
}
nkeynes@53
   393
nkeynes@53
   394
int TMU_load_state( FILE *f ) 
nkeynes@53
   395
{
nkeynes@53
   396
    fread( &TMU_timers, sizeof(TMU_timers), 1, f );
nkeynes@53
   397
    return 0;
nkeynes@53
   398
}
.