// license:BSD-3-Clause // copyright-holders:Carl // Analog Devices AD1848, main codec in Windows Sound System adapters // TODO: Emulate pin-compatible Crystal Semiconductor CS4231 and its extra Mode 2 features #include "emu.h" #include "sound/ad1848.h" #include "sound/volt_reg.h" #include "speaker.h" DEFINE_DEVICE_TYPE(AD1848, ad1848_device, "ad1848", "AD1848 16-bit SoundPort Stereo Codec") ad1848_device::ad1848_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, AD1848, tag, owner, clock), m_irq_cb(*this), m_drq_cb(*this), m_ldac(*this, "ldac"), m_rdac(*this, "rdac") { } MACHINE_CONFIG_START(ad1848_device::device_add_mconfig) SPEAKER(config, "lspeaker").front_left(); SPEAKER(config, "rspeaker").front_right(); MCFG_DEVICE_ADD("ldac", DAC_16BIT_R2R, 0) MCFG_SOUND_ROUTE(ALL_OUTPUTS, "lspeaker", 0.5) // unknown DAC MCFG_DEVICE_ADD("rdac", DAC_16BIT_R2R, 0) MCFG_SOUND_ROUTE(ALL_OUTPUTS, "rspeaker", 0.5) // unknown DAC MCFG_DEVICE_ADD("vref", VOLTAGE_REGULATOR, 0) MCFG_VOLTAGE_REGULATOR_OUTPUT(5.0) MCFG_SOUND_ROUTE(0, "ldac", 1.0, DAC_VREF_POS_INPUT) MCFG_SOUND_ROUTE(0, "ldac", -1.0, DAC_VREF_NEG_INPUT) MCFG_SOUND_ROUTE(0, "rdac", 1.0, DAC_VREF_POS_INPUT) MCFG_SOUND_ROUTE(0, "rdac", -1.0, DAC_VREF_NEG_INPUT) MACHINE_CONFIG_END void ad1848_device::device_start() { m_timer = timer_alloc(0, nullptr); m_irq_cb.resolve_safe(); m_drq_cb.resolve_safe(); save_item(NAME(m_regs.idx)); save_item(NAME(m_addr)); save_item(NAME(m_stat)); save_item(NAME(m_sam_cnt)); save_item(NAME(m_samples)); save_item(NAME(m_count)); save_item(NAME(m_play)); save_item(NAME(m_mce)); save_item(NAME(m_trd)); save_item(NAME(m_irq)); } void ad1848_device::device_reset() { memset(&m_regs.idx[0], '\0', sizeof(m_regs)); m_addr = 0; m_stat = 0; m_sam_cnt = 0; m_samples = 0; m_play = false; m_irq = false; } READ8_MEMBER(ad1848_device::read) { switch(offset) { case 0: return m_addr | (m_mce ? 0x40 : 0) | (m_trd ? 0x20 : 0); case 1: return m_regs.idx[m_addr]; case 2: return m_stat | (m_irq ? 1 : 0); case 3: break; // capture } return 0; } WRITE8_MEMBER(ad1848_device::write) { static constexpr int div_factor[] = {3072, 1536, 896, 768, 448, 384, 512, 2560}; switch(offset) { case 0: m_addr = data & 0xf; m_mce = data & 0x40 ? true : false; m_trd = data & 0x20 ? true : false; break; case 1: if(m_addr == 12) return; m_regs.idx[m_addr] = data; switch(m_addr) { case 8: if(!m_mce) return; m_regs.dform &= 0x7f; break; case 9: { m_play = (data & 1) ? true : false; // FIXME: provide external configuration for XTAL1 (24.576 MHz) and XTAL2 (16.9344 MHz) inputs attotime rate = m_play ? attotime::from_hz(((m_regs.dform & 1) ? 16.9344_MHz_XTAL : 24.576_MHz_XTAL) / div_factor[(m_regs.dform >> 1) & 7]) : attotime::never; m_timer->adjust(rate, 0 , rate); m_drq_cb(m_play ? ASSERT_LINE : CLEAR_LINE); break; } case 14: m_count = (m_regs.ubase << 8) | m_regs.lbase; break; } break; case 2: m_irq_cb(CLEAR_LINE); m_irq = false; if(m_regs.iface & 1) m_play = true; break; case 3: break; // playback } } READ8_MEMBER(ad1848_device::dack_r) { m_drq_cb(CLEAR_LINE); return 0; // not implemented } WRITE8_MEMBER(ad1848_device::dack_w) { if(!m_play) return; m_samples = (m_samples << 8) | data; m_sam_cnt++; switch(m_regs.dform >> 4) { case 0: case 2: case 6: m_sam_cnt = 0; break; case 1: case 3: case 4: case 7: if(m_sam_cnt == 2) m_sam_cnt = 0; break; case 5: if(m_sam_cnt == 4) m_sam_cnt = 0; break; } if(!m_sam_cnt) m_drq_cb(CLEAR_LINE); } void ad1848_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { if(!m_play) return; switch(m_regs.dform >> 4) { case 0: // 8bit mono m_ldac->write(m_samples << 8); m_rdac->write(m_samples << 8); break; case 1: // 8bit stereo m_ldac->write(m_samples << 8); m_rdac->write(m_samples & 0xff00); break; case 2: // ulaw mono case 3: // ulaw stereo case 6: // alaw mono case 7: // alaw stereo break; case 4: // 16bit mono m_ldac->write(m_samples ^ 0x8000); m_rdac->write(m_samples ^ 0x8000); break; case 5: // 16bit stereo m_ldac->write(m_samples ^ 0x8000); m_rdac->write((m_samples >> 16) ^ 0x8000); break; } if(!m_count) { if(m_regs.pinc & 2) { if(m_trd) m_play = false; m_irq_cb(ASSERT_LINE); m_irq = true; } m_count = (m_regs.ubase << 8) | m_regs.lbase; } else m_count--; m_drq_cb(ASSERT_LINE); }