Search
lxdream.org :: lxdream/src/sh4/timer.c
lxdream 0.9.1
released Jun 29
Download Now
filename src/sh4/timer.c
changeset 1127:4b8194e3974c
prev1124:aacaae9812ea
author nkeynes
date Fri May 29 18:47:05 2015 +1000 (8 years ago)
permissions -rw-r--r--
last change Fix test case
view annotate diff log raw
     1 /**
     2  * $Id$
     3  * 
     4  * SH4 Timer/Clock peripheral modules (CPG, TMU, RTC), combined together to
     5  * keep things simple (they intertwine a bit).
     6  *
     7  * Copyright (c) 2005 Nathan Keynes.
     8  *
     9  * This program is free software; you can redistribute it and/or modify
    10  * it under the terms of the GNU General Public License as published by
    11  * the Free Software Foundation; either version 2 of the License, or
    12  * (at your option) any later version.
    13  *
    14  * This program is distributed in the hope that it will be useful,
    15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    17  * GNU General Public License for more details.
    18  */
    20 #include <assert.h>
    21 #include "lxdream.h"
    22 #include "mem.h"
    23 #include "clock.h"
    24 #include "eventq.h"
    25 #include "sh4/sh4core.h"
    26 #include "sh4/sh4mmio.h"
    27 #include "sh4/intc.h"
    29 /********************************* CPG *************************************/
    30 /* This is the base clock from which all other clocks are derived. 
    31  * Note: The real clock runs at 33Mhz, which is multiplied by the PLL to
    32  * run the instruction clock at 200Mhz. For sake of simplicity/precision,
    33  * we instead use 200Mhz as the base rate and divide everything down instead.
    34  **/
    35 uint32_t sh4_input_freq = SH4_BASE_RATE;
    37 uint32_t sh4_cpu_multiplier = 2000; /* = 0.5 * frequency */
    39 uint32_t sh4_cpu_freq = SH4_BASE_RATE;
    40 uint32_t sh4_bus_freq = SH4_BASE_RATE / 2;
    41 uint32_t sh4_peripheral_freq = SH4_BASE_RATE / 4;
    43 uint32_t sh4_cpu_period = 1000 / SH4_BASE_RATE; /* in nanoseconds */
    44 uint32_t sh4_bus_period = 2* 1000 / SH4_BASE_RATE;
    45 uint32_t sh4_peripheral_period = 4 * 1000 / SH4_BASE_RATE;
    47 MMIO_REGION_READ_FN( CPG, reg )
    48 {
    49     return MMIO_READ( CPG, reg&0xFFF );
    50 }
    51 MMIO_REGION_READ_DEFSUBFNS(CPG)
    53 /* CPU + bus dividers (note officially only the first 6 values are valid) */
    54 int ifc_divider[8] = { 1, 2, 3, 4, 5, 8, 8, 8 };
    55 /* Peripheral clock dividers (only first 5 are officially valid) */
    56 int pfc_divider[8] = { 2, 3, 4, 6, 8, 8, 8, 8 };
    58 MMIO_REGION_WRITE_FN( CPG, reg, val )
    59 {
    60     uint32_t div;
    61     uint32_t primary_clock = sh4_input_freq;
    62     reg &= 0xFFF;
    63     switch( reg ) {
    64     case FRQCR: /* Frequency control */
    65         if( (val & FRQCR_PLL1EN) == 0 )
    66             primary_clock /= 6;
    67         div = ifc_divider[(val >> 6) & 0x07];
    68         sh4_cpu_freq = primary_clock / div;
    69         sh4_cpu_period = sh4_cpu_multiplier * div / sh4_input_freq;
    70         div = ifc_divider[(val >> 3) & 0x07];
    71         sh4_bus_freq = primary_clock / div;
    72         sh4_bus_period = 1000 * div / sh4_input_freq;
    73         div = pfc_divider[val & 0x07];
    74         sh4_peripheral_freq = primary_clock / div;
    75         sh4_peripheral_period = 1000 * div / sh4_input_freq;
    77         /* Update everything that depends on the peripheral frequency */
    78         SCIF_update_line_speed();
    79         break;
    80     case WTCSR: /* Watchdog timer */
    81         break;
    82     }
    84     MMIO_WRITE( CPG, reg, val );
    85 }
    87 /**
    88  * We don't really know what the default reset value is as it's determined
    89  * by the mode select pins. This is the standard value that the BIOS sets,
    90  * however, so it works for now.
    91  */
    92 void CPG_reset( )
    93 {
    94     mmio_region_CPG_write( FRQCR, 0x0E0A );
    95 }
    98 /********************************** RTC *************************************/
   100 uint32_t rtc_output_period;
   102 MMIO_REGION_READ_FN( RTC, reg )
   103 {
   104     return MMIO_READ( RTC, reg &0xFFF );
   105 }
   106 MMIO_REGION_READ_DEFSUBFNS(RTC)
   108 MMIO_REGION_WRITE_FN( RTC, reg, val )
   109 {
   110     MMIO_WRITE( RTC, reg &0xFFF, val );
   111 }
   113 /********************************** TMU *************************************/
   115 #define TCR_ICPF 0x0200
   116 #define TCR_UNF  0x0100
   117 #define TCR_UNIE 0x0020
   119 #define TCR_IRQ_ACTIVE (TCR_UNF|TCR_UNIE)
   121 #define TMU_IS_RUNNING(timer)  (MMIO_READ(TMU,TSTR) & (1<<timer))
   123 struct TMU_timer {
   124     uint32_t timer_period;
   125     uint32_t timer_remainder; /* left-over cycles from last count */
   126     uint32_t timer_run; /* cycles already run from this slice */
   127 };
   129 static struct TMU_timer TMU_timers[3];
   131 uint32_t TMU_count( int timer, uint32_t nanosecs );
   132 void TMU_schedule_timer( int timer );
   134 void TMU_event_callback( int eventid )
   135 {
   136     TMU_count( eventid - EVENT_TMU0, sh4r.slice_cycle );
   137     assert( MMIO_READ( TMU, TCR0 + (eventid - EVENT_TMU0)*12 ) & 0x100 );
   138 }
   140 void TMU_init(void)
   141 {
   142     register_event_callback( EVENT_TMU0, TMU_event_callback );
   143     register_event_callback( EVENT_TMU1, TMU_event_callback );
   144     register_event_callback( EVENT_TMU2, TMU_event_callback );
   145 }    
   147 void TMU_dump(unsigned timer)
   148 {
   149     fprintf(stderr, "Timer %d: %s %08x/%08x %dns run: %08X - %08X\n",
   150             timer, TMU_IS_RUNNING(timer) ? "running" : "stopped",
   151             MMIO_READ(TMU, TCNT0 + (timer*12)), MMIO_READ(TMU, TCOR0 + (timer*12)),
   152             TMU_timers[timer].timer_period,
   153             TMU_timers[timer].timer_run,
   154             TMU_timers[timer].timer_remainder );
   155 }
   158 void TMU_set_timer_control( int timer,  int tcr )
   159 {
   160     uint32_t period = 1;
   161     uint32_t oldtcr = MMIO_READ( TMU, TCR0 + (12*timer) );
   163     if( (oldtcr & TCR_UNF) == 0 ) {
   164         tcr = tcr & (~TCR_UNF);
   165     } else {
   166         if( ((oldtcr & TCR_UNIE) == 0) && 
   167                 (tcr & TCR_IRQ_ACTIVE) == TCR_IRQ_ACTIVE ) {
   168             intc_raise_interrupt( INT_TMU_TUNI0 + timer );
   169         } else if( (oldtcr & TCR_UNIE) != 0 && 
   170                 (tcr & TCR_IRQ_ACTIVE) != TCR_IRQ_ACTIVE ) {
   171             intc_clear_interrupt( INT_TMU_TUNI0 + timer );
   172         }
   173     }
   175     switch( tcr & 0x07 ) {
   176     case 0:
   177         period = sh4_peripheral_period << 2 ;
   178         break;
   179     case 1: 
   180         period = sh4_peripheral_period << 4;
   181         break;
   182     case 2:
   183         period = sh4_peripheral_period << 6;
   184         break;
   185     case 3: 
   186         period = sh4_peripheral_period << 8;
   187         break;
   188     case 4:
   189         period = sh4_peripheral_period << 10;
   190         break;
   191     case 5:
   192         /* Illegal value. */
   193         ERROR( "TMU %d period set to illegal value (5)", timer );
   194         period = sh4_peripheral_period << 12; /* for something to do */
   195         break;
   196     case 6:
   197         period = rtc_output_period;
   198         break;
   199     case 7:
   200         /* External clock... Hrm? */
   201         period = sh4_peripheral_period; /* I dunno... */
   202         break;
   203     }
   205     if( period != TMU_timers[timer].timer_period ) {
   206         if( TMU_IS_RUNNING(timer) ) {
   207             /* If we're changing clock speed while counting, sync up and reschedule */
   208             TMU_count(timer, sh4r.slice_cycle);
   209             TMU_timers[timer].timer_period = period;
   210             TMU_schedule_timer(timer);
   211         } else {
   212             TMU_timers[timer].timer_period = period;
   213         }
   214     }
   216     MMIO_WRITE( TMU, TCR0 + (12*timer), tcr );
   217 }
   219 void TMU_schedule_timer( int timer )
   220 {
   221     uint64_t duration = ((uint64_t)((uint32_t)(MMIO_READ( TMU, TCNT0 + 12*timer )))+1) * 
   222     (uint64_t)TMU_timers[timer].timer_period - TMU_timers[timer].timer_remainder;
   223     event_schedule_long( EVENT_TMU0+timer, (uint32_t)(duration / 1000000000), 
   224                          (uint32_t)(duration % 1000000000) );
   225 //    if( timer == 2 ) {
   226 //        WARN( "Schedule timer %d: %lldns", timer, duration );
   227 //        TMU_dump(timer);
   228 //    }
   229 }
   231 void TMU_start( int timer )
   232 {
   233     TMU_timers[timer].timer_run = sh4r.slice_cycle;
   234     TMU_timers[timer].timer_remainder = 0;
   235     TMU_schedule_timer( timer );
   236 }
   238 /**
   239  * Stop the given timer. Run it up to the current time and leave it there.
   240  */
   241 void TMU_stop( int timer )
   242 {
   243     TMU_count( timer, sh4r.slice_cycle );
   244     event_cancel( EVENT_TMU0+timer );
   245 }
   247 /**
   248  * Count the specified timer for a given number of nanoseconds.
   249  */
   250 uint32_t TMU_count( int timer, uint32_t nanosecs ) 
   251 {
   252     uint32_t run_ns = nanosecs + TMU_timers[timer].timer_remainder -
   253     TMU_timers[timer].timer_run;
   254     TMU_timers[timer].timer_remainder = 
   255         run_ns % TMU_timers[timer].timer_period;
   256     TMU_timers[timer].timer_run = nanosecs;
   257     uint32_t count = run_ns / TMU_timers[timer].timer_period;
   258     uint32_t value = MMIO_READ( TMU, TCNT0 + 12*timer );
   259     uint32_t reset = MMIO_READ( TMU, TCOR0 + 12*timer );
   260 //    if( timer == 2 )
   261 //        WARN( "Counting timer %d: %d ns, %d ticks", timer, run_ns, count );
   262     if( count > value ) {
   263         uint32_t tcr = MMIO_READ( TMU, TCR0 + 12*timer );
   264         tcr |= TCR_UNF;
   265         count -= value;
   266         value = reset - (count % reset) + 1;
   267         MMIO_WRITE( TMU, TCR0 + 12*timer, tcr );
   268         if( tcr & TCR_UNIE ) 
   269             intc_raise_interrupt( INT_TMU_TUNI0 + timer );
   270         MMIO_WRITE( TMU, TCNT0 + 12*timer, value );
   271 //        if( timer == 2 )
   272 //            WARN( "Underflowed timer %d", timer );
   273         TMU_schedule_timer(timer);
   274     } else {
   275         value -= count;
   276         MMIO_WRITE( TMU, TCNT0 + 12*timer, value );
   277     }
   278     return value;
   279 }
   281 MMIO_REGION_READ_FN( TMU, reg )
   282 {
   283     reg &= 0xFFF;
   284     switch( reg ) {
   285     case TCNT0:
   286         if( TMU_IS_RUNNING(0) )
   287             TMU_count( 0, sh4r.slice_cycle );
   288         break;
   289     case TCNT1:
   290         if( TMU_IS_RUNNING(1) )
   291             TMU_count( 1, sh4r.slice_cycle );
   292         break;
   293     case TCNT2:
   294         if( TMU_IS_RUNNING(2) )
   295             TMU_count( 2, sh4r.slice_cycle );
   296         break;
   297     }
   298     return MMIO_READ( TMU, reg );
   299 }
   300 MMIO_REGION_READ_DEFSUBFNS(TMU)
   303 MMIO_REGION_WRITE_FN( TMU, reg, val )
   304 {
   305     uint32_t oldval;
   306     int i;
   307     reg &= 0xFFF;
   308     switch( reg ) {
   309     case TSTR:
   310         oldval = MMIO_READ( TMU, TSTR );
   311         for( i=0; i<3; i++ ) {
   312             uint32_t tmp = 1<<i;
   313             if( (oldval & tmp) != 0 && (val&tmp) == 0  )
   314                 TMU_stop(i);
   315             else if( (oldval&tmp) == 0 && (val&tmp) != 0 )
   316                 TMU_start(i);
   317         }
   318         break;
   319     case TCR0:
   320         TMU_set_timer_control( 0, val );
   321         return;
   322     case TCR1:
   323         TMU_set_timer_control( 1, val );
   324         return;
   325     case TCR2:
   326         TMU_set_timer_control( 2, val );
   327         return;
   328     case TCNT0:
   329         MMIO_WRITE( TMU, reg, val );
   330         if( TMU_IS_RUNNING(0) ) { // reschedule
   331             TMU_timers[0].timer_run = sh4r.slice_cycle;
   332             TMU_schedule_timer( 0 );
   333         }
   334         return;
   335     case TCNT1:
   336         MMIO_WRITE( TMU, reg, val );
   337         if( TMU_IS_RUNNING(1) ) { // reschedule
   338             TMU_timers[1].timer_run = sh4r.slice_cycle;
   339             TMU_schedule_timer( 1 );
   340         }
   341         return;
   342     case TCNT2:
   343         MMIO_WRITE( TMU, reg, val );
   344         if( TMU_IS_RUNNING(2) ) { // reschedule
   345             TMU_timers[2].timer_run = sh4r.slice_cycle;
   346             TMU_schedule_timer( 2 );
   347         }
   348         return;
   349     }
   350     MMIO_WRITE( TMU, reg, val );
   351 }
   353 void TMU_count_all( uint32_t nanosecs )
   354 {
   355     int tcr = MMIO_READ( TMU, TSTR );
   356     if( tcr & 0x01 ) {
   357         TMU_count( 0, nanosecs );
   358     }
   359     if( tcr & 0x02 ) {
   360         TMU_count( 1, nanosecs );
   361     }
   362     if( tcr & 0x04 ) {
   363         TMU_count( 2, nanosecs );
   364     }
   365 }
   367 void TMU_run_slice( uint32_t nanosecs )
   368 {
   369     TMU_count_all( nanosecs );
   370     TMU_timers[0].timer_run = 0;
   371     TMU_timers[1].timer_run = 0;
   372     TMU_timers[2].timer_run = 0;
   373 }
   375 void TMU_update_clocks()
   376 {
   377     TMU_set_timer_control( 0, MMIO_READ( TMU, TCR0 ) );
   378     TMU_set_timer_control( 1, MMIO_READ( TMU, TCR1 ) );
   379     TMU_set_timer_control( 2, MMIO_READ( TMU, TCR2 ) );
   380 }
   382 void TMU_reset( )
   383 {
   384     TMU_timers[0].timer_remainder = 0;
   385     TMU_timers[0].timer_run = 0;
   386     TMU_timers[1].timer_remainder = 0;
   387     TMU_timers[1].timer_run = 0;
   388     TMU_timers[2].timer_remainder = 0;
   389     TMU_timers[2].timer_run = 0;
   390     TMU_update_clocks();
   391 }
   393 void TMU_save_state( FILE *f ) {
   394     fwrite( &TMU_timers, sizeof(TMU_timers), 1, f );
   395 }
   397 int TMU_load_state( FILE *f ) 
   398 {
   399     fread( &TMU_timers, sizeof(TMU_timers), 1, f );
   400     return 0;
   401 }
.