summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/ct1745.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/ct1745.cpp')
-rw-r--r--src/devices/sound/ct1745.cpp324
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(); }));
+
+ // ...
+}