/* * PlayStation DMA emulator * * Copyright 2003-2011 smf * */ #include "emu.h" #include "dma.h" #include "includes/psx.h" #define VERBOSE_LEVEL ( 0 ) INLINE void ATTR_PRINTF(3,4) verboselog( running_machine& machine, int n_level, const char *s_fmt, ... ) { if( VERBOSE_LEVEL >= n_level ) { va_list v; char buf[ 32768 ]; va_start( v, s_fmt ); vsprintf( buf, s_fmt, v ); va_end( v ); logerror( "%s: %s", machine.describe_context(), buf ); } } const device_type PSX_DMA = &device_creator; psxdma_device::psxdma_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) : device_t(mconfig, PSX_DMA, "PSX DMA", tag, owner, clock) { for( int index = 0; index < 7; index++ ) { psx_dma_channel *dma = &channel[ index ]; dma->fn_read = NULL; dma->fn_write = NULL; } } void psxdma_device::device_reset() { int n; n_dpcp = 0; n_dicr = 0; for( n = 0; n < 7; n++ ) { dma_stop_timer( n ); } } void psxdma_device::device_post_load() { int n; for( n = 0; n < 7; n++ ) { dma_timer_adjust( n ); } } void psxdma_device::device_start() { for( int index = 0; index < 7; index++ ) { psx_dma_channel *dma = &channel[ index ]; dma->timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(psxdma_device::dma_finished_callback), this)); machine().save().save_item( "psxdma", tag(), index, NAME(dma->n_base) ); machine().save().save_item( "psxdma", tag(), index, NAME(dma->n_blockcontrol) ); machine().save().save_item( "psxdma", tag(), index, NAME(dma->n_channelcontrol) ); machine().save().save_item( "psxdma", tag(), index, NAME(dma->n_ticks) ); machine().save().save_item( "psxdma", tag(), index, NAME(dma->b_running) ); } save_item( NAME(n_dpcp) ); save_item( NAME(n_dicr) ); } void psxdma_device::dma_start_timer( int index, UINT32 n_ticks ) { psx_dma_channel *dma = &channel[ index ]; dma->timer->adjust( attotime::from_hz(33868800) * n_ticks, index); dma->n_ticks = n_ticks; dma->b_running = 1; } void psxdma_device::dma_stop_timer( int index ) { psx_dma_channel *dma = &channel[ index ]; dma->timer->adjust( attotime::never); dma->b_running = 0; } void psxdma_device::dma_timer_adjust( int index ) { psx_dma_channel *dma = &channel[ index ]; if( dma->b_running ) { dma_start_timer( index, dma->n_ticks ); } else { dma_stop_timer( index ); } } void psxdma_device::dma_interrupt_update() { int n_int; int n_mask; n_int = ( n_dicr >> 24 ) & 0x7f; n_mask = ( n_dicr >> 16 ) & 0xff; if( ( n_mask & 0x80 ) != 0 && ( n_int & n_mask ) != 0 ) { verboselog( machine(), 2, "dma_interrupt_update( %02x, %02x ) interrupt triggered\n", n_int, n_mask ); n_dicr |= 0x80000000; psx_irq_set( machine(), PSX_IRQ_DMA ); } else if( n_int != 0 ) { verboselog( machine(), 2, "dma_interrupt_update( %02x, %02x ) interrupt not enabled\n", n_int, n_mask ); } n_dicr &= 0x00ffffff | ( n_dicr << 8 ); } void psxdma_device::dma_finished( int index ) { psx_machine *p_psx = machine().driver_data()->m_p_psx; UINT32 *p_n_psxram = p_psx->p_n_psxram; psx_dma_channel *dma = &channel[ index ]; if( dma->n_channelcontrol == 0x01000401 && index == 2 ) { UINT32 n_size; UINT32 n_total; UINT32 n_address = ( dma->n_base & 0xffffff ); UINT32 n_adrmask = p_psx->n_psxramsize - 1; UINT32 n_nextaddress; if( n_address != 0xffffff ) { n_total = 0; for( ;; ) { if( n_address == 0xffffff ) { dma->n_base = n_address; dma_start_timer( index, 19000 ); return; } if( n_total > 65535 ) { dma->n_base = n_address; //FIXME: // 16000 below is based on try and error. // Mametesters.org: sfex20103red //dma_start_timer( index, 16 ); dma_start_timer( index, 16000 ); return; } n_address &= n_adrmask; n_nextaddress = p_n_psxram[ n_address / 4 ]; n_size = n_nextaddress >> 24; (*dma->fn_write)( machine(), n_address + 4, n_size ); //FIXME: // The following conditions will cause an endless loop. // If stopping the transfer is correct I cannot judge // The patch is meant as a hint for somebody who knows // the hardware. // Mametesters.org: psyforce0105u5red, raystorm0111u1red if ((n_nextaddress & 0xffffff) != 0xffffff) if (n_address == p_n_psxram[ (n_nextaddress & 0xffffff) / 4]) break; if (n_address == (n_nextaddress & 0xffffff) ) break; n_address = ( n_nextaddress & 0xffffff ); n_total += ( n_size + 1 ); } } } dma->n_channelcontrol &= ~( ( 1L << 0x18 ) | ( 1L << 0x1c ) ); n_dicr |= 1 << ( 24 + index ); dma_interrupt_update(); dma_stop_timer( index ); } void psxdma_device::dma_finished_callback(void *ptr, int param) { dma_finished(param); } void psxdma_device::install_read_handler( int index, psx_dma_read_handler p_fn_dma_read ) { channel[ index ].fn_read = p_fn_dma_read; } void psxdma_device::install_write_handler( int index, psx_dma_read_handler p_fn_dma_write ) { channel[ index ].fn_write = p_fn_dma_write; } WRITE32_MEMBER( psxdma_device::write ) { psx_machine *p_psx = machine().driver_data()->m_p_psx; UINT32 *p_n_psxram = p_psx->p_n_psxram; int index = offset / 4; psx_dma_channel *dma = &channel[ index ]; if( index < 7 ) { switch( offset % 4 ) { case 0: verboselog( machine(), 2, "dmabase( %d ) = %08x\n", index, data ); dma->n_base = data; break; case 1: verboselog( machine(), 2, "dmablockcontrol( %d ) = %08x\n", index, data ); dma->n_blockcontrol = data; break; case 2: verboselog( machine(), 2, "dmachannelcontrol( %d ) = %08x\n", index, data ); dma->n_channelcontrol = data; if( ( dma->n_channelcontrol & ( 1L << 0x18 ) ) != 0 && ( n_dpcp & ( 1 << ( 3 + ( index * 4 ) ) ) ) != 0 ) { INT32 n_size; UINT32 n_address; UINT32 n_nextaddress; UINT32 n_adrmask; n_adrmask = p_psx->n_psxramsize - 1; n_address = ( dma->n_base & n_adrmask ); n_size = dma->n_blockcontrol; if( ( dma->n_channelcontrol & 0x200 ) != 0 ) { UINT32 n_ba; n_ba = dma->n_blockcontrol >> 16; if( n_ba == 0 ) { n_ba = 0x10000; } n_size = ( n_size & 0xffff ) * n_ba; } if( dma->n_channelcontrol == 0x01000000 && dma->fn_read != NULL ) { verboselog( machine(), 1, "dma %d read block %08x %08x\n", index, n_address, n_size ); (*dma->fn_read)( machine(), n_address, n_size ); dma_finished( index ); } else if (dma->n_channelcontrol == 0x11000000 && // CD DMA dma->fn_read != NULL ) { verboselog( machine(), 1, "dma %d read block %08x %08x\n", index, n_address, n_size ); // pSX's CD DMA size calc formula int oursize = (dma->n_blockcontrol>>16); oursize = (oursize > 1) ? oursize : 1; oursize *= (dma->n_blockcontrol&0xffff); (*dma->fn_read)( machine(), n_address, oursize ); dma_finished( index ); } else if( dma->n_channelcontrol == 0x01000200 && dma->fn_read != NULL ) { verboselog( machine(), 1, "dma %d read block %08x %08x\n", index, n_address, n_size ); (*dma->fn_read)( machine(), n_address, n_size ); if( index == 1 ) { dma_start_timer( index, 26000 ); } else { dma_finished( index ); } } else if( dma->n_channelcontrol == 0x01000201 && dma->fn_write != NULL ) { verboselog( machine(), 1, "dma %d write block %08x %08x\n", index, n_address, n_size ); (*dma->fn_write)( machine(), n_address, n_size ); dma_finished( index ); } else if( dma->n_channelcontrol == 0x11050100 && dma->fn_write != NULL ) { /* todo: check this is a write not a read... */ verboselog( machine(), 1, "dma %d write block %08x %08x\n", index, n_address, n_size ); (*dma->fn_write)( machine(), n_address, n_size ); dma_finished( index ); } else if( dma->n_channelcontrol == 0x11150100 && dma->fn_write != NULL ) { /* todo: check this is a write not a read... */ verboselog( machine(), 1, "dma %d write block %08x %08x\n", index, n_address, n_size ); (*dma->fn_write)( machine(), n_address, n_size ); dma_finished( index ); } else if( dma->n_channelcontrol == 0x01000401 && index == 2 && dma->fn_write != NULL ) { verboselog( machine(), 1, "dma %d write linked list %08x\n", index, dma->n_base ); dma_finished( index ); } else if( dma->n_channelcontrol == 0x11000002 && index == 6 ) { verboselog( machine(), 1, "dma 6 reverse clear %08x %08x\n", dma->n_base, dma->n_blockcontrol ); if( n_size > 0 ) { n_size--; while( n_size > 0 ) { n_nextaddress = ( n_address - 4 ) & 0xffffff; p_n_psxram[ n_address / 4 ] = n_nextaddress; n_address = n_nextaddress; n_size--; } p_n_psxram[ n_address / 4 ] = 0xffffff; } dma_start_timer( index, 2150 ); } else { verboselog( machine(), 1, "dma %d unknown mode %08x\n", index, dma->n_channelcontrol ); } } else if( dma->n_channelcontrol != 0 ) { verboselog( machine(), 1, "psx_dma_w( %04x, %08x, %08x ) channel not enabled\n", offset, dma->n_channelcontrol, mem_mask ); } break; default: verboselog( machine(), 1, "psx_dma_w( %04x, %08x, %08x ) Unknown dma channel register\n", offset, data, mem_mask ); break; } } else { switch( offset % 4 ) { case 0x0: verboselog( machine(), 1, "psx_dma_w( %04x, %08x, %08x ) dpcp\n", offset, data, mem_mask ); n_dpcp = ( n_dpcp & ~mem_mask ) | data; break; case 0x1: n_dicr = ( n_dicr & ( 0x80000000 | ~mem_mask ) ) | ( n_dicr & ~data & 0x7f000000 & mem_mask ) | ( data & 0x00ffffff & mem_mask ); if( ( n_dicr & 0x80000000 ) != 0 && ( n_dicr & 0x7f000000 ) == 0 ) { verboselog( machine(), 2, "dma interrupt cleared\n" ); n_dicr &= ~0x80000000; } verboselog( machine(), 1, "psx_dma_w( %04x, %08x, %08x ) dicr -> %08x\n", offset, data, mem_mask, n_dicr ); break; default: verboselog( machine(), 0, "psx_dma_w( %04x, %08x, %08x ) Unknown dma control register\n", offset, data, mem_mask ); break; } } } READ32_MEMBER( psxdma_device::read ) { int index = offset / 4; psx_dma_channel *dma = &channel[ index ]; if( index < 7 ) { switch( offset % 4 ) { case 0: verboselog( machine(), 1, "psx_dma_r dmabase[ %d ] ( %08x )\n", index, dma->n_base ); return dma->n_base; case 1: verboselog( machine(), 1, "psx_dma_r dmablockcontrol[ %d ] ( %08x )\n", index, dma->n_blockcontrol ); return dma->n_blockcontrol; case 2: verboselog( machine(), 1, "psx_dma_r dmachannelcontrol[ %d ] ( %08x )\n", index, dma->n_channelcontrol ); return dma->n_channelcontrol; default: verboselog( machine(), 0, "psx_dma_r( %08x, %08x ) Unknown dma channel register\n", offset, mem_mask ); break; } } else { switch( offset % 4 ) { case 0x0: verboselog( machine(), 1, "psx_dma_r dpcp ( %08x )\n", n_dpcp ); return n_dpcp; case 0x1: verboselog( machine(), 1, "psx_dma_r dicr ( %08x )\n", n_dicr ); return n_dicr; default: verboselog( machine(), 0, "psx_dma_r( %08x, %08x ) Unknown dma control register\n", offset, mem_mask ); break; } } return 0; }