summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/saa5070.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/saa5070.cpp')
-rw-r--r--src/devices/machine/saa5070.cpp350
1 files changed, 350 insertions, 0 deletions
diff --git a/src/devices/machine/saa5070.cpp b/src/devices/machine/saa5070.cpp
new file mode 100644
index 00000000000..21331917644
--- /dev/null
+++ b/src/devices/machine/saa5070.cpp
@@ -0,0 +1,350 @@
+// license:BSD-3-Clause
+// copyright-holders:Nigel Barnes
+/*********************************************************************
+
+ SAA5070 Viewdata Input/Output Peripheral (VIOP)
+
+ https://bitsavers.org/components/philips/_dataBooks/1985_IC02Na_Philips_Bipolar_MOS_Video_and_Associated_Systems.pdf
+
+ TODO:
+ - tape UART
+ - IBUS receiver/transmitter
+
+*********************************************************************/
+
+#include "emu.h"
+#include "saa5070.h"
+
+
+DEFINE_DEVICE_TYPE(SAA5070, saa5070_device, "saa5070", "SAA5070 Viewdata Input/Output Peripheral")
+DEFINE_DEVICE_TYPE(SAA5070_UART, saa5070_uart_device, "saa5070_uart", "SAA5070 UART")
+
+
+//**************************************************************************
+// LIVE DEVICE
+//**************************************************************************
+
+saa5070_uart_device::saa5070_uart_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, SAA5070_UART, tag, owner, clock)
+ , device_serial_interface(mconfig, *this)
+ , m_txd_handler(*this)
+ , m_rx_enabled(0)
+ , m_tx_enabled(0)
+ , m_rxd(1)
+{
+}
+
+
+void saa5070_uart_device::device_start()
+{
+ receive_register_reset();
+ transmit_register_reset();
+
+ save_item(NAME(m_status));
+}
+
+void saa5070_uart_device::device_reset()
+{
+ m_status = 0x80; // TXRDY
+}
+
+
+void saa5070_uart_device::set_baud_rate(int rxbaud, int txbaud, int parity)
+{
+ if (parity)
+ set_data_frame(1, 7, (parity == 1) ? PARITY_ODD : PARITY_EVEN, STOP_BITS_1);
+ else
+ set_data_frame(1, 8, PARITY_NONE, STOP_BITS_1);
+
+ set_rcv_rate(rxbaud);
+ set_tra_rate(txbaud);
+
+ receive_register_reset();
+ transmit_register_reset();
+}
+
+void saa5070_uart_device::rcv_callback()
+{
+ if (m_rx_enabled) // UART enabled
+ receive_register_update_bit(m_rxd);
+}
+
+void saa5070_uart_device::rcv_complete()
+{
+ receive_register_extract();
+
+ m_status |= 0x40; // RXRDY
+
+ if (is_receive_framing_error())
+ m_status |= 0x10; // FERR
+ else
+ m_status &= ~0x10;
+
+ if (is_receive_parity_error())
+ m_status |= 0x08; // PERR
+ else
+ m_status &= ~0x08;
+}
+
+void saa5070_uart_device::tra_callback()
+{
+ if (m_tx_enabled) // UART enabled
+ m_txd_handler(transmit_register_get_data_bit());
+}
+
+void saa5070_uart_device::tra_complete()
+{
+ m_status |= 0x80; // TXRDY
+}
+
+
+void saa5070_uart_device::write_rxd(int state)
+{
+ m_rxd = state;
+ device_serial_interface::rx_w(state);
+}
+
+void saa5070_uart_device::write_dcd(int state)
+{
+ if (state)
+ m_status |= 0x24; // DCD/IDCD
+ else
+ m_status &= ~0x24;
+}
+
+void saa5070_uart_device::tx_byte(uint8_t data)
+{
+ m_status &= ~0x80; // TXRDY
+
+ transmit_register_setup(data);
+}
+
+uint8_t saa5070_uart_device::rx_byte()
+{
+ m_status &= ~0x40; // RXRDY
+
+ return get_received_char();
+}
+
+
+saa5070_device::saa5070_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, SAA5070, tag, owner, clock)
+ , m_line_uart(*this, "line_uart")
+ , m_tape_uart(*this, "tape_uart")
+ , m_in_a_handler(*this, 0x00)
+ , m_in_b_handler(*this, 0x00)
+ , m_out_a_handler(*this)
+ , m_out_b_handler(*this)
+ , m_imp_handler(*this)
+ , m_txdata_handler(*this)
+ , m_timeout_timer(nullptr)
+ , m_dial_timer(nullptr)
+{
+}
+
+
+void saa5070_device::device_start()
+{
+ memset(m_sr, 0, sizeof(m_sr));
+
+ m_timeout_timer = timer_alloc(FUNC(saa5070_device::timeout), this);
+ m_dial_timer = timer_alloc(FUNC(saa5070_device::dial), this);
+
+ save_item(NAME(m_index));
+ save_item(NAME(m_sr));
+}
+
+void saa5070_device::device_reset()
+{
+ write_direct(0, 0x81); // Status 0: LTXRDY, IBTXRDY
+ write_direct(1, 0x80); // Status 1: TTXRDY
+ write_direct(2, 0x00); // Mode
+ write_direct(3, 0x00); // Command
+ write_direct(6, 0x00); // Port A
+ write_direct(7, 0x00); // Port B
+ write_direct(8, 0x00); // Dial Control and Timing
+}
+
+
+void saa5070_device::device_add_mconfig(machine_config &config)
+{
+ SAA5070_UART(config, m_line_uart, DERIVED_CLOCK(1, 1));
+ m_line_uart->txd_handler().set(FUNC(saa5070_device::write_txdata));
+ SAA5070_UART(config, m_tape_uart, DERIVED_CLOCK(1, 1));
+}
+
+
+TIMER_CALLBACK_MEMBER(saa5070_device::timeout)
+{
+ m_sr[8] &= ~0x10; // 1.5s TIMER
+ m_sr[8] &= ~0x40; // 60s TIMER
+}
+
+TIMER_CALLBACK_MEMBER(saa5070_device::dial)
+{
+ m_sr[8] &= ~0x20; // DIAL GO
+}
+
+
+void saa5070_device::address_w(uint8_t data)
+{
+ m_index = data & 0x0f;
+}
+
+uint8_t saa5070_device::data_r()
+{
+ switch (m_index)
+ {
+ case 0x0: // Status Register 0
+ // b0 IBTXRDY - IBUS B transmitter ready - previous transmission complete
+ // b1 IBRXRDY - IBUS B receiver ready - data available
+ // b2 LIDCD - line instantaneous data carrier detect
+ // b3 LPERR - line receiver parity error flag
+ // b4 LFERR - line receiver framing error - received stop bit not HIGH
+ // b5 LDCD - line data carrier detected
+ // b6 LRXRDY - line receiver ready - data available
+ // b7 LTXRDY - line transmitter ready - transmit holding register empty
+ m_sr[0] = (m_sr[0] & 0x27) | m_line_uart->status();
+ break;
+ case 0x1: // Status Register 1
+ // b0
+ // b1 IARXRDY - IBUS A receiver ready - data available
+ // b2
+ // b3 TPERR - tape receiver parity error flag
+ // b4 TFERR - tape receiver framing error - received stop nit not HIGH
+ // b5 TDCD - tape data carrier detected
+ // b6 TRXRDY - tape receiver ready - data available
+ // b7 TTXRDY - tape transmitter ready - transmit holding register empty
+ m_sr[1] = (m_sr[1] & 0x27) | m_tape_uart->status();
+ break;
+ case 0x2: // Mode Register
+ break;
+ case 0x3: // Command Register
+ break;
+ case 0x4: // Line Receive Holding Register
+ m_sr[4] = m_line_uart->rx_byte();
+ break;
+ case 0x5: // Tape Receive Holding Register
+ m_sr[5] = m_tape_uart->rx_byte();
+ break;
+ case 0x6: // Port A
+ m_sr[6] = m_in_a_handler() & 0x1f;
+ break;
+ case 0x7: // Port B
+ m_sr[7] = m_in_b_handler() & 0x0f;
+ break;
+ case 0x8: // Dial Control and Timing Register
+ break;
+ case 0xa: // IBUS A Registers
+ logerror("IBUS Receiver A not implemented %02x\n", m_sr[m_index]);
+ break;
+ case 0xb: // IBUS B Registers
+ logerror("IBUS Receiver B not implemented %02x\n", m_sr[m_index]);
+ break;
+ }
+
+ return m_sr[m_index];
+}
+
+void saa5070_device::data_w(uint8_t data)
+{
+ m_sr[m_index] = data;
+
+ switch (m_index)
+ {
+ case 0x2: // Mode Register
+ {
+ // b0
+ // b1
+ // b2 nTPEN - tape parity enable command
+ // b3 TPO/nE - tape parity odd/even command
+ // b4
+ // b5 n75/1200 - baud rate selection command for line modulator and line transmit shift register
+ // b6 nLPEN - line parity enable command
+ // b7 LPO/nE - line parity odd/even command
+ int tape_parity = device_serial_interface::PARITY_NONE;
+ if (BIT(data, 2))
+ tape_parity = BIT(data, 3) ? device_serial_interface::PARITY_ODD : device_serial_interface::PARITY_EVEN;
+ m_tape_uart->set_baud_rate(1300, 1300, tape_parity);
+ int line_parity = device_serial_interface::PARITY_NONE;
+ if (BIT(data, 6))
+ line_parity = BIT(data, 7) ? device_serial_interface::PARITY_ODD : device_serial_interface::PARITY_EVEN;
+ m_line_uart->set_baud_rate(1200, BIT(data, 5) ? 1200 : 75, line_parity); // V23 Modem (1200/75 baud)
+ break;
+ }
+ case 0x3: // Command Register
+ // b0 nDLEN A/nDLIM A - three line/two line control for IBUS A receiver
+ // b1 CLCK IN/nOUT - input/output control for 62.5 kHz clock pin
+ // b2 TTXEN - tape transmitter enable
+ // b3 TRXEN - tape receiver enable
+ // b4 LDBEN - line demodulator output buffer enable
+ // b5 RESET
+ // b6 LTXEN - line transmitter and modulator enable
+ // b7 LRXEN - line receiver enable
+ m_tape_uart->tx_enable(BIT(data, 2));
+ m_tape_uart->rx_enable(BIT(data, 3));
+
+ if (BIT(data, 5))
+ {
+ reset();
+ }
+
+ m_line_uart->tx_enable(BIT(data, 6));
+ m_line_uart->rx_enable(BIT(data, 7));
+ break;
+ case 0x4: // Line Transmit Holding Register
+ m_line_uart->tx_byte(data);
+ break;
+ case 0x5: // Tape Transmit Holding Register
+ m_tape_uart->tx_byte(data);
+ break;
+ case 0x6: // Port A
+ m_out_a_handler(data & 0x1f);
+ break;
+ case 0x7: // Port B
+ m_out_b_handler(data & 0x0f);
+ break;
+ case 0x8: // Dial Control and Timing Register
+ // b0 D10
+ // b1 D11
+ // b2 D12
+ // b3 D13
+ // b4 1.5s TIMER
+ // b5 DIAL GO
+ // b6 60s TIMER
+ // b7 UK/nEUR - impulsing ratio control for UK and European standards
+ if (BIT(data, 4))
+ {
+ m_timeout_timer->adjust(attotime::from_msec(1'500));
+ }
+ if (BIT(data, 5))
+ {
+ m_sr[0] &= ~0x24; // LIDCD/LDCD
+ int digit_counter = (data & 0x0f) == 0 ? 10 : (data & 0x0f);
+ // each digit impulse = 100ms, inter digit pause = 800ms
+ m_dial_timer->adjust(attotime::from_msec((100 * digit_counter) + 800)); // TODO: digit impulse generator output on IMP line
+ }
+ if (BIT(data, 6))
+ {
+ m_timeout_timer->adjust(attotime::from_msec(60'000));
+ }
+ break;
+ case 0xb: // IBUS B Registers
+ logerror("IBUS Transmitter B not implemented %02x\n", m_sr[m_index]);
+ break;
+ }
+}
+
+uint8_t saa5070_device::read_direct(offs_t offset)
+{
+ m_index = offset & 0x0f;
+
+ return data_r();
+}
+
+void saa5070_device::write_direct(offs_t offset, uint8_t data)
+{
+ m_index = offset & 0x0f;
+
+ data_w(data);
+}