nkeynes@31: /** nkeynes@561: * $Id$ 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@564: #include "sh4/sh4.h" nkeynes@2: #include "dreamcast.h" nkeynes@25: #include "maple/maple.h" nkeynes@25: #include "gdrom/ide.h" nkeynes@422: #include "pvr2/pvr2.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@302: static uint32_t asic_run_slice( uint32_t nanosecs ); nkeynes@155: static void asic_save_state( FILE *f ); nkeynes@155: static int asic_load_state( FILE *f ); nkeynes@302: static uint32_t g2_update_fifo_status( uint32_t slice_cycle ); nkeynes@155: nkeynes@302: struct dreamcast_module asic_module = { "ASIC", asic_init, asic_reset, NULL, asic_run_slice, nkeynes@736: NULL, asic_save_state, asic_load_state }; nkeynes@15: nkeynes@302: #define G2_BIT5_TICKS 60 nkeynes@302: #define G2_BIT4_TICKS 160 nkeynes@302: #define G2_BIT0_ON_TICKS 120 nkeynes@302: #define G2_BIT0_OFF_TICKS 420 nkeynes@137: nkeynes@137: struct asic_g2_state { nkeynes@302: int bit5_off_timer; nkeynes@302: int bit4_on_timer; nkeynes@302: int bit4_off_timer; nkeynes@302: int bit0_on_timer; nkeynes@302: int bit0_off_timer; nkeynes@155: }; nkeynes@155: nkeynes@155: static struct asic_g2_state g2_state; nkeynes@155: nkeynes@302: static uint32_t asic_run_slice( uint32_t nanosecs ) nkeynes@302: { nkeynes@302: g2_update_fifo_status(nanosecs); nkeynes@302: if( g2_state.bit5_off_timer <= (int32_t)nanosecs ) { nkeynes@736: g2_state.bit5_off_timer = -1; nkeynes@302: } else { nkeynes@736: g2_state.bit5_off_timer -= nanosecs; nkeynes@302: } nkeynes@302: nkeynes@302: if( g2_state.bit4_off_timer <= (int32_t)nanosecs ) { nkeynes@736: g2_state.bit4_off_timer = -1; nkeynes@302: } else { nkeynes@736: g2_state.bit4_off_timer -= nanosecs; nkeynes@302: } nkeynes@302: if( g2_state.bit4_on_timer <= (int32_t)nanosecs ) { nkeynes@736: g2_state.bit4_on_timer = -1; nkeynes@302: } else { nkeynes@736: g2_state.bit4_on_timer -= nanosecs; nkeynes@302: } nkeynes@736: nkeynes@302: if( g2_state.bit0_off_timer <= (int32_t)nanosecs ) { nkeynes@736: g2_state.bit0_off_timer = -1; nkeynes@302: } else { nkeynes@736: g2_state.bit0_off_timer -= nanosecs; nkeynes@302: } nkeynes@302: if( g2_state.bit0_on_timer <= (int32_t)nanosecs ) { nkeynes@736: g2_state.bit0_on_timer = -1; nkeynes@302: } else { nkeynes@736: g2_state.bit0_on_timer -= nanosecs; nkeynes@302: } nkeynes@736: nkeynes@302: return nanosecs; nkeynes@302: } nkeynes@302: 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@302: memset( &g2_state, 0xFF, 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@736: return 1; nkeynes@155: else nkeynes@736: return 0; nkeynes@155: } nkeynes@155: nkeynes@137: nkeynes@302: /** nkeynes@302: * Setup the timers for the 3 FIFO status bits following a write through the G2 nkeynes@302: * bus from the SH4 side. The timing is roughly as follows: (times are nkeynes@302: * approximate based on software readings - I wouldn't take this as gospel but nkeynes@302: * it seems to be enough to fool most programs). nkeynes@302: * 0ns: Bit 5 (Input fifo?) goes high immediately on the write nkeynes@302: * 40ns: Bit 5 goes low and bit 4 goes high nkeynes@302: * 120ns: Bit 4 goes low, bit 0 goes high nkeynes@302: * 240ns: Bit 0 goes low. nkeynes@302: * nkeynes@302: * Additional writes while the FIFO is in operation extend the time that the nkeynes@302: * bits remain high as one might expect, without altering the time at which nkeynes@302: * they initially go high. nkeynes@302: */ nkeynes@137: void asic_g2_write_word() nkeynes@137: { nkeynes@302: if( g2_state.bit5_off_timer < (int32_t)sh4r.slice_cycle ) { nkeynes@736: g2_state.bit5_off_timer = sh4r.slice_cycle + G2_BIT5_TICKS; nkeynes@302: } else { nkeynes@736: g2_state.bit5_off_timer += G2_BIT5_TICKS; nkeynes@302: } nkeynes@302: nkeynes@302: if( g2_state.bit4_on_timer < (int32_t)sh4r.slice_cycle ) { nkeynes@736: g2_state.bit4_on_timer = sh4r.slice_cycle + G2_BIT5_TICKS; nkeynes@302: } nkeynes@302: nkeynes@302: if( g2_state.bit4_off_timer < (int32_t)sh4r.slice_cycle ) { nkeynes@736: g2_state.bit4_off_timer = g2_state.bit4_on_timer + G2_BIT4_TICKS; nkeynes@302: } else { nkeynes@736: g2_state.bit4_off_timer += G2_BIT4_TICKS; nkeynes@302: } nkeynes@302: nkeynes@302: if( g2_state.bit0_on_timer < (int32_t)sh4r.slice_cycle ) { nkeynes@736: g2_state.bit0_on_timer = sh4r.slice_cycle + G2_BIT0_ON_TICKS; nkeynes@302: } nkeynes@302: nkeynes@302: if( g2_state.bit0_off_timer < (int32_t)sh4r.slice_cycle ) { nkeynes@736: g2_state.bit0_off_timer = g2_state.bit0_on_timer + G2_BIT0_OFF_TICKS; nkeynes@137: } else { nkeynes@736: g2_state.bit0_off_timer += G2_BIT0_OFF_TICKS; nkeynes@137: } nkeynes@302: nkeynes@137: MMIO_WRITE( ASIC, G2STATUS, MMIO_READ(ASIC, G2STATUS) | 0x20 ); nkeynes@137: } nkeynes@137: nkeynes@302: static uint32_t g2_update_fifo_status( uint32_t nanos ) nkeynes@137: { nkeynes@302: uint32_t val = MMIO_READ( ASIC, G2STATUS ); nkeynes@302: if( ((uint32_t)g2_state.bit5_off_timer) <= nanos ) { nkeynes@736: val = val & (~0x20); nkeynes@736: g2_state.bit5_off_timer = -1; nkeynes@163: } nkeynes@302: if( ((uint32_t)g2_state.bit4_on_timer) <= nanos ) { nkeynes@736: val = val | 0x10; nkeynes@736: g2_state.bit4_on_timer = -1; nkeynes@302: } nkeynes@302: if( ((uint32_t)g2_state.bit4_off_timer) <= nanos ) { nkeynes@736: val = val & (~0x10); nkeynes@736: g2_state.bit4_off_timer = -1; nkeynes@302: } nkeynes@302: nkeynes@302: if( ((uint32_t)g2_state.bit0_on_timer) <= nanos ) { nkeynes@736: val = val | 0x01; nkeynes@736: g2_state.bit0_on_timer = -1; nkeynes@302: } nkeynes@302: if( ((uint32_t)g2_state.bit0_off_timer) <= nanos ) { nkeynes@736: val = val & (~0x01); nkeynes@736: g2_state.bit0_off_timer = -1; nkeynes@302: } nkeynes@302: nkeynes@302: MMIO_WRITE( ASIC, G2STATUS, val ); nkeynes@302: return val; nkeynes@137: } nkeynes@137: nkeynes@302: static int g2_read_status() { nkeynes@302: return g2_update_fifo_status( sh4r.slice_cycle ); nkeynes@302: } nkeynes@302: 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@305: nkeynes@305: if( event >= 64 ) { /* Third word */ nkeynes@736: asic_event( EVENT_CASCADE2 ); nkeynes@305: } else if( event >= 32 ) { /* Second word */ nkeynes@736: asic_event( EVENT_CASCADE1 ); nkeynes@305: } 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@305: if( result == 0 ) { nkeynes@736: /* clear cascades if necessary */ nkeynes@736: if( event >= 64 ) { nkeynes@736: MMIO_WRITE( ASIC, PIRQ0, MMIO_READ( ASIC, PIRQ0 ) & 0x7FFFFFFF ); nkeynes@736: } else if( event >= 32 ) { nkeynes@736: MMIO_WRITE( ASIC, PIRQ0, MMIO_READ( ASIC, PIRQ0 ) & 0xBFFFFFFF ); nkeynes@736: } nkeynes@305: } nkeynes@736: 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@594: for( i=0; i<12; i+=4 ) { nkeynes@736: bits = MMIO_READ( ASIC, PIRQ0 + i ); nkeynes@736: setA |= (bits & MMIO_READ(ASIC, IRQA0 + i )); nkeynes@736: setB |= (bits & MMIO_READ(ASIC, IRQB0 + i )); nkeynes@736: setC |= (bits & MMIO_READ(ASIC, IRQC0 + i )); nkeynes@155: } nkeynes@155: if( setA == 0 ) nkeynes@736: intc_clear_interrupt( INT_IRQ13 ); nkeynes@155: if( setB == 0 ) nkeynes@736: intc_clear_interrupt( INT_IRQ11 ); nkeynes@155: if( setC == 0 ) nkeynes@736: intc_clear_interrupt( INT_IRQ9 ); nkeynes@155: } nkeynes@155: nkeynes@594: void asic_event_mask_changed( ) nkeynes@594: { nkeynes@594: int i, setA = 0, setB = 0, setC = 0; nkeynes@594: uint32_t bits; nkeynes@594: for( i=0; i<12; i+=4 ) { nkeynes@736: bits = MMIO_READ( ASIC, PIRQ0 + i ); nkeynes@736: setA |= (bits & MMIO_READ(ASIC, IRQA0 + i )); nkeynes@736: setB |= (bits & MMIO_READ(ASIC, IRQB0 + i )); nkeynes@736: setC |= (bits & MMIO_READ(ASIC, IRQC0 + i )); nkeynes@594: } nkeynes@594: if( setA == 0 ) nkeynes@736: intc_clear_interrupt( INT_IRQ13 ); nkeynes@594: else nkeynes@736: intc_raise_interrupt( INT_IRQ13 ); nkeynes@594: if( setB == 0 ) nkeynes@736: intc_clear_interrupt( INT_IRQ11 ); nkeynes@594: else nkeynes@736: intc_raise_interrupt( INT_IRQ11 ); nkeynes@594: if( setC == 0 ) nkeynes@736: intc_clear_interrupt( INT_IRQ9 ); nkeynes@594: else nkeynes@736: intc_raise_interrupt( INT_IRQ9 ); nkeynes@155: } nkeynes@155: nkeynes@279: void g2_dma_transfer( int channel ) nkeynes@279: { nkeynes@279: uint32_t offset = channel << 5; nkeynes@279: nkeynes@302: if( MMIO_READ( EXTDMA, G2DMA0CTL1 + offset ) == 1 ) { nkeynes@736: if( MMIO_READ( EXTDMA, G2DMA0CTL2 + offset ) == 1 ) { nkeynes@736: uint32_t extaddr = MMIO_READ( EXTDMA, G2DMA0EXT + offset ); nkeynes@736: uint32_t sh4addr = MMIO_READ( EXTDMA, G2DMA0SH4 + offset ); nkeynes@736: uint32_t length = MMIO_READ( EXTDMA, G2DMA0SIZ + offset ) & 0x1FFFFFFF; nkeynes@736: uint32_t dir = MMIO_READ( EXTDMA, G2DMA0DIR + offset ); nkeynes@736: // uint32_t mode = MMIO_READ( EXTDMA, G2DMA0MOD + offset ); nkeynes@736: unsigned char buf[length]; nkeynes@736: if( dir == 0 ) { /* SH4 to device */ nkeynes@736: mem_copy_from_sh4( buf, sh4addr, length ); nkeynes@736: mem_copy_to_sh4( extaddr, buf, length ); nkeynes@736: } else { /* Device to SH4 */ nkeynes@736: mem_copy_from_sh4( buf, extaddr, length ); nkeynes@736: mem_copy_to_sh4( sh4addr, buf, length ); nkeynes@736: } nkeynes@736: MMIO_WRITE( EXTDMA, G2DMA0CTL2 + offset, 0 ); nkeynes@736: asic_event( EVENT_G2_DMA0 + channel ); nkeynes@736: } else { nkeynes@736: MMIO_WRITE( EXTDMA, G2DMA0CTL2 + offset, 0 ); nkeynes@736: } nkeynes@279: } nkeynes@279: } nkeynes@155: nkeynes@155: void asic_ide_dma_transfer( ) nkeynes@155: { nkeynes@158: if( MMIO_READ( EXTDMA, IDEDMACTL2 ) == 1 ) { nkeynes@736: if( MMIO_READ( EXTDMA, IDEDMACTL1 ) == 1 ) { nkeynes@736: MMIO_WRITE( EXTDMA, IDEDMATXSIZ, 0 ); nkeynes@736: nkeynes@736: uint32_t addr = MMIO_READ( EXTDMA, IDEDMASH4 ); nkeynes@736: uint32_t length = MMIO_READ( EXTDMA, IDEDMASIZ ); nkeynes@736: // int dir = MMIO_READ( EXTDMA, IDEDMADIR ); nkeynes@736: nkeynes@736: uint32_t xfer = ide_read_data_dma( addr, length ); nkeynes@736: MMIO_WRITE( EXTDMA, IDEDMATXSIZ, xfer ); nkeynes@736: MMIO_WRITE( EXTDMA, IDEDMACTL2, 0 ); nkeynes@833: asic_event( EVENT_IDE_DMA ); nkeynes@736: } else { /* 0 */ nkeynes@736: MMIO_WRITE( EXTDMA, IDEDMACTL2, 0 ); nkeynes@736: } nkeynes@155: } nkeynes@155: } nkeynes@155: nkeynes@325: void pvr_dma_transfer( ) nkeynes@325: { nkeynes@325: sh4addr_t destaddr = MMIO_READ( ASIC, PVRDMADEST) &0x1FFFFFE0; nkeynes@325: uint32_t count = MMIO_READ( ASIC, PVRDMACNT ); nkeynes@430: unsigned char *data = alloca( count ); nkeynes@325: uint32_t rcount = DMAC_get_buffer( 2, data, count ); nkeynes@325: if( rcount != count ) nkeynes@736: WARN( "PVR received %08X bytes from DMA, expected %08X", rcount, count ); nkeynes@736: nkeynes@325: pvr2_dma_write( destaddr, data, rcount ); nkeynes@736: nkeynes@325: MMIO_WRITE( ASIC, PVRDMACTL, 0 ); nkeynes@325: MMIO_WRITE( ASIC, PVRDMACNT, 0 ); nkeynes@325: if( destaddr & 0x01000000 ) { /* Write to texture RAM */ nkeynes@736: MMIO_WRITE( ASIC, PVRDMADEST, destaddr + rcount ); nkeynes@325: } nkeynes@325: asic_event( EVENT_PVR_DMA ); nkeynes@325: } nkeynes@155: nkeynes@855: void pvr_dma2_transfer() nkeynes@1: { nkeynes@855: if( MMIO_READ( EXTDMA, PVRDMA2CTL2 ) == 1 ) { nkeynes@855: if( MMIO_READ( EXTDMA, PVRDMA2CTL1 ) == 1 ) { nkeynes@855: sh4addr_t extaddr = MMIO_READ( EXTDMA, PVRDMA2EXT ); nkeynes@855: sh4addr_t sh4addr = MMIO_READ( EXTDMA, PVRDMA2SH4 ); nkeynes@855: int dir = MMIO_READ( EXTDMA, PVRDMA2DIR ); nkeynes@855: uint32_t length = MMIO_READ( EXTDMA, PVRDMA2SIZ ); nkeynes@855: unsigned char buf[length]; nkeynes@855: if( dir == 0 ) { /* SH4 to PVR */ nkeynes@855: mem_copy_from_sh4( buf, sh4addr, length ); nkeynes@855: mem_copy_to_sh4( extaddr, buf, length ); nkeynes@855: } else { /* PVR to SH4 */ nkeynes@855: mem_copy_from_sh4( buf, extaddr, length ); nkeynes@855: mem_copy_to_sh4( sh4addr, buf, length ); nkeynes@855: } nkeynes@855: MMIO_WRITE( EXTDMA, PVRDMA2CTL2, 0 ); nkeynes@855: asic_event( EVENT_PVR_DMA2 ); nkeynes@855: } nkeynes@1: } nkeynes@1: } nkeynes@1: nkeynes@728: void sort_dma_transfer( ) nkeynes@728: { nkeynes@728: sh4addr_t table_addr = MMIO_READ( ASIC, SORTDMATBL ); nkeynes@728: sh4addr_t data_addr = MMIO_READ( ASIC, SORTDMADATA ); nkeynes@728: int table_size = MMIO_READ( ASIC, SORTDMATSIZ ); nkeynes@753: int addr_shift = MMIO_READ( ASIC, SORTDMAASIZ ) ? 5 : 0; nkeynes@753: int count = 1; nkeynes@736: nkeynes@753: uint32_t *table32 = (uint32_t *)mem_get_region( table_addr ); nkeynes@753: uint16_t *table16 = (uint16_t *)table32; nkeynes@753: uint32_t next = table_size ? (*table32++) : (uint32_t)(*table16++); nkeynes@753: while(1) { nkeynes@753: next &= 0x07FFFFFF; nkeynes@753: if( next == 1 ) { nkeynes@753: next = table_size ? (*table32++) : (uint32_t)(*table16++); nkeynes@753: count++; nkeynes@753: continue; nkeynes@753: } else if( next == 2 ) { nkeynes@753: asic_event( EVENT_SORT_DMA ); nkeynes@753: break; nkeynes@753: } nkeynes@753: uint32_t *data = (uint32_t *)mem_get_region(data_addr + (next<