diff options
Diffstat (limited to 'trunk/src/emu/machine/matsucd.c')
-rw-r--r-- | trunk/src/emu/machine/matsucd.c | 766 |
1 files changed, 766 insertions, 0 deletions
diff --git a/trunk/src/emu/machine/matsucd.c b/trunk/src/emu/machine/matsucd.c new file mode 100644 index 00000000000..7778eb5b0db --- /dev/null +++ b/trunk/src/emu/machine/matsucd.c @@ -0,0 +1,766 @@ +/*************************************************************************** + + Matsushita/Panasonic CDR521/522 type CDROM drive emulation + +Notes: +This version implements the drive found in Amiga CDTV. There are +different drives supporting this command-set (look in the sbpcd driver +in Linux for examples). Most drives support the exact same command +structure, but the command numbers differ. Eventually this driver +can be expanded with support for the other drives as needed. + +***************************************************************************/ + + +#include "emu.h" +#include "imagedev/chd_cd.h" +#include "sound/cdda.h" +#include "machine/matsucd.h" + + +#define MATSU_STATUS_READY ( 1 << 0 ) /* driver ready */ +#define MATSU_STATUS_DOORLOCKED ( 1 << 1 ) /* door locked */ +#define MATSU_STATUS_PLAYING ( 1 << 2 ) /* drive playing */ +#define MATSU_STATUS_SUCCESS ( 1 << 3 ) /* last command was successful */ +#define MATSU_STATUS_ERROR ( 1 << 4 ) /* last command failed */ +#define MATSU_STATUS_MOTOR ( 1 << 5 ) /* spinning */ +#define MATSU_STATUS_MEDIA ( 1 << 6 ) /* media present (in caddy or tray) */ +#define MATSU_STATUS_DOORCLOSED ( 1 << 7 ) /* tray status */ + +typedef struct +{ + UINT8 enabled; /* /ENABLE - Unit enabled */ + UINT8 cmd_signal; /* /CMD - Command mode */ + UINT8 stch_signal; /* /STCH - Status Changed */ + UINT8 sten_signal; /* /STEN - Status Enabled */ + UINT8 scor_signal; /* /STEN - Subcode Ready */ + UINT8 input[16]; + UINT8 input_pos; + UINT8 output[16]; + UINT8 output_pos; + UINT8 output_len; + UINT8 status; + UINT8 motor; + UINT16 sector_size; + UINT32 lba; + UINT16 num_blocks; + UINT16 xfer_offset; + UINT8 sector_buffer[CD_MAX_SECTOR_DATA]; + UINT8 cdda_set; + void (*sten_cb)( running_machine &machine, int level ); /* Status enabled callback */ + void (*stch_cb)( running_machine &machine, int level ); /* Status changed callback */ + void (*scor_cb)( running_machine &machine, int level ); /* Subcode ready callback */ + cdrom_file *cdrom; + device_t *cdda; + emu_timer *frame_timer; +} matsucd; + +static matsucd cd; + +#define MSF2LBA(msf) (((msf >> 16) & 0xff) * 60 * 75 + ((msf >> 8) & 0xff) * 75 + ((msf >> 0) & 0xff)) +#define LBA2MSF(lba) (((lba / (75 * 60)) << 16) | (((lba / 75) % 60) << 8) | (lba % 75)) + +static TIMER_CALLBACK(matsu_subcode_proc); + +void matsucd_init( cdrom_image_device *cdrom_device, const char *cdda_tag ) +{ + memset(&cd, 0, sizeof( matsucd ) ); + + cd.cdrom = cdrom_device->get_cdrom_file(); + cd.cdda = cdrom_device->machine().device(cdda_tag); + + cd.frame_timer = cdrom_device->machine().scheduler().timer_alloc(FUNC(matsu_subcode_proc)); + + cd.stch_signal = 1; +} + +void matsucd_set_status_enabled_callback( void (*sten_cb)( running_machine &machine, int level ) ) +{ + /* add the callback for status enabled signal */ + cd.sten_cb = sten_cb; +} + +void matsucd_set_status_changed_callback( void (*stch_cb)( running_machine &machine, int level ) ) +{ + /* add the callback for status changed signal */ + cd.stch_cb = stch_cb; +} + +void matsucd_set_subcode_ready_callback( void (*scor_cb)( running_machine &machine, int level ) ) +{ + /* add the callback for subcode ready signal */ + cd.scor_cb = scor_cb; +} + +static int matsucd_getsector_type( void ) +{ + switch( cd.sector_size ) + { + case 2048: return CD_TRACK_MODE1; + case 2324: return CD_TRACK_MODE2_FORM2; + case 2336: return CD_TRACK_MODE2; + case 2352: return CD_TRACK_MODE2_RAW; + + default: logerror( "MATSUCD: Sector size %d unsupported!\n", cd.sector_size ); break; + } + + return CD_TRACK_RAW_DONTCARE; +} + +void matsucd_read_next_block( void ) +{ + cd.xfer_offset = 0; + + if ( cd.num_blocks ) + { + cd.lba++; + cd.num_blocks--; + + if (!cdrom_read_data(cd.cdrom, cd.lba, cd.sector_buffer, matsucd_getsector_type())) + { + logerror( "MATSUCD - Warning: Read error on CD!\n" ); + } + } +} + +int matsucd_get_next_byte( UINT8 *data ) +{ + /* no more data to read on this sector */ + if ( cd.xfer_offset >= cd.sector_size ) + return -1; + + if ( data ) + *data = cd.sector_buffer[cd.xfer_offset++]; + + return 0; +} + +static void matsucd_cdda_stop( running_machine &machine ) +{ + device_t *cdda = cdda_from_cdrom(machine, cd.cdrom); + + if (cdda != NULL) + { + cdda_stop_audio(cdda); + cd.frame_timer->reset( ); + } +} + +static void matsucd_cdda_play( running_machine &machine, UINT32 lba, UINT32 num_blocks ) +{ + device_t *cdda = cdda_from_cdrom(machine, cd.cdrom); + if (cdda != NULL) + { + cdda_start_audio(cdda, lba, num_blocks); + cd.frame_timer->adjust(attotime::from_hz( 75 )); + } +} + +static void matsucd_cdda_pause( running_machine &machine, int pause ) +{ + device_t *cdda = cdda_from_cdrom(machine, cd.cdrom); + if (cdda != NULL) + { + cdda_pause_audio(cdda, pause); + + if ( pause ) + { + cd.frame_timer->reset( ); + } + else + { + cd.frame_timer->adjust(attotime::from_hz( 75 )); + } + } +} + +static UINT8 matsucd_cdda_getstatus( running_machine &machine, UINT32 *lba ) +{ + device_t *cdda = cdda_from_cdrom(machine, cd.cdrom); + + if ( lba ) *lba = 0; + + if (cdda != NULL) + { + if (cdda_audio_active(cdda)) + { + if ( lba ) *lba = cdda_get_audio_lba(cdda); + + if (cdda_audio_paused(cdda)) + { + return 0x12; /* audio paused */ + } + else + { + return 0x11; /* audio in progress */ + } + } + else if (cdda_audio_ended(cdda)) + { + return 0x13; /* audio ended */ + } + } + + return 0x15; /* no audio status */ +} + +void matsucd_enable_w( int level ) +{ + cd.enabled = ( level ) ? 0 : 1; +} + +void matsucd_cmd_w( int level ) +{ + cd.cmd_signal = ( level ) ? 0 : 1; +} + +int matsucd_stch_r( void ) +{ + return cd.stch_signal ? 0 : 1; +} + +int matsucd_sten_r( void ) +{ + return cd.sten_signal ? 0 : 1; +} + +int matsucd_scor_r( void ) +{ + return cd.scor_signal ? 0 : 1; +} + +static void update_status_enable( running_machine &machine, int level ) +{ + cd.sten_signal = level; + + if ( cd.sten_cb ) + { + (*cd.sten_cb)(machine, cd.sten_signal); + } +} + +static void update_status_changed( running_machine &machine, int level ) +{ + cd.stch_signal = level; + + if ( cd.stch_cb ) + { + (*cd.stch_cb)(machine, cd.stch_signal); + } +} + +static void update_subcode_ready( running_machine &machine, int level ) +{ + cd.scor_signal = level; + + if ( cd.scor_cb ) + { + (*cd.scor_cb)(machine, cd.scor_signal); + } +} + +static TIMER_CALLBACK(matsucd_set_status_end) +{ + update_status_changed( machine, 1 ); +} + +static void matsucd_set_status( running_machine &machine, UINT8 status ) +{ + if ( status != cd.status ) + { + cd.status = status; + + if ( cd.stch_signal != 0 ) + { + update_status_changed( machine, 0 ); + machine.scheduler().timer_set(attotime::from_msec(1), FUNC(matsucd_set_status_end)); + } + } +} + +static TIMER_CALLBACK(matsu_subcode_proc) +{ + device_t *cdda = cdda_from_cdrom(machine, cd.cdrom); + + (void)param; + + if (cdda != NULL) + { + UINT8 s = matsucd_cdda_getstatus(machine, NULL); + UINT8 newstatus = cd.status; + + if ( s == 0x11 || s == 0x12 ) + { + if ( s == 0x11 ) + { + update_subcode_ready( machine, 1 ); + update_subcode_ready( machine, 0 ); + } + + newstatus |= MATSU_STATUS_PLAYING; + + cd.frame_timer->adjust(attotime::from_hz( 75 )); + } + else + { + newstatus &= ~MATSU_STATUS_PLAYING; + } + + matsucd_set_status( machine, newstatus ); + } +} + +static void matsucd_command_error( running_machine &machine ) +{ + UINT8 newstatus = cd.status; + + newstatus &= ~MATSU_STATUS_SUCCESS; + newstatus |= MATSU_STATUS_ERROR; + + matsucd_set_status( machine, newstatus ); +} + +static void matsucd_complete_cmd( running_machine &machine, UINT8 len ) +{ + UINT8 newstatus = cd.status; + + cd.input_pos = 0; + cd.output_pos = 0; + cd.output_len = len; + + newstatus &= ~MATSU_STATUS_ERROR; + newstatus |= MATSU_STATUS_SUCCESS; + + matsucd_set_status( machine, newstatus ); + + update_status_enable( machine, 1 ); + update_status_enable( machine, 0 ); +} + +UINT8 matsucd_response_r( running_machine &machine ) +{ + UINT8 v = cd.output[cd.output_pos++]; + + if ( cd.output_pos < cd.output_len ) + { + update_status_enable( machine, 1 ); + update_status_enable( machine, 0 ); + } + + return v; +} + +void matsucd_command_w( running_machine &machine, UINT8 data ) +{ + UINT8 cmd; + + /* make sure we're enabled */ + if ( cd.enabled == 0 ) + return; + + /* make sure /CMD is asserted */ + if ( cd.cmd_signal == 0 ) + return; + + if ( cd.cdda_set == 0 ) + { + // 2009-10, FP: for some reason, cdda_from_cdrom was not returning the correct + // CDDA device. Hence, as a temp workaround, I added the cdda to the struct + // and its tag is configured in matsucd_init + if ( cd.cdrom ) + cdda_set_cdrom( cd.cdda, cd.cdrom); + + cd.cdda_set = 1; + } + + cd.input[cd.input_pos++] = data; + + cmd = cd.input[0]; + + switch( cmd ) + { + case 0x01: /* seek */ + { + if ( cd.input_pos < 7 ) + return; + + /* stop CDDA audio if necessary */ + matsucd_cdda_stop(machine); + + cd.motor = 1; + + memset( cd.output, 0, 6 ); + matsucd_complete_cmd( machine, 0 ); + } + break; + + case 0x02: /* read sectors */ + { + if ( cd.input_pos < 7 ) + return; + + /* stop CDDA audio if necessary */ + matsucd_cdda_stop(machine); + + /* LBA */ + cd.lba = cd.input[1]; + cd.lba <<= 8; + cd.lba |= cd.input[2]; + cd.lba <<= 8; + cd.lba |= cd.input[3]; + + /* Number of blocks */ + cd.num_blocks = cd.input[4]; + cd.num_blocks <<= 8; + cd.num_blocks |= cd.input[5]; + + /* Reset transfer count */ + cd.xfer_offset = 0; + + /* go ahead and cache the first block */ + if (!cdrom_read_data(cd.cdrom, cd.lba, cd.sector_buffer, matsucd_getsector_type())) + { + logerror( "MATSUCD - Warning: Read error on CD!\n" ); + matsucd_command_error( machine ); + return; + } + + cd.motor = 1; + + memset( cd.output, 0, 6 ); + matsucd_complete_cmd( machine, 0 ); + } + break; + + case 0x04: /* motor on */ + { + if ( cd.input_pos < 7 ) + return; + + cd.motor = 1; + + memset( cd.output, 0, 6 ); + matsucd_complete_cmd( machine, 0 ); + } + break; + + case 0x05: /* motor off */ + { + if ( cd.input_pos < 7 ) + return; + + /* stop CDDA audio if necessary */ + matsucd_cdda_stop(machine); + + cd.motor = 0; + + memset( cd.output, 0, 6 ); + matsucd_complete_cmd( machine, 0 ); + } + break; + + case 0x09: /* play audio cd, LBA mode */ + { + UINT32 lba, numblocks; + + if ( cd.input_pos < 7 ) + return; + + lba = cd.input[1]; + lba <<= 8; + lba |= cd.input[2]; + lba <<= 8; + lba |= cd.input[3]; + + numblocks = cd.input[4]; + numblocks <<= 8; + numblocks |= cd.input[5]; + numblocks <<= 8; + numblocks |= cd.input[6]; + + matsucd_cdda_play( machine, lba, numblocks ); + + cd.motor = 1; + + memset( cd.output, 0, 6 ); + matsucd_complete_cmd( machine, 0 ); + } + break; + + case 0x0a: /* play audio cd, MSF mode */ + { + UINT32 start, end, lba_start, lba_end; + + if ( cd.input_pos < 7 ) + return; + + start = cd.input[1]; + start <<= 8; + start |= cd.input[2]; + start <<= 8; + start |= cd.input[3]; + + end = cd.input[4]; + end <<= 8; + end |= cd.input[5]; + end <<= 8; + end |= cd.input[6]; + + lba_start = MSF2LBA( start ); + lba_end = MSF2LBA( end ); + + if ( end == 0xffffff ) + { + lba_end = cdrom_get_track_start(cd.cdrom,cdrom_get_last_track(cd.cdrom)-1); + lba_end += cdrom_get_toc(cd.cdrom)->tracks[cdrom_get_last_track(cd.cdrom)-1].frames; + } + + if ( lba_end <= lba_start ) + { + matsucd_cdda_stop(machine); + } + else + { + matsucd_cdda_play( machine, lba_start, lba_end - lba_start ); + cd.motor = 1; + } + + memset( cd.output, 0, 6 ); + matsucd_complete_cmd( machine, 0 ); + } + break; + + case 0x0b: /* play audio track and index */ + { + UINT8 track_start = cd.input[1]; + UINT8 index_start = cd.input[2]; + UINT8 track_end = cd.input[3]; + UINT8 index_end = cd.input[4]; + UINT32 lba_start, lba_end; + + /* TODO: Add index support once the CDDA engine supports it */ + (void)index_start; + (void)index_end; + + /* sanitize values */ + if ( track_start == 0 ) track_start++; + if ( track_end == 0 ) track_end++; + if ( track_end > cdrom_get_last_track(cd.cdrom) ) + track_end = cdrom_get_last_track(cd.cdrom); + + /* find the start and stop positions */ + lba_start = cdrom_get_track_start(cd.cdrom,track_start-1); + lba_end = cdrom_get_track_start(cd.cdrom,track_end-1); + + lba_end += cdrom_get_toc(cd.cdrom)->tracks[track_end-1].frames; + + if ( lba_end <= lba_start ) + { + matsucd_cdda_stop(machine); + } + else + { + matsucd_cdda_play( machine, lba_start, lba_end - lba_start ); + cd.motor = 1; + } + + memset( cd.output, 0, 6 ); + matsucd_complete_cmd( machine, 0 ); + } + break; + + case 0x81: /* status read */ + { + UINT8 newstatus = cd.status; + + newstatus &= MATSU_STATUS_SUCCESS | MATSU_STATUS_ERROR | MATSU_STATUS_PLAYING; + newstatus |= MATSU_STATUS_READY; + + if (cd.cdrom) + { + newstatus |= MATSU_STATUS_MEDIA; + } + + if (cd.motor) + newstatus |= MATSU_STATUS_MOTOR; + + cd.output[0] = newstatus; + + matsucd_set_status( machine, newstatus ); + + matsucd_complete_cmd( machine, 1 ); + } + break; + + case 0x82: /* error read */ + { + if ( cd.input_pos < 7 ) + return; + + memset( cd.output, 0, 6 ); + matsucd_complete_cmd( machine, 6 ); + } + break; + + case 0x84: /* set mode */ + { + if ( cd.input_pos < 7 ) + return; + + cd.sector_size = cd.input[2]; + cd.sector_size <<= 8; + cd.sector_size |= cd.input[3]; + + memset( cd.output, 0, 6 ); + matsucd_complete_cmd( machine, 0 ); + } + break; + + case 0x87: /* read SUBQ */ + { + int msfmode; + UINT32 lba; + UINT8 track; + + if ( cd.input_pos < 7 ) + return; + + msfmode = (cd.input[1] & 0x02) ? 1 : 0; + + memset( cd.output, 0, 13 ); + + cd.output[0] = matsucd_cdda_getstatus( machine, &lba ); + + if ( lba > 0 ) + { + UINT32 disk_pos; + UINT32 track_pos; + + track = cdrom_get_track(cd.cdrom, lba); + + cd.output[1] = cdrom_get_adr_control(cd.cdrom, track); + cd.output[2] = track+1; + cd.output[3] = 0; /* index */ + + disk_pos = lba; + if ( msfmode ) disk_pos = LBA2MSF(disk_pos); + + cd.output[4] = (disk_pos >> 24) & 0xff; + cd.output[5] = (disk_pos >> 16) & 0xff; + cd.output[6] = (disk_pos >> 8) & 0xff; + cd.output[7] = (disk_pos) & 0xff; + + track_pos = lba - cdrom_get_track_start(cd.cdrom, track); + if ( msfmode ) track_pos = LBA2MSF(track_pos); + + cd.output[8] = (track_pos >> 24) & 0xff; + cd.output[9] = (track_pos >> 16) & 0xff; + cd.output[10] = (track_pos >> 8) & 0xff; + cd.output[11] = (track_pos) & 0xff; + + /* TODO: UPC flag at offset 12 */ + cd.output[12] = 0; + } + + matsucd_complete_cmd( machine, 13 ); + } + break; + + case 0x89: /* read disk info */ + { + UINT32 end; + + if ( cd.input_pos < 7 ) + return; + + memset( cd.output, 0, 5 ); + + cd.output[0] = cdrom_get_last_track(cd.cdrom) ? 1 : 0; + cd.output[1] = cdrom_get_last_track(cd.cdrom); + end = cdrom_get_track_start(cd.cdrom,cd.output[1]-1); + end += cdrom_get_toc(cd.cdrom)->tracks[cd.output[1]-1].frames; + end = LBA2MSF(end); + cd.output[2] = (end >> 16) & 0xff; + cd.output[3] = (end >> 8) & 0xff; + cd.output[4] = (end) & 0xff; + + matsucd_complete_cmd( machine, 5 ); + } + break; + + case 0x8a: /* read toc */ + { + UINT8 track; + int msfmode; + UINT32 track_start; + + if ( cd.input_pos < 7 ) + return; + + /* stop CDDA audio if necessary */ + matsucd_cdda_stop(machine); + + track = cd.input[2]; + msfmode = (cd.input[1] & 0x02) ? 1 : 0; + + if ( cd.cdrom == NULL ) + { + logerror( "MATSUCD - Warning: Reading TOC without a CD!\n" ); + matsucd_command_error( machine ); + return; + } + + if ( track > cdrom_get_last_track(cd.cdrom) ) + { + logerror( "MATSUCD - Warning: Reading invalid track entry from TOC!\n" ); + matsucd_command_error( machine ); + return; + } + + memset( cd.output, 0, 7 ); + + track_start = cdrom_get_track_start(cd.cdrom, track > 0 ? (track-1) : track ); + if ( msfmode ) track_start = LBA2MSF( track_start ); + + cd.output[1] = cdrom_get_adr_control(cd.cdrom, track > 0 ? (track-1) : track); + cd.output[2] = track; + cd.output[3] = (track == 0 ) ? cdrom_get_last_track(cd.cdrom) : 0; + cd.output[4] = (track_start >> 24) & 0xff; + cd.output[5] = (track_start >> 16) & 0xff; + cd.output[6] = (track_start >> 8) & 0xff; + cd.output[7] = (track_start) & 0xff; + + cd.motor = 1; + + matsucd_complete_cmd( machine, 8 ); + } + break; + + case 0x8b: /* pause audio */ + { + if ( cd.input_pos < 7 ) + return; + + matsucd_cdda_pause( machine, (cd.input[1] == 0) ? 1 : 0 ); + memset( cd.output, 0, 7 ); + matsucd_complete_cmd( machine, 0 ); + } + break; + + case 0xa3: /* front panel */ + { + if ( cd.input_pos < 7 ) + return; + + /* TODO: ??? */ + + memset( cd.output, 0, 7 ); + matsucd_complete_cmd( machine, 0 ); + } + break; + + default: + logerror( "MATSUCD: Unknown/inimplemented command %08x\n", cmd ); + break; + } +} |