diff options
Diffstat (limited to 'src/devices/machine/spg290_cdservo.cpp')
-rw-r--r-- | src/devices/machine/spg290_cdservo.cpp | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/src/devices/machine/spg290_cdservo.cpp b/src/devices/machine/spg290_cdservo.cpp new file mode 100644 index 00000000000..02a65e5be72 --- /dev/null +++ b/src/devices/machine/spg290_cdservo.cpp @@ -0,0 +1,415 @@ +// license:BSD-3-Clause +// copyright-holders:Sandro Ronco +/***************************************************************************** + + SPG290 CD Servo + +*****************************************************************************/ + +#include "emu.h" +#include "spg290_cdservo.h" +#include "coreutil.h" + + +#define SPG290_LEADIN_LEN 7500 +#define SPG290_LEADOUT_LEN 6750 + + +DEFINE_DEVICE_TYPE(SPG290_CDSERVO, spg290_cdservo_device, "spg290_cdservo", "SPG290 CDServo HLE") + + +spg290_cdservo_device::spg290_cdservo_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, SPG290_CDSERVO, tag, owner, clock) + , m_cdrom(*this, finder_base::DUMMY_TAG) + , m_irq_cb(*this) + , m_space_write_cb(*this) +{ +} + + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void spg290_cdservo_device::device_start() +{ + m_cdtimer = timer_alloc(FUNC(spg290_cdservo_device::cd_update), this); + m_dsp_memory = std::make_unique<uint32_t[]>(0x10000); + + save_item(NAME(m_addr)); + save_item(NAME(m_data)); + save_item(NAME(m_buf_start)); + save_item(NAME(m_buf_end)); + save_item(NAME(m_buf_ptr)); + save_item(NAME(m_speed)); + save_item(NAME(m_seek_min)); + save_item(NAME(m_seek_sec)); + save_item(NAME(m_seek_frm)); + save_item(NAME(m_seek_lba)); + save_item(NAME(m_sector_size)); + save_item(NAME(m_dsp_data)); + save_item(NAME(m_frame_found)); + save_item(NAME(m_control1)); + save_item(NAME(m_skip)); + save_item(NAME(m_dsp_regs)); + save_item(NAME(m_tot_sectors)); + save_item(NAME(m_cur_sector)); +} + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void spg290_cdservo_device::device_reset() +{ + m_addr = 0; + m_data = 0; + m_buf_start = 0; + m_buf_end = 0; + m_buf_ptr = 0; + m_speed = 1; + m_seek_min = 0; + m_seek_sec = 0; + m_seek_frm = 0; + m_seek_lba = 0; + m_sector_size = 0; + m_dsp_data = 0; + m_frame_found = false; + m_control1 = 0; + m_skip = 0; + + std::fill(std::begin(m_dsp_regs), std::end(m_dsp_regs), 0); + std::fill_n(m_dsp_memory.get(), 0x10000, 0); + + m_tot_sectors = 0; + m_cur_sector = 0; + m_qsub = nullptr; + + m_irq_cb(CLEAR_LINE); + + // generate Q subchannel + if (m_cdrom.found() && m_cdrom->exists()) + generate_qsub(); + + change_status(); +} + +TIMER_CALLBACK_MEMBER(spg290_cdservo_device::cd_update) +{ + if (!(m_control1 & 0x04) && m_cur_sector == m_seek_lba + SPG290_LEADIN_LEN) + { + uint8_t cdbuf[2448] = { 0 }; + + if (BIT(m_control0, 15)) // CDDA + { + m_cdrom->read_data(m_cur_sector - 150 - SPG290_LEADIN_LEN, cdbuf, cdrom_file::CD_TRACK_AUDIO); + + for (int i=0; i<2352; i++) + { + m_space_write_cb(m_buf_ptr++, cdbuf[i]); + if (m_buf_ptr > m_buf_end) + m_buf_ptr = m_buf_start; + } + } + else + { + m_cdrom->read_data(m_cur_sector - 150 - SPG290_LEADIN_LEN, cdbuf, cdrom_file::CD_TRACK_MODE1_RAW); + + // FIXME: this is required for load iso images + if (m_cdrom->get_track_type(m_qsub[m_cur_sector * 12 + 1] - 1) == cdrom_file::CD_TRACK_MODE1) + { + int lba = (bcd_2_dec(cdbuf[12]) * 60 + bcd_2_dec(cdbuf[13])) * 75 + bcd_2_dec(cdbuf[14]); + uint32_t msf = cdrom_file::lba_to_msf(lba + 150); + cdbuf[12] = (msf >> 16) & 0xff; + cdbuf[13] = (msf >> 8) & 0xff; + cdbuf[14] = (msf >> 0) & 0xff; + } + + for (int i=0; i<m_sector_size; i++) + { + m_space_write_cb(m_buf_ptr++, cdbuf[i]); + if (m_buf_ptr > m_buf_end) + m_buf_ptr = m_buf_start; + } + } + + m_seek_lba++; + m_frame_found = true; + m_irq_cb(ASSERT_LINE); + } + + if ((m_cur_sector < m_seek_lba + SPG290_LEADIN_LEN) && (m_cur_sector < m_tot_sectors)) + m_cur_sector++; +} + +uint32_t spg290_cdservo_device::read(offs_t offset, uint32_t mem_mask) +{ + switch (offset << 2) + { + case 0x08: + return m_data; // CD servo DSP reply + case 0x0c: + return 0; // CD servo ready + case 0x40: + return m_control0; + case 0x44: + return m_control1; + case 0x48: + return m_seek_min; + case 0x4c: + return m_seek_sec; + case 0x50: + return m_seek_frm; + case 0x54: + return 0; // sector CRC error flags + case 0x60: + return m_buf_start; + case 0x64: + return m_buf_end; + case 0x68: + return m_buf_ptr; + case 0x6c: + return m_sector_size; + default: + logerror("[%s] %s: unknown read %x\n", tag(), machine().describe_context(), offset << 2); + } + + return 0; +} + +void spg290_cdservo_device::write(offs_t offset, uint32_t data, uint32_t mem_mask) +{ + switch (offset << 2) + { + case 0x04: // CD servo DSP command + COMBINE_DATA(&m_addr); + break; + case 0x08: // CD servo DSP data + COMBINE_DATA(&m_data); + break; + case 0x0c: // CD servo DSP exec + if (ACCESSING_BITS_0_7) + { + if (data & 1) + servo_cmd_r(); + else + servo_cmd_w(); + } + break; + case 0x40: + COMBINE_DATA(&m_control0); + break; + case 0x44: + COMBINE_DATA(&m_control1); + m_seek_lba = (bcd_2_dec(m_seek_min) * 60 + bcd_2_dec(m_seek_sec)) * 75 + bcd_2_dec(m_seek_frm); + m_cur_sector = SPG290_LEADIN_LEN + m_seek_lba; // TODO: seek time + break; + case 0x48: + if (ACCESSING_BITS_0_7) + m_seek_min = data; + break; + case 0x4c: + if (ACCESSING_BITS_0_7) + m_seek_sec = data; + break; + case 0x50: + if (ACCESSING_BITS_0_7) + m_seek_frm = data; + break; + case 0x60: + COMBINE_DATA(&m_buf_start); + break; + case 0x54: + break; + case 0x64: + COMBINE_DATA(&m_buf_end); + break; + case 0x68: + COMBINE_DATA(&m_buf_ptr); + break; + case 0x6c: + COMBINE_DATA(&m_sector_size); + break; + default: + logerror("[%s] %s: unknown write %x = %x\n", tag(), machine().describe_context(), offset << 2, data); + } +} + +void spg290_cdservo_device::servo_cmd_r() +{ + if (m_addr == 0x079) + m_data = 0xe3; // servo status + else if (m_addr == 0x07b) + m_data = 0x06; // unknown, should be in the range 4-7 + else if (m_addr == 0x07c) + m_data = m_dsp_data >> 8; + else if (m_addr == 0x07d) + m_data = m_dsp_data; + else if (m_addr == 0x082) + m_data = 0x04; + else if (m_addr == 0x083) + m_data = machine().rand() & 0xff; + else if (m_addr == 0x307) + m_data = m_frame_found; + else if (m_addr >= 0x340 && m_addr <= 0x349) // read Q subchannel + m_data = m_qsub[m_cur_sector * 12 + (m_addr & 0x0f)]; + else if (m_addr == 0x34c) + m_data = 0x40; // Q subchannel ready + else if (m_addr == 0x506) + m_data = 0x04; // DSP ready + else if (m_addr >= 0x500 && m_addr < 0x50a) + m_data = m_dsp_regs[m_addr & 0x0f]; + else if (m_addr == 0x41f) + m_data = 0xff; // if zero cause servo timeout + else + m_data = 0; +} + + +void spg290_cdservo_device::servo_cmd_w() +{ + if (m_addr == 0x013) // skip n sectors + { + if (m_data & 0x01) + m_cur_sector += (m_data & 0x10) ? -m_skip : m_skip; + + if (m_cur_sector < 0) m_cur_sector = 0; + if (m_cur_sector >= m_tot_sectors) m_cur_sector = m_tot_sectors - 1; + } + else if (m_addr == 0x020) // speed + { + if (m_speed != 1 << (m_data & 0x03)) + { + m_speed = 1 << (m_data & 0x03); + change_status(); + } + } + else if (m_addr == 0x030) // Disc ID + { + // < 10 no disc + // < 97 CD-RW + // >= 97 CD-ROM + if (m_data == 0x13) + m_dsp_data = 8; + else + m_dsp_data = m_qsub ? 100 : 0; + } + else if (m_addr == 0x032) // DSP version + m_dsp_data = 0x0102; + else if (m_addr == 0x19a) + m_skip = (m_skip & 0x00ff) | (m_data << 8); + else if (m_addr == 0x29a) + m_skip = (m_skip & 0xff00) | m_data; + else if (m_addr == 0x307) + m_frame_found = false; + else if (m_addr == 0x505) // DSP memory + { + if ((m_data & 0x0f) == 0x02) // write + { + // maincpu upload the DSP code in 3 byte chunks + uint16_t addr = (m_dsp_regs[0] << 8) | m_dsp_regs[1]; + m_dsp_memory[addr] = (m_dsp_regs[2] << 16) | (m_dsp_regs[3] << 8) | m_dsp_regs[4]; + } + else if ((m_data & 0x0f) == 0x03) // read + { + uint16_t addr = (m_dsp_regs[0] << 8) | m_dsp_regs[1]; + m_dsp_regs[7] = (m_dsp_memory[addr] >> 16) & 0xff; + m_dsp_regs[8] = (m_dsp_memory[addr] >> 8) & 0xff; + m_dsp_regs[9] = (m_dsp_memory[addr] >> 0) & 0xff; + } + } + else if (m_addr >= 0x500 && m_addr < 0x50a) + m_dsp_regs[m_addr & 0x0f] = m_data; +} + + +void spg290_cdservo_device::change_status() +{ + if (m_speed == 0 || !m_cdrom.found() || !m_cdrom->exists()) + m_cdtimer->adjust(attotime::never); + else + m_cdtimer->adjust(attotime::from_hz(75 * m_speed), 0, attotime::from_hz(75 * m_speed)); +} + +void spg290_cdservo_device::add_qsub(int sector, uint8_t addrctrl, uint8_t track, uint8_t index, uint32_t rel_msf, uint32_t abs_msf, uint16_t crc16) +{ + uint8_t *dest = m_qsub.get() + sector * 12; + dest[0] = ((addrctrl & 0x0f) << 4) | ((addrctrl & 0xf0) >> 4); + dest[1] = track; + dest[2] = index; + dest[3] = rel_msf >> 16; + dest[4] = rel_msf >> 8; + dest[5] = rel_msf; + dest[6] = 0; + dest[7] = abs_msf >> 16; + dest[8] = abs_msf >> 8; + dest[9] = abs_msf; + dest[10] = crc16 >> 8; + dest[11] = crc16; +} + +void spg290_cdservo_device::generate_qsub() +{ + const cdrom_file::toc &toc = m_cdrom->get_toc(); + int numtracks = m_cdrom->get_last_track(); + uint32_t total_sectors = m_cdrom->get_track_start(numtracks - 1) + toc.tracks[numtracks - 1].frames + 150; + + m_tot_sectors = SPG290_LEADIN_LEN + total_sectors + SPG290_LEADOUT_LEN; + m_qsub = std::make_unique<uint8_t[]>(m_tot_sectors * 12); + + int lba = 0; + + // 7500 sectors lead-in + for (int s=0; s < SPG290_LEADIN_LEN; s += numtracks + 3) + { + if (lba < SPG290_LEADIN_LEN) add_qsub(lba++, 0x14, 0, 0xa0, cdrom_file::lba_to_msf(s + 0), 1 << 16); // first track number + if (lba < SPG290_LEADIN_LEN) add_qsub(lba++, 0x14, 0, 0xa1, cdrom_file::lba_to_msf(s + 1), numtracks << 16); // last track number + if (lba < SPG290_LEADIN_LEN) add_qsub(lba++, 0x14, 0, 0xa2, cdrom_file::lba_to_msf(s + 2), cdrom_file::lba_to_msf(total_sectors)); // start time of lead-out + + for(int track = 0; track < numtracks; track++) + { + uint32_t track_start = m_cdrom->get_track_start(track) + 150; + if (lba < SPG290_LEADIN_LEN) + add_qsub(lba++, m_cdrom->get_adr_control(track), 0, dec_2_bcd(track + 1), cdrom_file::lba_to_msf(s + 3 + track), cdrom_file::lba_to_msf(track_start)); + } + } + + // data tracks + for(int track = 0; track < numtracks; track++) + { + uint32_t control = m_cdrom->get_adr_control(track); + uint32_t track_start = m_cdrom->get_track_start(track); + + // pregap + uint32_t pregap = toc.tracks[track].pregap; + + // first track should have a 150 frames pregap + if (track == 0 && toc.tracks[0].pregap == 0) + pregap = 150; + else if (track != 0 && toc.tracks[0].pregap == 0) + track_start += 150; + + for(int s = 0; s < pregap; s++) + add_qsub(lba++, control, dec_2_bcd(track + 1), 0, cdrom_file::lba_to_msf(s), cdrom_file::lba_to_msf(track_start + s)); + + track_start += pregap; + + for(int s = 0; s < toc.tracks[track].frames; s++) + { + // TODO: if present use subcode from CHD + add_qsub(lba++, control, dec_2_bcd(track + 1), 1, cdrom_file::lba_to_msf(s), cdrom_file::lba_to_msf(track_start + s)); + } + + track_start += toc.tracks[track].frames; + + // postgap + for(int s = 0; s < toc.tracks[track].postgap; s++) + add_qsub(lba++, control, dec_2_bcd(track + 1), 2, cdrom_file::lba_to_msf(s), cdrom_file::lba_to_msf(track_start + s)); + + track_start += toc.tracks[track].postgap; + } + + // 6750 sectors lead-out + for(int s = 0; s < SPG290_LEADOUT_LEN; s++) + add_qsub(lba++, 0x14, 0xaa, 1, cdrom_file::lba_to_msf(s), cdrom_file::lba_to_msf(total_sectors + s)); +} |