mirror of
https://github.com/holub/mame
synced 2025-04-16 13:34:55 +03:00

-ucb1200: Added skeleton device for Philips UCB1200 audio/telecom codec. [Ryan Holtz] -sa1110: Added rudimentary framework for the MCP codec interface module. [Ryan Holtz]
377 lines
15 KiB
C++
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;
|
|
}
|
|
}
|