diff options
Diffstat (limited to 'src/devices/sound/ct1745.cpp')
-rw-r--r-- | src/devices/sound/ct1745.cpp | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/src/devices/sound/ct1745.cpp b/src/devices/sound/ct1745.cpp new file mode 100644 index 00000000000..1ca962d1b49 --- /dev/null +++ b/src/devices/sound/ct1745.cpp @@ -0,0 +1,324 @@ +// license:BSD-3-Clause +// copyright-holders:Angelo Salese +/************************************************************************************************** + +Creative Labs CT1745 SB16 Mixer chip + +References: +- https://the.earth.li/~tfm/oldpage/sb_mixer.html + +TODO: +- Pinout reference; +- IRQ select & mask for card host; +- Get a tag reference for CD and (ISA/C-Bus) buzzer speaker volume controls; +- Mic/Line In; +- Stereo control and low-pass filter; +- Input/Output Control (left/right rerouting); +- Gain Control + AGC; +- Bass/Treble; +- Track down really unavailable regs, return 0x80 in index status; +- Track down interactions with LLE DSP (UART?); + +**************************************************************************************************/ + +#include "emu.h" +#include "ct1745.h" + +DEFINE_DEVICE_TYPE(CT1745, ct1745_mixer_device, "ct1745", "Creative Labs CT1745 SB16 Mixer") + +ct1745_mixer_device::ct1745_mixer_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, CT1745, tag, owner, clock) + , device_memory_interface(mconfig, *this) + , device_mixer_interface(mconfig, *this) + , m_irq_status_cb(*this, 0) + , m_fm(*this, finder_base::DUMMY_TAG) + , m_ldac(*this, finder_base::DUMMY_TAG) + , m_rdac(*this, finder_base::DUMMY_TAG) +{ + m_space_config = address_space_config("regs", ENDIANNESS_LITTLE, 8, 8, 0, address_map_constructor(FUNC(ct1745_mixer_device::map), this)); +} + +device_memory_interface::space_config_vector ct1745_mixer_device::memory_space_config() const +{ + return space_config_vector { + std::make_pair(0, &m_space_config) + }; +} + +void ct1745_mixer_device::device_start() +{ + save_item(NAME(m_index)); + save_pointer(NAME(m_master_level), 2); + save_pointer(NAME(m_dac_level), 2); + save_pointer(NAME(m_fm_level), 2); + save_pointer(NAME(m_cd_level), 2); + save_pointer(NAME(m_linein_level), 2); + save_item(NAME(m_mic_level)); + save_item(NAME(m_pc_speaker_level)); + save_item(NAME(m_output_sw)); + save_pointer(NAME(m_input_sw), 2); + save_pointer(NAME(m_input_gain), 2); + save_pointer(NAME(m_output_gain), 2); + save_item(NAME(m_agc)); + save_pointer(NAME(m_treble), 2); + save_pointer(NAME(m_bass), 2); +} + +void ct1745_mixer_device::device_reset() +{ + m_index = 0; + + reset_state(); +} + +void ct1745_mixer_device::reset_state() +{ + m_master_level[0] = m_master_level[1] = 0xc0; + m_dac_level[0] = m_dac_level[1] = 0xc0; + m_fm_level[0] = m_fm_level[1] = 0xc0; + m_cd_level[0] = m_cd_level[1] = 0xc0; + m_linein_level[0] = m_linein_level[1] = 0; + m_mic_level = 0; + m_pc_speaker_level = 0x80; + m_output_sw = 0x1f; + m_input_sw[0] = 0x15; + m_input_sw[1] = 0x0b; + m_input_gain[0] = m_input_gain[1] = 0x00; + m_output_gain[0] = m_output_gain[1] = 0x00; + m_treble[0] = m_treble[1] = 0x80; + m_bass[0] = m_bass[1] = 0x80; + m_agc = false; + + //m_dac_speaker_off = true; + update_gain_levels(); +} + +void ct1745_mixer_device::dac_speaker_off_cb(int state) +{ + m_dac_speaker_off = !!state; + update_gain_levels(); +} + +u8 ct1745_mixer_device::read(offs_t offset) +{ + if (offset == 0) + return m_index; + + return space().read_byte(m_index); +} + +void ct1745_mixer_device::write(offs_t offset, u8 data) +{ + if (offset == 0) + { + m_index = data; + return; + } + + space().write_byte(m_index, data); + if (!BIT(m_index, 7)) + update_gain_levels(); +} + +// TODO: bare copy of HLE version, convert to sound_stream_update instead +void ct1745_mixer_device::update_gain_levels() +{ + float lmain = m_master_level[0]/248.0; + float rmain = m_master_level[1]/248.0; + + m_ldac->set_output_gain(ALL_OUTPUTS, m_dac_speaker_off ? 0.0 : lmain*(m_dac_level[0]/248.0f)); + m_rdac->set_output_gain(ALL_OUTPUTS, m_dac_speaker_off ? 0.0 : rmain*(m_dac_level[1]/248.0f)); + m_fm->set_output_gain(0, lmain*(m_fm_level[0]/248.0f)); + m_fm->set_output_gain(1, rmain*(m_fm_level[1]/248.0f)); + m_fm->set_output_gain(2, lmain*(m_fm_level[0]/248.0f)); + m_fm->set_output_gain(3, rmain*(m_fm_level[1]/248.0f)); + + // TODO: convert to LIVE_AUDIO_VIEW + //popmessage("%f %f (%02x %02x) | %02x %02x | %02x %02x", lmain, rmain, m_master_level[0], m_master_level[1], m_dac_level[0], m_dac_level[1], m_fm_level[0], m_fm_level[1]); +} + +void ct1745_mixer_device::map(address_map &map) +{ + map(0x00, 0x00).lw8( + NAME([this] (offs_t offset, u8 data) { + // TODO: requires that the index is loaded by a set time first. + if (!BIT(data, 0)) + reset_state(); + }) + ); +// SB1/2 compatibility section + map(0x04, 0x04).lrw8( + NAME([this] (offs_t offset) { + return (m_dac_level[0] & 0xf0) | (m_dac_level[1] >> 4); + }), + NAME([this] (offs_t offset, u8 data) { + m_dac_level[0] = (data & 0xf0) | 8; + m_dac_level[1] = ((data & 0x0f) << 4) | 8; + }) + ); + +// map(0x06, 0x06) FM Output Control? +// map(0x0a, 0x0a) Microphone level? +// map(0x0c, 0x0c) Input/Filter Select + // RMW (to the index!?) by ibm5170_cdrom:zyclunt, should be "Output Filter/Stereo Select" +// map(0x0e, 0x0e).lr8(NAME([] () { return 0x02; })); // Output/Stereo Select + map(0x22, 0x22).lrw8( + NAME([this] (offs_t offset) { + return (m_master_level[0] & 0xf0) | (m_master_level[1] >> 4); + }), + NAME([this] (offs_t offset, u8 data) { + m_master_level[0] = (data & 0xf0) | 8; + m_master_level[1] = ((data & 0x0f) << 4) | 8; + }) + ); + map(0x26, 0x26).lrw8( + NAME([this] (offs_t offset) { + return (m_fm_level[0] & 0xf0) | (m_fm_level[1] >> 4); + }), + NAME([this] (offs_t offset, u8 data) { + m_fm_level[0] = (data & 0xf0) | 8; + m_fm_level[1] = ((data & 0x0f) << 4) | 8; + }) + ); + map(0x26, 0x26).lrw8( + NAME([this] (offs_t offset) { + return (m_cd_level[0] & 0xf0) | (m_cd_level[1] >> 4); + }), + NAME([this] (offs_t offset, u8 data) { + m_cd_level[0] = (data & 0xf0) | 8; + m_cd_level[1] = ((data & 0x0f) << 4) | 8; + }) + ); + map(0x2e, 0x2e).lrw8( + NAME([this] (offs_t offset) { + return (m_linein_level[0] & 0xf0) | (m_linein_level[1] >> 4); + }), + NAME([this] (offs_t offset, u8 data) { + m_linein_level[0] = (data & 0xf0) | 8; + m_linein_level[1] = ((data & 0x0f) << 4) | 8; + }) + ); + map(0x30, 0x31).lrw8( + NAME([this] (offs_t offset) { + return m_master_level[offset & 1]; + }), + NAME([this] (offs_t offset, u8 data) { + m_master_level[offset & 1] = data & 0xf8; + }) + ); + map(0x32, 0x33).lrw8( + NAME([this] (offs_t offset) { + return m_dac_level[offset & 1]; + }), + NAME([this] (offs_t offset, u8 data) { + m_dac_level[offset & 1] = data & 0xf8; + }) + ); + map(0x34, 0x35).lrw8( + NAME([this] (offs_t offset) { + return m_fm_level[offset & 1]; + }), + NAME([this] (offs_t offset, u8 data) { + m_fm_level[offset & 1] = data & 0xf8; + }) + ); + map(0x36, 0x37).lrw8( + NAME([this] (offs_t offset) { + return m_cd_level[offset & 1]; + }), + NAME([this] (offs_t offset, u8 data) { + m_cd_level[offset & 1] = data & 0xf8; + }) + ); + map(0x38, 0x39).lrw8( + NAME([this] (offs_t offset) { + return m_linein_level[offset & 1]; + }), + NAME([this] (offs_t offset, u8 data) { + m_linein_level[offset & 1] = data & 0xf8; + }) + ); + map(0x3a, 0x3a).lrw8( + NAME([this] (offs_t offset) { + return m_mic_level; + }), + NAME([this] (offs_t offset, u8 data) { + m_mic_level = data & 0xf8; + }) + ); + map(0x3b, 0x3b).lrw8( + NAME([this] (offs_t offset) { + return m_pc_speaker_level; + }), + NAME([this] (offs_t offset, u8 data) { + m_pc_speaker_level = data & 0xc0; + }) + ); + map(0x3c, 0x3c).lrw8( + NAME([this] (offs_t offset) { + return m_output_sw; + }), + NAME([this] (offs_t offset, u8 data) { + m_output_sw = data & 0x1f; + }) + ); + // NOTE: start section where L/R are A0 swapped ... + map(0x3d, 0x3e).lrw8( + NAME([this] (offs_t offset) { + return m_input_sw[(offset & 1) ^ 1]; + }), + NAME([this] (offs_t offset, u8 data) { + m_input_sw[(offset & 1) ^ 1] = data & 0x7f; + }) + ); + map(0x3f, 0x40).lrw8( + NAME([this] (offs_t offset) { + return m_input_gain[(offset & 1) ^ 1]; + }), + NAME([this] (offs_t offset, u8 data) { + m_input_gain[(offset & 1) ^ 1] = data & 0xc0; + }) + ); + map(0x41, 0x42).lrw8( + NAME([this] (offs_t offset) { + return m_output_gain[(offset & 1) ^ 1]; + }), + NAME([this] (offs_t offset, u8 data) { + m_output_gain[(offset & 1) ^ 1] = data & 0xc0; + }) + ); + // ... end section + map(0x43, 0x43).lrw8( + NAME([this] (offs_t offset) { + return m_agc; + }), + NAME([this] (offs_t offset, u8 data) { + m_agc = !!BIT(data, 0); + }) + ); + map(0x44, 0x45).lrw8( + NAME([this] (offs_t offset) { + return m_treble[offset & 1]; + }), + NAME([this] (offs_t offset, u8 data) { + m_treble[offset & 1] = data & 0xf0; + }) + ); + map(0x46, 0x47).lrw8( + NAME([this] (offs_t offset) { + return m_bass[offset & 1]; + }), + NAME([this] (offs_t offset, u8 data) { + m_bass[offset & 1] = data & 0xf0; + }) + ); + + // PnP ports + // IRQ/DMA select + map(0x80, 0x80).lr8(NAME([] () { return 0x12; })); + map(0x81, 0x81).lr8(NAME([] () { return 0x22; })); + // IRQ Status + map(0x82, 0x82).lr8(NAME([this] () { return m_irq_status_cb(); })); + + // ... +} |