summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/dac3350a.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/dac3350a.cpp')
-rw-r--r--src/devices/sound/dac3350a.cpp332
1 files changed, 332 insertions, 0 deletions
diff --git a/src/devices/sound/dac3350a.cpp b/src/devices/sound/dac3350a.cpp
new file mode 100644
index 00000000000..91e0593c358
--- /dev/null
+++ b/src/devices/sound/dac3350a.cpp
@@ -0,0 +1,332 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/*
+ Micronas DAC 3550A Stereo Audio DAC
+ i2c code based on mas3507d
+
+ Accepts 8-bit and 16-bit I2C write operations and has no read operations
+*/
+
+#include "emu.h"
+#include "dac3350a.h"
+
+#define VERBOSE (LOG_GENERAL)
+
+#include "logmacro.h"
+
+enum
+{
+ CMD_DEV_WRITE = 0x9a,
+};
+
+enum
+{
+ IDLE = 0,
+ STARTED,
+ NAK,
+ ACK,
+ ACK2,
+};
+
+enum
+{
+ UNKNOWN = 0,
+ VALIDATED,
+ WRONG,
+};
+
+enum
+{
+ REG_UNKNOWN = 0,
+ REG_SR = 1,
+ REG_AVOL,
+ REG_GCFG,
+};
+
+enum
+{
+ // sample rate control register
+ SR_LR_SEL_BIT = 4, // L/R-bit
+ SR_LR_SEL_LEFT = 0, // (WSI = 0 -> left channel
+ SR_LR_SEL_RIGHT = 1, // (WSI = 0 -> right channel
+
+ SR_SP_SEL_BIT = 3, // delay bit
+ SR_SP_SEL_NO_DELAY = 0,
+ SR_SP_SEL_1_BIT_DELAY = 1,
+
+ SR_SRC_BIT = 0, // 3 bits wide, sample rate control
+ SR_SRC_48 = 0, // 32-48 kHz
+ SR_SRC_32, // 26-32 kHz
+ SR_SRC_24, // 20-26 kHz
+ SR_SRC_16, // 14-20 kHz
+ SR_SRC_12, // 10-14 kHz
+ SR_SRC_8, // 8-10 kHz
+ SR_SRC_A, // autoselect
+};
+
+enum
+{
+ // analog volume register
+ AVOL_DEEM_BIT = 14, // deemphasis on/off
+ AVOL_DEEM_OFF = 0,
+ AVOL_DEEM_ON = 1,
+
+ AVOL_L_BIT = 8, // 6 bits wide, analog audio volume level left
+ AVOL_R_BIT = 0, // 6 bits wide, analog audio volume level right
+};
+
+enum
+{
+ // global configuration register
+ GCFG_SEL_53V_BIT = 6, // select 3V-5V mode
+ GCFG_SEL_53V_3V = 0,
+ GCFG_SEL_53V_5V = 1,
+
+ GCFG_PWMD_BIT = 5, // power-mode
+ GCFG_PWMD_NORMAL = 0,
+ GCFG_PWMD_LOW_POWER = 1,
+
+ GCFG_INSEL_AUX2_BIT = 4, // AUX2 select
+ GCFG_INSEL_AUX2_OFF = 0,
+ GCFG_INSEL_AUX2_ON = 1,
+
+ GCFG_INSEL_AUX1_BIT = 3, // AUX1 select
+ GCFG_INSEL_AUX1_OFF = 0,
+ GCFG_INSEL_AUX1_ON = 1,
+
+ GCFG_INSEL_DAC_BIT = 2, // DAC select
+ GCFG_INSEL_DAC_OFF = 0,
+ GCFG_INSEL_DAC_ON = 1,
+
+ GCFG_AUX_MS_BIT = 1, // aux-mono/stereo
+ GCFG_AUX_MS_STEREO = 0,
+ GCFG_AUX_MS_MONO = 1,
+
+ GCFG_IPRA_BIT = 0, // invert right power amplifier
+ GCFG_IPRA_NOT_INVERTED = 0,
+ GCFG_IPRA_INVERTED = 1,
+};
+
+
+DEFINE_DEVICE_TYPE(DAC3350A, dac3350a_device, "dac3350a", "Micronas DAC 3550A Stereo Audio DAC")
+
+dac3350a_device::dac3350a_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, DAC3350A, tag, owner, clock)
+ , device_sound_interface(mconfig, *this)
+{
+}
+
+void dac3350a_device::device_start()
+{
+ // TODO: use configured clock for sample rate and respond to device_clock_changed
+ m_stream = stream_alloc(2, 2, 44100);
+
+ save_item(NAME(m_i2c_bus_state));
+ save_item(NAME(m_i2c_bus_address));
+ save_item(NAME(m_i2c_scli));
+ save_item(NAME(m_i2c_sdai));
+ save_item(NAME(m_i2c_bus_curbit));
+ save_item(NAME(m_i2c_bus_curval));
+ save_item(NAME(m_i2c_bytecount));
+
+ save_item(NAME(m_i2c_subadr));
+ save_item(NAME(m_i2c_data));
+
+ save_item(NAME(m_dac_enable));
+ save_item(NAME(m_volume));
+}
+
+void dac3350a_device::device_reset()
+{
+ m_i2c_scli = m_i2c_sdai = 1;
+ m_i2c_bus_state = IDLE;
+ m_i2c_bus_address = UNKNOWN;
+ m_i2c_bus_curbit = -1;
+ m_i2c_bus_curval = 0;
+
+ m_i2c_subadr = 0;
+ m_i2c_data = 0;
+
+ m_dac_enable = true;
+
+ std::fill(std::begin(m_volume), std::end(m_volume), 1.0);
+}
+
+void dac3350a_device::i2c_scl_w(int line)
+{
+ if (line == m_i2c_scli)
+ return;
+
+ m_i2c_scli = line;
+
+ if (m_i2c_scli)
+ {
+ if (m_i2c_bus_state == STARTED)
+ {
+ m_i2c_bus_curval |= m_i2c_sdai << m_i2c_bus_curbit;
+ m_i2c_bus_curbit--;
+
+ if (m_i2c_bus_curbit == -1)
+ {
+ if (m_i2c_bus_address == UNKNOWN)
+ {
+ if (m_i2c_bus_curval == CMD_DEV_WRITE)
+ {
+ m_i2c_bus_state = ACK;
+ m_i2c_bus_address = VALIDATED;
+ m_i2c_bus_curval = 0;
+ }
+ else
+ {
+ m_i2c_bus_state = NAK;
+ m_i2c_bus_address = WRONG;
+ }
+ }
+ else if (m_i2c_bus_address == VALIDATED)
+ {
+ if (m_i2c_bytecount < 3)
+ {
+ m_i2c_bus_state = ACK;
+
+ if (m_i2c_bytecount == 0)
+ {
+ m_i2c_subadr = m_i2c_bus_curval;
+ }
+ else
+ {
+ m_i2c_data <<= 8;
+ m_i2c_data |= m_i2c_bus_curval;
+ }
+
+ m_i2c_bytecount++;
+ }
+ else
+ {
+ // Only accept 8-bit and 16-bit writes
+ m_i2c_bus_state = NAK;
+ }
+ }
+ }
+ }
+ else if (m_i2c_bus_state == ACK)
+ {
+ m_i2c_bus_state = ACK2;
+ }
+ }
+ else
+ {
+ if (m_i2c_bus_state == ACK2)
+ {
+ m_i2c_bus_state = STARTED;
+ m_i2c_bus_curbit = 7;
+ m_i2c_bus_curval = 0;
+ }
+ }
+}
+
+void dac3350a_device::i2c_sda_w(int line)
+{
+ if (line == m_i2c_sdai)
+ return;
+
+ m_i2c_sdai = line;
+
+ if (m_i2c_scli)
+ {
+ if (m_i2c_sdai)
+ i2c_device_handle_write();
+
+ m_i2c_bus_state = m_i2c_sdai ? IDLE : STARTED;
+ m_i2c_bus_address = UNKNOWN;
+ m_i2c_bus_curbit = 7;
+ m_i2c_bus_curval = 0;
+ }
+}
+
+void dac3350a_device::i2c_device_handle_write()
+{
+ const auto bytecount = m_i2c_bytecount;
+
+ m_i2c_bytecount = 0;
+
+ if (bytecount < 2) // not a valid command, ignore
+ return;
+
+ // const int mcs = BIT(m_i2c_subadr, 6, 2); // chip select, 3 = MPEG mode
+ const int reg_adr = BIT(m_i2c_subadr, 0, 2);
+
+ if (reg_adr == REG_SR)
+ {
+ // sample rate control
+ const int sample_rate = BIT(m_i2c_data, SR_SRC_BIT, 3);
+ const int delay = BIT(m_i2c_data, SR_SP_SEL_BIT);
+ const int lr = BIT(m_i2c_data, SR_LR_SEL_BIT);
+
+ LOG("DAC: SR register %d %d %d\n", sample_rate, delay, lr);
+ }
+ else if (reg_adr == REG_AVOL)
+ {
+ // set volume, requires 2 bytes
+ if (bytecount <= 2)
+ logerror("AVOL command requires 2 bytes of data, found %d\n", bytecount);
+
+ const int vol_r = BIT(m_i2c_data, AVOL_R_BIT, 6);
+ const int vol_l = BIT(m_i2c_data, AVOL_L_BIT, 6);
+ const int deemph = BIT(m_i2c_data, AVOL_DEEM_BIT);
+
+ m_volume[0] = calculate_volume(vol_l);
+ m_volume[1] = calculate_volume(vol_r);
+
+ LOG("DAC: AVOL register %d %d %d (%f %f)\n", vol_r, vol_l, deemph, m_volume[0], m_volume[1]);
+ }
+ else if (reg_adr == REG_GCFG)
+ {
+ // global configuration
+ const int ipra = BIT(m_i2c_data, GCFG_IPRA_BIT);
+ const int aux_ms = BIT(m_i2c_data, GCFG_AUX_MS_BIT);
+ const int insel_dac = BIT(m_i2c_data, GCFG_INSEL_DAC_BIT);
+ const int insel_aux1 = BIT(m_i2c_data, GCFG_INSEL_AUX1_BIT);
+ const int insel_aux2 = BIT(m_i2c_data, GCFG_INSEL_AUX2_BIT);
+ const int pwmd = BIT(m_i2c_data, GCFG_PWMD_BIT);
+ const int sel_53v = BIT(m_i2c_data, GCFG_SEL_53V_BIT);
+
+ LOG("DAC: GCFG register %d %d %d %d %d %d %d\n", ipra, aux_ms, insel_dac, insel_aux1, insel_aux2, pwmd, sel_53v);
+ }
+ else
+ {
+ LOG("DAC: Unknown register selected! %d\n", reg_adr);
+ }
+}
+
+float dac3350a_device::calculate_volume(int val)
+{
+ if (val == 0)
+ return 0.0; // mute
+
+ double db = 0;
+ if (val <= 7)
+ {
+ // -75 dB ... -54 dB: 3 dB steps
+ db = -54 - ((8 - val) * 3);
+ }
+ else
+ {
+ // -54 dB ... +18 dB: 1.5 dB steps
+ db = -54 + ((val - 8) * 1.5);
+ }
+
+ db = std::clamp(db, -75.0, 18.0); // range is +18 dB to -75 dB
+
+ return powf(10.0, db / 20.0);
+}
+
+void dac3350a_device::sound_stream_update(sound_stream &stream)
+{
+ const sound_stream::sample_t enable_scale = m_dac_enable ? 1.0 : 0.0;
+
+ for (int channel = 0; channel < 2 && channel < stream.output_count(); channel++)
+ {
+ for (int sampindex = 0; sampindex < stream.samples(); sampindex++)
+ stream.put(channel, sampindex, stream.get(channel, sampindex) * enable_scale * m_volume[channel]);
+ }
+}