nkeynes@23: /** nkeynes@561: * $Id$ nkeynes@23: * nkeynes@23: * SH4 Timer/Clock peripheral modules (CPG, TMU, RTC), combined together to nkeynes@23: * keep things simple (they intertwine a bit). nkeynes@23: * nkeynes@23: * Copyright (c) 2005 Nathan Keynes. nkeynes@23: * nkeynes@23: * This program is free software; you can redistribute it and/or modify nkeynes@23: * it under the terms of the GNU General Public License as published by nkeynes@23: * the Free Software Foundation; either version 2 of the License, or nkeynes@23: * (at your option) any later version. nkeynes@23: * nkeynes@23: * This program is distributed in the hope that it will be useful, nkeynes@23: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@23: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@23: * GNU General Public License for more details. nkeynes@23: */ nkeynes@23: nkeynes@23: #include "dream.h" nkeynes@23: #include "mem.h" nkeynes@23: #include "clock.h" nkeynes@23: #include "sh4core.h" nkeynes@23: #include "sh4mmio.h" nkeynes@53: #include "intc.h" nkeynes@23: nkeynes@23: /********************************* CPG *************************************/ nkeynes@53: /* This is the base clock from which all other clocks are derived */ nkeynes@53: uint32_t sh4_input_freq = SH4_BASE_RATE; nkeynes@53: nkeynes@414: uint32_t sh4_cpu_multiplier = 2000; /* = 0.5 * frequency */ nkeynes@414: nkeynes@53: uint32_t sh4_cpu_freq = SH4_BASE_RATE; nkeynes@53: uint32_t sh4_bus_freq = SH4_BASE_RATE; nkeynes@53: uint32_t sh4_peripheral_freq = SH4_BASE_RATE / 2; nkeynes@53: nkeynes@53: uint32_t sh4_cpu_period = 1000 / SH4_BASE_RATE; /* in nanoseconds */ nkeynes@53: uint32_t sh4_bus_period = 1000 / SH4_BASE_RATE; nkeynes@53: uint32_t sh4_peripheral_period = 2000 / SH4_BASE_RATE; nkeynes@23: nkeynes@23: int32_t mmio_region_CPG_read( uint32_t reg ) nkeynes@23: { nkeynes@23: return MMIO_READ( CPG, reg ); nkeynes@23: } nkeynes@23: nkeynes@53: /* CPU + bus dividers (note officially only the first 6 values are valid) */ nkeynes@53: int ifc_divider[8] = { 1, 2, 3, 4, 5, 8, 8, 8 }; nkeynes@53: /* Peripheral clock dividers (only first 5 are officially valid) */ nkeynes@53: int pfc_divider[8] = { 2, 3, 4, 6, 8, 8, 8, 8 }; nkeynes@53: nkeynes@23: void mmio_region_CPG_write( uint32_t reg, uint32_t val ) nkeynes@23: { nkeynes@53: uint32_t div; nkeynes@53: switch( reg ) { nkeynes@53: case FRQCR: /* Frequency control */ nkeynes@53: div = ifc_divider[(val >> 6) & 0x07]; nkeynes@53: sh4_cpu_freq = sh4_input_freq / div; nkeynes@414: sh4_cpu_period = sh4_cpu_multiplier * div / sh4_input_freq; nkeynes@53: div = ifc_divider[(val >> 3) & 0x07]; nkeynes@53: sh4_bus_freq = sh4_input_freq / div; nkeynes@53: sh4_bus_period = 1000 * div / sh4_input_freq; nkeynes@53: div = pfc_divider[val & 0x07]; nkeynes@53: sh4_peripheral_freq = sh4_input_freq / div; nkeynes@53: sh4_peripheral_period = 1000 * div / sh4_input_freq; nkeynes@53: nkeynes@53: /* Update everything that depends on the peripheral frequency */ nkeynes@53: SCIF_update_line_speed(); nkeynes@53: break; nkeynes@53: case WTCSR: /* Watchdog timer */ nkeynes@53: break; nkeynes@53: } nkeynes@53: nkeynes@23: MMIO_WRITE( CPG, reg, val ); nkeynes@23: } nkeynes@23: nkeynes@260: /** nkeynes@260: * We don't really know what the default reset value is as it's determined nkeynes@260: * by the mode select pins. This is the standard value that the BIOS sets, nkeynes@260: * however, so it works for now. nkeynes@260: */ nkeynes@260: void CPG_reset( ) nkeynes@260: { nkeynes@260: mmio_region_CPG_write( FRQCR, 0x0E0A ); nkeynes@260: } nkeynes@260: nkeynes@260: nkeynes@23: /********************************** RTC *************************************/ nkeynes@23: nkeynes@53: uint32_t rtc_output_period; nkeynes@53: nkeynes@23: int32_t mmio_region_RTC_read( uint32_t reg ) nkeynes@23: { nkeynes@23: return MMIO_READ( RTC, reg ); nkeynes@23: } nkeynes@23: nkeynes@23: void mmio_region_RTC_write( uint32_t reg, uint32_t val ) nkeynes@23: { nkeynes@23: MMIO_WRITE( RTC, reg, val ); nkeynes@23: } nkeynes@23: nkeynes@23: /********************************** TMU *************************************/ nkeynes@23: nkeynes@260: uint32_t TMU_count( int timer, uint32_t nanosecs ); nkeynes@260: nkeynes@260: nkeynes@53: #define TCR_ICPF 0x0200 nkeynes@53: #define TCR_UNF 0x0100 nkeynes@53: #define TCR_UNIE 0x0020 nkeynes@53: nkeynes@115: #define TCR_IRQ_ACTIVE (TCR_UNF|TCR_UNIE) nkeynes@115: nkeynes@53: struct TMU_timer { nkeynes@53: uint32_t timer_period; nkeynes@53: uint32_t timer_remainder; /* left-over cycles from last count */ nkeynes@53: uint32_t timer_run; /* cycles already run from this slice */ nkeynes@53: }; nkeynes@53: nkeynes@53: struct TMU_timer TMU_timers[3]; nkeynes@23: nkeynes@23: int32_t mmio_region_TMU_read( uint32_t reg ) nkeynes@23: { nkeynes@260: switch( reg ) { nkeynes@260: case TCNT0: nkeynes@260: TMU_count( 0, sh4r.slice_cycle ); nkeynes@260: TMU_timers[0].timer_run = sh4r.slice_cycle; nkeynes@260: break; nkeynes@260: case TCNT1: nkeynes@260: TMU_count( 1, sh4r.slice_cycle ); nkeynes@260: TMU_timers[1].timer_run = sh4r.slice_cycle; nkeynes@260: break; nkeynes@260: case TCNT2: nkeynes@260: TMU_count( 2, sh4r.slice_cycle ); nkeynes@260: TMU_timers[2].timer_run = sh4r.slice_cycle; nkeynes@260: break; nkeynes@260: } nkeynes@23: return MMIO_READ( TMU, reg ); nkeynes@23: } nkeynes@23: nkeynes@115: void TMU_set_timer_control( int timer, int tcr ) nkeynes@53: { nkeynes@53: uint32_t period = 1; nkeynes@115: uint32_t oldtcr = MMIO_READ( TMU, TCR0 + (12*timer) ); nkeynes@115: nkeynes@115: if( (oldtcr & TCR_UNF) == 0 ) { nkeynes@115: tcr = tcr & (~TCR_UNF); nkeynes@115: } else { nkeynes@422: if( ((oldtcr & TCR_UNIE) == 0) && nkeynes@115: (tcr & TCR_IRQ_ACTIVE) == TCR_IRQ_ACTIVE ) { nkeynes@115: intc_raise_interrupt( INT_TMU_TUNI0 + timer ); nkeynes@115: } else if( (oldtcr & TCR_UNIE) != 0 && nkeynes@115: (tcr & TCR_IRQ_ACTIVE) != TCR_IRQ_ACTIVE ) { nkeynes@115: intc_clear_interrupt( INT_TMU_TUNI0 + timer ); nkeynes@115: } nkeynes@115: } nkeynes@115: nkeynes@53: switch( tcr & 0x07 ) { nkeynes@53: case 0: nkeynes@53: period = sh4_peripheral_period << 2 ; nkeynes@53: break; nkeynes@53: case 1: nkeynes@53: period = sh4_peripheral_period << 4; nkeynes@53: break; nkeynes@53: case 2: nkeynes@53: period = sh4_peripheral_period << 6; nkeynes@53: break; nkeynes@53: case 3: nkeynes@53: period = sh4_peripheral_period << 8; nkeynes@53: break; nkeynes@53: case 4: nkeynes@53: period = sh4_peripheral_period << 10; nkeynes@53: break; nkeynes@53: case 5: nkeynes@53: /* Illegal value. */ nkeynes@53: ERROR( "TMU %d period set to illegal value (5)", timer ); nkeynes@53: period = sh4_peripheral_period << 12; /* for something to do */ nkeynes@53: break; nkeynes@53: case 6: nkeynes@53: period = rtc_output_period; nkeynes@53: break; nkeynes@53: case 7: nkeynes@53: /* External clock... Hrm? */ nkeynes@53: period = sh4_peripheral_period; /* I dunno... */ nkeynes@53: break; nkeynes@53: } nkeynes@53: TMU_timers[timer].timer_period = period; nkeynes@115: nkeynes@115: MMIO_WRITE( TMU, TCR0 + (12*timer), tcr ); nkeynes@53: } nkeynes@23: nkeynes@53: void TMU_start( int timer ) nkeynes@23: { nkeynes@260: TMU_timers[timer].timer_run = sh4r.slice_cycle; nkeynes@53: TMU_timers[timer].timer_remainder = 0; nkeynes@53: } nkeynes@53: nkeynes@264: /** nkeynes@264: * Stop the given timer. Run it up to the current time and leave it there. nkeynes@264: */ nkeynes@53: void TMU_stop( int timer ) nkeynes@53: { nkeynes@264: TMU_count( timer, sh4r.slice_cycle ); nkeynes@264: TMU_timers[timer].timer_run = sh4r.slice_cycle; nkeynes@53: } nkeynes@53: nkeynes@53: /** nkeynes@53: * Count the specified timer for a given number of nanoseconds. nkeynes@53: */ nkeynes@53: uint32_t TMU_count( int timer, uint32_t nanosecs ) nkeynes@53: { nkeynes@53: nanosecs = nanosecs + TMU_timers[timer].timer_remainder - nkeynes@53: TMU_timers[timer].timer_run; nkeynes@53: TMU_timers[timer].timer_remainder = nkeynes@53: nanosecs % TMU_timers[timer].timer_period; nkeynes@53: uint32_t count = nanosecs / TMU_timers[timer].timer_period; nkeynes@53: uint32_t value = MMIO_READ( TMU, TCNT0 + 12*timer ); nkeynes@53: uint32_t reset = MMIO_READ( TMU, TCOR0 + 12*timer ); nkeynes@53: if( count > value ) { nkeynes@53: uint32_t tcr = MMIO_READ( TMU, TCR0 + 12*timer ); nkeynes@53: tcr |= TCR_UNF; nkeynes@53: count -= value; nkeynes@53: value = reset - (count % reset); nkeynes@53: MMIO_WRITE( TMU, TCR0 + 12*timer, tcr ); nkeynes@53: if( tcr & TCR_UNIE ) nkeynes@53: intc_raise_interrupt( INT_TMU_TUNI0 + timer ); nkeynes@53: } else { nkeynes@53: value -= count; nkeynes@23: } nkeynes@53: MMIO_WRITE( TMU, TCNT0 + 12*timer, value ); nkeynes@53: return value; nkeynes@23: } nkeynes@23: nkeynes@23: void mmio_region_TMU_write( uint32_t reg, uint32_t val ) nkeynes@23: { nkeynes@53: uint32_t oldval; nkeynes@53: int i; nkeynes@23: switch( reg ) { nkeynes@53: case TSTR: nkeynes@53: oldval = MMIO_READ( TMU, TSTR ); nkeynes@53: for( i=0; i<3; i++ ) { nkeynes@53: uint32_t tmp = 1<