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@619: #include nkeynes@619: #include "lxdream.h" nkeynes@23: #include "mem.h" nkeynes@23: #include "clock.h" nkeynes@619: #include "eventq.h" nkeynes@619: #include "sh4/sh4core.h" nkeynes@619: #include "sh4/sh4mmio.h" nkeynes@619: #include "sh4/intc.h" nkeynes@23: nkeynes@23: /********************************* CPG *************************************/ nkeynes@859: /* This is the base clock from which all other clocks are derived. nkeynes@859: * Note: The real clock runs at 33Mhz, which is multiplied by the PLL to nkeynes@859: * run the instruction clock at 200Mhz. For sake of simplicity/precision, nkeynes@859: * we instead use 200Mhz as the base rate and divide everything down instead. nkeynes@859: **/ 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@859: uint32_t sh4_bus_freq = SH4_BASE_RATE / 2; nkeynes@859: uint32_t sh4_peripheral_freq = SH4_BASE_RATE / 4; nkeynes@53: nkeynes@53: uint32_t sh4_cpu_period = 1000 / SH4_BASE_RATE; /* in nanoseconds */ nkeynes@859: uint32_t sh4_bus_period = 2* 1000 / SH4_BASE_RATE; nkeynes@859: uint32_t sh4_peripheral_period = 4 * 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@859: uint32_t primary_clock = sh4_input_freq; nkeynes@859: nkeynes@53: switch( reg ) { nkeynes@53: case FRQCR: /* Frequency control */ nkeynes@859: if( (val & FRQCR_PLL1EN) == 0 ) nkeynes@859: primary_clock /= 6; nkeynes@736: div = ifc_divider[(val >> 6) & 0x07]; nkeynes@859: sh4_cpu_freq = primary_clock / div; nkeynes@736: sh4_cpu_period = sh4_cpu_multiplier * div / sh4_input_freq; nkeynes@736: div = ifc_divider[(val >> 3) & 0x07]; nkeynes@859: sh4_bus_freq = primary_clock / div; nkeynes@736: sh4_bus_period = 1000 * div / sh4_input_freq; nkeynes@736: div = pfc_divider[val & 0x07]; nkeynes@859: sh4_peripheral_freq = primary_clock / div; nkeynes@736: sh4_peripheral_period = 1000 * div / sh4_input_freq; nkeynes@53: nkeynes@736: /* Update everything that depends on the peripheral frequency */ nkeynes@736: SCIF_update_line_speed(); nkeynes@736: break; nkeynes@53: case WTCSR: /* Watchdog timer */ nkeynes@736: break; nkeynes@53: } nkeynes@736: 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@619: #define TMU_IS_RUNNING(timer) (MMIO_READ(TMU,TSTR) & (1< value ) { nkeynes@736: uint32_t tcr = MMIO_READ( TMU, TCR0 + 12*timer ); nkeynes@736: tcr |= TCR_UNF; nkeynes@736: count -= value; nkeynes@619: value = reset - (count % reset) + 1; nkeynes@736: MMIO_WRITE( TMU, TCR0 + 12*timer, tcr ); nkeynes@736: if( tcr & TCR_UNIE ) nkeynes@736: intc_raise_interrupt( INT_TMU_TUNI0 + timer ); nkeynes@736: MMIO_WRITE( TMU, TCNT0 + 12*timer, value ); nkeynes@736: TMU_schedule_timer(timer); nkeynes@53: } else { nkeynes@736: value -= count; nkeynes@736: MMIO_WRITE( TMU, TCNT0 + 12*timer, value ); nkeynes@23: } 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@736: oldval = MMIO_READ( TMU, TSTR ); nkeynes@736: for( i=0; i<3; i++ ) { nkeynes@736: uint32_t tmp = 1<