mame/src/devices/machine/ucb1200.cpp
Ryan Holtz be92cc6c65 -scoop: Added skeleton device for Sharp "Scoop" peripheral IC from the Zaurus PDA line. [Ryan Holtz]
-ucb1200: Added skeleton device for Philips UCB1200 audio/telecom codec. [Ryan Holtz]

-sa1110: Added rudimentary framework for the MCP codec interface module. [Ryan Holtz]
2020-12-06 14:52:40 +01:00

377 lines
15 KiB
C++

// license:BSD-3-Clause
// copyright-holders:Ryan Holtz
/***************************************************************************
Philips UCB1200 Advanced modem/audio analog front-end skeleton
***************************************************************************/
#include "emu.h"
#include "ucb1200.h"
#define LOG_UNKNOWN (1 << 1)
#define LOG_READS (1 << 2)
#define LOG_WRITES (1 << 3)
#define LOG_ALL (LOG_UNKNOWN | LOG_READS | LOG_WRITES)
#define VERBOSE (LOG_ALL)
#include "logmacro.h"
DEFINE_DEVICE_TYPE(UCB1200, ucb1200_device, "ucb1200", "Philips UCB1200 modem/audio codec")
ucb1200_device::ucb1200_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, UCB1200, tag, owner, clock)
, m_gpio_out(*this)
, m_audio_out(*this)
, m_telecom_out(*this)
, m_irq_out(*this)
, m_adc_in(*this)
{
}
void ucb1200_device::device_start()
{
m_gpio_out.resolve_all_safe();
m_audio_out.resolve_safe();
m_telecom_out.resolve_safe();
m_irq_out.resolve_safe();
m_adc_in.resolve_all_safe(0xffff);
save_item(NAME(m_gpio_out_latch));
save_item(NAME(m_gpio_in_latch));
save_item(NAME(m_gpio_dir));
save_item(NAME(m_rising_int_en));
save_item(NAME(m_falling_int_en));
save_item(NAME(m_int_status));
save_item(NAME(m_telecom_ctrl_a));
save_item(NAME(m_telecom_ctrl_b));
save_item(NAME(m_audio_ctrl_a));
save_item(NAME(m_audio_ctrl_b));
save_item(NAME(m_touch_ctrl));
save_item(NAME(m_adc_ctrl));
save_item(NAME(m_adc_data));
save_item(NAME(m_id));
save_item(NAME(m_mode));
}
void ucb1200_device::device_reset()
{
m_gpio_out_latch = 0;
m_gpio_in_latch = 0;
m_gpio_dir = 0;
m_rising_int_en = 0;
m_falling_int_en = 0;
m_int_status = 0;
m_telecom_ctrl_a = 0x10 << TEL_DIV_BIT;
m_telecom_ctrl_b = 0;
m_audio_ctrl_a = 0x06 << AUD_DIV_BIT;
m_audio_ctrl_b = 0;
m_touch_ctrl = 0;
m_adc_ctrl = 0;
m_adc_data = 0;
m_id = 0x1004;
m_mode = 0;
}
void ucb1200_device::gpio_in(const uint16_t line, const int state)
{
const uint16_t mask = (1 << line);
const uint16_t old_latch = m_gpio_in_latch;
m_gpio_in_latch &= ~mask;
m_gpio_in_latch |= (state << line);
const bool old_irq = (m_int_status != 0);
if (old_latch != m_gpio_in_latch && !BIT(m_gpio_dir, line))
{
if (state && BIT(m_rising_int_en, line))
m_int_status |= mask;
else if (!state && BIT(m_falling_int_en, line))
m_int_status |= mask;
}
if (!old_irq && m_int_status != 0)
m_irq_out(1);
}
void ucb1200_device::update_gpio_direction(const uint16_t old_dir)
{
const uint16_t new_outputs = ~old_dir & m_gpio_dir;
if (new_outputs)
{
for (uint32_t line = 0; line < 10; line++)
{
if (BIT(new_outputs, line))
{
m_gpio_out[line](BIT(m_gpio_out_latch, line));
}
}
}
// TODO: Do we need to check rising/falling edges based on the transition from output to input?
}
void ucb1200_device::update_gpio_outputs(const uint16_t old_latch, const uint16_t changed)
{
uint16_t remaining_changed = changed;
for (uint32_t line = 0; line < 10 && remaining_changed != 0; line++)
{
if (BIT(remaining_changed, line))
{
m_gpio_out[line](BIT(m_gpio_out_latch, line));
remaining_changed &= ~(1 << line);
}
}
}
void ucb1200_device::audio_sample_in(const uint16_t sample)
{
// TODO: Handle incoming audio samples
}
void ucb1200_device::telecom_sample_in(const uint16_t sample)
{
// TODO: Handle incoming telecom samples
}
void ucb1200_device::adc_begin_conversion()
{
const uint16_t adc_input = (m_adc_ctrl & ADC_INPUT_MASK) >> ADC_INPUT_BIT;
switch (adc_input)
{
case ADC_INPUT_TSPX:
case ADC_INPUT_TSMX:
case ADC_INPUT_TSPY:
case ADC_INPUT_TSMY:
default:
m_adc_data = 0;
break;
case ADC_INPUT_AD0:
case ADC_INPUT_AD1:
case ADC_INPUT_AD2:
case ADC_INPUT_AD3:
m_adc_data = m_adc_in[adc_input - ADC_INPUT_AD0]();
m_adc_data <<= ADC_DATA_BIT;
m_adc_data &= ADC_DATA_MASK;
break;
}
m_adc_data |= (1 << ADC_DAT_VAL_BIT);
}
uint16_t ucb1200_device::read(offs_t offset)
{
switch (offset)
{
case 0:
{
const uint16_t data = (m_gpio_out_latch & m_gpio_dir) | (m_gpio_in_latch & ~m_gpio_dir);
LOGMASKED(LOG_READS, "%s: read: GPIO Data Register: %04x\n", machine().describe_context(), data);
return data;
}
case 1:
LOGMASKED(LOG_READS, "%s: read: GPIO Direction Register: %04x\n", machine().describe_context(), m_gpio_dir);
return m_gpio_dir;
case 2:
LOGMASKED(LOG_READS, "%s: read: GPIO Rising-Edge Interrupt Enable Register: %04x\n", machine().describe_context(), m_rising_int_en);
return m_rising_int_en;
case 3:
LOGMASKED(LOG_READS, "%s: read: GPIO Falling-Edge Interrupt Enable Register: %04x\n", machine().describe_context(), m_falling_int_en);
return m_falling_int_en;
case 4:
LOGMASKED(LOG_READS, "%s: read: Interrupt Clear/Status Register: %04x\n", machine().describe_context(), m_int_status);
return m_int_status;
case 5:
LOGMASKED(LOG_READS, "%s: read: Telecom Control Register A: %04x\n", machine().describe_context(), m_telecom_ctrl_a);
return m_telecom_ctrl_a;
case 6:
LOGMASKED(LOG_READS, "%s: read: Telecom Control Register B: %04x\n", machine().describe_context(), m_telecom_ctrl_b);
return m_telecom_ctrl_b;
case 7:
LOGMASKED(LOG_READS, "%s: read: Audio Control Register A: %04x\n", machine().describe_context(), m_audio_ctrl_a);
return m_audio_ctrl_a;
case 8:
LOGMASKED(LOG_READS, "%s: read: Audio Control Register B: %04x\n", machine().describe_context(), m_audio_ctrl_b);
return m_audio_ctrl_b;
case 9:
LOGMASKED(LOG_READS, "%s: read: Touchscreen Control Register: %04x\n", machine().describe_context(), m_touch_ctrl);
return m_touch_ctrl;
case 10:
LOGMASKED(LOG_READS, "%s: read: ADC Control Register: %04x\n", machine().describe_context(), m_adc_ctrl);
return m_adc_ctrl;
case 11:
LOGMASKED(LOG_READS, "%s: read: ADC Data Register: %04x\n", machine().describe_context(), m_adc_data);
return m_adc_data;
case 12:
LOGMASKED(LOG_READS, "%s: read: ID Register: %04x\n", machine().describe_context(), m_id);
return m_id;
case 13:
LOGMASKED(LOG_READS, "%s: read: Mode Register: %04x\n", machine().describe_context(), m_mode);
return m_mode;
case 14:
LOGMASKED(LOG_READS, "%s: read: Reserved Register: %04x\n", machine().describe_context(), 0);
return 0;
case 15:
LOGMASKED(LOG_READS, "%s: read: NULL Register: %04x\n", machine().describe_context(), 0xffff);
return 0xffff;
default:
LOGMASKED(LOG_READS | LOG_UNKNOWN, "%s: read: Unknown Register: %d\n", machine().describe_context(), offset);
return 0;
}
}
void ucb1200_device::write(offs_t offset, uint16_t data)
{
switch (offset)
{
case 0:
{
LOGMASKED(LOG_WRITES, "%s: write: GPIO Data Register: %04x\n", machine().describe_context(), data);
const uint16_t old = m_gpio_out_latch;
m_gpio_out_latch = data;
const uint16_t changed = (old ^ m_gpio_out_latch) & m_gpio_dir;
if (changed)
update_gpio_outputs(old, changed);
break;
}
case 1:
{
LOGMASKED(LOG_WRITES, "%s: write: GPIO Direction Register: %04x\n", machine().describe_context(), data);
const uint16_t old = m_gpio_dir;
m_gpio_dir = data;
if (old != m_gpio_dir)
update_gpio_direction(old);
break;
}
case 2:
LOGMASKED(LOG_WRITES, "%s: write: GPIO Rising-Edge Interrupt Enable Register: %04x\n", machine().describe_context(), data);
m_rising_int_en = data;
break;
case 3:
LOGMASKED(LOG_WRITES, "%s: write: GPIO Falling-Edge Interrupt Enable Register: %04x\n", machine().describe_context(), data);
m_falling_int_en = data;
break;
case 4:
{
LOGMASKED(LOG_WRITES, "%s: write: Interrupt Clear/Status Register: %04x\n", machine().describe_context(), data);
const uint16_t old = m_int_status;
m_int_status &= ~data;
if (old != 0 && m_int_status == 0)
m_irq_out(0);
break;
}
case 5:
LOGMASKED(LOG_WRITES, "%s: write: Telecom Control Register A: %04x\n", machine().describe_context(), data);
LOGMASKED(LOG_WRITES, "%s: Telecom Codec Sample Rate Divisor: %02x\n", machine().describe_context(), (data & TEL_DIV_MASK) >> TEL_DIV_BIT);
LOGMASKED(LOG_WRITES, "%s: Telecom Codec Loopback: %d\n", machine().describe_context(), BIT(data, TEL_LOOP_BIT));
m_telecom_ctrl_a = data;
break;
case 6:
{
LOGMASKED(LOG_WRITES, "%s: write: Telecom Control Register B: %04x\n", machine().describe_context(), data);
LOGMASKED(LOG_WRITES, "%s: Telecom Voice Band Filter: %d\n", machine().describe_context(), BIT(data, TEL_VOICE_ENA_BIT));
LOGMASKED(LOG_WRITES, "%s: Telecom Clip Detect Clear: %d\n", machine().describe_context(), BIT(data, TEL_CLIP_BIT));
LOGMASKED(LOG_WRITES, "%s: Telecom Input Attenuation: %d\n", machine().describe_context(), BIT(data, TEL_ATT_BIT));
LOGMASKED(LOG_WRITES, "%s: Telecom Sidetone Suppression: %d\n", machine().describe_context(), BIT(data, TEL_SIDE_ENA_BIT));
LOGMASKED(LOG_WRITES, "%s: Telecom Output Mute: %d\n", machine().describe_context(), BIT(data, TEL_MUTE_BIT));
LOGMASKED(LOG_WRITES, "%s: Telecom Input Enable: %d\n", machine().describe_context(), BIT(data, TEL_IN_ENA_BIT));
LOGMASKED(LOG_WRITES, "%s: Telecom Output Enable: %d\n", machine().describe_context(), BIT(data, TEL_OUT_ENA_BIT));
const uint16_t old_clip = m_telecom_ctrl_b & (1 << TEL_CLIP_BIT);
const uint16_t new_clip = data & (1 << TEL_CLIP_BIT);
m_telecom_ctrl_b = data;
if (new_clip)
m_telecom_ctrl_b &= ~(1 << TEL_CLIP_BIT);
else if (old_clip)
m_telecom_ctrl_b |= (1 << TEL_CLIP_BIT);
break;
}
case 7:
LOGMASKED(LOG_WRITES, "%s: write: Audio Control Register A: %04x\n", machine().describe_context(), data);
LOGMASKED(LOG_WRITES, "%s: Audio Codec Sample Rate Divisor: %02x\n", machine().describe_context(), (data & AUD_DIV_MASK) >> AUD_DIV_BIT);
LOGMASKED(LOG_WRITES, "%s: Audio Input Gain: %02x\n", machine().describe_context(), (data & AUD_GAIN_MASK) >> AUD_GAIN_BIT);
m_audio_ctrl_a = data;
break;
case 8:
{
LOGMASKED(LOG_WRITES, "%s: write: Audio Control Register B: %04x\n", machine().describe_context(), data);
LOGMASKED(LOG_WRITES, "%s: Audio Output Attenuation: %02x\n", machine().describe_context(), (data & AUD_ATT_MASK) >> AUD_ATT_BIT);
LOGMASKED(LOG_WRITES, "%s: Audio Clip Detect Clear: %d\n", machine().describe_context(), BIT(data, AUD_CLIP_BIT));
LOGMASKED(LOG_WRITES, "%s: Audio Codec Loopback: %d\n", machine().describe_context(), BIT(data, AUD_LOOP_BIT));
LOGMASKED(LOG_WRITES, "%s: Audio Output Mute: %d\n", machine().describe_context(), BIT(data, AUD_MUTE_BIT));
LOGMASKED(LOG_WRITES, "%s: Audio Input Enable: %d\n", machine().describe_context(), BIT(data, AUD_IN_ENA_BIT));
LOGMASKED(LOG_WRITES, "%s: Audio Output Enable: %d\n", machine().describe_context(), BIT(data, AUD_OUT_ENA_BIT));
const uint16_t old_clip = m_audio_ctrl_b & (1 << AUD_CLIP_BIT);
const uint16_t new_clip = data & (1 << AUD_CLIP_BIT);
m_audio_ctrl_b = data;
if (new_clip)
m_audio_ctrl_b &= ~(1 << AUD_CLIP_BIT);
else if (old_clip)
m_audio_ctrl_b |= (1 << AUD_CLIP_BIT);
break;
}
case 9:
{
static const char *const s_tsc_modes[4] = { "Interrupt", "Pressure", "Position [2]", "Position [3]" };
LOGMASKED(LOG_WRITES, "%s: write: Touchscreen Control Register: %04x\n", machine().describe_context(), data);
LOGMASKED(LOG_WRITES, "%s: TSMX Pin Powered: %d\n", machine().describe_context(), BIT(data, TSMX_POW_BIT));
LOGMASKED(LOG_WRITES, "%s: TSPX Pin Powered: %d\n", machine().describe_context(), BIT(data, TSPX_POW_BIT));
LOGMASKED(LOG_WRITES, "%s: TSMY Pin Powered: %d\n", machine().describe_context(), BIT(data, TSMY_POW_BIT));
LOGMASKED(LOG_WRITES, "%s: TSPY Pin Powered: %d\n", machine().describe_context(), BIT(data, TSPY_POW_BIT));
LOGMASKED(LOG_WRITES, "%s: TSMX Pin Grounded: %d\n", machine().describe_context(), BIT(data, TSMX_GND_BIT));
LOGMASKED(LOG_WRITES, "%s: TSPX Pin Grounded: %d\n", machine().describe_context(), BIT(data, TSPX_GND_BIT));
LOGMASKED(LOG_WRITES, "%s: TSMY Pin Grounded: %d\n", machine().describe_context(), BIT(data, TSMY_GND_BIT));
LOGMASKED(LOG_WRITES, "%s: TSPY Pin Grounded: %d\n", machine().describe_context(), BIT(data, TSPY_GND_BIT));
LOGMASKED(LOG_WRITES, "%s: Touch Screen Mode: %s\n", machine().describe_context(), s_tsc_modes[(data & TSC_MODE_MASK) >> TSC_MODE_BIT]);
LOGMASKED(LOG_WRITES, "%s: Touch Screen Bias Circuit Active: %d\n", machine().describe_context(), BIT(data, TSC_BIAS_ENA_BIT));
m_touch_ctrl &= ~TOUCH_WRITE_MASK;
m_touch_ctrl |= data & TOUCH_WRITE_MASK;
break;
}
case 10:
{
static const char *const s_adc_inputs[8] = { "TSPX", "TSMX", "TSPY", "TSMY", "AD0", "AD1", "AD2", "AD3" };
LOGMASKED(LOG_WRITES, "%s: write: ADC Control Register: %04x\n", machine().describe_context(), data);
LOGMASKED(LOG_WRITES, "%s: ADC Sync Mode: %d\n", machine().describe_context(), BIT(data, ADC_SYNC_ENA_BIT));
LOGMASKED(LOG_WRITES, "%s: Connect Internal Vref to VREFBYP Pin: %d\n", machine().describe_context(), BIT(data, VREFBYP_CON_BIT));
LOGMASKED(LOG_WRITES, "%s: ADC Input Select: %s\n", machine().describe_context(), s_adc_inputs[(data & ADC_INPUT_MASK) >> ADC_INPUT_BIT]);
LOGMASKED(LOG_WRITES, "%s: Apply External Voltage to VREFBYP Pin: %d\n", machine().describe_context(), BIT(data, EXT_REF_ENA_BIT));
LOGMASKED(LOG_WRITES, "%s: ADC Conversion Start: %d\n", machine().describe_context(), BIT(data, ADC_START_BIT));
LOGMASKED(LOG_WRITES, "%s: ADC Enabled: %d\n", machine().describe_context(), BIT(data, ADC_ENA_BIT));
const uint16_t old = m_adc_ctrl;
m_adc_ctrl = data;
if (!BIT(old, ADC_START_BIT) && BIT(data, ADC_START_BIT) && BIT(data, ADC_ENA_BIT))
adc_begin_conversion();
break;
}
case 11:
LOGMASKED(LOG_WRITES, "%s: write: ADC Data Register (ignored): %04x\n", machine().describe_context(), data);
break;
case 12:
LOGMASKED(LOG_WRITES, "%s: write: ID Register (ignored): %04x\n", machine().describe_context(), data);
break;
case 13:
LOGMASKED(LOG_WRITES, "%s: write: Mode Register: %04x\n", machine().describe_context(), data);
LOGMASKED(LOG_WRITES, "%s: Analog Audio Test Mode: %d\n", machine().describe_context(), BIT(data, AUD_TEST_BIT));
LOGMASKED(LOG_WRITES, "%s: Analog Telecom Test Mode: %d\n", machine().describe_context(), BIT(data, TEL_TEST_BIT));
LOGMASKED(LOG_WRITES, "%s: Production Test Mode: %02x\n", machine().describe_context(), (data & PROD_TEST_MODE_MASK) >> PROD_TEST_MODE_BIT);
LOGMASKED(LOG_WRITES, "%s: Dynamic Data Valid Flag Mode: %d\n", machine().describe_context(), BIT(data, DYN_VFLAG_ENA_BIT));
LOGMASKED(LOG_WRITES, "%s: Audio Offset-Cancelling: %d\n", machine().describe_context(), BIT(data, AUD_OFF_CAN_BIT));
break;
case 14:
LOGMASKED(LOG_WRITES, "%s: write: Reserved Register (ignored): %04x\n", machine().describe_context(), data);
break;
case 15:
LOGMASKED(LOG_WRITES, "%s: write: NULL Register (ignored): %04x\n", machine().describe_context(), data);
break;
default:
LOGMASKED(LOG_WRITES | LOG_UNKNOWN, "%s: write: Unknown Register: %d = %04x\n", machine().describe_context(), offset, data);
break;
}
}