diff options
Diffstat (limited to 'src/devices/machine/psion_asic2.cpp')
-rw-r--r-- | src/devices/machine/psion_asic2.cpp | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/src/devices/machine/psion_asic2.cpp b/src/devices/machine/psion_asic2.cpp new file mode 100644 index 00000000000..e91dd370b67 --- /dev/null +++ b/src/devices/machine/psion_asic2.cpp @@ -0,0 +1,448 @@ +// license:BSD-3-Clause +// copyright-holders:Nigel Barnes +/****************************************************************************** + + Psion ASIC2 + + ASIC2 is the peripheral controller chip for the SIBO architecture. It contains + the system clock oscillator and controls switching between the standby and + operating states. ASIC2 provides an interface to the power supply, keyboard, + buzzer and SSDs. ASIC 2 includes the eight-channel SIBO serial protocol + controller and provides interface circuitry to both the reduced external and + extended internal peripheral expansion ports. + +******************************************************************************/ + +#include "emu.h" +#include "psion_asic2.h" + +#define VERBOSE 0 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + +DEFINE_DEVICE_TYPE(PSION_ASIC2, psion_asic2_device, "psion_asic2", "Psion ASIC2") + +//************************************************************************** +// LIVE DEVICE +//************************************************************************** + +psion_asic2_device::psion_asic2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, PSION_ASIC2, tag, owner, clock) + , m_int_cb(*this) + , m_nmi_cb(*this) + , m_cbusy_cb(*this) + , m_buz_cb(*this) + , m_buzvol_cb(*this) + , m_dr_cb(*this) + , m_col_cb(*this, 0xff) + , m_read_pd_cb(*this, 0x00) + , m_write_pd_cb(*this) + , m_data_r(*this, 0x00) + , m_data_w(*this) +{ +} + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void psion_asic2_device::device_start() +{ + m_a2_status = 0x00; + + m_busy_timer = timer_alloc(FUNC(psion_asic2_device::busy), this); + + save_item(NAME(m_a2_index)); + save_item(NAME(m_a2_icontrol0)); + save_item(NAME(m_a2_icontrol1)); + save_item(NAME(m_a2_iddr)); + save_item(NAME(m_a2_control1)); + save_item(NAME(m_a2_control2)); + save_item(NAME(m_a2_control3)); + save_item(NAME(m_a2_serial_data)); + save_item(NAME(m_a2_serial_control)); + save_item(NAME(m_a2_interrupt_status)); + save_item(NAME(m_a2_status)); +} + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void psion_asic2_device::device_reset() +{ + m_a2_index = 0x00; + m_a2_icontrol0 = 0x00; + m_a2_icontrol1 = 0x00; + m_a2_iddr = 0x00; + m_a2_control1 = 0x00; + m_a2_control2 = 0x00; + m_a2_control3 = 0x00; + m_a2_serial_data = 0x00; + m_a2_serial_control = 0x00; + m_a2_interrupt_status = 0x00; + m_a2_status = 0x00; + m_a2_channel_control = 0x00; +} + + +void psion_asic2_device::on_clr_w(int state) +{ + if (state) + m_a2_status |= 0x01; // A1OnKey + else + m_a2_status &= ~0x01; + + update_interrupts(); +} + +void psion_asic2_device::sds_int_w(int state) +{ + if (state) + m_a2_status |= 0x20; // A2Sdis + else + m_a2_status &= ~0x20; + + update_interrupts(); +} + +void psion_asic2_device::dnmi_w(int state) +{ + if (state) + m_a2_interrupt_status |= 0x01; // DNMI + else + m_a2_interrupt_status &= ~0x01; + + update_interrupts(); +} + +void psion_asic2_device::frcovl_w(int state) +{ + if (BIT(m_a2_control2, 4)) + { + m_buz_cb(state); + } +} + +void psion_asic2_device::reset_w(int state) +{ + if (!state) + m_a2_status |= 0x04; // A1ResetFlag +} + +void psion_asic2_device::update_interrupts() +{ + int irq = m_a2_status & 0x21; + int nmi = BIT(m_a2_interrupt_status, 0, 2) & BIT(m_a2_control3, 4, 2); + + m_int_cb(irq ? ASSERT_LINE : CLEAR_LINE); + m_nmi_cb(nmi ? ASSERT_LINE : CLEAR_LINE); +} + + +TIMER_CALLBACK_MEMBER(psion_asic2_device::busy) +{ + m_a2_status &= ~0x10; // clear data serial controller is busy + m_cbusy_cb(1); +} + +//************************************************************************** +// READ/WRITE HANDLERS +//************************************************************************** + +uint8_t psion_asic2_device::io_r(offs_t offset) +{ + uint8_t data = 0x00; + + switch (offset & 7) + { + case 0x00: // A2Index - Index Register + data = m_a2_index; + LOG("%s io_r: A2Index => %02x\n", machine().describe_context(), data); + break; + + case 0x01: // A2Control - Indexed Control Register + switch (m_a2_index) + { + case 0: // A2IControl0 - System Control Register 0 + data = m_a2_icontrol0; + LOG("%s io_r: A2IControl0 => %02x\n", machine().describe_context(), data); + break; + case 1: // A2IControl1 - Serial Clock Control Register + data = m_a2_icontrol1; + LOG("%s io_r: A2IControl1 => %02x\n", machine().describe_context(), data); + break; + case 2: // A2IWrite - Port Output Data Register (Write only) + LOG("%s io_r: A2IWrite => %02x\n", machine().describe_context(), data); + break; + case 3: // A2IDDR - Port Data Direction Register + data = m_a2_iddr; + LOG("%s io_r: A2IDDR => %02x\n", machine().describe_context(), data); + break; + } + break; + + case 0x02: // A2External - External status register + switch (m_a2_control1 & 0x0f) + { + case 0x0f: // all keyboard columns + for (int i = 0; i < 10; i++) + data |= m_col_cb(i); + break; + + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0a: + data = m_col_cb((m_a2_control1 & 0x0f) - 1); + break; + + default: + data = 0x00; + break; + } + LOG("%s io_r: A2External => %02x\n", machine().describe_context(), data); + break; + + case 0x03: // A2InterruptStatus - Interrupt status register + // b0 DoorInterrupt - Status of pack doors, 1 if open + // b1 ExpansionInterrupt - Status of expansion interface doors, 1 if open + // b2 SlaveDataValid - Slave data valid, 1 if valid frame arrived + // b3 SlaveDataControl - Slave control or data frame, 1 if control frame + // b4 SlaveDataOverrun - Slave data overrun, 1 if overrun + // b5 LowBatteryNMI - Low battery NMI, 1 if NMI occurred + // b6 0 + // b7 0 + data = m_a2_interrupt_status; + LOG("%s io_r: A2InterruptStatus => %02x\n", machine().describe_context(), data); + break; + + case 0x04: // A2Status - Status register + // b0 A1OnKey - 1 if on key pressed + // b1 WakeUp - 1 if external wakeup valid + // b2 A1ResetFlag - 1 if system reset + // b3 SerialClockState - 1 if slave clock is high + // b4 SerialBusy - 1 while data serial controller is busy + // b5 A2Sdis - 1 if slave data signal is high + // b6 Ext - 1 in Extended Mode, 0 in Compatible Mode + // b7 RevId - 1 if Revision 4 part + data = m_a2_status; + if (!machine().side_effects_disabled()) + { + m_a2_status &= ~0x01; // clear A1OnKey + m_a2_status &= ~0x04; // clear A1ResetFlag + } + LOG("%s io_r: A2Status => %02x\n", machine().describe_context(), data); + break; + + case 0x05: // A2SerialData - Serial channel read data register + data = m_a2_serial_data; + if ((m_a2_serial_control & 0x10) == 0x10) + m_a2_serial_data = receive_frame(); + LOG("%s io_r: A2SerialData => %02x\n", machine().describe_context(), data); + break; + + case 0x06: // A2KeyData - Keyboard poll register + // Extended mode read from Port I/O lines + data = m_read_pd_cb(); + LOG("%s io_r: A2KeyData => %02x\n", machine().describe_context(), data); + break; + + case 0x07: // A2SlaveData - Slave data read register + data = 0x00; + LOG("%s io_r: A2SlaveData => %02x\n", machine().describe_context(), data); + break; + + default: + LOG("%s io_r: Unhandled register %02x => %02x\n", machine().describe_context(), offset, data); + break; + } + return data; +} + +void psion_asic2_device::io_w(offs_t offset, uint8_t data) +{ + switch (offset & 7) + { + case 0x00: // A2Index - Index Register + // b0-b1 INDEX0,1 - Register 1 Index + LOG("%s io_w: A2Index <= %02x\n", machine().describe_context(), data); + m_a2_index = data & 3; + break; + + case 0x01: // A2Control - Indexed Control Register + switch (m_a2_index) + { + case 0: // A2IControl0 - System Control Register 0 + // b0-b1 CLKSEL0-1 - Crystal Divider Select (0,1 - 23.04MHz divider = 3, 2 - 15.36MHz divider = 2, 3 - 7.68MHz divider = 1) + // b2 CHEN3 - Serial Channel 3 is enabled when this bit is set + // b3 CHEN4 - Serial Channel 4 is enabled when this bit is set + // b4 CHEN6 - Serial Channel 6 is enabled when this bit is set + // b5 EXONOFF - EXON & SCKS witch-on is disabled when this bit is set + // b6 INTSEL - Interrupt Source Select (0 - Frame received, 1 - SDIS direct) + LOG("%s io_w: A2IControl0 => %02x\n", machine().describe_context(), data); + m_a2_icontrol0 = data; + break; + case 1: // A2IControl1 - Serial Clock Control Register + // b0 CKEN1 - Serial Channel 1 continuous clock is enabled when this bit is set + // b1 CKEN2 - Serial Channel 2 continuous clock is enabled when this bit is set + // b2 CKEN3 - Serial Channel 3 continuous clock is enabled when this bit is set + // b3 CKEN4 - Serial Channel 4 continuous clock is enabled when this bit is set + LOG("%s io_w: A2IControl1 => %02x\n", machine().describe_context(), data); + m_a2_icontrol1 = data; + break; + case 2: // A2IWrite - Port Output Data Register + LOG("%s io_w: A2IWrite => %02x\n", machine().describe_context(), data); + m_write_pd_cb(data & m_a2_iddr); + break; + case 3: // A2IDDR - Port Data Direction Register + // b0-b7 DO0-7 - 0 = PD* is and input, 1 = PD* is an output + LOG("%s io_w: A2IDDR => %02x\n", machine().describe_context(), data); + m_a2_iddr = data; + break; + } + break; + + case 0x02: // A2Control1 - Control register 1 + // b0-b3 KeyScan - Values 0-15 drive the keyboard poll columns + // b4-b5 SerialClockRate - Sets the clock frequency for channels 1-4 and 7 as below: + // 0 = ClockRateMedium 1.536 MHz + // 2 = ClockRateSlow 256 KHz + // 3 = ClockRateFast 3.84 MHz + LOG("%s io_w: A2Control1 <= %02x\n", machine().describe_context(), data); + m_a2_control1 = data; + break; + + case 0x03: // A2Control2 - Control register 2 + // b0 DigitizerEnable - 1 to enable digitizer drivers + // b1 XySwitch - 0 to drive digitizer X scan, 1 to drive Y + // b2 BuzzerToggle - Toggles the piezo speaker + // b3 BuzzerVolume - 1 if loud buzzer volume, 0 is soft + // b4 BuzzerMode - Piezo driving mode, 0 - toggled by BZTOG, 1 - driven by FRC + // b5 ClockEnable5 - Enable continuous clock on serial channel 5 + // b6 ClockEnable6 - Enable continuous clock on serial channel 6 + // b7 ClockEnable7 - Enable continuous clock on serial channel 7 + LOG("%s io_w: A2Control2 <= %02x\n", machine().describe_context(), data); + m_a2_control2 = data; + m_dr_cb(BIT(data, 1)); + if (!BIT(data, 4)) + { + m_buz_cb(BIT(data, 2)); + } + m_buzvol_cb(BIT(data, 3)); + break; + + case 0x04: // A2Control3 - Control register 3 + // b0 SerialNull - 1 to send a null frame + // b1 Ps34Acknowledge - 1 to reset PS34 on power up + // b2 A2SldEnable - 1 to enable SLD bus clock + // b3 SerialEnable - 1 to enable access to pack channels 1-4 + // b4 DoorEnable - 1 to enable pack door NMIs + // b5 ExpansionEnable - 1 to enable expansion door NMIs + // b6 VhControl - 1 to enable VPP to packs + // b7 ElEnable - 1 to enable LCD back light + LOG("%s io_w: A2Control3 <= %02x\n", machine().describe_context(), data); + m_a2_control3 = data; + if (BIT(m_a2_control3, 0)) + transmit_frame(NULL_FRAME); + break; + + case 0x05: // A2SerialData - Serial channel write data register + LOG("%s io_w: A2SerialData <= %02x\n", machine().describe_context(), data); + if ((m_a2_serial_control & 0xc0) == 0x80) + transmit_frame(DATA_FRAME | data); + break; + + case 0x06: // A2SerialControl - Serial channel write control register + // WriteSingle 10000000b; ReadSingle 11000000b + // WriteMulti 10010000b; ReadMulti 11010000b + // Reset 00000000b; Select 01000000b + // Asic2SlaveId 001h; Asic5PackId 002h + // Asic5NormalId 003h; Asic6Id 004h + // Asic8Id 005h; Asic4Id 006h + LOG("%s io_w: A2SerialControl <= %02x\n", machine().describe_context(), data); + m_a2_serial_control = data; + transmit_frame(CONTROL_FRAME | data); + + if ((m_a2_serial_control & 0x40) == 0x40) + m_a2_serial_data = receive_frame(); + break; + + case 0x07: // A2ChannelControl - Serial channel select register + // b0 Pack1Enable - 1 to select pack 1 + // b1 Pack2Enable - 1 to select pack 2 + // b2 Pack3Enable - 1 to select pack 3 + // b3 Pack4Enable - 1 to select pack 4 + // b4-b6 ChannelSelect - 0-7 to select channel 0,5-8 as below: + // 4 = PK0, 5 = PK5, 6 = PK6, 7 = PK7 + // b7 MultiplexEnable - 1 to loop slave channel to pack channel + LOG("%s io_w: A2ChannelControl <= %02x Channels %c %c %c %c %c %c %c %c\n", machine().describe_context(), data, + BIT(data, 4, 3) == 4 ? '0' : ' ', + BIT(data, 0) ? '1' : ' ', + BIT(data, 1) ? '2' : ' ', + BIT(data, 2) ? '3' : ' ', + BIT(data, 3) ? '4' : ' ', + BIT(data, 4, 3) == 5 ? '5' : ' ', + BIT(data, 4, 3) == 6 ? '6' : ' ', + BIT(data, 4, 3) == 7 ? '7' : ' '); + m_a2_channel_control = data; + break; + + default: + LOG("%s io_w: Unhandled register %02x <= %02x\n", machine().describe_context(), offset, data); + break; + } +} + + +//------------------------------------------------- +// SIBO Serial Protocol Controller +//------------------------------------------------- + +bool psion_asic2_device::channel_active(int channel) +{ + switch (channel) + { + case 0: + return BIT(m_a2_channel_control, 4, 3) == 4; + case 1: case 2: case 3: case 4: + return BIT(m_a2_channel_control, channel - 1); + case 5: case 6: case 7: + return BIT(m_a2_channel_control, 4, 3) == channel; + } + return false; +} + +void psion_asic2_device::transmit_frame(uint16_t data) +{ + m_a2_status |= 0x10; // set data serial controller is busy + m_busy_timer->adjust(attotime::from_ticks(12, clock() / 2)); + m_cbusy_cb(0); + + for (int ch = 0; ch < 8; ch++) + { + if (channel_active(ch)) + { + LOG("%s Channel %d Transmit %s frame %02x\n", machine().describe_context(), ch, (data & DATA_FRAME) ? "Data" : (data & CONTROL_FRAME) ? "Control" : "Null", data & 0xff); + m_data_w[ch](data); + } + } +} + +uint8_t psion_asic2_device::receive_frame() +{ + uint8_t data = 0x00; + + m_a2_status |= 0x10; // set data serial controller is busy + m_busy_timer->adjust(attotime::from_ticks(12, clock() / 2)); + m_cbusy_cb(0); + + for (int ch = 0; ch < 8; ch++) + { + if (channel_active(ch)) + { + data |= m_data_r[ch](); + LOG("%s Channel %d Receive Data frame %02x\n", machine().describe_context(), ch, data); + } + } + + return data; +} |