summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/emu/cpu/psx/dma.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/emu/cpu/psx/dma.c')
-rw-r--r--trunk/src/emu/cpu/psx/dma.c421
1 files changed, 421 insertions, 0 deletions
diff --git a/trunk/src/emu/cpu/psx/dma.c b/trunk/src/emu/cpu/psx/dma.c
new file mode 100644
index 00000000000..a877bb9a142
--- /dev/null
+++ b/trunk/src/emu/cpu/psx/dma.c
@@ -0,0 +1,421 @@
+/*
+ * 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::psxdma_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : device_t(mconfig, PSX_DMA, "PSX DMA", tag, owner, clock)
+{
+}
+
+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_state *p_psx = machine().driver_data<psx_state>();
+ UINT32 *p_n_psxram = p_psx->m_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->m_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( 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_delegate p_fn_dma_read )
+{
+ channel[ index ].fn_read = p_fn_dma_read;
+}
+
+void psxdma_device::install_write_handler( int index, psx_dma_read_delegate p_fn_dma_write )
+{
+ channel[ index ].fn_write = p_fn_dma_write;
+}
+
+WRITE32_MEMBER( psxdma_device::write )
+{
+ psx_state *p_psx = machine().driver_data<psx_state>();
+
+ UINT32 *p_n_psxram = p_psx->m_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->m_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.isnull() )
+ {
+ verboselog( machine(), 1, "dma %d read block %08x %08x\n", index, n_address, n_size );
+ dma->fn_read( n_address, n_size );
+ dma_finished( index );
+ }
+ else if (dma->n_channelcontrol == 0x11000000 && // CD DMA
+ !dma->fn_read.isnull() )
+ {
+ 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( n_address, oursize );
+ dma_finished( index );
+ }
+ else if( dma->n_channelcontrol == 0x01000200 &&
+ !dma->fn_read.isnull() )
+ {
+ verboselog( machine(), 1, "dma %d read block %08x %08x\n", index, n_address, n_size );
+ dma->fn_read( 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.isnull() )
+ {
+ verboselog( machine(), 1, "dma %d write block %08x %08x\n", index, n_address, n_size );
+ dma->fn_write( n_address, n_size );
+ dma_finished( index );
+ }
+ else if( dma->n_channelcontrol == 0x11050100 &&
+ !dma->fn_write.isnull() )
+ {
+ /* 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( n_address, n_size );
+ dma_finished( index );
+ }
+ else if( dma->n_channelcontrol == 0x11150100 &&
+ !dma->fn_write.isnull() )
+ {
+ /* 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( n_address, n_size );
+ dma_finished( index );
+ }
+ else if( dma->n_channelcontrol == 0x01000401 &&
+ index == 2 &&
+ !dma->fn_write.isnull() )
+ {
+ 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;
+}