nkeynes@31: /** nkeynes@255: * $Id: asic.c,v 1.22 2006-12-21 11:12:19 nkeynes Exp $ nkeynes@31: * nkeynes@31: * Support for the miscellaneous ASIC functions (Primarily event multiplexing, nkeynes@31: * and DMA). nkeynes@31: * nkeynes@31: * Copyright (c) 2005 Nathan Keynes. nkeynes@31: * nkeynes@31: * This program is free software; you can redistribute it and/or modify nkeynes@31: * it under the terms of the GNU General Public License as published by nkeynes@31: * the Free Software Foundation; either version 2 of the License, or nkeynes@31: * (at your option) any later version. nkeynes@31: * nkeynes@31: * This program is distributed in the hope that it will be useful, nkeynes@31: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@31: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@31: * GNU General Public License for more details. nkeynes@31: */ nkeynes@35: nkeynes@35: #define MODULE asic_module nkeynes@35: nkeynes@1: #include nkeynes@137: #include nkeynes@1: #include "dream.h" nkeynes@1: #include "mem.h" nkeynes@1: #include "sh4/intc.h" nkeynes@56: #include "sh4/dmac.h" nkeynes@2: #include "dreamcast.h" nkeynes@25: #include "maple/maple.h" nkeynes@25: #include "gdrom/ide.h" nkeynes@15: #include "asic.h" nkeynes@1: #define MMIO_IMPL nkeynes@1: #include "asic.h" nkeynes@1: /* nkeynes@1: * Open questions: nkeynes@1: * 1) Does changing the mask after event occurance result in the nkeynes@1: * interrupt being delivered immediately? nkeynes@1: * TODO: Logic diagram of ASIC event/interrupt logic. nkeynes@1: * nkeynes@1: * ... don't even get me started on the "EXTDMA" page, about which, apparently, nkeynes@1: * practically nothing is publicly known... nkeynes@1: */ nkeynes@1: nkeynes@155: static void asic_check_cleared_events( void ); nkeynes@155: static void asic_init( void ); nkeynes@155: static void asic_reset( void ); nkeynes@155: static void asic_save_state( FILE *f ); nkeynes@155: static int asic_load_state( FILE *f ); nkeynes@155: nkeynes@155: struct dreamcast_module asic_module = { "ASIC", asic_init, asic_reset, NULL, NULL, nkeynes@155: NULL, asic_save_state, asic_load_state }; nkeynes@15: nkeynes@137: #define G2_BIT5_TICKS 8 nkeynes@137: #define G2_BIT4_TICKS 16 nkeynes@137: #define G2_BIT0_ON_TICKS 24 nkeynes@137: #define G2_BIT0_OFF_TICKS 24 nkeynes@137: nkeynes@137: struct asic_g2_state { nkeynes@163: unsigned int last_update_time; nkeynes@137: unsigned int bit5_off_timer; nkeynes@137: unsigned int bit4_on_timer; nkeynes@137: unsigned int bit4_off_timer; nkeynes@137: unsigned int bit0_on_timer; nkeynes@137: unsigned int bit0_off_timer; nkeynes@155: }; nkeynes@155: nkeynes@155: static struct asic_g2_state g2_state; nkeynes@155: nkeynes@155: static void asic_init( void ) nkeynes@155: { nkeynes@155: register_io_region( &mmio_region_ASIC ); nkeynes@155: register_io_region( &mmio_region_EXTDMA ); nkeynes@155: asic_reset(); nkeynes@155: } nkeynes@155: nkeynes@155: static void asic_reset( void ) nkeynes@155: { nkeynes@155: memset( &g2_state, 0, sizeof(g2_state) ); nkeynes@155: } nkeynes@155: nkeynes@155: static void asic_save_state( FILE *f ) nkeynes@155: { nkeynes@155: fwrite( &g2_state, sizeof(g2_state), 1, f ); nkeynes@155: } nkeynes@155: nkeynes@155: static int asic_load_state( FILE *f ) nkeynes@155: { nkeynes@155: if( fread( &g2_state, sizeof(g2_state), 1, f ) != 1 ) nkeynes@155: return 1; nkeynes@155: else nkeynes@155: return 0; nkeynes@155: } nkeynes@155: nkeynes@137: nkeynes@137: /* FIXME: Handle rollover */ nkeynes@137: void asic_g2_write_word() nkeynes@137: { nkeynes@163: g2_state.last_update_time = sh4r.icount; nkeynes@137: g2_state.bit5_off_timer = sh4r.icount + G2_BIT5_TICKS; nkeynes@137: if( g2_state.bit4_off_timer < sh4r.icount ) nkeynes@137: g2_state.bit4_on_timer = sh4r.icount + G2_BIT5_TICKS; nkeynes@137: g2_state.bit4_off_timer = max(sh4r.icount,g2_state.bit4_off_timer) + G2_BIT4_TICKS; nkeynes@137: if( g2_state.bit0_off_timer < sh4r.icount ) { nkeynes@137: g2_state.bit0_on_timer = sh4r.icount + G2_BIT0_ON_TICKS; nkeynes@137: g2_state.bit0_off_timer = g2_state.bit0_on_timer + G2_BIT0_OFF_TICKS; nkeynes@137: } else { nkeynes@137: g2_state.bit0_off_timer += G2_BIT0_OFF_TICKS; nkeynes@137: } nkeynes@137: MMIO_WRITE( ASIC, G2STATUS, MMIO_READ(ASIC, G2STATUS) | 0x20 ); nkeynes@137: } nkeynes@137: nkeynes@137: static uint32_t g2_read_status() nkeynes@137: { nkeynes@163: if( sh4r.icount < g2_state.last_update_time ) { nkeynes@163: /* Rollover */ nkeynes@163: if( g2_state.last_update_time < g2_state.bit5_off_timer ) nkeynes@163: g2_state.bit5_off_timer = 0; nkeynes@163: if( g2_state.last_update_time < g2_state.bit4_off_timer ) nkeynes@163: g2_state.bit4_off_timer = 0; nkeynes@163: if( g2_state.last_update_time < g2_state.bit4_on_timer ) nkeynes@163: g2_state.bit4_on_timer = 0; nkeynes@163: if( g2_state.last_update_time < g2_state.bit0_off_timer ) nkeynes@163: g2_state.bit0_off_timer = 0; nkeynes@163: if( g2_state.last_update_time < g2_state.bit0_on_timer ) nkeynes@163: g2_state.bit0_on_timer = 0; nkeynes@163: } nkeynes@137: uint32_t val = MMIO_READ( ASIC, G2STATUS ); nkeynes@137: if( g2_state.bit5_off_timer <= sh4r.icount ) nkeynes@137: val = val & (~0x20); nkeynes@163: if( g2_state.bit4_off_timer <= sh4r.icount || nkeynes@163: (sh4r.icount + G2_BIT5_TICKS) < g2_state.bit4_off_timer ) nkeynes@137: val = val & (~0x10); nkeynes@137: else if( g2_state.bit4_on_timer <= sh4r.icount ) nkeynes@137: val = val | 0x10; nkeynes@137: if( g2_state.bit0_off_timer <= sh4r.icount ) nkeynes@137: val = val & (~0x01); nkeynes@137: else if( g2_state.bit0_on_timer <= sh4r.icount ) nkeynes@137: val = val | 0x01; nkeynes@137: return val | 0x0E; nkeynes@137: } nkeynes@137: nkeynes@20: nkeynes@155: void asic_event( int event ) nkeynes@1: { nkeynes@155: int offset = ((event&0x60)>>3); nkeynes@155: int result = (MMIO_READ(ASIC, PIRQ0 + offset)) |= (1<<(event&0x1F)); nkeynes@155: nkeynes@155: if( result & MMIO_READ(ASIC, IRQA0 + offset) ) nkeynes@155: intc_raise_interrupt( INT_IRQ13 ); nkeynes@155: if( result & MMIO_READ(ASIC, IRQB0 + offset) ) nkeynes@155: intc_raise_interrupt( INT_IRQ11 ); nkeynes@155: if( result & MMIO_READ(ASIC, IRQC0 + offset) ) nkeynes@155: intc_raise_interrupt( INT_IRQ9 ); nkeynes@1: } nkeynes@1: nkeynes@155: void asic_clear_event( int event ) { nkeynes@155: int offset = ((event&0x60)>>3); nkeynes@155: uint32_t result = MMIO_READ(ASIC, PIRQ0 + offset) & (~(1<<(event&0x1F))); nkeynes@155: MMIO_WRITE( ASIC, PIRQ0 + offset, result ); nkeynes@155: nkeynes@155: asic_check_cleared_events(); nkeynes@155: } nkeynes@155: nkeynes@155: void asic_check_cleared_events( ) nkeynes@155: { nkeynes@155: int i, setA = 0, setB = 0, setC = 0; nkeynes@155: uint32_t bits; nkeynes@155: for( i=0; i<3; i++ ) { nkeynes@155: bits = MMIO_READ( ASIC, PIRQ0 + i ); nkeynes@155: setA |= (bits & MMIO_READ(ASIC, IRQA0 + i )); nkeynes@155: setB |= (bits & MMIO_READ(ASIC, IRQB0 + i )); nkeynes@155: setC |= (bits & MMIO_READ(ASIC, IRQC0 + i )); nkeynes@155: } nkeynes@155: if( setA == 0 ) nkeynes@155: intc_clear_interrupt( INT_IRQ13 ); nkeynes@155: if( setB == 0 ) nkeynes@155: intc_clear_interrupt( INT_IRQ11 ); nkeynes@155: if( setC == 0 ) nkeynes@155: intc_clear_interrupt( INT_IRQ9 ); nkeynes@155: } nkeynes@155: nkeynes@155: nkeynes@155: void asic_ide_dma_transfer( ) nkeynes@155: { nkeynes@158: if( MMIO_READ( EXTDMA, IDEDMACTL2 ) == 1 ) { nkeynes@158: if( MMIO_READ( EXTDMA, IDEDMACTL1 ) == 1 ) { nkeynes@158: MMIO_WRITE( EXTDMA, IDEDMATXSIZ, 0 ); nkeynes@158: nkeynes@158: uint32_t addr = MMIO_READ( EXTDMA, IDEDMASH4 ); nkeynes@158: uint32_t length = MMIO_READ( EXTDMA, IDEDMASIZ ); nkeynes@158: int dir = MMIO_READ( EXTDMA, IDEDMADIR ); nkeynes@158: nkeynes@158: uint32_t xfer = ide_read_data_dma( addr, length ); nkeynes@158: MMIO_WRITE( EXTDMA, IDEDMATXSIZ, xfer ); nkeynes@158: MMIO_WRITE( EXTDMA, IDEDMACTL2, 0 ); nkeynes@158: } else { /* 0 */ nkeynes@158: MMIO_WRITE( EXTDMA, IDEDMACTL2, 0 ); nkeynes@155: } nkeynes@155: } nkeynes@155: nkeynes@155: } nkeynes@155: nkeynes@155: nkeynes@1: void mmio_region_ASIC_write( uint32_t reg, uint32_t val ) nkeynes@1: { nkeynes@1: switch( reg ) { nkeynes@125: case PIRQ1: nkeynes@125: val = val & 0xFFFFFFFE; /* Prevent the IDE event from clearing */ nkeynes@125: /* fallthrough */ nkeynes@56: case PIRQ0: nkeynes@56: case PIRQ2: nkeynes@56: /* Clear any interrupts */ nkeynes@56: MMIO_WRITE( ASIC, reg, MMIO_READ(ASIC, reg)&~val ); nkeynes@56: asic_check_cleared_events(); nkeynes@56: break; nkeynes@244: case SYSRESET: nkeynes@244: if( val == 0x7611 ) { nkeynes@244: dreamcast_reset(); nkeynes@255: sh4r.new_pc = sh4r.pc; nkeynes@244: } else { nkeynes@244: WARN( "Unknown value %08X written to SYSRESET port", val ); nkeynes@244: } nkeynes@244: break; nkeynes@56: case MAPLE_STATE: nkeynes@56: MMIO_WRITE( ASIC, reg, val ); nkeynes@56: if( val & 1 ) { nkeynes@56: uint32_t maple_addr = MMIO_READ( ASIC, MAPLE_DMA) &0x1FFFFFE0; nkeynes@56: maple_handle_buffer( maple_addr ); nkeynes@56: MMIO_WRITE( ASIC, reg, 0 ); nkeynes@56: } nkeynes@56: break; nkeynes@56: case PVRDMACTL: /* Initiate PVR DMA transfer */ nkeynes@94: MMIO_WRITE( ASIC, reg, val ); nkeynes@56: if( val & 1 ) { nkeynes@56: uint32_t dest_addr = MMIO_READ( ASIC, PVRDMADEST) &0x1FFFFFE0; nkeynes@56: uint32_t count = MMIO_READ( ASIC, PVRDMACNT ); nkeynes@56: char *data = alloca( count ); nkeynes@56: uint32_t rcount = DMAC_get_buffer( 2, data, count ); nkeynes@56: if( rcount != count ) nkeynes@56: WARN( "PVR received %08X bytes from DMA, expected %08X", rcount, count ); nkeynes@100: mem_copy_to_sh4( dest_addr, data, rcount ); nkeynes@56: asic_event( EVENT_PVR_DMA ); nkeynes@186: MMIO_WRITE( ASIC, PVRDMACTL, 0 ); nkeynes@186: MMIO_WRITE( ASIC, PVRDMACNT, 0 ); nkeynes@56: } nkeynes@56: break; nkeynes@158: case PVRDMADEST: case PVRDMACNT: case MAPLE_DMA: nkeynes@158: MMIO_WRITE( ASIC, reg, val ); nkeynes@158: break; nkeynes@56: default: nkeynes@56: MMIO_WRITE( ASIC, reg, val ); nkeynes@1: } nkeynes@1: } nkeynes@1: nkeynes@1: int32_t mmio_region_ASIC_read( uint32_t reg ) nkeynes@1: { nkeynes@1: int32_t val; nkeynes@1: switch( reg ) { nkeynes@2: /* nkeynes@2: case 0x89C: nkeynes@2: sh4_stop(); nkeynes@2: return 0x000000B; nkeynes@2: */ nkeynes@94: case PIRQ0: nkeynes@94: case PIRQ1: nkeynes@94: case PIRQ2: nkeynes@94: case IRQA0: nkeynes@94: case IRQA1: nkeynes@94: case IRQA2: nkeynes@94: case IRQB0: nkeynes@94: case IRQB1: nkeynes@94: case IRQB2: nkeynes@94: case IRQC0: nkeynes@94: case IRQC1: nkeynes@94: case IRQC2: nkeynes@158: case MAPLE_STATE: nkeynes@94: val = MMIO_READ(ASIC, reg); nkeynes@94: return val; nkeynes@94: case G2STATUS: nkeynes@137: return g2_read_status(); nkeynes@94: default: nkeynes@94: val = MMIO_READ(ASIC, reg); nkeynes@94: return val; nkeynes@1: } nkeynes@94: nkeynes@1: } nkeynes@1: nkeynes@1: MMIO_REGION_WRITE_FN( EXTDMA, reg, val ) nkeynes@1: { nkeynes@244: if( !idereg.interface_enabled && IS_IDE_REGISTER(reg) ) { nkeynes@244: return; /* disabled */ nkeynes@244: } nkeynes@244: nkeynes@2: switch( reg ) { nkeynes@125: case IDEALTSTATUS: /* Device control */ nkeynes@125: ide_write_control( val ); nkeynes@125: break; nkeynes@125: case IDEDATA: nkeynes@125: ide_write_data_pio( val ); nkeynes@125: break; nkeynes@125: case IDEFEAT: nkeynes@125: if( ide_can_write_regs() ) nkeynes@125: idereg.feature = (uint8_t)val; nkeynes@125: break; nkeynes@125: case IDECOUNT: nkeynes@125: if( ide_can_write_regs() ) nkeynes@125: idereg.count = (uint8_t)val; nkeynes@125: break; nkeynes@125: case IDELBA0: nkeynes@125: if( ide_can_write_regs() ) nkeynes@125: idereg.lba0 = (uint8_t)val; nkeynes@125: break; nkeynes@125: case IDELBA1: nkeynes@125: if( ide_can_write_regs() ) nkeynes@125: idereg.lba1 = (uint8_t)val; nkeynes@125: break; nkeynes@125: case IDELBA2: nkeynes@125: if( ide_can_write_regs() ) nkeynes@125: idereg.lba2 = (uint8_t)val; nkeynes@125: break; nkeynes@125: case IDEDEV: nkeynes@125: if( ide_can_write_regs() ) nkeynes@125: idereg.device = (uint8_t)val; nkeynes@125: break; nkeynes@125: case IDECMD: nkeynes@240: if( ide_can_write_regs() || val == IDE_CMD_NOP ) { nkeynes@125: ide_write_command( (uint8_t)val ); nkeynes@125: } nkeynes@125: break; nkeynes@125: case IDEDMACTL1: nkeynes@155: MMIO_WRITE( EXTDMA, reg, val ); nkeynes@125: case IDEDMACTL2: nkeynes@125: MMIO_WRITE( EXTDMA, reg, val ); nkeynes@155: asic_ide_dma_transfer( ); nkeynes@125: break; nkeynes@244: case IDEACTIVATE: nkeynes@244: if( val == 0x001FFFFF ) { nkeynes@244: idereg.interface_enabled = TRUE; nkeynes@244: /* Conventional wisdom says that this is necessary but not nkeynes@244: * sufficient to enable the IDE interface. nkeynes@244: */ nkeynes@244: } else if( val == 0x000042FE ) { nkeynes@244: idereg.interface_enabled = FALSE; nkeynes@244: } nkeynes@125: default: nkeynes@2: MMIO_WRITE( EXTDMA, reg, val ); nkeynes@2: } nkeynes@1: } nkeynes@1: nkeynes@1: MMIO_REGION_READ_FN( EXTDMA, reg ) nkeynes@1: { nkeynes@56: uint32_t val; nkeynes@244: if( !idereg.interface_enabled && IS_IDE_REGISTER(reg) ) { nkeynes@244: return 0xFFFFFFFF; /* disabled */ nkeynes@244: } nkeynes@244: nkeynes@1: switch( reg ) { nkeynes@158: case IDEALTSTATUS: nkeynes@158: val = idereg.status; nkeynes@158: return val; nkeynes@158: case IDEDATA: return ide_read_data_pio( ); nkeynes@158: case IDEFEAT: return idereg.error; nkeynes@158: case IDECOUNT:return idereg.count; nkeynes@158: case IDELBA0: return idereg.disc; nkeynes@158: case IDELBA1: return idereg.lba1; nkeynes@158: case IDELBA2: return idereg.lba2; nkeynes@158: case IDEDEV: return idereg.device; nkeynes@158: case IDECMD: nkeynes@158: val = ide_read_status(); nkeynes@158: return val; nkeynes@158: default: nkeynes@158: val = MMIO_READ( EXTDMA, reg ); nkeynes@158: return val; nkeynes@1: } nkeynes@1: } nkeynes@1: