diff options
author | 2013-05-28 14:41:28 +0000 | |
---|---|---|
committer | 2013-05-28 14:41:28 +0000 | |
commit | ec27de6e7d37f035205ae4c893854ccfe0528852 (patch) | |
tree | e358427a1041cf3dc6e719ed3e1b15ec9fa0ada1 /src/mess/machine | |
parent | 9c9172383da151af87b3adbc7cf3ab6c0d17bd92 (diff) |
(MESS) pce.c: moved the cd addon to be a separate device. now save states
work in the driver for HuCard games, not yet for CD games. [Fabio Priuli]
Diffstat (limited to 'src/mess/machine')
-rw-r--r-- | src/mess/machine/pce.c | 1400 | ||||
-rw-r--r-- | src/mess/machine/pce_cd.c | 1495 | ||||
-rw-r--r-- | src/mess/machine/pce_cd.h | 179 |
3 files changed, 1693 insertions, 1381 deletions
diff --git a/src/mess/machine/pce.c b/src/mess/machine/pce.c index 2b20ab91c08..0e8925a009b 100644 --- a/src/mess/machine/pce.c +++ b/src/mess/machine/pce.c @@ -66,45 +66,8 @@ CD Interface Register 0x0f - ADPCM fade in/out register *************************************************************/ #include "emu.h" -#include "coreutil.h" #include "cpu/h6280/h6280.h" #include "includes/pce.h" -#include "imagedev/chd_cd.h" -#include "sound/msm5205.h" -#include "sound/cdda.h" - - -#define PCE_BRAM_SIZE 0x800 -#define PCE_ADPCM_RAM_SIZE 0x10000 -#define PCE_ACARD_RAM_SIZE 0x200000 -#define PCE_CD_COMMAND_BUFFER_SIZE 0x100 - -#define PCE_CD_IRQ_TRANSFER_READY 0x40 -#define PCE_CD_IRQ_TRANSFER_DONE 0x20 -#define PCE_CD_IRQ_BRAM 0x10 /* ??? */ -#define PCE_CD_IRQ_SAMPLE_FULL_PLAY 0x08 -#define PCE_CD_IRQ_SAMPLE_HALF_PLAY 0x04 - -#define PCE_CD_ADPCM_PLAY_FLAG 0x08 -#define PCE_CD_ADPCM_STOP_FLAG 0x01 - -#define PCE_CD_DATA_FRAMES_PER_SECOND 75 - -enum { - PCE_CD_CDDA_OFF=0, - PCE_CD_CDDA_PLAYING, - PCE_CD_CDDA_PAUSED -}; - - -/* CD Unit RAM */ - -/* MSM5205 ADPCM decoder definition */ -const msm5205_interface pce_cd_msm5205_interface = -{ - DEVCB_DRIVER_LINE_MEMBER(pce_state, pce_cd_msm5205_int), /* interrupt function */ - MSM5205_S48_4B /* 1/48 prescaler, 4bit data */ -}; /* joystick related data*/ @@ -131,10 +94,11 @@ DRIVER_INIT_MEMBER(pce_state,sgx) MACHINE_START_MEMBER(pce_state,pce) { - pce_cd_init(); - machine().device<nvram_device>("nvram")->set_base(m_cd.bram, PCE_BRAM_SIZE); + if (m_cd) + m_cd->late_setup(); - // *partial* saving (no cd items) + // saving is only partially supported: it should be fine with cart games + // OTOH CD states are saved but not correctly restored! save_item(NAME(m_io_port_options)); save_item(NAME(m_acard)); save_item(NAME(m_joystick_port_select)); @@ -144,23 +108,9 @@ MACHINE_START_MEMBER(pce_state,pce) MACHINE_RESET_MEMBER(pce_state,mess_pce) { - int joy_i; - - for (joy_i = 0; joy_i < 5; joy_i++) + for (int joy_i = 0; joy_i < 5; joy_i++) m_joy_6b_packet[joy_i] = 0; - m_cd.adpcm_read_buf = 0; - m_cd.adpcm_write_buf = 0; - - // TODO: add CD-DA stop command here - //m_cd.cdda_status = PCE_CD_CDDA_OFF; - //cdda_stop_audio( machine().device( "cdda" ) ); - - m_cd.regs[0x0c] |= PCE_CD_ADPCM_STOP_FLAG; - m_cd.regs[0x0c] &= ~PCE_CD_ADPCM_PLAY_FLAG; - //m_cd.regs[0x03] = (m_cd.regs[0x03] & ~0x0c) | (PCE_CD_SAMPLE_STOP_PLAY); - m_cd.msm_idle = 1; - /* Note: Arcade Card BIOS contents are the same as System 3, only internal HW differs. We use a category to select between modes (some games can be run in either S-CD or A-CD modes) */ m_acard = ioport("A_CARD")->read() & 1; @@ -221,7 +171,7 @@ READ8_MEMBER(pce_state::mess_pce_joystick_r) if (m_joystick_port_select <= 4) { - switch((joy_type >> (m_joystick_port_select*2)) & 3) + switch ((joy_type >> (m_joystick_port_select*2)) & 3) { case 0: //2-buttons pad data = ioport(joyname[0][m_joystick_port_select])->read(); @@ -256,1134 +206,29 @@ READ8_MEMBER(pce_state::mess_pce_joystick_r) return (ret); } -void pce_state::pce_set_cd_bram() -{ - membank("bank10")->set_base(m_cd.bram + (m_cd.bram_locked ? PCE_BRAM_SIZE : 0)); -} - -void pce_state::adpcm_stop(UINT8 irq_flag) -{ - m_cd.regs[0x0c] |= PCE_CD_ADPCM_STOP_FLAG; - m_cd.regs[0x0c] &= ~PCE_CD_ADPCM_PLAY_FLAG; - //m_cd.regs[0x03] = (m_cd.regs[0x03] & ~0x0c) | (PCE_CD_SAMPLE_STOP_PLAY); - if (irq_flag) - pce_cd_set_irq_line(PCE_CD_IRQ_SAMPLE_FULL_PLAY, ASSERT_LINE); - m_cd.regs[0x0d] &= ~0x60; - m_cd.msm_idle = 1; -} - -void pce_state::adpcm_play() -{ - m_cd.regs[0x0c] &= ~PCE_CD_ADPCM_STOP_FLAG; - m_cd.regs[0x0c] |= PCE_CD_ADPCM_PLAY_FLAG; - pce_cd_set_irq_line(PCE_CD_IRQ_SAMPLE_FULL_PLAY, CLEAR_LINE); - m_cd.regs[0x03] = (m_cd.regs[0x03] & ~0x0c); - m_cd.msm_idle = 0; -} - - -/* Callback for new data from the MSM5205. - The PCE cd unit actually divides the clock signal supplied to - the MSM5205. Currently we can only use static clocks for the - MSM5205. - */ -WRITE_LINE_MEMBER(pce_state::pce_cd_msm5205_int) -{ - UINT8 msm_data; - -// popmessage("%08x %08x %08x %02x %02x",m_cd.msm_start_addr,m_cd.msm_end_addr,m_cd.msm_half_addr,m_cd.regs[0x0c],m_cd.regs[0x0d]); - - if (m_cd.msm_idle) - return; - - /* Supply new ADPCM data */ - msm_data = (m_cd.msm_nibble) ? (m_cd.adpcm_ram[m_cd.msm_start_addr] & 0x0f) : ((m_cd.adpcm_ram[m_cd.msm_start_addr] & 0xf0) >> 4); - - m_msm5205->data_w(msm_data); - m_cd.msm_nibble ^= 1; - - if(m_cd.msm_nibble == 0) - { - m_cd.msm_start_addr++; - - if(m_cd.msm_start_addr == m_cd.msm_half_addr) - { - //pce_cd_set_irq_line(PCE_CD_IRQ_SAMPLE_FULL_PLAY, CLEAR_LINE); - //pce_cd_set_irq_line(PCE_CD_IRQ_SAMPLE_HALF_PLAY, ASSERT_LINE); - } - - if(m_cd.msm_start_addr > m_cd.msm_end_addr) - { - //pce_cd_set_irq_line(PCE_CD_IRQ_SAMPLE_HALF_PLAY, CLEAR_LINE); - //pce_cd_set_irq_line(PCE_CD_IRQ_SAMPLE_FULL_PLAY, CLEAR_LINE); - adpcm_stop(1); - m_msm5205->reset_w(1); - } - } -} - -#define SCSI_STATUS_OK 0x00 -#define SCSI_CHECK_CONDITION 0x02 - -void pce_state::pce_cd_reply_status_byte( UINT8 status ) -{ - logerror("Setting CD in reply_status_byte\n"); - m_cd.scsi_CD = m_cd.scsi_IO = m_cd.scsi_REQ = 1; - m_cd.scsi_MSG = 0; - m_cd.message_after_status = 1; - m_cd.status_sent = m_cd.message_sent = 0; - - if (status == SCSI_STATUS_OK) - { - m_cd.regs[0x01] = 0x00; - } - else - { - m_cd.regs[0x01] = 0x01; - } -} - -/* 0x00 - TEST UNIT READY */ -void pce_state::pce_cd_test_unit_ready() -{ - logerror("test unit ready\n"); - if ( m_cd.cd ) - { - logerror( "Sending STATUS_OK status\n" ); - pce_cd_reply_status_byte(SCSI_STATUS_OK ); - } - else - { - logerror( "Sending CHECK_CONDITION status\n" ); - pce_cd_reply_status_byte(SCSI_CHECK_CONDITION ); - } -} - -/* 0x08 - READ (6) */ -void pce_state::pce_cd_read_6() -{ - UINT32 frame = ( ( m_cd.command_buffer[1] & 0x1f ) << 16 ) | ( m_cd.command_buffer[2] << 8 ) | m_cd.command_buffer[3]; - UINT32 frame_count = m_cd.command_buffer[4]; - printf("%08x %08x\n",frame,frame_count); - - /* Check for presence of a CD */ - if (!m_cd.cd) - { - pce_cd_reply_status_byte(SCSI_CHECK_CONDITION); - return; - } - - if ( m_cd.cdda_status != PCE_CD_CDDA_OFF ) - { - m_cd.cdda_status = PCE_CD_CDDA_OFF; - machine().device<cdda_device>("cdda")->stop_audio(); - m_cd.end_mark = 0; - } - - m_cd.current_frame = frame; - m_cd.end_frame = frame + frame_count; - - if ( frame_count == 0 ) - { - /* Star Breaker uses this */ - popmessage("Read Sector frame count == 0, contact MESSdev"); - pce_cd_reply_status_byte(SCSI_STATUS_OK ); - } - else - { - m_cd.data_timer->adjust(attotime::from_hz( PCE_CD_DATA_FRAMES_PER_SECOND ), 0, attotime::from_hz( PCE_CD_DATA_FRAMES_PER_SECOND )); - } - - /* TODO: correct place? */ - pce_cd_set_irq_line(PCE_CD_IRQ_TRANSFER_READY, ASSERT_LINE); -} - -/* 0xD8 - SET AUDIO PLAYBACK START POSITION (NEC) */ -void pce_state::pce_cd_nec_set_audio_start_position() -{ - UINT32 frame = 0; - - if (!m_cd.cd) - { - /* Throw some error here */ - pce_cd_reply_status_byte(SCSI_CHECK_CONDITION); - return; - } - - switch( m_cd.command_buffer[9] & 0xC0 ) - { - case 0x00: - popmessage("CD-DA set start mode 0x00, contact MESSdev"); - frame = ( m_cd.command_buffer[3] << 16 ) | ( m_cd.command_buffer[4] << 8 ) | m_cd.command_buffer[5]; - break; - case 0x40: - { - UINT8 m,s,f; - - m = bcd_2_dec( m_cd.command_buffer[2]); - s = bcd_2_dec( m_cd.command_buffer[3]); - f = bcd_2_dec( m_cd.command_buffer[4]); - - frame = f + 75 * (s + m * 60); - // PCE tries to be clever here and set (start of track + track pregap size) to skip the pregap - // (I guess it wants the TOC to have the real start sector for data tracks and the start of the pregap for audio?) - frame -= m_cd.toc->tracks[cdrom_get_track(m_cd.cd, frame)].pregap; - break; - } - case 0x80: - frame = m_cd.toc->tracks[ bcd_2_dec( m_cd.command_buffer[2] ) - 1 ].logframeofs; - break; - default: - popmessage("CD-DA set start mode 0xc0, contact MESSdev"); - //assert( NULL == pce_cd_nec_set_audio_start_position ); - break; - } - - m_cd.current_frame = frame; - - if ( m_cd.cdda_status == PCE_CD_CDDA_PAUSED ) - { - m_cd.cdda_status = PCE_CD_CDDA_OFF; - machine().device<cdda_device>("cdda")->stop_audio(); - m_cd.end_frame = m_cd.last_frame; - m_cd.end_mark = 0; - } - else - { - if(m_cd.command_buffer[1] & 0x03) - { - m_cd.cdda_status = PCE_CD_CDDA_PLAYING; - m_cd.end_frame = m_cd.last_frame; //get the end of the CD - machine().device<cdda_device>("cdda")->start_audio( m_cd.current_frame, m_cd.end_frame - m_cd.current_frame ); - m_cd.cdda_play_mode = (m_cd.command_buffer[1] & 0x02) ? 2 : 3; // mode 2 sets IRQ at end - m_cd.end_mark = (m_cd.command_buffer[1] & 0x02) ? 1 : 0; - } - else - { - m_cd.cdda_status = PCE_CD_CDDA_PLAYING; - m_cd.end_frame = m_cd.toc->tracks[ cdrom_get_track(m_cd.cd, m_cd.current_frame) + 1 ].logframeofs; //get the end of THIS track - machine().device<cdda_device>("cdda")->start_audio( m_cd.current_frame, m_cd.end_frame - m_cd.current_frame ); - m_cd.end_mark = 0; - m_cd.cdda_play_mode = 3; - } - } - - pce_cd_reply_status_byte(SCSI_STATUS_OK); - pce_cd_set_irq_line(PCE_CD_IRQ_TRANSFER_DONE, ASSERT_LINE); -} - -/* 0xD9 - SET AUDIO PLAYBACK END POSITION (NEC) */ -void pce_state::pce_cd_nec_set_audio_stop_position() -{ - UINT32 frame = 0; - - if (!m_cd.cd) - { - /* Throw some error here */ - pce_cd_reply_status_byte(SCSI_CHECK_CONDITION); - return; - } - - switch( m_cd.command_buffer[9] & 0xC0 ) - { - case 0x00: - popmessage("CD-DA set end mode 0x00, contact MESSdev"); - frame = ( m_cd.command_buffer[3] << 16 ) | ( m_cd.command_buffer[4] << 8 ) | m_cd.command_buffer[5]; - break; - case 0x40: - { - UINT8 m,s,f; - - m = bcd_2_dec( m_cd.command_buffer[2]); - s = bcd_2_dec( m_cd.command_buffer[3]); - f = bcd_2_dec( m_cd.command_buffer[4]); - - frame = f + 75 * (s + m * 60); -// if(frame >= 525) // TODO: seven seconds gap? O_o -// frame -= 525; - break; - } - case 0x80: - frame = m_cd.toc->tracks[ bcd_2_dec( m_cd.command_buffer[2] ) - 1 ].logframeofs; - break; - default: - popmessage("CD-DA set end mode 0xc0, contact MESSdev"); - //assert( NULL == pce_cd_nec_set_audio_start_position ); - break; - } - - m_cd.end_frame = frame; - m_cd.cdda_play_mode = m_cd.command_buffer[1] & 0x03; - - if ( m_cd.cdda_play_mode ) - { - if ( m_cd.cdda_status == PCE_CD_CDDA_PAUSED ) - { - machine().device<cdda_device>("cdda")->pause_audio( 0 ); - } - else - { - //printf("%08x %08x\n",m_cd.current_frame,m_cd.end_frame - m_cd.current_frame); - machine().device<cdda_device>("cdda")->start_audio( m_cd.current_frame, m_cd.end_frame - m_cd.current_frame ); - m_cd.end_mark = 1; - } - m_cd.cdda_status = PCE_CD_CDDA_PLAYING; - } - else - { - m_cd.cdda_status = PCE_CD_CDDA_OFF; - machine().device<cdda_device>("cdda")->stop_audio(); - m_cd.end_frame = m_cd.last_frame; - m_cd.end_mark = 0; -// assert( NULL == pce_cd_nec_set_audio_stop_position ); - } - - pce_cd_reply_status_byte(SCSI_STATUS_OK); - pce_cd_set_irq_line(PCE_CD_IRQ_TRANSFER_DONE, ASSERT_LINE); -} - -/* 0xDA - PAUSE (NEC) */ -void pce_state::pce_cd_nec_pause() -{ - /* If no cd mounted throw an error */ - if (!m_cd.cd) - { - pce_cd_reply_status_byte(SCSI_CHECK_CONDITION); - return; - } - - /* If there was no cdda playing, throw an error */ - if ( m_cd.cdda_status == PCE_CD_CDDA_OFF ) - { - pce_cd_reply_status_byte(SCSI_CHECK_CONDITION); - return; - } - - m_cd.cdda_status = PCE_CD_CDDA_PAUSED; - m_cd.current_frame = machine().device<cdda_device>("cdda")->get_audio_lba(); - machine().device<cdda_device>("cdda")->pause_audio( 1 ); - pce_cd_reply_status_byte(SCSI_STATUS_OK); -} - -/* 0xDD - READ SUBCHANNEL Q (NEC) */ -void pce_state::pce_cd_nec_get_subq() -{ - /* WP - I do not have access to chds with subchannel information yet, so I'm faking something here */ - UINT32 msf_abs, msf_rel, track, frame; - - if (!m_cd.cd) - { - /* Throw some error here */ - pce_cd_reply_status_byte(SCSI_CHECK_CONDITION); - return; - } - - frame = m_cd.current_frame; - - switch( m_cd.cdda_status ) - { - case PCE_CD_CDDA_PAUSED: - m_cd.data_buffer[0] = 2; - frame = machine().device<cdda_device>("cdda")->get_audio_lba(); - break; - case PCE_CD_CDDA_PLAYING: - m_cd.data_buffer[0] = 0; - frame = machine().device<cdda_device>("cdda")->get_audio_lba(); - break; - default: - m_cd.data_buffer[0] = 3; - break; - } - - msf_abs = lba_to_msf_alt( frame ); - track = cdrom_get_track( m_cd.cd, frame ); - msf_rel = lba_to_msf_alt( frame - cdrom_get_track_start( m_cd.cd, track ) ); - - m_cd.data_buffer[1] = 0x01 | ((cdrom_get_track_type(m_cd.cd, cdrom_get_track(m_cd.cd, track+1)) == CD_TRACK_AUDIO) ? 0x00 : 0x40); - m_cd.data_buffer[2] = dec_2_bcd( track+1 ); /* track */ - m_cd.data_buffer[3] = 1; /* index */ - m_cd.data_buffer[4] = dec_2_bcd(( msf_rel >> 16 ) & 0xFF);/* M (relative) */ - m_cd.data_buffer[5] = dec_2_bcd(( msf_rel >> 8 ) & 0xFF); /* S (relative) */ - m_cd.data_buffer[6] = dec_2_bcd(msf_rel & 0xFF); /* F (relative) */ - m_cd.data_buffer[7] = dec_2_bcd(( msf_abs >> 16 ) & 0xFF);/* M (absolute) */ - m_cd.data_buffer[8] = dec_2_bcd(( msf_abs >> 8 ) & 0xFF); /* S (absolute) */ - m_cd.data_buffer[9] = dec_2_bcd(msf_abs & 0xFF); /* F (absolute) */ - m_cd.data_buffer_size = 10; - - m_cd.data_buffer_index = 0; - m_cd.data_transferred = 1; - m_cd.scsi_IO = 1; - m_cd.scsi_CD = 0; -} - -/* 0xDE - GET DIR INFO (NEC) */ -void pce_state::pce_cd_nec_get_dir_info() -{ - UINT32 frame, msf, track = 0; - const cdrom_toc *toc; - logerror("nec get dir info\n"); - - if (!m_cd.cd) - { - /* Throw some error here */ - pce_cd_reply_status_byte(SCSI_CHECK_CONDITION); - } - - toc = cdrom_get_toc( m_cd.cd ); - - switch( m_cd.command_buffer[1] ) - { - case 0x00: /* Get first and last track numbers */ - m_cd.data_buffer[0] = dec_2_bcd(1); - m_cd.data_buffer[1] = dec_2_bcd(toc->numtrks); - m_cd.data_buffer_size = 2; - break; - case 0x01: /* Get total disk size in MSF format */ - frame = toc->tracks[toc->numtrks-1].logframeofs; - frame += toc->tracks[toc->numtrks-1].frames; - msf = lba_to_msf( frame + 150 ); - - m_cd.data_buffer[0] = ( msf >> 16 ) & 0xFF; /* M */ - m_cd.data_buffer[1] = ( msf >> 8 ) & 0xFF; /* S */ - m_cd.data_buffer[2] = msf & 0xFF; /* F */ - m_cd.data_buffer_size = 3; - break; - case 0x02: /* Get track information */ - if ( m_cd.command_buffer[2] == 0xAA ) - { - frame = toc->tracks[toc->numtrks-1].logframeofs; - frame += toc->tracks[toc->numtrks-1].frames; - m_cd.data_buffer[3] = 0x04; /* correct? */ - } else - { - track = MAX( bcd_2_dec( m_cd.command_buffer[2] ), 1 ); - frame = toc->tracks[track-1].logframeofs; - // PCE wants the start sector for data tracks to *not* include the pregap - if (toc->tracks[track-1].trktype != CD_TRACK_AUDIO) - { - frame += toc->tracks[track-1].pregap; - } - m_cd.data_buffer[3] = ( toc->tracks[track-1].trktype == CD_TRACK_AUDIO ) ? 0x00 : 0x04; - } - logerror("track = %d, frame = %d\n", track, frame ); - msf = lba_to_msf( frame + 150 ); - m_cd.data_buffer[0] = ( msf >> 16 ) & 0xFF; /* M */ - m_cd.data_buffer[1] = ( msf >> 8 ) & 0xFF; /* S */ - m_cd.data_buffer[2] = msf & 0xFF; /* F */ - m_cd.data_buffer_size = 4; - break; - default: -// assert( pce_cd_nec_get_dir_info == NULL ); // Not implemented yet - break; - } - - m_cd.data_buffer_index = 0; - m_cd.data_transferred = 1; - m_cd.scsi_IO = 1; - m_cd.scsi_CD = 0; -} - -void pce_state::pce_cd_end_of_list() -{ - pce_cd_reply_status_byte(SCSI_CHECK_CONDITION); -} - -typedef void (pce_state::*command_handler_func)(); - -void pce_state::pce_cd_handle_data_output() -{ - static const struct { - UINT8 command_byte; - UINT8 command_size; - command_handler_func command_handler; - } pce_cd_commands[] = { - { 0x00, 6, &pce_state::pce_cd_test_unit_ready }, /* TEST UNIT READY */ - { 0x08, 6, &pce_state::pce_cd_read_6 }, /* READ (6) */ - { 0xD8,10, &pce_state::pce_cd_nec_set_audio_start_position }, /* NEC SET AUDIO PLAYBACK START POSITION */ - { 0xD9,10, &pce_state::pce_cd_nec_set_audio_stop_position }, /* NEC SET AUDIO PLAYBACK END POSITION */ - { 0xDA,10, &pce_state::pce_cd_nec_pause }, /* NEC PAUSE */ - { 0xDD,10, &pce_state::pce_cd_nec_get_subq }, /* NEC GET SUBCHANNEL Q */ - { 0xDE,10, &pce_state::pce_cd_nec_get_dir_info }, /* NEC GET DIR INFO */ - { 0xFF, 1, &pce_state::pce_cd_end_of_list } /* end of list marker */ - }; - - if ( m_cd.scsi_REQ && m_cd.scsi_ACK ) - { - /* Command byte received */ - logerror( "Command byte $%02X received\n", m_cd.regs[0x01] ); - - /* Check for buffer overflow */ - assert( m_cd.command_buffer_index < PCE_CD_COMMAND_BUFFER_SIZE ); - - m_cd.command_buffer[m_cd.command_buffer_index] = m_cd.regs[0x01]; - m_cd.command_buffer_index++; - m_cd.scsi_REQ = 0; - } - - if ( ! m_cd.scsi_REQ && ! m_cd.scsi_ACK && m_cd.command_buffer_index ) - { - int i = 0; - - logerror( "Check if command done\n" ); - - for( i = 0; m_cd.command_buffer[0] > pce_cd_commands[i].command_byte; i++ ); - - /* Check for unknown commands */ - if ( m_cd.command_buffer[0] != pce_cd_commands[i].command_byte ) - { - logerror("Unrecognized command: %02X\n", m_cd.command_buffer[0] ); - if(m_cd.command_buffer[0] == 0x03) - popmessage("CD command 0x03 issued (Request Sense), contact MESSdev"); - } - assert( m_cd.command_buffer[0] == pce_cd_commands[i].command_byte ); - - if ( m_cd.command_buffer_index == pce_cd_commands[i].command_size ) - { - //printf("%02x command issued\n",m_cd.command_buffer[0]); - (this->*pce_cd_commands[i].command_handler)(); - m_cd.command_buffer_index = 0; - } - else - { - m_cd.scsi_REQ = 1; - } - } -} - -void pce_state::pce_cd_handle_data_input() -{ - if ( m_cd.scsi_CD ) - { - /* Command / Status byte */ - if ( m_cd.scsi_REQ && m_cd.scsi_ACK ) - { - logerror( "status sent\n" ); - m_cd.scsi_REQ = 0; - m_cd.status_sent = 1; - } - - if ( ! m_cd.scsi_REQ && ! m_cd.scsi_ACK && m_cd.status_sent ) - { - m_cd.status_sent = 0; - if ( m_cd.message_after_status ) - { - logerror( "message after status\n" ); - m_cd.message_after_status = 0; - m_cd.scsi_MSG = m_cd.scsi_REQ = 1; - m_cd.regs[0x01] = 0; - } - } - } - else - { - /* Data */ - if ( m_cd.scsi_REQ && m_cd.scsi_ACK ) - { - m_cd.scsi_REQ = 0; - } - - if ( ! m_cd.scsi_REQ && ! m_cd.scsi_ACK ) - { - if ( m_cd.data_buffer_index == m_cd.data_buffer_size ) - { - pce_cd_set_irq_line(PCE_CD_IRQ_TRANSFER_READY, CLEAR_LINE); - if ( m_cd.data_transferred ) - { - m_cd.data_transferred = 0; - pce_cd_reply_status_byte(SCSI_STATUS_OK); - pce_cd_set_irq_line(PCE_CD_IRQ_TRANSFER_DONE, ASSERT_LINE); - } - } - else - { - logerror("Transfer byte %02x from offset %d %d\n",m_cd.data_buffer[m_cd.data_buffer_index] , m_cd.data_buffer_index, m_cd.current_frame); - m_cd.regs[0x01] = m_cd.data_buffer[m_cd.data_buffer_index]; - m_cd.data_buffer_index++; - m_cd.scsi_REQ = 1; - } - } - } -} - -void pce_state::pce_cd_handle_message_output() -{ - if (m_cd.scsi_REQ && m_cd.scsi_ACK) - m_cd.scsi_REQ = 0; -} - -void pce_state::pce_cd_handle_message_input() -{ - if ( m_cd.scsi_REQ && m_cd.scsi_ACK ) - { - m_cd.scsi_REQ = 0; - m_cd.message_sent = 1; - } - - if ( ! m_cd.scsi_REQ && ! m_cd.scsi_ACK && m_cd.message_sent ) - { - m_cd.message_sent = 0; - m_cd.scsi_BSY = 0; - } -} - -/* Update internal CD statuses */ -void pce_state::pce_cd_update() -{ - /* Check for reset of CD unit */ - if ( m_cd.scsi_RST != m_cd.scsi_last_RST ) - { - if ( m_cd.scsi_RST ) - { - logerror("Performing CD reset\n"); - /* Reset internal data */ - m_cd.scsi_BSY = m_cd.scsi_SEL = m_cd.scsi_CD = m_cd.scsi_IO = 0; - m_cd.scsi_MSG = m_cd.scsi_REQ = m_cd.scsi_ATN = 0; - m_cd.cd_motor_on = 0; - m_cd.selected = 0; - m_cd.cdda_status = PCE_CD_CDDA_OFF; - machine().device<cdda_device>("cdda")->stop_audio(); - m_cd.adpcm_dma_timer->adjust(attotime::never); // stop ADPCM DMA here - } - m_cd.scsi_last_RST = m_cd.scsi_RST; - } - - /* Check if bus can be freed */ - if ( ! m_cd.scsi_SEL && ! m_cd.scsi_BSY && m_cd.selected ) - { - logerror( "freeing bus\n" ); - m_cd.selected = 0; - m_cd.scsi_CD = m_cd.scsi_MSG = m_cd.scsi_IO = m_cd.scsi_REQ = 0; - pce_cd_set_irq_line(PCE_CD_IRQ_TRANSFER_DONE, CLEAR_LINE); - } - - /* Select the CD device */ - if ( m_cd.scsi_SEL ) - { - if ( ! m_cd.selected ) - { - m_cd.selected = 1; - logerror("Setting CD in device selection\n"); - m_cd.scsi_BSY = m_cd.scsi_REQ = m_cd.scsi_CD = 1; - m_cd.scsi_MSG = m_cd.scsi_IO = 0; - } - } - - if ( m_cd.scsi_ATN ) - { - } - else - { - /* Check for data and message phases */ - if ( m_cd.scsi_BSY ) - { - if ( m_cd.scsi_MSG ) - { - /* message phase */ - if ( m_cd.scsi_IO ) - { - pce_cd_handle_message_input(); - } - else - { - pce_cd_handle_message_output(); - } - } - else - { - /* data phase */ - if ( m_cd.scsi_IO ) - { - /* Reading data from target */ - pce_cd_handle_data_input(); - } - else - { - /* Sending data to target */ - pce_cd_handle_data_output(); - } - } - } - } - - /* FIXME: presumably CD-DA needs an irq interface for this */ - if(machine().device<cdda_device>("cdda")->audio_ended() && m_cd.end_mark == 1) - { - switch(m_cd.cdda_play_mode & 3) - { - case 1: machine().device<cdda_device>("cdda")->start_audio( m_cd.current_frame, m_cd.end_frame - m_cd.current_frame ); m_cd.end_mark = 1; break; //play with repeat - case 2: pce_cd_set_irq_line(PCE_CD_IRQ_TRANSFER_DONE, ASSERT_LINE); m_cd.end_mark = 0; break; //irq when finished - case 3: m_cd.end_mark = 0; break; //play without repeat - } - } -} - -void pce_state::pce_cd_set_irq_line( int num, int state ) -{ - if (state == ASSERT_LINE) - m_cd.regs[0x03] |= num; - else - m_cd.regs[0x03] &= ~num; - - if (m_cd.regs[0x02] & m_cd.regs[0x03] & 0x7c) - { - //printf("IRQ PEND = %02x MASK = %02x IRQ ENABLE %02X\n",m_cd.regs[0x02] & m_cd.regs[0x03] & 0x7c,m_cd.regs[0x02] & 0x7c,m_cd.regs[0x03] & 0x7c); - m_maincpu->set_input_line(1, ASSERT_LINE); - } - else - { - m_maincpu->set_input_line(1, CLEAR_LINE); - } -} - -TIMER_CALLBACK_MEMBER(pce_state::pce_cd_data_timer_callback) -{ - if ( m_cd.data_buffer_index == m_cd.data_buffer_size ) - { - /* Read next data sector */ - logerror("read sector %d\n", m_cd.current_frame ); - if ( ! cdrom_read_data( m_cd.cd, m_cd.current_frame, m_cd.data_buffer, CD_TRACK_MODE1 ) ) - { - logerror("Mode1 CD read failed for frame #%d\n", m_cd.current_frame ); - } - else - { - logerror("Succesfully read mode1 frame #%d\n", m_cd.current_frame ); - } - - m_cd.data_buffer_index = 0; - m_cd.data_buffer_size = 2048; - m_cd.current_frame++; - - m_cd.scsi_IO = 1; - m_cd.scsi_CD = 0; - - if ( m_cd.current_frame == m_cd.end_frame ) - { - /* We are done, disable the timer */ - logerror("Last frame read from CD\n"); - m_cd.data_transferred = 1; - m_cd.data_timer->adjust(attotime::never); - } - else - { - m_cd.data_transferred = 0; - } - } -} - -void pce_state::pce_cd_init() -{ - cdrom_image_device *device; - - /* Initialize pce_cd struct */ - memset(&m_cd, 0, sizeof(m_cd)); - - /* Initialize BRAM */ - m_cd.bram = auto_alloc_array(machine(), UINT8, PCE_BRAM_SIZE * 2 ); - memset( m_cd.bram, 0, PCE_BRAM_SIZE ); - memset( m_cd.bram + PCE_BRAM_SIZE, 0xFF, PCE_BRAM_SIZE ); - m_cd.bram_locked = 1; - pce_set_cd_bram(); - - /* set up adpcm related things */ - m_cd.adpcm_ram = auto_alloc_array(machine(), UINT8, PCE_ADPCM_RAM_SIZE ); - memset( m_cd.adpcm_ram, 0, PCE_ADPCM_RAM_SIZE ); - m_cd.adpcm_clock_divider = 1; - machine().device<msm5205_device>("msm5205")->change_clock_w((PCE_CD_CLOCK / 6) / m_cd.adpcm_clock_divider); - - /* Set up cd command buffer */ - m_cd.command_buffer = auto_alloc_array(machine(), UINT8, PCE_CD_COMMAND_BUFFER_SIZE ); - memset( m_cd.command_buffer, 0, PCE_CD_COMMAND_BUFFER_SIZE ); - m_cd.command_buffer_index = 0; - - /* Set up Arcade Card RAM buffer */ - m_cd.acard_ram = auto_alloc_array(machine(), UINT8, PCE_ACARD_RAM_SIZE ); - memset( m_cd.acard_ram, 0, PCE_ACARD_RAM_SIZE ); - - m_cd.data_buffer = auto_alloc_array(machine(), UINT8, 8192 ); - memset( m_cd.data_buffer, 0, 8192 ); - m_cd.data_buffer_size = 0; - m_cd.data_buffer_index = 0; - - m_cd.subcode_buffer = auto_alloc_array(machine(), UINT8, 96 ); - - device = machine().device<cdrom_image_device>("cdrom"); - if ( device ) - { - m_cd.cd = device->get_cdrom_file(); - if ( m_cd.cd ) - { - m_cd.toc = cdrom_get_toc( m_cd.cd ); - machine().device<cdda_device>("cdda")->set_cdrom( m_cd.cd ); - m_cd.last_frame = cdrom_get_track_start( m_cd.cd, cdrom_get_last_track( m_cd.cd ) - 1 ); - m_cd.last_frame += m_cd.toc->tracks[ cdrom_get_last_track( m_cd.cd ) - 1 ].frames; - m_cd.end_frame = m_cd.last_frame; - } - } - - m_cd.data_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(pce_state::pce_cd_data_timer_callback),this)); - m_cd.data_timer->adjust(attotime::never); - m_cd.adpcm_dma_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(pce_state::pce_cd_adpcm_dma_timer_callback),this)); - m_cd.adpcm_dma_timer->adjust(attotime::never); - - m_cd.cdda_fadeout_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(pce_state::pce_cd_cdda_fadeout_callback),this)); - m_cd.cdda_fadeout_timer->adjust(attotime::never); - m_cd.cdda_fadein_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(pce_state::pce_cd_cdda_fadein_callback),this)); - m_cd.cdda_fadein_timer->adjust(attotime::never); - - m_cd.adpcm_fadeout_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(pce_state::pce_cd_adpcm_fadeout_callback),this)); - m_cd.adpcm_fadeout_timer->adjust(attotime::never); - m_cd.adpcm_fadein_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(pce_state::pce_cd_adpcm_fadein_callback),this)); - m_cd.adpcm_fadein_timer->adjust(attotime::never); -} - -WRITE8_MEMBER(pce_state::pce_cd_bram_w) -{ - if ( ! m_cd.bram_locked ) - { - m_cd.bram[ offset ] = data; - } -} - -void pce_state::pce_cd_set_adpcm_ram_byte(UINT8 val) -{ - if(m_cd.adpcm_write_buf > 0) - { - m_cd.adpcm_write_buf--; - } - else - { - m_cd.adpcm_ram[m_cd.adpcm_write_ptr] = val; - m_cd.adpcm_write_ptr = ((m_cd.adpcm_write_ptr + 1) & 0xffff); - //TODO: length + 1 - } -} - -TIMER_CALLBACK_MEMBER(pce_state::pce_cd_cdda_fadeout_callback) -{ - m_cd.cdda_volume-= 0.1; - - if(m_cd.cdda_volume <= 0) - { - m_cd.cdda_volume = 0.0; - machine().device<cdda_device>("cdda")->set_volume(0.0); - m_cd.cdda_fadeout_timer->adjust(attotime::never); - } - else - { - machine().device<cdda_device>("cdda")->set_volume(m_cd.cdda_volume); - m_cd.cdda_fadeout_timer->adjust(attotime::from_usec(param), param); - } -} - -TIMER_CALLBACK_MEMBER(pce_state::pce_cd_cdda_fadein_callback) -{ - m_cd.cdda_volume+= 0.1; - - if(m_cd.cdda_volume >= 100.0) - { - m_cd.cdda_volume = 100.0; - machine().device<cdda_device>("cdda")->set_volume(100.0); - m_cd.cdda_fadein_timer->adjust(attotime::never); - } - else - { - machine().device<cdda_device>("cdda")->set_volume(m_cd.cdda_volume); - m_cd.cdda_fadein_timer->adjust(attotime::from_usec(param), param); - } -} - -TIMER_CALLBACK_MEMBER(pce_state::pce_cd_adpcm_fadeout_callback) -{ - m_cd.adpcm_volume-= 0.1; - - if(m_cd.adpcm_volume <= 0) - { - m_cd.adpcm_volume = 0.0; - m_msm5205->set_volume(0.0); - m_cd.adpcm_fadeout_timer->adjust(attotime::never); - } - else - { - m_msm5205->set_volume(m_cd.adpcm_volume); - m_cd.adpcm_fadeout_timer->adjust(attotime::from_usec(param), param); - } -} - -TIMER_CALLBACK_MEMBER(pce_state::pce_cd_adpcm_fadein_callback) -{ - m_cd.adpcm_volume+= 0.1; - - if(m_cd.adpcm_volume >= 100.0) - { - m_cd.adpcm_volume = 100.0; - m_msm5205->set_volume(100.0); - m_cd.adpcm_fadein_timer->adjust(attotime::never); - } - else - { - m_msm5205->set_volume(m_cd.adpcm_volume); - m_cd.adpcm_fadein_timer->adjust(attotime::from_usec(param), param); - } -} - WRITE8_MEMBER(pce_state::pce_cd_intf_w) { - pce_cd_update(); - - if(offset & 0x200 && m_sys3_card && m_acard) // route Arcade Card handling ports - return pce_cd_acard_w(space,offset,data); - - logerror("%04X: write to CD interface offset %02X, data %02X\n", space.device().safe_pc(), offset, data ); - - switch( offset & 0xf ) - { - case 0x00: /* CDC status */ - /* select device (which bits??) */ - m_cd.scsi_SEL = 1; - pce_cd_update(); - m_cd.scsi_SEL = 0; - m_cd.adpcm_dma_timer->adjust(attotime::never); // stop ADPCM DMA here - /* any write here clears CD transfer irqs */ - pce_cd_set_irq_line(0x70, CLEAR_LINE); - break; - case 0x01: /* CDC command / status / data */ - break; - case 0x02: /* ADPCM / CD control / IRQ enable/disable */ - /* bit 6 - transfer ready irq */ - /* bit 5 - transfer done irq */ - /* bit 4 - BRAM irq? */ - /* bit 3 - ADPCM FULL irq */ - /* bit 2 - ADPCM HALF irq */ - m_cd.scsi_ACK = data & 0x80; - /* Update mask register now otherwise it won't catch the irq enable/disable change */ - m_cd.regs[0x02] = data; - /* Don't set or reset any irq lines, but just verify the current state */ - pce_cd_set_irq_line(0, 0); - break; - case 0x03: /* BRAM lock / CD status / IRQ - Read Only register */ - break; - case 0x04: /* CD reset */ - m_cd.scsi_RST = data & 0x02; - break; - case 0x05: /* Convert PCM data / PCM data */ - case 0x06: /* PCM data */ - break; - case 0x07: /* BRAM unlock / CD status */ - if (data & 0x80) - { - m_cd.bram_locked = 0; - pce_set_cd_bram(); - } - break; - case 0x08: /* ADPCM address (LSB) / CD data */ - break; - case 0x09: /* ADPCM address (MSB) */ - break; - case 0x0A: /* ADPCM RAM data port */ - pce_cd_set_adpcm_ram_byte(data); - break; - case 0x0B: /* ADPCM DMA control */ - if ( data & 0x03 ) - { - /* Start CD to ADPCM transfer */ - m_cd.adpcm_dma_timer->adjust(attotime::from_hz( PCE_CD_DATA_FRAMES_PER_SECOND * 2048 ), 0, attotime::from_hz( PCE_CD_DATA_FRAMES_PER_SECOND * 2048 ) ); - m_cd.regs[0x0c] |= 4; - } - break; - case 0x0C: /* ADPCM status */ - break; - case 0x0D: /* ADPCM address control */ - if ( ( m_cd.regs[0x0D] & 0x80 ) && ! ( data & 0x80 ) ) // ADPCM reset - { - /* Reset ADPCM hardware */ - m_cd.adpcm_read_ptr = 0; - m_cd.adpcm_write_ptr = 0; - m_cd.msm_start_addr = 0; - m_cd.msm_end_addr = 0; - m_cd.msm_half_addr = 0; - m_cd.msm_nibble = 0; - adpcm_stop(0); - m_msm5205->reset_w(1); - } - - if ( ( data & 0x40) && ((m_cd.regs[0x0D] & 0x40) == 0) ) // ADPCM play - { - m_cd.msm_start_addr = (m_cd.adpcm_read_ptr); - m_cd.msm_end_addr = (m_cd.adpcm_read_ptr + m_cd.adpcm_length) & 0xffff; - m_cd.msm_half_addr = (m_cd.adpcm_read_ptr + (m_cd.adpcm_length / 2)) & 0xffff; - m_cd.msm_nibble = 0; - adpcm_play(); - m_msm5205->reset_w(0); + m_cd->update(); - //popmessage("%08x %08x",m_cd.adpcm_read_ptr,m_cd.adpcm_length); - } - else if ( (data & 0x40) == 0 ) - { - /* used by Buster Bros to cancel an in-flight sample */ - adpcm_stop(0); - m_msm5205->reset_w(1); - } - - m_cd.msm_repeat = BIT(data, 5); - - if ( data & 0x10 ) //ADPCM set length - { - m_cd.adpcm_length = ( m_cd.regs[0x09] << 8 ) | m_cd.regs[0x08]; - } - if ( data & 0x08 ) //ADPCM set read address - { - m_cd.adpcm_read_ptr = ( m_cd.regs[0x09] << 8 ) | m_cd.regs[0x08]; - m_cd.adpcm_read_buf = 2; - } - if ( ( data & 0x02 ) == 0x02 ) //ADPCM set write address - { - m_cd.adpcm_write_ptr = ( m_cd.regs[0x09] << 8 ) | m_cd.regs[0x08]; - m_cd.adpcm_write_buf = data & 1; - } - break; - case 0x0E: /* ADPCM playback rate */ - m_cd.adpcm_clock_divider = 0x10 - ( data & 0x0F ); - m_msm5205->change_clock_w((PCE_CD_CLOCK / 6) / m_cd.adpcm_clock_divider); - break; - case 0x0F: /* ADPCM and CD audio fade timer */ - /* TODO: timers needs HW tests */ - if(m_cd.regs[0xf] != data) - { - switch(data & 0xf) - { - case 0x00: //CD-DA / ADPCM enable (100 msecs) - m_cd.cdda_volume = 0.0; - m_cd.cdda_fadein_timer->adjust(attotime::from_usec(100), 100); - m_cd.adpcm_volume = 0.0; - m_cd.adpcm_fadein_timer->adjust(attotime::from_usec(100), 100); - m_cd.cdda_fadeout_timer->adjust(attotime::never); - m_cd.adpcm_fadeout_timer->adjust(attotime::never); - break; - case 0x01: //CD-DA enable (100 msecs) - m_cd.cdda_volume = 0.0; - m_cd.cdda_fadein_timer->adjust(attotime::from_usec(100), 100); - m_cd.cdda_fadeout_timer->adjust(attotime::never); - break; - case 0x08: //CD-DA short (1500 msecs) fade out / ADPCM enable - m_cd.cdda_volume = 100.0; - m_cd.cdda_fadeout_timer->adjust(attotime::from_usec(1500), 1500); - m_cd.adpcm_volume = 0.0; - m_cd.adpcm_fadein_timer->adjust(attotime::from_usec(100), 100); - m_cd.cdda_fadein_timer->adjust(attotime::never); - m_cd.adpcm_fadeout_timer->adjust(attotime::never); - break; - case 0x09: //CD-DA long (5000 msecs) fade out - m_cd.cdda_volume = 100.0; - m_cd.cdda_fadeout_timer->adjust(attotime::from_usec(5000), 5000); - m_cd.cdda_fadein_timer->adjust(attotime::never); - break; - case 0x0a: //ADPCM long (5000 msecs) fade out - m_cd.adpcm_volume = 100.0; - m_cd.adpcm_fadeout_timer->adjust(attotime::from_usec(5000), 5000); - m_cd.adpcm_fadein_timer->adjust(attotime::never); - break; - case 0x0c: //CD-DA short (1500 msecs) fade out / ADPCM enable - m_cd.cdda_volume = 100.0; - m_cd.cdda_fadeout_timer->adjust(attotime::from_usec(1500), 1500); - m_cd.adpcm_volume = 0.0; - m_cd.adpcm_fadein_timer->adjust(attotime::from_usec(100), 100); - m_cd.cdda_fadein_timer->adjust(attotime::never); - m_cd.adpcm_fadeout_timer->adjust(attotime::never); - break; - case 0x0d: //CD-DA short (1500 msecs) fade out - m_cd.cdda_volume = 100.0; - m_cd.cdda_fadeout_timer->adjust(attotime::from_usec(1500), 1500); - m_cd.cdda_fadein_timer->adjust(attotime::never); - break; - case 0x0e: //ADPCM short (1500 msecs) fade out - m_cd.adpcm_volume = 100.0; - m_cd.adpcm_fadeout_timer->adjust(attotime::from_usec(1500), 1500); - m_cd.adpcm_fadein_timer->adjust(attotime::never); - break; - default: - popmessage("CD-DA / ADPCM Fade effect mode %02x, contact MESSdev",data & 0x0f); - break; - } - } - break; - default: - return; - } - m_cd.regs[offset & 0xf] = data; - pce_cd_update(); -} - -TIMER_CALLBACK_MEMBER(pce_state::pce_cd_clear_ack) -{ - pce_cd_update(); - m_cd.scsi_ACK = 0; - pce_cd_update(); - if ( m_cd.scsi_CD ) - { - m_cd.regs[0x0B] &= 0xFC; - } -} - -UINT8 pce_state::pce_cd_get_cd_data_byte() -{ - UINT8 data = m_cd.regs[0x01]; - if ( m_cd.scsi_REQ && ! m_cd.scsi_ACK && ! m_cd.scsi_CD ) - { - if ( m_cd.scsi_IO ) - { - m_cd.scsi_ACK = 1; - machine().scheduler().timer_set(m_maincpu->cycles_to_attotime(15), timer_expired_delegate(FUNC(pce_state::pce_cd_clear_ack),this)); - } - } - return data; -} - - -TIMER_CALLBACK_MEMBER(pce_state::pce_cd_adpcm_dma_timer_callback) -{ - if ( m_cd.scsi_REQ && ! m_cd.scsi_ACK && ! m_cd.scsi_CD && m_cd.scsi_IO ) - { - m_cd.adpcm_ram[m_cd.adpcm_write_ptr] = pce_cd_get_cd_data_byte(); - m_cd.adpcm_write_ptr = ( m_cd.adpcm_write_ptr + 1 ) & 0xFFFF; - - m_cd.regs[0x0c] &= ~4; - } -} - -UINT8 pce_state::pce_cd_get_adpcm_ram_byte() -{ - if(m_cd.adpcm_read_buf > 0) - { - m_cd.adpcm_read_buf--; - return 0; - } - else - { - UINT8 res; - - res = m_cd.adpcm_ram[m_cd.adpcm_read_ptr]; - m_cd.adpcm_read_ptr = ((m_cd.adpcm_read_ptr + 1) & 0xffff); + if (offset & 0x200 && m_sys3_card && m_acard) // route Arcade Card handling ports + return m_cd->acard_w(space, offset, data); + + m_cd->intf_w(space, offset, data); - return res; - } + m_cd->update(); } READ8_MEMBER(pce_state::pce_cd_intf_r) { - UINT8 data = m_cd.regs[offset & 0x0F]; - - pce_cd_update(); + m_cd->update(); - if(offset & 0x200 && m_sys3_card && m_acard) // route Arcade Card handling ports - return pce_cd_acard_r(space,offset); + if (offset & 0x200 && m_sys3_card && m_acard) // route Arcade Card handling ports + return m_cd->acard_r(space, offset); - logerror("%04X: read from CD interface offset %02X\n", space.device().safe_pc(), offset ); - - if((offset & 0xc0) == 0xc0 && m_sys3_card) //System 3 Card header handling + if ((offset & 0xc0) == 0xc0 && m_sys3_card) //System 3 Card header handling { - switch(offset & 0xcf) + switch (offset & 0xcf) { case 0xc1: return 0xaa; case 0xc2: return 0x55; @@ -1393,217 +238,10 @@ READ8_MEMBER(pce_state::pce_cd_intf_r) case 0xc7: return 0x03; } } - - switch( offset & 0xf ) - { - case 0x00: /* CDC status */ - data &= 0x07; - data |= m_cd.scsi_BSY ? 0x80 : 0; - data |= m_cd.scsi_REQ ? 0x40 : 0; - data |= m_cd.scsi_MSG ? 0x20 : 0; - data |= m_cd.scsi_CD ? 0x10 : 0; - data |= m_cd.scsi_IO ? 0x08 : 0; - break; - case 0x01: /* CDC command / status / data */ - break; - case 0x02: /* ADPCM / CD control */ - break; - case 0x03: /* BRAM lock / CD status */ - /* bit 4 set when CD motor is on */ - /* bit 2 set when less than half of the ADPCM data is remaining ?? */ - m_cd.bram_locked = 1; - pce_set_cd_bram(); - data = data & 0x6E; - data |= ( m_cd.cd_motor_on ? 0x10 : 0 ); - m_cd.regs[0x03] ^= 0x02; /* TODO: get rid of this hack */ - break; - case 0x04: /* CD reset */ - break; - case 0x05: /* Convert PCM data / PCM data */ - data = machine().device<cdda_device>("cdda")->get_channel_volume((m_cd.regs[0x03] & 2) ? 0 : 1) & 0xff; - break; - case 0x06: /* PCM data */ - data = machine().device<cdda_device>("cdda")->get_channel_volume((m_cd.regs[0x03] & 2) ? 0 : 1) >> 8; - break; - case 0x07: /* BRAM unlock / CD status */ - data = ( m_cd.bram_locked ? ( data & 0x7F ) : ( data | 0x80 ) ); - break; - case 0x08: /* ADPCM address (LSB) / CD data */ - data = pce_cd_get_cd_data_byte(); - break; - case 0x0A: /* ADPCM RAM data port */ - data = pce_cd_get_adpcm_ram_byte(); - break; - case 0x0B: /* ADPCM DMA control */ - break; - case 0x0C: /* ADPCM status */ - break; - case 0x0D: /* ADPCM address control */ - break; - /* These are read-only registers */ - case 0x09: /* ADPCM address (MSB) */ - case 0x0E: /* ADPCM playback rate */ - case 0x0F: /* ADPCM and CD audio fade timer */ - return 0; - default: - data = 0xFF; - break; - } - - return data; -} - - -/* - -PC Engine Arcade Card emulation - -*/ - -READ8_MEMBER(pce_state::pce_cd_acard_r) -{ - UINT8 r_num; - - if((offset & 0x2e0) == 0x2e0) - { - switch(offset & 0x2ef) - { - case 0x2e0: return (m_cd.acard_shift >> 0) & 0xff; - case 0x2e1: return (m_cd.acard_shift >> 8) & 0xff; - case 0x2e2: return (m_cd.acard_shift >> 16) & 0xff; - case 0x2e3: return (m_cd.acard_shift >> 24) & 0xff; - case 0x2e4: return (m_cd.acard_shift_reg); - case 0x2e5: return m_cd.acard_latch; - case 0x2ee: return 0x10; - case 0x2ef: return 0x51; - } - - return 0; - } - - r_num = (offset & 0x30) >> 4; - - switch(offset & 0x0f) - { - case 0x00: - case 0x01: - { - UINT8 res; - if(m_cd.acard_ctrl[r_num] & 2) - res = m_cd.acard_ram[(m_cd.acard_base_addr[r_num] + m_cd.acard_addr_offset[r_num]) & 0x1fffff]; - else - res = m_cd.acard_ram[m_cd.acard_base_addr[r_num] & 0x1fffff]; - - if(m_cd.acard_ctrl[r_num] & 0x1) - { - if(m_cd.acard_ctrl[r_num] & 0x10) - { - m_cd.acard_base_addr[r_num] += m_cd.acard_addr_inc[r_num]; - m_cd.acard_base_addr[r_num] &= 0xffffff; - } - else - { - m_cd.acard_addr_offset[r_num] += m_cd.acard_addr_inc[r_num]; - } - } - - return res; - } - case 0x02: return (m_cd.acard_base_addr[r_num] >> 0) & 0xff; - case 0x03: return (m_cd.acard_base_addr[r_num] >> 8) & 0xff; - case 0x04: return (m_cd.acard_base_addr[r_num] >> 16) & 0xff; - case 0x05: return (m_cd.acard_addr_offset[r_num] >> 0) & 0xff; - case 0x06: return (m_cd.acard_addr_offset[r_num] >> 8) & 0xff; - case 0x07: return (m_cd.acard_addr_inc[r_num] >> 0) & 0xff; - case 0x08: return (m_cd.acard_addr_inc[r_num] >> 8) & 0xff; - case 0x09: return m_cd.acard_ctrl[r_num]; - default: return 0; - } - - return 0; + + return m_cd->intf_r(space, offset); } -WRITE8_MEMBER(pce_state::pce_cd_acard_w) -{ - UINT8 w_num; - - if((offset & 0x2e0) == 0x2e0) - { - switch(offset & 0x0f) - { - case 0: m_cd.acard_shift = (data & 0xff) | (m_cd.acard_shift & 0xffffff00); break; - case 1: m_cd.acard_shift = (data << 8) | (m_cd.acard_shift & 0xffff00ff); break; - case 2: m_cd.acard_shift = (data << 16) | (m_cd.acard_shift & 0xff00ffff); break; - case 3: m_cd.acard_shift = (data << 24) | (m_cd.acard_shift & 0x00ffffff); break; - case 4: - { - m_cd.acard_shift_reg = data & 0x0f; - - if(m_cd.acard_shift_reg != 0) - { - m_cd.acard_shift = (m_cd.acard_shift_reg < 8) ? - (m_cd.acard_shift << m_cd.acard_shift_reg) - : (m_cd.acard_shift >> (16 - m_cd.acard_shift_reg)); - } - } - break; - case 5: m_cd.acard_latch = data; break; - } - } - else - { - w_num = (offset & 0x30) >> 4; - - switch(offset & 0x0f) - { - case 0x00: - case 0x01: - if(m_cd.acard_ctrl[w_num] & 2) - m_cd.acard_ram[(m_cd.acard_base_addr[w_num] + m_cd.acard_addr_offset[w_num]) & 0x1fffff] = data; - else - m_cd.acard_ram[m_cd.acard_base_addr[w_num] & 0x1FFFFF] = data; - - if(m_cd.acard_ctrl[w_num] & 0x1) - { - if(m_cd.acard_ctrl[w_num] & 0x10) - { - m_cd.acard_base_addr[w_num] += m_cd.acard_addr_inc[w_num]; - m_cd.acard_base_addr[w_num] &= 0xffffff; - } - else - { - m_cd.acard_addr_offset[w_num] += m_cd.acard_addr_inc[w_num]; - } - } - - break; - - case 0x02: m_cd.acard_base_addr[w_num] = (data & 0xff) | (m_cd.acard_base_addr[w_num] & 0xffff00); break; - case 0x03: m_cd.acard_base_addr[w_num] = (data << 8) | (m_cd.acard_base_addr[w_num] & 0xff00ff); break; - case 0x04: m_cd.acard_base_addr[w_num] = (data << 16) | (m_cd.acard_base_addr[w_num] & 0x00ffff); break; - case 0x05: m_cd.acard_addr_offset[w_num] = (data & 0xff) | (m_cd.acard_addr_offset[w_num] & 0xff00); break; - case 0x06: - m_cd.acard_addr_offset[w_num] = (data << 8) | (m_cd.acard_addr_offset[w_num] & 0x00ff); - - if((m_cd.acard_ctrl[w_num] & 0x60) == 0x40) - { - m_cd.acard_base_addr[w_num] += m_cd.acard_addr_offset[w_num] + ((m_cd.acard_ctrl[w_num] & 0x08) ? 0xff0000 : 0); - m_cd.acard_base_addr[w_num] &= 0xffffff; - } - break; - case 0x07: m_cd.acard_addr_inc[w_num] = (data & 0xff) | (m_cd.acard_addr_inc[w_num] & 0xff00); break; - case 0x08: m_cd.acard_addr_inc[w_num] = (data << 8) | (m_cd.acard_addr_inc[w_num] & 0x00ff); break; - case 0x09: m_cd.acard_ctrl[w_num] = data & 0x7f; break; - case 0x0a: - if((m_cd.acard_ctrl[w_num] & 0x60) == 0x60) - { - m_cd.acard_base_addr[w_num] += m_cd.acard_addr_offset[w_num]; - m_cd.acard_base_addr[w_num] &= 0xffffff; - } - break; - } - } -} READ8_MEMBER(pce_state::pce_cd_acard_wram_r) { diff --git a/src/mess/machine/pce_cd.c b/src/mess/machine/pce_cd.c new file mode 100644 index 00000000000..e090f273723 --- /dev/null +++ b/src/mess/machine/pce_cd.c @@ -0,0 +1,1495 @@ +/************************************************************ + +PC Engine CD HW notes: + +TODO: +- Dragon Ball Z: ADPCM dies after the first upload; +- Dragon Slayer - The Legend of Heroes: black screen; +- Mirai Shonen Conan: dies at new game selection; +- Snatcher: black screen after Konami logo, tries set up CD-DA + while transferring data? +- Steam Heart's: needs transfer ready irq to get past the + gameplay hang, don't know exactly when it should fire +- Steam Heart's: bad ADPCM irq, dialogue is cutted due of it; + +============================================================= + +CD Interface Register 0x00 - CDC status +x--- ---- busy signal +-x-- ---- request signal +---x ---- cd signal +---- x--- i/o signal + +CD Interface Register 0x03 - BRAM lock / CD status +-x-- ---- acknowledge signal +--x- ---- done signal +---x ---- bram signal +---- x--- ADPCM 2 +---- -x-- ADPCM 1 +---- --x- CDDA left/right speaker select + +CD Interface Register 0x05 - CD-DA Volume low 8-bit port + +CD Interface Register 0x06 - CD-DA Volume high 8-bit port + +CD Interface Register 0x07 - BRAM unlock / CD status +x--- ---- Enables BRAM + +CD Interface Register 0x0c - ADPCM status +x--- ---- ADPCM is reading data +---- x--- ADPCM playback (0) stopped (1) currently playing +---- -x-- pending ADPCM data write +---- ---x ADPCM playback (1) stopped (0) currently playing + +CD Interface Register 0x0d - ADPCM address control +x--- ---- ADPCM reset +-x-- ---- ADPCM play +--x- ---- ADPCM repeat +---x ---- ADPCM set length +---- x--- ADPCM set read address +---- --xx ADPCM set write address +(note: some games reads bit 5 and wants it to be low otherwise they hangs, surely NOT an ADPCM repeat flag read because it doesn't make sense) + +CD Interface Register 0x0e - ADPCM playback rate + +CD Interface Register 0x0f - ADPCM fade in/out register +---- xxxx command setting: +0x00 ADPCM/CD-DA Fade-in +0x01 CD-DA fade-in +0x08 CD-DA fade-out (short) ADPCM fade-in +0x09 CD-DA fade-out (long) +0x0a ADPCM fade-out (long) +0x0c CD-DA fade-out (short) ADPCM fade-in +0x0d CD-DA fade-out (short) +0x0e ADPCM fade-out (short) + +*************************************************************/ + +#include "emu.h" +#include "coreutil.h" +#include "machine/pce_cd.h" +#include "sound/cdda.h" + + +#define PCE_CD_CLOCK 9216000 + + +const device_type PCE_CD = &device_creator<pce_cd_device>; + + +pce_cd_device::pce_cd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : device_t(mconfig, PCE_CD, "PCE CD Add-on", tag, owner, clock, "pcecd", __FILE__), + m_nvram(*this, "bram"), + m_cdrom(*this, "cdrom") +{ +} + +void pce_cd_device::device_start() +{ + /* Initialize BRAM */ + m_bram = auto_alloc_array(machine(), UINT8, PCE_BRAM_SIZE * 2); + memset(m_bram, 0, PCE_BRAM_SIZE); + memset(m_bram + PCE_BRAM_SIZE, 0xff, PCE_BRAM_SIZE); + m_bram_locked = 1; + + m_nvram->set_base(m_bram, PCE_BRAM_SIZE); + + /* set up adpcm related things */ + m_adpcm_ram = auto_alloc_array_clear(machine(), UINT8, PCE_ADPCM_RAM_SIZE); + m_adpcm_clock_divider = 1; + machine().device<msm5205_device>("msm5205")->change_clock_w((PCE_CD_CLOCK / 6) / m_adpcm_clock_divider); + + /* Set up cd command buffer */ + m_command_buffer = auto_alloc_array_clear(machine(), UINT8, PCE_CD_COMMAND_BUFFER_SIZE); + m_command_buffer_index = 0; + + /* Set up Arcade Card RAM buffer */ + m_acard_ram = auto_alloc_array_clear(machine(), UINT8, PCE_ACARD_RAM_SIZE); + + m_data_buffer = auto_alloc_array_clear(machine(), UINT8, 8192); + m_data_buffer_size = 0; + m_data_buffer_index = 0; + + m_subcode_buffer = auto_alloc_array(machine(), UINT8, 96); + + m_data_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(pce_cd_device::data_timer_callback),this)); + m_data_timer->adjust(attotime::never); + m_adpcm_dma_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(pce_cd_device::adpcm_dma_timer_callback),this)); + m_adpcm_dma_timer->adjust(attotime::never); + + m_cdda_fadeout_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(pce_cd_device::cdda_fadeout_callback),this)); + m_cdda_fadeout_timer->adjust(attotime::never); + m_cdda_fadein_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(pce_cd_device::cdda_fadein_callback),this)); + m_cdda_fadein_timer->adjust(attotime::never); + + m_adpcm_fadeout_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(pce_cd_device::adpcm_fadeout_callback),this)); + m_adpcm_fadeout_timer->adjust(attotime::never); + m_adpcm_fadein_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(pce_cd_device::adpcm_fadein_callback),this)); + m_adpcm_fadein_timer->adjust(attotime::never); + + // m_cd_file pointer is setup at a later stage because it is still empty when this function is called + + // TODO: add proper restore for the cd data... + save_item(NAME(m_regs)); + save_pointer(NAME(m_bram), PCE_BRAM_SIZE * 2); + save_pointer(NAME(m_adpcm_ram), PCE_ADPCM_RAM_SIZE); + save_item(NAME(m_bram_locked)); + save_item(NAME(m_adpcm_read_ptr)); + save_item(NAME(m_adpcm_read_buf)); + save_item(NAME(m_adpcm_write_ptr)); + save_item(NAME(m_adpcm_write_buf)); + save_item(NAME(m_adpcm_length)); + save_item(NAME(m_adpcm_clock_divider)); + save_item(NAME(m_msm_start_addr)); + save_item(NAME(m_msm_end_addr)); + save_item(NAME(m_msm_half_addr)); + save_item(NAME(m_msm_nibble)); + save_item(NAME(m_msm_idle)); + save_item(NAME(m_msm_repeat)); + save_item(NAME(m_scsi_BSY)); + save_item(NAME(m_scsi_SEL)); + save_item(NAME(m_scsi_CD)); + save_item(NAME(m_scsi_IO)); + save_item(NAME(m_scsi_MSG)); + save_item(NAME(m_scsi_REQ)); + save_item(NAME(m_scsi_ACK)); + save_item(NAME(m_scsi_ATN)); + save_item(NAME(m_scsi_RST)); + save_item(NAME(m_scsi_last_RST)); + save_item(NAME(m_cd_motor_on)); + save_item(NAME(m_selected)); + save_pointer(NAME(m_command_buffer), PCE_CD_COMMAND_BUFFER_SIZE); + save_item(NAME(m_command_buffer_index)); + save_item(NAME(m_status_sent)); + save_item(NAME(m_message_after_status)); + save_item(NAME(m_message_sent)); + save_pointer(NAME(m_data_buffer), 8192); + save_item(NAME(m_data_buffer_size)); + save_item(NAME(m_data_buffer_index)); + save_item(NAME(m_data_transferred)); + save_pointer(NAME(m_acard_ram), PCE_ACARD_RAM_SIZE); + save_item(NAME(m_acard_latch)); + save_item(NAME(m_acard_ctrl)); + save_item(NAME(m_acard_base_addr)); + save_item(NAME(m_acard_addr_offset)); + save_item(NAME(m_acard_addr_inc)); + save_item(NAME(m_acard_shift)); + save_item(NAME(m_acard_shift_reg)); + save_item(NAME(m_current_frame)); + save_item(NAME(m_end_frame)); + save_item(NAME(m_last_frame)); + save_item(NAME(m_cdda_status)); + save_item(NAME(m_cdda_play_mode)); + save_pointer(NAME(m_subcode_buffer), 96); + save_item(NAME(m_end_mark)); + save_item(NAME(m_cdda_volume)); + save_item(NAME(m_adpcm_volume)); +} + +void pce_cd_device::device_reset() +{ + m_adpcm_read_buf = 0; + m_adpcm_write_buf = 0; + + // TODO: add CD-DA stop command here + //m_cdda_status = PCE_CD_CDDA_OFF; + //cdda_stop_audio(machine().device("cdda")); + + memset(m_regs, 0, sizeof(m_regs)); + + m_regs[0x0c] |= PCE_CD_ADPCM_STOP_FLAG; + m_regs[0x0c] &= ~PCE_CD_ADPCM_PLAY_FLAG; + //m_regs[0x03] = (m_regs[0x03] & ~0x0c) | (PCE_CD_SAMPLE_STOP_PLAY); + m_msm_idle = 1; +} + + +void pce_cd_device::late_setup() +{ + // at device start, the cdrom is not 'ready' yet, so we postpone this part of the initialization at machine_start in the driver + m_cd_file = m_cdrom->get_cdrom_file(); + if (m_cd_file) + { + m_toc = cdrom_get_toc(m_cd_file); + machine().device<cdda_device>("cdda")->set_cdrom(m_cd_file); + m_last_frame = cdrom_get_track_start(m_cd_file, cdrom_get_last_track(m_cd_file) - 1); + m_last_frame += m_toc->tracks[cdrom_get_last_track(m_cd_file) - 1].frames; + m_end_frame = m_last_frame; + } + +} + +struct cdrom_interface pce_cdrom = +{ + "pce_cdrom", + NULL +}; + + +static MACHINE_CONFIG_FRAGMENT( pce_cd ) + MCFG_NVRAM_ADD_0FILL("bram") + +// TODO: move here MSM5205 and CDDA! + MCFG_CDROM_ADD("cdrom", pce_cdrom) +MACHINE_CONFIG_END + +machine_config_constructor pce_cd_device::device_mconfig_additions() const +{ + return MACHINE_CONFIG_NAME( pce_cd ); +} + + +void pce_cd_device::adpcm_stop(UINT8 irq_flag) +{ + m_regs[0x0c] |= PCE_CD_ADPCM_STOP_FLAG; + m_regs[0x0c] &= ~PCE_CD_ADPCM_PLAY_FLAG; + //m_regs[0x03] = (m_regs[0x03] & ~0x0c) | (PCE_CD_SAMPLE_STOP_PLAY); + if (irq_flag) + set_irq_line(PCE_CD_IRQ_SAMPLE_FULL_PLAY, ASSERT_LINE); + m_regs[0x0d] &= ~0x60; + m_msm_idle = 1; +} + +void pce_cd_device::adpcm_play() +{ + m_regs[0x0c] &= ~PCE_CD_ADPCM_STOP_FLAG; + m_regs[0x0c] |= PCE_CD_ADPCM_PLAY_FLAG; + set_irq_line(PCE_CD_IRQ_SAMPLE_FULL_PLAY, CLEAR_LINE); + m_regs[0x03] = (m_regs[0x03] & ~0x0c); + m_msm_idle = 0; +} + + +/* Callback for new data from the MSM5205. + The PCE cd unit actually divides the clock signal supplied to + the MSM5205. Currently we can only use static clocks for the + MSM5205. + */ +void pce_cd_device::msm5205_int(msm5205_device *device) +{ + UINT8 msm_data; + + // popmessage("%08x %08x %08x %02x %02x",m_msm_start_addr,m_msm_end_addr,m_msm_half_addr,m_regs[0x0c],m_regs[0x0d]); + + if (m_msm_idle) + return; + + /* Supply new ADPCM data */ + msm_data = (m_msm_nibble) ? (m_adpcm_ram[m_msm_start_addr] & 0x0f) : ((m_adpcm_ram[m_msm_start_addr] & 0xf0) >> 4); + + device->data_w(msm_data); + m_msm_nibble ^= 1; + + if (m_msm_nibble == 0) + { + m_msm_start_addr++; + + if (m_msm_start_addr == m_msm_half_addr) + { + //set_irq_line(PCE_CD_IRQ_SAMPLE_FULL_PLAY, CLEAR_LINE); + //set_irq_line(PCE_CD_IRQ_SAMPLE_HALF_PLAY, ASSERT_LINE); + } + + if (m_msm_start_addr > m_msm_end_addr) + { + //set_irq_line(PCE_CD_IRQ_SAMPLE_HALF_PLAY, CLEAR_LINE); + //set_irq_line(PCE_CD_IRQ_SAMPLE_FULL_PLAY, CLEAR_LINE); + adpcm_stop(1); + device->reset_w(1); + } + } +} + +#define SCSI_STATUS_OK 0x00 +#define SCSI_CHECK_CONDITION 0x02 + +void pce_cd_device::reply_status_byte(UINT8 status) +{ + logerror("Setting CD in reply_status_byte\n"); + m_scsi_CD = m_scsi_IO = m_scsi_REQ = 1; + m_scsi_MSG = 0; + m_message_after_status = 1; + m_status_sent = m_message_sent = 0; + + if (status == SCSI_STATUS_OK) + { + m_regs[0x01] = 0x00; + } + else + { + m_regs[0x01] = 0x01; + } +} + +/* 0x00 - TEST UNIT READY */ +void pce_cd_device::test_unit_ready() +{ + logerror("test unit ready\n"); + if (m_cd_file) + { + logerror("Sending STATUS_OK status\n"); + reply_status_byte(SCSI_STATUS_OK); + } + else + { + logerror("Sending CHECK_CONDITION status\n"); + reply_status_byte(SCSI_CHECK_CONDITION); + } +} + +/* 0x08 - READ (6) */ +void pce_cd_device::read_6() +{ + UINT32 frame = ((m_command_buffer[1] & 0x1f) << 16) | (m_command_buffer[2] << 8) | m_command_buffer[3]; + UINT32 frame_count = m_command_buffer[4]; + printf("%08x %08x\n",frame,frame_count); + + /* Check for presence of a CD */ + if (!m_cd_file) + { + reply_status_byte(SCSI_CHECK_CONDITION); + return; + } + + if (m_cdda_status != PCE_CD_CDDA_OFF) + { + m_cdda_status = PCE_CD_CDDA_OFF; + machine().device<cdda_device>("cdda")->stop_audio(); + m_end_mark = 0; + } + + m_current_frame = frame; + m_end_frame = frame + frame_count; + + if (frame_count == 0) + { + /* Star Breaker uses this */ + popmessage("Read Sector frame count == 0, contact MESSdev"); + reply_status_byte(SCSI_STATUS_OK); + } + else + { + m_data_timer->adjust(attotime::from_hz(PCE_CD_DATA_FRAMES_PER_SECOND), 0, attotime::from_hz(PCE_CD_DATA_FRAMES_PER_SECOND)); + } + + /* TODO: correct place? */ + set_irq_line(PCE_CD_IRQ_TRANSFER_READY, ASSERT_LINE); +} + +/* 0xD8 - SET AUDIO PLAYBACK START POSITION (NEC) */ +void pce_cd_device::nec_set_audio_start_position() +{ + UINT32 frame = 0; + + if (!m_cd_file) + { + /* Throw some error here */ + reply_status_byte(SCSI_CHECK_CONDITION); + return; + } + + switch (m_command_buffer[9] & 0xC0) + { + case 0x00: + popmessage("CD-DA set start mode 0x00, contact MESSdev"); + frame = (m_command_buffer[3] << 16) | (m_command_buffer[4] << 8) | m_command_buffer[5]; + break; + case 0x40: + { + UINT8 m,s,f; + + m = bcd_2_dec(m_command_buffer[2]); + s = bcd_2_dec(m_command_buffer[3]); + f = bcd_2_dec(m_command_buffer[4]); + + frame = f + 75 * (s + m * 60); + // PCE tries to be clever here and set (start of track + track pregap size) to skip the pregap + // (I guess it wants the TOC to have the real start sector for data tracks and the start of the pregap for audio?) + frame -= m_toc->tracks[cdrom_get_track(m_cd_file, frame)].pregap; + break; + } + case 0x80: + frame = m_toc->tracks[ bcd_2_dec(m_command_buffer[2]) - 1 ].logframeofs; + break; + default: + popmessage("CD-DA set start mode 0xc0, contact MESSdev"); + //assert(NULL == nec_set_audio_start_position); + break; + } + + m_current_frame = frame; + + if (m_cdda_status == PCE_CD_CDDA_PAUSED) + { + m_cdda_status = PCE_CD_CDDA_OFF; + machine().device<cdda_device>("cdda")->stop_audio(); + m_end_frame = m_last_frame; + m_end_mark = 0; + } + else + { + if (m_command_buffer[1] & 0x03) + { + m_cdda_status = PCE_CD_CDDA_PLAYING; + m_end_frame = m_last_frame; //get the end of the CD + machine().device<cdda_device>("cdda")->start_audio(m_current_frame, m_end_frame - m_current_frame); + m_cdda_play_mode = (m_command_buffer[1] & 0x02) ? 2 : 3; // mode 2 sets IRQ at end + m_end_mark = (m_command_buffer[1] & 0x02) ? 1 : 0; + } + else + { + m_cdda_status = PCE_CD_CDDA_PLAYING; + m_end_frame = m_toc->tracks[ cdrom_get_track(m_cd_file, m_current_frame) + 1 ].logframeofs; //get the end of THIS track + machine().device<cdda_device>("cdda")->start_audio(m_current_frame, m_end_frame - m_current_frame); + m_end_mark = 0; + m_cdda_play_mode = 3; + } + } + + reply_status_byte(SCSI_STATUS_OK); + set_irq_line(PCE_CD_IRQ_TRANSFER_DONE, ASSERT_LINE); +} + +/* 0xD9 - SET AUDIO PLAYBACK END POSITION (NEC) */ +void pce_cd_device::nec_set_audio_stop_position() +{ + UINT32 frame = 0; + + if (!m_cd_file) + { + /* Throw some error here */ + reply_status_byte(SCSI_CHECK_CONDITION); + return; + } + + switch (m_command_buffer[9] & 0xC0) + { + case 0x00: + popmessage("CD-DA set end mode 0x00, contact MESSdev"); + frame = (m_command_buffer[3] << 16) | (m_command_buffer[4] << 8) | m_command_buffer[5]; + break; + case 0x40: + { + UINT8 m,s,f; + + m = bcd_2_dec(m_command_buffer[2]); + s = bcd_2_dec(m_command_buffer[3]); + f = bcd_2_dec(m_command_buffer[4]); + + frame = f + 75 * (s + m * 60); + // if (frame >= 525) // TODO: seven seconds gap? O_o + // frame -= 525; + break; + } + case 0x80: + frame = m_toc->tracks[ bcd_2_dec(m_command_buffer[2]) - 1 ].logframeofs; + break; + default: + popmessage("CD-DA set end mode 0xc0, contact MESSdev"); + //assert(NULL == nec_set_audio_start_position); + break; + } + + m_end_frame = frame; + m_cdda_play_mode = m_command_buffer[1] & 0x03; + + if (m_cdda_play_mode) + { + if (m_cdda_status == PCE_CD_CDDA_PAUSED) + { + machine().device<cdda_device>("cdda")->pause_audio(0); + } + else + { + //printf("%08x %08x\n",m_current_frame,m_end_frame - m_current_frame); + machine().device<cdda_device>("cdda")->start_audio(m_current_frame, m_end_frame - m_current_frame); + m_end_mark = 1; + } + m_cdda_status = PCE_CD_CDDA_PLAYING; + } + else + { + m_cdda_status = PCE_CD_CDDA_OFF; + machine().device<cdda_device>("cdda")->stop_audio(); + m_end_frame = m_last_frame; + m_end_mark = 0; +// assert(NULL == nec_set_audio_stop_position); + } + + reply_status_byte(SCSI_STATUS_OK); + set_irq_line(PCE_CD_IRQ_TRANSFER_DONE, ASSERT_LINE); +} + +/* 0xDA - PAUSE (NEC) */ +void pce_cd_device::nec_pause() +{ + /* If no cd mounted throw an error */ + if (!m_cd_file) + { + reply_status_byte(SCSI_CHECK_CONDITION); + return; + } + + /* If there was no cdda playing, throw an error */ + if (m_cdda_status == PCE_CD_CDDA_OFF) + { + reply_status_byte(SCSI_CHECK_CONDITION); + return; + } + + m_cdda_status = PCE_CD_CDDA_PAUSED; + m_current_frame = machine().device<cdda_device>("cdda")->get_audio_lba(); + machine().device<cdda_device>("cdda")->pause_audio(1); + reply_status_byte(SCSI_STATUS_OK); +} + +/* 0xDD - READ SUBCHANNEL Q (NEC) */ +void pce_cd_device::nec_get_subq() +{ + /* WP - I do not have access to chds with subchannel information yet, so I'm faking something here */ + UINT32 msf_abs, msf_rel, track, frame; + + if (!m_cd_file) + { + /* Throw some error here */ + reply_status_byte(SCSI_CHECK_CONDITION); + return; + } + + frame = m_current_frame; + + switch (m_cdda_status) + { + case PCE_CD_CDDA_PAUSED: + m_data_buffer[0] = 2; + frame = machine().device<cdda_device>("cdda")->get_audio_lba(); + break; + case PCE_CD_CDDA_PLAYING: + m_data_buffer[0] = 0; + frame = machine().device<cdda_device>("cdda")->get_audio_lba(); + break; + default: + m_data_buffer[0] = 3; + break; + } + + msf_abs = lba_to_msf_alt(frame); + track = cdrom_get_track(m_cd_file, frame); + msf_rel = lba_to_msf_alt(frame - cdrom_get_track_start(m_cd_file, track)); + + m_data_buffer[1] = 0x01 | ((cdrom_get_track_type(m_cd_file, cdrom_get_track(m_cd_file, track+1)) == CD_TRACK_AUDIO) ? 0x00 : 0x40); + m_data_buffer[2] = dec_2_bcd(track+1); /* track */ + m_data_buffer[3] = 1; /* index */ + m_data_buffer[4] = dec_2_bcd((msf_rel >> 16) & 0xFF);/* M (relative) */ + m_data_buffer[5] = dec_2_bcd((msf_rel >> 8) & 0xFF); /* S (relative) */ + m_data_buffer[6] = dec_2_bcd(msf_rel & 0xFF); /* F (relative) */ + m_data_buffer[7] = dec_2_bcd((msf_abs >> 16) & 0xFF);/* M (absolute) */ + m_data_buffer[8] = dec_2_bcd((msf_abs >> 8) & 0xFF); /* S (absolute) */ + m_data_buffer[9] = dec_2_bcd(msf_abs & 0xFF); /* F (absolute) */ + m_data_buffer_size = 10; + + m_data_buffer_index = 0; + m_data_transferred = 1; + m_scsi_IO = 1; + m_scsi_CD = 0; +} + +/* 0xDE - GET DIR INFO (NEC) */ +void pce_cd_device::nec_get_dir_info() +{ + UINT32 frame, msf, track = 0; + const cdrom_toc *toc; + logerror("nec get dir info\n"); + + if (!m_cd_file) + { + /* Throw some error here */ + reply_status_byte(SCSI_CHECK_CONDITION); + } + + toc = cdrom_get_toc(m_cd_file); + + switch (m_command_buffer[1]) + { + case 0x00: /* Get first and last track numbers */ + m_data_buffer[0] = dec_2_bcd(1); + m_data_buffer[1] = dec_2_bcd(toc->numtrks); + m_data_buffer_size = 2; + break; + case 0x01: /* Get total disk size in MSF format */ + frame = toc->tracks[toc->numtrks-1].logframeofs; + frame += toc->tracks[toc->numtrks-1].frames; + msf = lba_to_msf(frame + 150); + + m_data_buffer[0] = (msf >> 16) & 0xFF; /* M */ + m_data_buffer[1] = (msf >> 8) & 0xFF; /* S */ + m_data_buffer[2] = msf & 0xFF; /* F */ + m_data_buffer_size = 3; + break; + case 0x02: /* Get track information */ + if (m_command_buffer[2] == 0xAA) + { + frame = toc->tracks[toc->numtrks-1].logframeofs; + frame += toc->tracks[toc->numtrks-1].frames; + m_data_buffer[3] = 0x04; /* correct? */ + } else + { + track = MAX(bcd_2_dec(m_command_buffer[2]), 1); + frame = toc->tracks[track-1].logframeofs; + // PCE wants the start sector for data tracks to *not* include the pregap + if (toc->tracks[track-1].trktype != CD_TRACK_AUDIO) + { + frame += toc->tracks[track-1].pregap; + } + m_data_buffer[3] = (toc->tracks[track-1].trktype == CD_TRACK_AUDIO) ? 0x00 : 0x04; + } + logerror("track = %d, frame = %d\n", track, frame); + msf = lba_to_msf(frame + 150); + m_data_buffer[0] = (msf >> 16) & 0xFF; /* M */ + m_data_buffer[1] = (msf >> 8) & 0xFF; /* S */ + m_data_buffer[2] = msf & 0xFF; /* F */ + m_data_buffer_size = 4; + break; + default: +// assert(pce_cd_nec_get_dir_info == NULL); // Not implemented yet + break; + } + + m_data_buffer_index = 0; + m_data_transferred = 1; + m_scsi_IO = 1; + m_scsi_CD = 0; +} + +void pce_cd_device::end_of_list() +{ + reply_status_byte(SCSI_CHECK_CONDITION); +} + +typedef void (pce_cd_device::*command_handler_func)(); + +void pce_cd_device::handle_data_output() +{ + static const struct { + UINT8 command_byte; + UINT8 command_size; + command_handler_func command_handler; + } pce_cd_commands[] = { + { 0x00, 6, &pce_cd_device::test_unit_ready }, /* TEST UNIT READY */ + { 0x08, 6, &pce_cd_device::read_6 }, /* READ (6) */ + { 0xD8,10, &pce_cd_device::nec_set_audio_start_position }, /* NEC SET AUDIO PLAYBACK START POSITION */ + { 0xD9,10, &pce_cd_device::nec_set_audio_stop_position }, /* NEC SET AUDIO PLAYBACK END POSITION */ + { 0xDA,10, &pce_cd_device::nec_pause }, /* NEC PAUSE */ + { 0xDD,10, &pce_cd_device::nec_get_subq }, /* NEC GET SUBCHANNEL Q */ + { 0xDE,10, &pce_cd_device::nec_get_dir_info }, /* NEC GET DIR INFO */ + { 0xFF, 1, &pce_cd_device::end_of_list } /* end of list marker */ + }; + + if (m_scsi_REQ && m_scsi_ACK) + { + /* Command byte received */ + logerror("Command byte $%02X received\n", m_regs[0x01]); + + /* Check for buffer overflow */ + assert(m_command_buffer_index < PCE_CD_COMMAND_BUFFER_SIZE); + + m_command_buffer[m_command_buffer_index] = m_regs[0x01]; + m_command_buffer_index++; + m_scsi_REQ = 0; + } + + if (! m_scsi_REQ && ! m_scsi_ACK && m_command_buffer_index) + { + int i = 0; + + logerror("Check if command done\n"); + + for(i = 0; m_command_buffer[0] > pce_cd_commands[i].command_byte; i++); + + /* Check for unknown commands */ + if (m_command_buffer[0] != pce_cd_commands[i].command_byte) + { + logerror("Unrecognized command: %02X\n", m_command_buffer[0]); + if (m_command_buffer[0] == 0x03) + popmessage("CD command 0x03 issued (Request Sense), contact MESSdev"); + } + assert(m_command_buffer[0] == pce_cd_commands[i].command_byte); + + if (m_command_buffer_index == pce_cd_commands[i].command_size) + { + //printf("%02x command issued\n",m_command_buffer[0]); + (this->*pce_cd_commands[i].command_handler)(); + m_command_buffer_index = 0; + } + else + { + m_scsi_REQ = 1; + } + } +} + +void pce_cd_device::handle_data_input() +{ + if (m_scsi_CD) + { + /* Command / Status byte */ + if (m_scsi_REQ && m_scsi_ACK) + { + logerror("status sent\n"); + m_scsi_REQ = 0; + m_status_sent = 1; + } + + if (! m_scsi_REQ && ! m_scsi_ACK && m_status_sent) + { + m_status_sent = 0; + if (m_message_after_status) + { + logerror("message after status\n"); + m_message_after_status = 0; + m_scsi_MSG = m_scsi_REQ = 1; + m_regs[0x01] = 0; + } + } + } + else + { + /* Data */ + if (m_scsi_REQ && m_scsi_ACK) + { + m_scsi_REQ = 0; + } + + if (! m_scsi_REQ && ! m_scsi_ACK) + { + if (m_data_buffer_index == m_data_buffer_size) + { + set_irq_line(PCE_CD_IRQ_TRANSFER_READY, CLEAR_LINE); + if (m_data_transferred) + { + m_data_transferred = 0; + reply_status_byte(SCSI_STATUS_OK); + set_irq_line(PCE_CD_IRQ_TRANSFER_DONE, ASSERT_LINE); + } + } + else + { + logerror("Transfer byte %02x from offset %d %d\n",m_data_buffer[m_data_buffer_index] , m_data_buffer_index, m_current_frame); + m_regs[0x01] = m_data_buffer[m_data_buffer_index]; + m_data_buffer_index++; + m_scsi_REQ = 1; + } + } + } +} + +void pce_cd_device::handle_message_output() +{ + if (m_scsi_REQ && m_scsi_ACK) + m_scsi_REQ = 0; +} + +void pce_cd_device::handle_message_input() +{ + if (m_scsi_REQ && m_scsi_ACK) + { + m_scsi_REQ = 0; + m_message_sent = 1; + } + + if (! m_scsi_REQ && ! m_scsi_ACK && m_message_sent) + { + m_message_sent = 0; + m_scsi_BSY = 0; + } +} + +/* Update internal CD statuses */ +void pce_cd_device::update() +{ + /* Check for reset of CD unit */ + if (m_scsi_RST != m_scsi_last_RST) + { + if (m_scsi_RST) + { + logerror("Performing CD reset\n"); + /* Reset internal data */ + m_scsi_BSY = m_scsi_SEL = m_scsi_CD = m_scsi_IO = 0; + m_scsi_MSG = m_scsi_REQ = m_scsi_ATN = 0; + m_cd_motor_on = 0; + m_selected = 0; + m_cdda_status = PCE_CD_CDDA_OFF; + machine().device<cdda_device>("cdda")->stop_audio(); + m_adpcm_dma_timer->adjust(attotime::never); // stop ADPCM DMA here + } + m_scsi_last_RST = m_scsi_RST; + } + + /* Check if bus can be freed */ + if (! m_scsi_SEL && ! m_scsi_BSY && m_selected) + { + logerror("freeing bus\n"); + m_selected = 0; + m_scsi_CD = m_scsi_MSG = m_scsi_IO = m_scsi_REQ = 0; + set_irq_line(PCE_CD_IRQ_TRANSFER_DONE, CLEAR_LINE); + } + + /* Select the CD device */ + if (m_scsi_SEL) + { + if (! m_selected) + { + m_selected = 1; + logerror("Setting CD in device selection\n"); + m_scsi_BSY = m_scsi_REQ = m_scsi_CD = 1; + m_scsi_MSG = m_scsi_IO = 0; + } + } + + if (m_scsi_ATN) + { + } + else + { + /* Check for data and message phases */ + if (m_scsi_BSY) + { + if (m_scsi_MSG) + { + /* message phase */ + if (m_scsi_IO) + { + handle_message_input(); + } + else + { + handle_message_output(); + } + } + else + { + /* data phase */ + if (m_scsi_IO) + { + /* Reading data from target */ + handle_data_input(); + } + else + { + /* Sending data to target */ + handle_data_output(); + } + } + } + } + + /* FIXME: presumably CD-DA needs an irq interface for this */ + if (machine().device<cdda_device>("cdda")->audio_ended() && m_end_mark == 1) + { + switch (m_cdda_play_mode & 3) + { + case 1: machine().device<cdda_device>("cdda")->start_audio(m_current_frame, m_end_frame - m_current_frame); m_end_mark = 1; break; //play with repeat + case 2: set_irq_line(PCE_CD_IRQ_TRANSFER_DONE, ASSERT_LINE); m_end_mark = 0; break; //irq when finished + case 3: m_end_mark = 0; break; //play without repeat + } + } +} + +void pce_cd_device::set_irq_line(int num, int state) +{ + if (state == ASSERT_LINE) + m_regs[0x03] |= num; + else + m_regs[0x03] &= ~num; + + if (m_regs[0x02] & m_regs[0x03] & 0x7c) + { + //printf("IRQ PEND = %02x MASK = %02x IRQ ENABLE %02X\n",m_regs[0x02] & m_regs[0x03] & 0x7c,m_regs[0x02] & 0x7c,m_regs[0x03] & 0x7c); + machine().device<cpu_device>("maincpu")->set_input_line(1, ASSERT_LINE); + } + else + { + machine().device<cpu_device>("maincpu")->set_input_line(1, CLEAR_LINE); + } +} + +TIMER_CALLBACK_MEMBER(pce_cd_device::data_timer_callback) +{ + if (m_data_buffer_index == m_data_buffer_size) + { + /* Read next data sector */ + logerror("read sector %d\n", m_current_frame); + if (! cdrom_read_data(m_cd_file, m_current_frame, m_data_buffer, CD_TRACK_MODE1)) + { + logerror("Mode1 CD read failed for frame #%d\n", m_current_frame); + } + else + { + logerror("Succesfully read mode1 frame #%d\n", m_current_frame); + } + + m_data_buffer_index = 0; + m_data_buffer_size = 2048; + m_current_frame++; + + m_scsi_IO = 1; + m_scsi_CD = 0; + + if (m_current_frame == m_end_frame) + { + /* We are done, disable the timer */ + logerror("Last frame read from CD\n"); + m_data_transferred = 1; + m_data_timer->adjust(attotime::never); + } + else + { + m_data_transferred = 0; + } + } +} + +WRITE8_MEMBER(pce_cd_device::bram_w) +{ + if (!m_bram_locked) + { + m_bram[offset & (PCE_BRAM_SIZE - 1)] = data; + } +} + +READ8_MEMBER(pce_cd_device::bram_r) +{ + return m_bram[(offset & (PCE_BRAM_SIZE - 1)) + m_bram_locked * PCE_BRAM_SIZE]; +} + +void pce_cd_device::set_adpcm_ram_byte(UINT8 val) +{ + if (m_adpcm_write_buf > 0) + { + m_adpcm_write_buf--; + } + else + { + m_adpcm_ram[m_adpcm_write_ptr] = val; + m_adpcm_write_ptr = ((m_adpcm_write_ptr + 1) & 0xffff); + //TODO: length + 1 + } +} + +TIMER_CALLBACK_MEMBER(pce_cd_device::cdda_fadeout_callback) +{ + m_cdda_volume -= 0.1; + + if (m_cdda_volume <= 0) + { + m_cdda_volume = 0.0; + machine().device<cdda_device>("cdda")->set_volume(0.0); + m_cdda_fadeout_timer->adjust(attotime::never); + } + else + { + machine().device<cdda_device>("cdda")->set_volume(m_cdda_volume); + m_cdda_fadeout_timer->adjust(attotime::from_usec(param), param); + } +} + +TIMER_CALLBACK_MEMBER(pce_cd_device::cdda_fadein_callback) +{ + m_cdda_volume += 0.1; + + if (m_cdda_volume >= 100.0) + { + m_cdda_volume = 100.0; + machine().device<cdda_device>("cdda")->set_volume(100.0); + m_cdda_fadein_timer->adjust(attotime::never); + } + else + { + machine().device<cdda_device>("cdda")->set_volume(m_cdda_volume); + m_cdda_fadein_timer->adjust(attotime::from_usec(param), param); + } +} + +TIMER_CALLBACK_MEMBER(pce_cd_device::adpcm_fadeout_callback) +{ + m_adpcm_volume -= 0.1; + + if (m_adpcm_volume <= 0) + { + m_adpcm_volume = 0.0; + machine().device<msm5205_device>("msm5205")->set_volume(0.0); + m_adpcm_fadeout_timer->adjust(attotime::never); + } + else + { + machine().device<msm5205_device>("msm5205")->set_volume(m_adpcm_volume); + m_adpcm_fadeout_timer->adjust(attotime::from_usec(param), param); + } +} + +TIMER_CALLBACK_MEMBER(pce_cd_device::adpcm_fadein_callback) +{ + m_adpcm_volume += 0.1; + + if (m_adpcm_volume >= 100.0) + { + m_adpcm_volume = 100.0; + machine().device<msm5205_device>("msm5205")->set_volume(100.0); + m_adpcm_fadein_timer->adjust(attotime::never); + } + else + { + machine().device<msm5205_device>("msm5205")->set_volume(m_adpcm_volume); + m_adpcm_fadein_timer->adjust(attotime::from_usec(param), param); + } +} + + +WRITE8_MEMBER(pce_cd_device::intf_w) +{ + logerror("%04X: write to CD interface offset %02X, data %02X\n", space.device().safe_pc(), offset, data); + + switch (offset & 0xf) + { + case 0x00: /* CDC status */ + /* select device (which bits??) */ + m_scsi_SEL = 1; + update(); + m_scsi_SEL = 0; + m_adpcm_dma_timer->adjust(attotime::never); // stop ADPCM DMA here + /* any write here clears CD transfer irqs */ + set_irq_line(0x70, CLEAR_LINE); + break; + case 0x01: /* CDC command / status / data */ + break; + case 0x02: /* ADPCM / CD control / IRQ enable/disable */ + /* bit 6 - transfer ready irq */ + /* bit 5 - transfer done irq */ + /* bit 4 - BRAM irq? */ + /* bit 3 - ADPCM FULL irq */ + /* bit 2 - ADPCM HALF irq */ + m_scsi_ACK = data & 0x80; + /* Update mask register now otherwise it won't catch the irq enable/disable change */ + m_regs[0x02] = data; + /* Don't set or reset any irq lines, but just verify the current state */ + set_irq_line(0, 0); + break; + case 0x03: /* BRAM lock / CD status / IRQ - Read Only register */ + break; + case 0x04: /* CD reset */ + m_scsi_RST = data & 0x02; + break; + case 0x05: /* Convert PCM data / PCM data */ + case 0x06: /* PCM data */ + break; + case 0x07: /* BRAM unlock / CD status */ + if (data & 0x80) + m_bram_locked = 0; + break; + case 0x08: /* ADPCM address (LSB) / CD data */ + break; + case 0x09: /* ADPCM address (MSB) */ + break; + case 0x0A: /* ADPCM RAM data port */ + set_adpcm_ram_byte(data); + break; + case 0x0B: /* ADPCM DMA control */ + if (data & 0x03) + { + /* Start CD to ADPCM transfer */ + m_adpcm_dma_timer->adjust(attotime::from_hz(PCE_CD_DATA_FRAMES_PER_SECOND * 2048), 0, attotime::from_hz(PCE_CD_DATA_FRAMES_PER_SECOND * 2048)); + m_regs[0x0c] |= 4; + } + break; + case 0x0C: /* ADPCM status */ + break; + case 0x0D: /* ADPCM address control */ + if ((m_regs[0x0D] & 0x80) && !(data & 0x80)) // ADPCM reset + { + /* Reset ADPCM hardware */ + m_adpcm_read_ptr = 0; + m_adpcm_write_ptr = 0; + m_msm_start_addr = 0; + m_msm_end_addr = 0; + m_msm_half_addr = 0; + m_msm_nibble = 0; + adpcm_stop(0); + machine().device<msm5205_device>("msm5205")->reset_w(1); + } + + if ((data & 0x40) && ((m_regs[0x0D] & 0x40) == 0)) // ADPCM play + { + m_msm_start_addr = (m_adpcm_read_ptr); + m_msm_end_addr = (m_adpcm_read_ptr + m_adpcm_length) & 0xffff; + m_msm_half_addr = (m_adpcm_read_ptr + (m_adpcm_length / 2)) & 0xffff; + m_msm_nibble = 0; + adpcm_play(); + machine().device<msm5205_device>("msm5205")->reset_w(0); + + //popmessage("%08x %08x",m_adpcm_read_ptr,m_adpcm_length); + } + else if ((data & 0x40) == 0) + { + /* used by Buster Bros to cancel an in-flight sample */ + adpcm_stop(0); + machine().device<msm5205_device>("msm5205")->reset_w(1); + } + + m_msm_repeat = BIT(data, 5); + + if (data & 0x10) //ADPCM set length + { + m_adpcm_length = (m_regs[0x09] << 8) | m_regs[0x08]; + } + if (data & 0x08) //ADPCM set read address + { + m_adpcm_read_ptr = (m_regs[0x09] << 8) | m_regs[0x08]; + m_adpcm_read_buf = 2; + } + if ((data & 0x02) == 0x02) //ADPCM set write address + { + m_adpcm_write_ptr = (m_regs[0x09] << 8) | m_regs[0x08]; + m_adpcm_write_buf = data & 1; + } + break; + case 0x0E: /* ADPCM playback rate */ + m_adpcm_clock_divider = 0x10 - (data & 0x0f); + machine().device<msm5205_device>("msm5205")->change_clock_w((PCE_CD_CLOCK / 6) / m_adpcm_clock_divider); + break; + case 0x0F: /* ADPCM and CD audio fade timer */ + /* TODO: timers needs HW tests */ + if (m_regs[0xf] != data) + { + switch (data & 0xf) + { + case 0x00: //CD-DA / ADPCM enable (100 msecs) + m_cdda_volume = 0.0; + m_cdda_fadein_timer->adjust(attotime::from_usec(100), 100); + m_adpcm_volume = 0.0; + m_adpcm_fadein_timer->adjust(attotime::from_usec(100), 100); + m_cdda_fadeout_timer->adjust(attotime::never); + m_adpcm_fadeout_timer->adjust(attotime::never); + break; + case 0x01: //CD-DA enable (100 msecs) + m_cdda_volume = 0.0; + m_cdda_fadein_timer->adjust(attotime::from_usec(100), 100); + m_cdda_fadeout_timer->adjust(attotime::never); + break; + case 0x08: //CD-DA short (1500 msecs) fade out / ADPCM enable + m_cdda_volume = 100.0; + m_cdda_fadeout_timer->adjust(attotime::from_usec(1500), 1500); + m_adpcm_volume = 0.0; + m_adpcm_fadein_timer->adjust(attotime::from_usec(100), 100); + m_cdda_fadein_timer->adjust(attotime::never); + m_adpcm_fadeout_timer->adjust(attotime::never); + break; + case 0x09: //CD-DA long (5000 msecs) fade out + m_cdda_volume = 100.0; + m_cdda_fadeout_timer->adjust(attotime::from_usec(5000), 5000); + m_cdda_fadein_timer->adjust(attotime::never); + break; + case 0x0a: //ADPCM long (5000 msecs) fade out + m_adpcm_volume = 100.0; + m_adpcm_fadeout_timer->adjust(attotime::from_usec(5000), 5000); + m_adpcm_fadein_timer->adjust(attotime::never); + break; + case 0x0c: //CD-DA short (1500 msecs) fade out / ADPCM enable + m_cdda_volume = 100.0; + m_cdda_fadeout_timer->adjust(attotime::from_usec(1500), 1500); + m_adpcm_volume = 0.0; + m_adpcm_fadein_timer->adjust(attotime::from_usec(100), 100); + m_cdda_fadein_timer->adjust(attotime::never); + m_adpcm_fadeout_timer->adjust(attotime::never); + break; + case 0x0d: //CD-DA short (1500 msecs) fade out + m_cdda_volume = 100.0; + m_cdda_fadeout_timer->adjust(attotime::from_usec(1500), 1500); + m_cdda_fadein_timer->adjust(attotime::never); + break; + case 0x0e: //ADPCM short (1500 msecs) fade out + m_adpcm_volume = 100.0; + m_adpcm_fadeout_timer->adjust(attotime::from_usec(1500), 1500); + m_adpcm_fadein_timer->adjust(attotime::never); + break; + default: + popmessage("CD-DA / ADPCM Fade effect mode %02x, contact MESSdev",data & 0x0f); + break; + } + } + break; + default: + return; + } + m_regs[offset & 0xf] = data; +} + +TIMER_CALLBACK_MEMBER(pce_cd_device::clear_ack) +{ + update(); + m_scsi_ACK = 0; + update(); + if (m_scsi_CD) + { + m_regs[0x0B] &= 0xFC; + } +} + +UINT8 pce_cd_device::get_cd_data_byte() +{ + UINT8 data = m_regs[0x01]; + if (m_scsi_REQ && !m_scsi_ACK && !m_scsi_CD) + { + if (m_scsi_IO) + { + m_scsi_ACK = 1; + machine().scheduler().timer_set(machine().device<cpu_device>("maincpu")->cycles_to_attotime(15), timer_expired_delegate(FUNC(pce_cd_device::clear_ack),this)); + } + } + return data; +} + + +TIMER_CALLBACK_MEMBER(pce_cd_device::adpcm_dma_timer_callback) +{ + if (m_scsi_REQ && !m_scsi_ACK && !m_scsi_CD && m_scsi_IO) + { + m_adpcm_ram[m_adpcm_write_ptr] = get_cd_data_byte(); + m_adpcm_write_ptr = (m_adpcm_write_ptr + 1) & 0xFFFF; + + m_regs[0x0c] &= ~4; + } +} + +UINT8 pce_cd_device::get_adpcm_ram_byte() +{ + if (m_adpcm_read_buf > 0) + { + m_adpcm_read_buf--; + return 0; + } + else + { + UINT8 res; + + res = m_adpcm_ram[m_adpcm_read_ptr]; + m_adpcm_read_ptr = ((m_adpcm_read_ptr + 1) & 0xffff); + + return res; + } +} + +READ8_MEMBER(pce_cd_device::intf_r) +{ + UINT8 data = m_regs[offset & 0x0F]; + + logerror("%04X: read from CD interface offset %02X\n", space.device().safe_pc(), offset ); + + switch (offset & 0xf) + { + case 0x00: /* CDC status */ + data &= 0x07; + data |= m_scsi_BSY ? 0x80 : 0; + data |= m_scsi_REQ ? 0x40 : 0; + data |= m_scsi_MSG ? 0x20 : 0; + data |= m_scsi_CD ? 0x10 : 0; + data |= m_scsi_IO ? 0x08 : 0; + break; + case 0x01: /* CDC command / status / data */ + break; + case 0x02: /* ADPCM / CD control */ + break; + case 0x03: /* BRAM lock / CD status */ + /* bit 4 set when CD motor is on */ + /* bit 2 set when less than half of the ADPCM data is remaining ?? */ + m_bram_locked = 1; + data = data & 0x6E; + data |= (m_cd_motor_on ? 0x10 : 0); + m_regs[0x03] ^= 0x02; /* TODO: get rid of this hack */ + break; + case 0x04: /* CD reset */ + break; + case 0x05: /* Convert PCM data / PCM data */ + data = machine().device<cdda_device>("cdda")->get_channel_volume((m_regs[0x03] & 2) ? 0 : 1) & 0xff; + break; + case 0x06: /* PCM data */ + data = machine().device<cdda_device>("cdda")->get_channel_volume((m_regs[0x03] & 2) ? 0 : 1) >> 8; + break; + case 0x07: /* BRAM unlock / CD status */ + data = (m_bram_locked ? (data & 0x7f) : (data | 0x80)); + break; + case 0x08: /* ADPCM address (LSB) / CD data */ + data = get_cd_data_byte(); + break; + case 0x0A: /* ADPCM RAM data port */ + data = get_adpcm_ram_byte(); + break; + case 0x0B: /* ADPCM DMA control */ + break; + case 0x0C: /* ADPCM status */ + break; + case 0x0D: /* ADPCM address control */ + break; + /* These are read-only registers */ + case 0x09: /* ADPCM address (MSB) */ + case 0x0E: /* ADPCM playback rate */ + case 0x0F: /* ADPCM and CD audio fade timer */ + return 0; + default: + data = 0xFF; + break; + } + + return data; +} + + +/* + +PC Engine Arcade Card emulation + +*/ + +READ8_MEMBER(pce_cd_device::acard_r) +{ + UINT8 r_num; + + if ((offset & 0x2e0) == 0x2e0) + { + switch (offset & 0x2ef) + { + case 0x2e0: return (m_acard_shift >> 0) & 0xff; + case 0x2e1: return (m_acard_shift >> 8) & 0xff; + case 0x2e2: return (m_acard_shift >> 16) & 0xff; + case 0x2e3: return (m_acard_shift >> 24) & 0xff; + case 0x2e4: return (m_acard_shift_reg); + case 0x2e5: return m_acard_latch; + case 0x2ee: return 0x10; + case 0x2ef: return 0x51; + } + + return 0; + } + + r_num = (offset & 0x30) >> 4; + + switch (offset & 0x0f) + { + case 0x00: + case 0x01: + { + UINT8 res; + if (m_acard_ctrl[r_num] & 2) + res = m_acard_ram[(m_acard_base_addr[r_num] + m_acard_addr_offset[r_num]) & 0x1fffff]; + else + res = m_acard_ram[m_acard_base_addr[r_num] & 0x1fffff]; + + if (m_acard_ctrl[r_num] & 0x1) + { + if (m_acard_ctrl[r_num] & 0x10) + { + m_acard_base_addr[r_num] += m_acard_addr_inc[r_num]; + m_acard_base_addr[r_num] &= 0xffffff; + } + else + { + m_acard_addr_offset[r_num] += m_acard_addr_inc[r_num]; + } + } + + return res; + } + case 0x02: return (m_acard_base_addr[r_num] >> 0) & 0xff; + case 0x03: return (m_acard_base_addr[r_num] >> 8) & 0xff; + case 0x04: return (m_acard_base_addr[r_num] >> 16) & 0xff; + case 0x05: return (m_acard_addr_offset[r_num] >> 0) & 0xff; + case 0x06: return (m_acard_addr_offset[r_num] >> 8) & 0xff; + case 0x07: return (m_acard_addr_inc[r_num] >> 0) & 0xff; + case 0x08: return (m_acard_addr_inc[r_num] >> 8) & 0xff; + case 0x09: return m_acard_ctrl[r_num]; + default: return 0; + } + + return 0; +} + +WRITE8_MEMBER(pce_cd_device::acard_w) +{ + UINT8 w_num; + + if ((offset & 0x2e0) == 0x2e0) + { + switch (offset & 0x0f) + { + case 0: m_acard_shift = (data & 0xff) | (m_acard_shift & 0xffffff00); break; + case 1: m_acard_shift = (data << 8) | (m_acard_shift & 0xffff00ff); break; + case 2: m_acard_shift = (data << 16) | (m_acard_shift & 0xff00ffff); break; + case 3: m_acard_shift = (data << 24) | (m_acard_shift & 0x00ffffff); break; + case 4: + { + m_acard_shift_reg = data & 0x0f; + + if (m_acard_shift_reg != 0) + { + m_acard_shift = (m_acard_shift_reg < 8) ? + (m_acard_shift << m_acard_shift_reg) + : (m_acard_shift >> (16 - m_acard_shift_reg)); + } + } + break; + case 5: m_acard_latch = data; break; + } + } + else + { + w_num = (offset & 0x30) >> 4; + + switch (offset & 0x0f) + { + case 0x00: + case 0x01: + if (m_acard_ctrl[w_num] & 2) + m_acard_ram[(m_acard_base_addr[w_num] + m_acard_addr_offset[w_num]) & 0x1fffff] = data; + else + m_acard_ram[m_acard_base_addr[w_num] & 0x1FFFFF] = data; + + if (m_acard_ctrl[w_num] & 0x1) + { + if (m_acard_ctrl[w_num] & 0x10) + { + m_acard_base_addr[w_num] += m_acard_addr_inc[w_num]; + m_acard_base_addr[w_num] &= 0xffffff; + } + else + { + m_acard_addr_offset[w_num] += m_acard_addr_inc[w_num]; + } + } + + break; + + case 0x02: m_acard_base_addr[w_num] = (data & 0xff) | (m_acard_base_addr[w_num] & 0xffff00); break; + case 0x03: m_acard_base_addr[w_num] = (data << 8) | (m_acard_base_addr[w_num] & 0xff00ff); break; + case 0x04: m_acard_base_addr[w_num] = (data << 16) | (m_acard_base_addr[w_num] & 0x00ffff); break; + case 0x05: m_acard_addr_offset[w_num] = (data & 0xff) | (m_acard_addr_offset[w_num] & 0xff00); break; + case 0x06: + m_acard_addr_offset[w_num] = (data << 8) | (m_acard_addr_offset[w_num] & 0x00ff); + + if ((m_acard_ctrl[w_num] & 0x60) == 0x40) + { + m_acard_base_addr[w_num] += m_acard_addr_offset[w_num] + ((m_acard_ctrl[w_num] & 0x08) ? 0xff0000 : 0); + m_acard_base_addr[w_num] &= 0xffffff; + } + break; + case 0x07: m_acard_addr_inc[w_num] = (data & 0xff) | (m_acard_addr_inc[w_num] & 0xff00); break; + case 0x08: m_acard_addr_inc[w_num] = (data << 8) | (m_acard_addr_inc[w_num] & 0x00ff); break; + case 0x09: m_acard_ctrl[w_num] = data & 0x7f; break; + case 0x0a: + if ((m_acard_ctrl[w_num] & 0x60) == 0x60) + { + m_acard_base_addr[w_num] += m_acard_addr_offset[w_num]; + m_acard_base_addr[w_num] &= 0xffffff; + } + break; + } + } +} diff --git a/src/mess/machine/pce_cd.h b/src/mess/machine/pce_cd.h new file mode 100644 index 00000000000..f1e8e41c4a0 --- /dev/null +++ b/src/mess/machine/pce_cd.h @@ -0,0 +1,179 @@ +#ifndef __PCE_CD_H +#define __PCE_CD_H + +/*************************************************************************** + TYPE DEFINITIONS + ***************************************************************************/ + +#include "imagedev/chd_cd.h" +#include "sound/msm5205.h" +#include "machine/nvram.h" + +#define PCE_BRAM_SIZE 0x800 +#define PCE_ADPCM_RAM_SIZE 0x10000 +#define PCE_ACARD_RAM_SIZE 0x200000 +#define PCE_CD_COMMAND_BUFFER_SIZE 0x100 + +#define PCE_CD_IRQ_TRANSFER_READY 0x40 +#define PCE_CD_IRQ_TRANSFER_DONE 0x20 +#define PCE_CD_IRQ_BRAM 0x10 /* ??? */ +#define PCE_CD_IRQ_SAMPLE_FULL_PLAY 0x08 +#define PCE_CD_IRQ_SAMPLE_HALF_PLAY 0x04 + +#define PCE_CD_ADPCM_PLAY_FLAG 0x08 +#define PCE_CD_ADPCM_STOP_FLAG 0x01 + +#define PCE_CD_DATA_FRAMES_PER_SECOND 75 + +enum { + PCE_CD_CDDA_OFF = 0, + PCE_CD_CDDA_PLAYING, + PCE_CD_CDDA_PAUSED +}; + + + +// ======================> pce_cd_device + +class pce_cd_device : public device_t +{ +public: + // construction/destruction + pce_cd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + virtual ~pce_cd_device() {} + + // device-level overrides + virtual void device_start(); + virtual machine_config_constructor device_mconfig_additions() const; + virtual void device_reset(); + + void msm5205_int(msm5205_device *device); + void update(); + + void late_setup(); + + DECLARE_WRITE8_MEMBER(bram_w); + DECLARE_WRITE8_MEMBER(intf_w); + DECLARE_WRITE8_MEMBER(acard_w); + DECLARE_READ8_MEMBER(bram_r); + DECLARE_READ8_MEMBER(intf_r); + DECLARE_READ8_MEMBER(acard_r); + +private: + void adpcm_stop(UINT8 irq_flag); + void adpcm_play(); + void reply_status_byte(UINT8 status); + void test_unit_ready(); + void read_6(); + void nec_set_audio_start_position(); + void nec_set_audio_stop_position(); + void nec_pause(); + void nec_get_subq(); + void nec_get_dir_info(); + void end_of_list(); + void handle_data_output(); + void handle_data_input(); + void handle_message_output(); + void handle_message_input(); + void set_irq_line(int num, int state); + void set_adpcm_ram_byte(UINT8 val); + UINT8 get_cd_data_byte(); + UINT8 get_adpcm_ram_byte(); + + TIMER_CALLBACK_MEMBER(data_timer_callback); + TIMER_CALLBACK_MEMBER(cdda_fadeout_callback); + TIMER_CALLBACK_MEMBER(cdda_fadein_callback); + TIMER_CALLBACK_MEMBER(adpcm_fadeout_callback); + TIMER_CALLBACK_MEMBER(adpcm_fadein_callback); + TIMER_CALLBACK_MEMBER(clear_ack); + TIMER_CALLBACK_MEMBER(adpcm_dma_timer_callback); + + UINT8 m_regs[16]; + UINT8 *m_bram; + UINT8 *m_adpcm_ram; + int m_bram_locked; + int m_adpcm_read_ptr; + UINT8 m_adpcm_read_buf; + int m_adpcm_write_ptr; + UINT8 m_adpcm_write_buf; + int m_adpcm_length; + int m_adpcm_clock_divider; + UINT32 m_msm_start_addr; + UINT32 m_msm_end_addr; + UINT32 m_msm_half_addr; + UINT8 m_msm_nibble; + UINT8 m_msm_idle; + UINT8 m_msm_repeat; + + /* SCSI signals */ + int m_scsi_BSY; /* Busy. Bus in use */ + int m_scsi_SEL; /* Select. Initiator has won arbitration and has selected a target */ + int m_scsi_CD; /* Control/Data. Target is sending control (data) information */ + int m_scsi_IO; /* Input/Output. Target is sending (receiving) information */ + int m_scsi_MSG; /* Message. Target is sending or receiving a message */ + int m_scsi_REQ; /* Request. Target is requesting a data transfer */ + int m_scsi_ACK; /* Acknowledge. Initiator acknowledges that it is ready for a data transfer */ + int m_scsi_ATN; /* Attention. Initiator has a message ready for the target */ + int m_scsi_RST; /* Reset. Initiator forces all targets and any other initiators to do a warm reset */ + int m_scsi_last_RST; /* To catch setting of RST signal */ + int m_cd_motor_on; + int m_selected; + UINT8 *m_command_buffer; + int m_command_buffer_index; + int m_status_sent; + int m_message_after_status; + int m_message_sent; + UINT8 *m_data_buffer; + int m_data_buffer_size; + int m_data_buffer_index; + int m_data_transferred; + + /* Arcade Card specific */ + UINT8 *m_acard_ram; + UINT8 m_acard_latch; + UINT8 m_acard_ctrl[4]; + UINT32 m_acard_base_addr[4]; + UINT16 m_acard_addr_offset[4]; + UINT16 m_acard_addr_inc[4]; + UINT32 m_acard_shift; + UINT8 m_acard_shift_reg; + + UINT32 m_current_frame; + UINT32 m_end_frame; + UINT32 m_last_frame; + UINT8 m_cdda_status; + UINT8 m_cdda_play_mode; + UINT8 *m_subcode_buffer; + UINT8 m_end_mark; + + required_device<nvram_device> m_nvram; + required_device<cdrom_image_device> m_cdrom; + + cdrom_file *m_cd_file; + const cdrom_toc* m_toc; + emu_timer *m_data_timer; + emu_timer *m_adpcm_dma_timer; + + emu_timer *m_cdda_fadeout_timer; + emu_timer *m_cdda_fadein_timer; + double m_cdda_volume; + emu_timer *m_adpcm_fadeout_timer; + emu_timer *m_adpcm_fadein_timer; + double m_adpcm_volume; +}; + + + +// device type definition +extern const device_type PCE_CD; + + +/*************************************************************************** + DEVICE CONFIGURATION MACROS + ***************************************************************************/ + +#define MCFG_PCE_CD_ADD(_tag, _cd_tag) \ + MCFG_DEVICE_ADD(_tag, PCE_CD, 0) \ + + +#endif |