From 73e2a3f542e868149f3b0be6c1bf395283711f33 Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Wed, 1 Mar 2017 16:25:12 +1100 Subject: ti85.cpp: Convert TI-8x link port to a bus with emulated peripherals, should work with TI-82 and TI-85. * tee allows two peripherals to be connected in parallel * glinkhle is an RS232 (9600 8N1) adaptor * bitsock sends raw assert/release line signals to/from a bitbanger device * monospkr is a speaker connected between tip/ring in parallel and sleeve * stereospkr is two speakers: left across tip and sleeve, right across ring and sleeve Use glinkhle to make emulated calculators talk with cooked sockets, e.g. mame ti82 -linkport glinkhle -linkport:glinkhle:rs232 null_modem -bitb socket.127.0.0.1:2345 Use bitsock to make emulated calculators talk with cooked sockets, e.g. mame ti82 -linkport bitsock -bitb socket.127.0.0.1:2345 You can use tee to do stuff like listen to data activity for debugging purposes, e.g. mame ti82 -linkport tee -linkport:tee:a stereospkr -linkport:tee:b glinkhle -linkport:tee:b:glinkhle:rs232 null_modem -bitb socket.127.0.0.1:2345 --- src/devices/bus/ti8x/bitsocket.cpp | 89 +++++ src/devices/bus/ti8x/bitsocket.h | 55 +++ src/devices/bus/ti8x/graphlinkhle.cpp | 162 +++++++++ src/devices/bus/ti8x/graphlinkhle.h | 66 ++++ src/devices/bus/ti8x/teeconn.cpp | 113 ++++++ src/devices/bus/ti8x/teeconn.h | 45 +++ src/devices/bus/ti8x/ti8x.cpp | 622 ++++++++++++++++++++++++++++++++++ src/devices/bus/ti8x/ti8x.h | 213 ++++++++++++ src/devices/bus/ti8x/tispeaker.cpp | 117 +++++++ src/devices/bus/ti8x/tispeaker.h | 55 +++ 10 files changed, 1537 insertions(+) create mode 100644 src/devices/bus/ti8x/bitsocket.cpp create mode 100644 src/devices/bus/ti8x/bitsocket.h create mode 100644 src/devices/bus/ti8x/graphlinkhle.cpp create mode 100644 src/devices/bus/ti8x/graphlinkhle.h create mode 100644 src/devices/bus/ti8x/teeconn.cpp create mode 100644 src/devices/bus/ti8x/teeconn.h create mode 100644 src/devices/bus/ti8x/ti8x.cpp create mode 100644 src/devices/bus/ti8x/ti8x.h create mode 100644 src/devices/bus/ti8x/tispeaker.cpp create mode 100644 src/devices/bus/ti8x/tispeaker.h (limited to 'src/devices/bus/ti8x') diff --git a/src/devices/bus/ti8x/bitsocket.cpp b/src/devices/bus/ti8x/bitsocket.cpp new file mode 100644 index 00000000000..71a153b2562 --- /dev/null +++ b/src/devices/bus/ti8x/bitsocket.cpp @@ -0,0 +1,89 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb + +#include "bitsocket.h" + + +device_type const TI8X_BIT_SOCKET = device_creator; + + +namespace bus { namespace ti8x { + +namespace { + +MACHINE_CONFIG_FRAGMENT(bit_socket) + MCFG_DEVICE_ADD("stream", BITBANGER, 0) +MACHINE_CONFIG_END + +} // anonymous namespace + + +bit_socket_device::bit_socket_device( + machine_config const &mconfig, + char const *tag, + device_t *owner, + uint32_t clock) + : device_t(mconfig, TI8X_BIT_SOCKET, "TI-8x Bit Socket", tag, owner, clock, "ti8xbitsock", __FILE__) + , device_ti8x_link_port_interface(mconfig, *this) + , m_stream(*this, "stream") + , m_poll_timer(nullptr) + , m_tip_in(true) + , m_ring_in(true) +{ +} + + +machine_config_constructor bit_socket_device::device_mconfig_additions() const +{ + return MACHINE_CONFIG_NAME(bit_socket); +} + + +void bit_socket_device::device_start() +{ + m_poll_timer = timer_alloc(TIMER_ID_POLL); + + save_item(NAME(m_tip_in)); + save_item(NAME(m_ring_in)); + + m_tip_in = m_ring_in = true; + + m_poll_timer->adjust(attotime::from_hz(200000), 0, attotime::from_hz(200000)); +} + + +void bit_socket_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + switch (id) + { + case TIMER_ID_POLL: + { + u8 data; + while (m_stream->input(&data, 1)) + { + if (BIT(data, 1)) output_tip(BIT(data, 0)); + if (BIT(data, 2)) output_ring(BIT(data, 0)); + } + } + break; + + default: + break; + } +} + + +WRITE_LINE_MEMBER(bit_socket_device::input_tip) +{ + m_tip_in = bool(state); + m_stream->output((m_tip_in ? 0x01 : 0x00) | 0x02); +} + + +WRITE_LINE_MEMBER(bit_socket_device::input_ring) +{ + m_ring_in = bool(state); + m_stream->output((m_ring_in ? 0x01 : 0x00) | 0x04); +} + +} } // namespace bus::ti8x diff --git a/src/devices/bus/ti8x/bitsocket.h b/src/devices/bus/ti8x/bitsocket.h new file mode 100644 index 00000000000..f4c635c9dfc --- /dev/null +++ b/src/devices/bus/ti8x/bitsocket.h @@ -0,0 +1,55 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/* + Sends raw assert/release signals over a socket. Seriously limits + transfer rates and probably won't work if there's much latency, but it + allows communication between instances using non-standard protocols. + + bit 0 = data + bit 1 = set tip + bit 2 = set ring + */ +#ifndef MAME_DEVICES_BUS_TI8X_BITSOCKET_H +#define MAME_DEVICES_BUS_TI8X_BITSOCKET_H + +#pragma once + +#include "ti8x.h" + +#include "imagedev/bitbngr.h" + + +extern device_type const TI8X_BIT_SOCKET; + + +namespace bus { namespace ti8x { + +class bit_socket_device + : public device_t + , public device_ti8x_link_port_interface +{ +public: + bit_socket_device(machine_config const &mconfig, char const *tag, device_t *owner, uint32_t clock); + +protected: + enum + { + TIMER_ID_POLL = 1 + }; + + virtual machine_config_constructor device_mconfig_additions() const override; + virtual void device_start() override; + virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; + + virtual DECLARE_WRITE_LINE_MEMBER(input_tip) override; + virtual DECLARE_WRITE_LINE_MEMBER(input_ring) override; + +private: + required_device m_stream; + emu_timer * m_poll_timer; + bool m_tip_in, m_ring_in; +}; + +} } // namespace bus::ti8x + +#endif // MAME_DEVICES_BUS_TI8X_BITSOCKET_H \ No newline at end of file diff --git a/src/devices/bus/ti8x/graphlinkhle.cpp b/src/devices/bus/ti8x/graphlinkhle.cpp new file mode 100644 index 00000000000..bcfcbd9fcb4 --- /dev/null +++ b/src/devices/bus/ti8x/graphlinkhle.cpp @@ -0,0 +1,162 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb + +#include "graphlinkhle.h" + + +device_type const TI8X_GRAPH_LINK_HLE = device_creator; + + +namespace bus { namespace ti8x { + +namespace { + +MACHINE_CONFIG_FRAGMENT(graph_link_hle) + MCFG_RS232_PORT_ADD("rs232", default_rs232_devices, nullptr) + MCFG_RS232_RXD_HANDLER(WRITELINE(graph_link_hle_device, rx_w)) +MACHINE_CONFIG_END + +} // anonymous namespace + + +graph_link_hle_device::graph_link_hle_device( + machine_config const &mconfig, + char const *tag, + device_t *owner, + uint32_t clock) + : device_t(mconfig, TI8X_GRAPH_LINK_HLE, "TI-Graph Link (grey, HLE)", tag, owner, clock, "glinkhle", __FILE__) + , device_ti8x_link_port_byte_interface(mconfig, *this) + , device_serial_interface(mconfig, *this) + , m_serial_port(*this, "rs232") + , m_buffer() + , m_head(0) + , m_tail(0) + , m_empty(true) + , m_ready(true) +{ +} + + +void graph_link_hle_device::device_start() +{ + device_serial_interface::register_save_state(machine().save(), this); + + m_buffer = std::make_unique(BUFLEN); + + save_pointer(NAME(m_buffer.get()), BUFLEN); +} + + +void graph_link_hle_device::device_reset() +{ + set_data_frame(1, 8, PARITY_NONE, STOP_BITS_1); + set_rate(9600); + receive_register_reset(); + transmit_register_reset(); + + m_head = m_tail = 0; + m_empty = true; + m_ready = true; +} + + +void graph_link_hle_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + device_ti8x_link_port_byte_interface::device_timer(timer, id, param, ptr); + device_serial_interface::device_timer(timer, id, param, ptr); +} + + +machine_config_constructor graph_link_hle_device::device_mconfig_additions() const +{ + return MACHINE_CONFIG_NAME(graph_link_hle); +} + + +void graph_link_hle_device::byte_collision() +{ + if (m_empty) + { + m_ready = true; + } + else + { + send_byte(m_buffer[m_tail]); + m_tail = (m_tail + 1) % BUFLEN; + m_empty = m_head == m_tail; + } +} + + +void graph_link_hle_device::byte_send_timeout() +{ + if (m_empty) + { + m_ready = true; + } + else + { + send_byte(m_buffer[m_tail]); + m_tail = (m_tail + 1) % BUFLEN; + m_empty = m_head == m_tail; + } +} + + +void graph_link_hle_device::byte_receive_timeout() +{ +} + + +void graph_link_hle_device::byte_sent() +{ + if (m_empty) + { + m_ready = true; + } + else + { + send_byte(m_buffer[m_tail]); + m_tail = (m_tail + 1) % BUFLEN; + m_empty = m_head == m_tail; + } +} + + +void graph_link_hle_device::byte_received(u8 data) +{ + transmit_register_setup(data); +} + + +void graph_link_hle_device::rcv_complete() +{ + receive_register_extract(); + if (m_ready) + { + assert(m_empty); + + send_byte(get_received_char()); + m_ready = false; + } + else + { + m_buffer[m_head] = get_received_char(); + m_head = (m_head + 1) % BUFLEN; + m_empty = false; + } +} + + +void graph_link_hle_device::tra_callback() +{ + m_serial_port->write_txd(transmit_register_get_data_bit()); +} + + +void graph_link_hle_device::tra_complete() +{ + accept_byte(); +} + +} } // namespace bus::ti8x diff --git a/src/devices/bus/ti8x/graphlinkhle.h b/src/devices/bus/ti8x/graphlinkhle.h new file mode 100644 index 00000000000..8f2d0eaf242 --- /dev/null +++ b/src/devices/bus/ti8x/graphlinkhle.h @@ -0,0 +1,66 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/* + High-level emulation of a somewhat idealised TI-Graph Link "grey" + model. Translates byte-oriented TI-8x link port protocol to/from + 9600 Baud RS232. A real TI-Graph Link requires some delay between + bytes. + + The buffer is there so that if you connect two emulated calculators + together with these it has some chance of working. The receiving + calculator can't slow the sending calculator down like it would be able + to in real life, so you get inevitable overruns without the buffer. + */ +#ifndef MAME_DEVICES_BUS_TI8X_GRAPHLINKHLE_H +#define MAME_DEVICES_BUS_TI8X_GRAPHLINKHLE_H + +#pragma once + +#include "ti8x.h" + +#include "bus/rs232/rs232.h" + +#include + + +extern device_type const TI8X_GRAPH_LINK_HLE; + + +namespace bus { namespace ti8x { + +class graph_link_hle_device + : public device_t + , public device_ti8x_link_port_byte_interface + , public device_serial_interface +{ +public: + graph_link_hle_device(machine_config const &mconfig, char const *tag, device_t *owner, uint32_t clock); + +protected: + virtual machine_config_constructor device_mconfig_additions() const override; + virtual void device_start() override; + virtual void device_reset() override; + virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; + + virtual void byte_collision() override; + virtual void byte_send_timeout() override; + virtual void byte_receive_timeout() override; + virtual void byte_sent() override; + virtual void byte_received(u8 data) override; + + virtual void rcv_complete() override; + virtual void tra_callback() override; + virtual void tra_complete() override; + +private: + static constexpr unsigned BUFLEN = 1U << 16; + + required_device m_serial_port; + std::unique_ptr m_buffer; + unsigned m_head, m_tail; + bool m_empty, m_ready; +}; + +} } // namespace bus::ti8x + +#endif // MAME_DEVICES_BUS_TI8X_GRAPHLINKHLE_H \ No newline at end of file diff --git a/src/devices/bus/ti8x/teeconn.cpp b/src/devices/bus/ti8x/teeconn.cpp new file mode 100644 index 00000000000..d887f1fd85a --- /dev/null +++ b/src/devices/bus/ti8x/teeconn.cpp @@ -0,0 +1,113 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb + +#include "teeconn.h" + + +device_type const TI8X_TEE_CONNECTOR = device_creator; + + +namespace bus { namespace ti8x { + +namespace { + +MACHINE_CONFIG_FRAGMENT(tee_connector) + MCFG_TI8X_LINK_PORT_ADD("a", default_ti8x_link_devices, nullptr) + MCFG_TI8X_LINK_TIP_HANDLER(WRITELINE(tee_connector_device, tip_a_w)) + MCFG_TI8X_LINK_RING_HANDLER(WRITELINE(tee_connector_device, ring_a_w)) + + MCFG_TI8X_LINK_PORT_ADD("b", default_ti8x_link_devices, nullptr) + MCFG_TI8X_LINK_TIP_HANDLER(WRITELINE(tee_connector_device, tip_b_w)) + MCFG_TI8X_LINK_RING_HANDLER(WRITELINE(tee_connector_device, ring_b_w)) +MACHINE_CONFIG_END + +} // anonymous namespace + + +tee_connector_device::tee_connector_device( + machine_config const &mconfig, + char const *tag, + device_t *owner, + uint32_t clock) + : device_t(mconfig, TI8X_TEE_CONNECTOR, "T-connector", tag, owner, clock, "ti8xtconn", __FILE__) + , device_ti8x_link_port_interface(mconfig, *this) + , m_port_a(*this, "a") + , m_port_b(*this, "b") + , m_tip_host(true) + , m_tip_a(true) + , m_tip_b(true) + , m_ring_host(true) + , m_ring_a(true) + , m_ring_b(true) +{ +} + + +WRITE_LINE_MEMBER(tee_connector_device::tip_a_w) +{ + m_tip_a = bool(state); + output_tip((m_tip_a && m_tip_b) ? 1 : 0); + m_port_b->tip_w((m_tip_host && m_tip_a) ? 1 : 0); +} + + +WRITE_LINE_MEMBER(tee_connector_device::ring_a_w) +{ + m_ring_a = bool(state); + output_ring((m_ring_a && m_ring_b) ? 1 : 0); + m_port_b->ring_w((m_ring_host && m_ring_a) ? 1 : 0); +} + + +WRITE_LINE_MEMBER(tee_connector_device::tip_b_w) +{ + m_tip_b = bool(state); + output_tip((m_tip_a && m_tip_b) ? 1 : 0); + m_port_a->tip_w((m_tip_host && m_tip_b) ? 1 : 0); +} + + +WRITE_LINE_MEMBER(tee_connector_device::ring_b_w) +{ + m_ring_b = bool(state); + output_ring((m_ring_a && m_ring_b) ? 1 : 0); + m_port_a->ring_w((m_ring_host && m_ring_b) ? 1 : 0); +} + + +machine_config_constructor tee_connector_device::device_mconfig_additions() const +{ + return MACHINE_CONFIG_NAME(tee_connector); +} + + +void tee_connector_device::device_start() +{ + save_item(NAME(m_tip_host)); + save_item(NAME(m_tip_a)); + save_item(NAME(m_tip_b)); + save_item(NAME(m_ring_host)); + save_item(NAME(m_ring_a)); + save_item(NAME(m_ring_b)); + + m_tip_host = m_tip_a = m_tip_b = true; + m_ring_host = m_ring_a = m_ring_b = true; +} + + +WRITE_LINE_MEMBER(tee_connector_device::input_tip) +{ + m_tip_host = bool(state); + m_port_a->tip_w((m_tip_host && m_tip_b) ? 1 : 0); + m_port_b->tip_w((m_tip_host && m_tip_a) ? 1 : 0); +} + + +WRITE_LINE_MEMBER(tee_connector_device::input_ring) +{ + m_ring_host = bool(state); + m_port_a->ring_w((m_ring_host && m_ring_b) ? 1 : 0); + m_port_b->ring_w((m_ring_host && m_ring_a) ? 1 : 0); +} + +} } // namespace bus::ti8x diff --git a/src/devices/bus/ti8x/teeconn.h b/src/devices/bus/ti8x/teeconn.h new file mode 100644 index 00000000000..3c6beb31b46 --- /dev/null +++ b/src/devices/bus/ti8x/teeconn.h @@ -0,0 +1,45 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/* + A T-connector, strangely enough. + */ +#ifndef MAME_DEVICES_BUS_TI8X_TEECONN_H +#define MAME_DEVICES_BUS_TI8X_TEECONN_H + +#pragma once + +#include "ti8x.h" + + +extern device_type const TI8X_TEE_CONNECTOR; + + +namespace bus { namespace ti8x { + +class tee_connector_device : public device_t, public device_ti8x_link_port_interface +{ +public: + tee_connector_device(machine_config const &mconfig, char const *tag, device_t *owner, uint32_t clock); + + DECLARE_WRITE_LINE_MEMBER(tip_a_w); + DECLARE_WRITE_LINE_MEMBER(ring_a_w); + DECLARE_WRITE_LINE_MEMBER(tip_b_w); + DECLARE_WRITE_LINE_MEMBER(ring_b_w); + +protected: + virtual machine_config_constructor device_mconfig_additions() const override; + virtual void device_start() override; + + virtual DECLARE_WRITE_LINE_MEMBER(input_tip) override; + virtual DECLARE_WRITE_LINE_MEMBER(input_ring) override; + + required_device m_port_a; + required_device m_port_b; + + bool m_tip_host, m_tip_a, m_tip_b; + bool m_ring_host, m_ring_a, m_ring_b; +}; + +} } // namespace bus::ti8x + +#endif // MAME_DEVICES_BUS_TI8X_TEECONN_H diff --git a/src/devices/bus/ti8x/ti8x.cpp b/src/devices/bus/ti8x/ti8x.cpp new file mode 100644 index 00000000000..0fa99837288 --- /dev/null +++ b/src/devices/bus/ti8x/ti8x.cpp @@ -0,0 +1,622 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb + +#include "emu.h" +#include "ti8x.h" + +#define LOG_GENERAL (1U << 0) +#define LOG_BITPROTO (1U << 1) +#define LOG_BYTEPROTO (1U << 2) + +//#define VERBOSE (LOG_GENERAL | LOG_BITPROTO | LOG_BYTEPROTO) +#define LOG_OUTPUT_FUNC device().logerror +#include "logmacro.h" + +#define LOGBITPROTO(...) LOGMASKED(LOG_BITPROTO, __VA_ARGS__) +#define LOGBYTEPROTO(...) LOGMASKED(LOG_BYTEPROTO, __VA_ARGS__) + + + +device_type const TI8X_LINK_PORT = device_creator; + + +ti8x_link_port_device::ti8x_link_port_device( + machine_config const &mconfig, + char const *tag, + device_t *owner, + uint32_t clock) + : ti8x_link_port_device(mconfig, TI8X_LINK_PORT, "TI-8x Link Port", tag, owner, clock, "ti8xlink", __FILE__) +{ +} + + +ti8x_link_port_device::ti8x_link_port_device( + machine_config const &mconfig, + device_type type, + char const *name, + char const *tag, + device_t *owner, + uint32_t clock, + char const *shortname, + char const *source) + : device_t(mconfig, type, name, tag, owner, clock, shortname, source) + , device_slot_interface(mconfig, *this) + , m_tip_handler(*this) + , m_ring_handler(*this) + , m_dev(nullptr) + , m_tip_in(true) + , m_tip_out(true) + , m_ring_in(true) + , m_ring_out(true) +{ +} + + +WRITE_LINE_MEMBER(ti8x_link_port_device::tip_w) +{ + if (bool(state) != m_tip_out) + { + m_tip_out = bool(state); + if (m_dev) + m_dev->input_tip(m_tip_out ? 1 : 0); + } +} + + +WRITE_LINE_MEMBER(ti8x_link_port_device::ring_w) +{ + if (bool(state) != m_ring_out) + { + m_ring_out = bool(state); + if (m_dev) + m_dev->input_ring(m_ring_out ? 1 : 0); + } +} + + +void ti8x_link_port_device::device_start() +{ + m_tip_handler.resolve_safe(); + m_ring_handler.resolve_safe(); + + save_item(NAME(m_tip_in)); + save_item(NAME(m_tip_out)); + save_item(NAME(m_ring_in)); + save_item(NAME(m_ring_out)); + + m_tip_in = m_tip_out = true; + m_ring_in = m_ring_out = true; +} + + +void ti8x_link_port_device::device_config_complete() +{ + m_dev = dynamic_cast(get_card_device()); +} + + + +device_ti8x_link_port_interface::device_ti8x_link_port_interface( + machine_config const &mconfig, + device_t &device) + : device_slot_card_interface(mconfig, device) + , m_port(dynamic_cast(device.owner())) +{ +} + + + +device_ti8x_link_port_bit_interface::device_ti8x_link_port_bit_interface( + machine_config const &mconfig, + device_t &device) + : device_ti8x_link_port_interface(mconfig, device) + , m_error_timer(nullptr) + , m_bit_phase(IDLE) + , m_tx_bit_buffer(EMPTY) + , m_tip_in(true) + , m_ring_in(true) +{ +} + + +void device_ti8x_link_port_bit_interface::interface_pre_start() +{ + device_ti8x_link_port_interface::interface_pre_start(); + + m_error_timer = device().timer_alloc(TIMER_ID_BIT_TIMEOUT); + + device().save_item(NAME(m_bit_phase)); + device().save_item(NAME(m_tx_bit_buffer)); + device().save_item(NAME(m_tip_in)); + device().save_item(NAME(m_ring_in)); + + m_bit_phase = IDLE; + m_tx_bit_buffer = EMPTY; + m_tip_in = m_ring_in = true; +} + + +void device_ti8x_link_port_bit_interface::interface_pre_reset() +{ + device_ti8x_link_port_interface::interface_pre_reset(); + + m_error_timer->reset(); + m_bit_phase = (m_tip_in && m_ring_in) ? IDLE : WAIT_IDLE; + m_tx_bit_buffer = EMPTY; + + output_tip(1); + output_ring(1); +} + + +void device_ti8x_link_port_bit_interface::device_timer( + emu_timer &timer, + device_timer_id id, + int param, void *ptr) +{ + switch (id) + { + case TIMER_ID_BIT_TIMEOUT: + switch (m_bit_phase) + { + // something very bad happened (heap smash?) + case IDLE: + case HOLD_0: + case HOLD_1: + default: + throw false; + + // receive timeout + case ACK_0: + case ACK_1: + LOGBITPROTO("timeout acknowledging %d bit\n", (ACK_0 == m_bit_phase) ? 0 : 1); + output_tip(1); + output_ring(1); + if (m_tip_in && m_ring_in) + { + check_tx_bit_buffer(); + } + else + { + LOGBITPROTO("waiting for bus idle\n"); + m_error_timer->reset((EMPTY == m_tx_bit_buffer) ? attotime::never : attotime(1, 0)); // TODO: configurable timeout + m_bit_phase = WAIT_IDLE; + } + bit_receive_timeout(); + break; + + // send timeout: + case WAIT_IDLE: + assert(EMPTY != m_tx_bit_buffer); + case WAIT_ACK_0: + case WAIT_ACK_1: + case WAIT_REL_0: + case WAIT_REL_1: + LOGBITPROTO("timeout sending bit\n"); + m_error_timer->reset(); + m_bit_phase = (m_tip_in && m_ring_in) ? IDLE : WAIT_IDLE; + m_tx_bit_buffer = EMPTY; + output_tip(1); + output_ring(1); + bit_send_timeout(); + break; + } + break; + + default: + break; + } +} + + +void device_ti8x_link_port_bit_interface::send_bit(bool data) +{ + LOGBITPROTO("queue %d bit\n", data ? 1 : 0); + + if (EMPTY != m_tx_bit_buffer) + device().logerror("device_ti8x_link_port_bit_interface: warning: transmit buffer overrun\n"); + + m_tx_bit_buffer = data ? PENDING_1 : PENDING_0; + if (IDLE == m_bit_phase) + check_tx_bit_buffer(); + else if (WAIT_IDLE == m_bit_phase) + m_error_timer->reset(attotime(1, 0)); // TODO: configurable timeout +} + + +void device_ti8x_link_port_bit_interface::accept_bit() +{ + switch (m_bit_phase) + { + // can't accept a bit that isn't being held + case IDLE: + case WAIT_ACK_0: + case WAIT_ACK_1: + case WAIT_REL_0: + case WAIT_REL_1: + case ACK_0: + case ACK_1: + case WAIT_IDLE: + fatalerror("device_ti8x_link_port_bit_interface: attempt to accept bit when not holding"); + break; + + // release the acknowledgement - if the ring doesn't rise we've lost sync + case HOLD_0: + assert(m_tip_in); + + output_ring(1); + if (m_ring_in) + { + LOGBITPROTO("accepted 0 bit\n"); + check_tx_bit_buffer(); + } + else + { + LOGBITPROTO("accepted 0 bit, ring low (collision) - waiting for bus idle\n"); + m_error_timer->reset((EMPTY == m_tx_bit_buffer) ? attotime::never : attotime(1, 0)); // TODO: configurable timeout + m_bit_phase = WAIT_IDLE; + bit_collision(); + } + break; + + // release the acknowledgement - if the tip doesn't rise we've lost sync + case HOLD_1: + assert(m_ring_in); + + output_tip(1); + if (m_tip_in) + { + LOGBITPROTO("accepted 1 bit\n"); + check_tx_bit_buffer(); + } + else + { + LOGBITPROTO("accepted 1 bit, tip low (collision) - waiting for bus idle\n"); + m_error_timer->reset((EMPTY == m_tx_bit_buffer) ? attotime::never : attotime(1, 0)); // TODO: configurable timeout + m_bit_phase = WAIT_IDLE; + bit_collision(); + } + break; + + // something very bad happened (heap smash?) + default: + throw false; + } +} + + +WRITE_LINE_MEMBER(device_ti8x_link_port_bit_interface::input_tip) +{ + m_tip_in = bool(state); + switch (m_bit_phase) + { + // if tip falls while idle, it's the beginning of an incoming 0 + case IDLE: + if (!m_tip_in) + { + LOGBITPROTO("falling edge on tip, acknowledging 0 bit\n"); + m_error_timer->reset(attotime(1, 0)); // TODO: configurable timeout + m_bit_phase = ACK_0; + output_ring(0); + } + break; + + // we're driving tip low in this state, ignore it + case WAIT_ACK_0: + case ACK_1: + case HOLD_1: + break; + + // tip must fall to acknowledge outgoing 1 + case WAIT_ACK_1: + if (!m_tip_in) + { + LOGBITPROTO("falling edge on tip, 1 bit acknowledged, confirming\n"); + m_error_timer->reset(attotime(1, 0)); // TODO: configurable timeout + m_bit_phase = WAIT_REL_1; + output_ring(1); + } + break; + + // if tip falls now, we've lost sync + case WAIT_REL_0: + case HOLD_0: + if (!m_tip_in) + { + LOGBITPROTO("falling edge on tip, lost sync, waiting for bus idle\n"); + m_error_timer->reset((EMPTY == m_tx_bit_buffer) ? attotime::never : attotime(1, 0)); // TODO: configurable timeout + m_bit_phase = WAIT_IDLE; + output_ring(1); + bit_collision(); + } + break; + + // tip must rise to complete outgoing 1 sequence + case WAIT_REL_1: + if (m_tip_in) + { + assert(!m_ring_in); + + LOGBITPROTO("rising edge on tip, 1 bit sent\n"); + check_tx_bit_buffer(); + bit_sent(); + } + break; + + // tip must rise to accept our acknowledgement + case ACK_0: + if (m_tip_in) + { + LOGBITPROTO("rising edge on tip, 0 bit acknowledge confirmed, holding\n"); + m_error_timer->reset(); + m_bit_phase = HOLD_0; + bit_received(false); + } + break; + + // if the bus is available, check for bit to send + case WAIT_IDLE: + if (m_tip_in && m_ring_in) + { + LOGBITPROTO("rising edge on tip, bus idle detected\n"); + check_tx_bit_buffer(); + } + break; + + // something very bad happened (heap smash?) + default: + throw false; + } +} + + +WRITE_LINE_MEMBER(device_ti8x_link_port_bit_interface::input_ring) +{ + m_ring_in = bool(state); + switch (m_bit_phase) + { + // if ring falls while idle, it's the beginning of an incoming 1 + case IDLE: + if (!m_ring_in) + { + LOGBITPROTO("falling edge on ring, acknowledging 1 bit\n"); + m_error_timer->reset(attotime(1, 0)); // TODO: configurable timeout + m_bit_phase = ACK_1; + output_tip(0); + } + break; + + // ring must fall to acknowledge outgoing 0 + case WAIT_ACK_0: + if (!m_ring_in) + { + LOGBITPROTO("falling edge on ring, 0 bit acknowledged, confirming\n"); + m_error_timer->reset(attotime(1, 0)); // TODO: configurable timeout + m_bit_phase = WAIT_REL_0; + output_tip(1); + } + break; + + // we're driving ring low in this state, ignore it + case WAIT_ACK_1: + case ACK_0: + case HOLD_0: + break; + + // ring must rise to complete outgoing 0 sequence + case WAIT_REL_0: + if (m_ring_in) + { + assert(!m_tip_in); + + LOGBITPROTO("rising edge on ring, 0 bit sent\n"); + check_tx_bit_buffer(); + bit_sent(); + } + break; + + // if ring falls now, we've lost sync + case WAIT_REL_1: + case HOLD_1: + if (!m_ring_in) + { + LOGBITPROTO("falling edge on ring, lost sync, waiting for bus idle\n"); + m_error_timer->reset((EMPTY == m_tx_bit_buffer) ? attotime::never : attotime(1, 0)); // TODO: configurable timeout + m_bit_phase = WAIT_IDLE; + output_tip(1); + bit_collision(); + } + break; + + // ring must rise to accept our acknowledgement + case ACK_1: + if (m_ring_in) + { + LOGBITPROTO("rising edge on ring, 1 bit acknowledge confirmed, holding\n"); + m_error_timer->reset(); + m_bit_phase = HOLD_1; + bit_received(true); + } + break; + + // if the bus is available, check for bit to send + case WAIT_IDLE: + if (m_tip_in && m_ring_in) + { + LOGBITPROTO("rising edge on tip, bus idle detected\n"); + check_tx_bit_buffer(); + } + break; + + // something very bad happened (heap smash?) + default: + throw false; + } +} + + +void device_ti8x_link_port_bit_interface::check_tx_bit_buffer() +{ + assert(m_tip_in); + assert(m_ring_in); + + switch (m_tx_bit_buffer) + { + // nothing to do + case EMPTY: + LOGBITPROTO("no pending bit, entering idle state\n"); + m_error_timer->reset(); + m_bit_phase = IDLE; + break; + + // pull tip low and wait for acknowledgement + case PENDING_0: + LOGBITPROTO("sending 0 bit, pulling tip low\n"); + m_error_timer->reset(attotime(1, 0)); // TODO: configurable timeout + m_bit_phase = WAIT_ACK_0; + m_tx_bit_buffer = EMPTY; + output_tip(0); + break; + + // pull ring low and wait for acknowledgement + case PENDING_1: + LOGBITPROTO("sending 1 bit, pulling ring low\n"); + m_error_timer->reset(attotime(1, 0)); // TODO: configurable timeout + m_bit_phase = WAIT_ACK_1; + m_tx_bit_buffer = EMPTY; + output_ring(0); + break; + + // something very bad happened (heap smash?) + default: + throw false; + } +} + + + +device_ti8x_link_port_byte_interface::device_ti8x_link_port_byte_interface( + machine_config const &mconfig, + device_t &device) + : device_ti8x_link_port_bit_interface(mconfig, device) + , m_tx_byte_buffer(0U) + , m_rx_byte_buffer(0U) +{ +} + + +void device_ti8x_link_port_byte_interface::interface_pre_start() +{ + device_ti8x_link_port_bit_interface::interface_pre_start(); + + device().save_item(NAME(m_tx_byte_buffer)); + device().save_item(NAME(m_rx_byte_buffer)); + + m_tx_byte_buffer = m_rx_byte_buffer = 0U; +} + + +void device_ti8x_link_port_byte_interface::interface_pre_reset() +{ + device_ti8x_link_port_bit_interface::interface_pre_reset(); + + m_tx_byte_buffer = m_rx_byte_buffer = 0U; +} + + +void device_ti8x_link_port_byte_interface::send_byte(u8 data) +{ + if (m_tx_byte_buffer) + device().logerror("device_ti8x_link_port_byte_interface: warning: transmit buffer overrun\n"); + + LOGBYTEPROTO("sending byte 0x%02X\n", data); + m_tx_byte_buffer = 0x0080 | u16(data >> 1); + send_bit(BIT(data, 0)); +} + + +void device_ti8x_link_port_byte_interface::accept_byte() +{ + assert(BIT(m_rx_byte_buffer, 8)); + + LOGBYTEPROTO("accepting final bit of byte\n"); + m_rx_byte_buffer = 0U; + accept_bit(); +} + + +void device_ti8x_link_port_byte_interface::bit_collision() +{ + LOGBYTEPROTO("bit collection, clearing byte buffers\n"); + m_tx_byte_buffer = m_rx_byte_buffer = 0U; + byte_collision(); +} + + +void device_ti8x_link_port_byte_interface::bit_send_timeout() +{ + LOGBYTEPROTO("bit send timeout, clearing send byte buffer\n"); + m_tx_byte_buffer = 0U; + byte_send_timeout(); +} + + +void device_ti8x_link_port_byte_interface::bit_receive_timeout() +{ + LOGBYTEPROTO("bit receive timeout, clearing receive byte buffer\n"); + m_rx_byte_buffer = 0U; + byte_receive_timeout(); +} + + +void device_ti8x_link_port_byte_interface::bit_sent() +{ + assert(m_tx_byte_buffer); + + bool const data(BIT(m_tx_byte_buffer, 0)); + if (m_tx_byte_buffer >>= 1) + { + LOGBYTEPROTO("bit sent, sending next bit of byte\n"); + send_bit(data); + } + else + { + assert(data); + + LOGBYTEPROTO("final bit of byte sent\n"); + byte_sent(); + } +} + + +void device_ti8x_link_port_byte_interface::bit_received(bool data) +{ + assert(!BIT(m_rx_byte_buffer, 8)); + + m_rx_byte_buffer = (!m_rx_byte_buffer ? 0x8000 : (m_rx_byte_buffer >> 1)) | (data ? 0x0080U : 0x0000U); + if (BIT(m_rx_byte_buffer, 8)) + { + LOGBYTEPROTO("received final bit of byte 0x%02X\n", u8(m_rx_byte_buffer)); + byte_received(u8(m_rx_byte_buffer)); + } + else + { + LOGBYTEPROTO("bit received, accepting\n"); + accept_bit(); + } +} + + + +#include "bitsocket.h" +#include "graphlinkhle.h" +#include "teeconn.h" +#include "tispeaker.h" + +SLOT_INTERFACE_START(default_ti8x_link_devices) + SLOT_INTERFACE("bitsock", TI8X_BIT_SOCKET) + SLOT_INTERFACE("glinkhle", TI8X_GRAPH_LINK_HLE) + SLOT_INTERFACE("tee", TI8X_TEE_CONNECTOR) + SLOT_INTERFACE("monospkr", TI8X_SPEAKER_MONO) + SLOT_INTERFACE("stereospkr", TI8X_SPEAKER_STEREO) +SLOT_INTERFACE_END diff --git a/src/devices/bus/ti8x/ti8x.h b/src/devices/bus/ti8x/ti8x.h new file mode 100644 index 00000000000..fdb998cb66c --- /dev/null +++ b/src/devices/bus/ti8x/ti8x.h @@ -0,0 +1,213 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/* + TI-8x calculator link port. + + 2.5mm TRS connector on each device, tip and ring both used as I/O + lines. Each device has passive pull-ups and software-controlled active + pull-downs. In TI products, the tip is connected to a red wire and the + ring is connected to a white wire. + + The link ports are only intended to allow linking two devices together, + but there is open source software implementing multi-master protocols + including I2C. The number of devices that can be connected together is + limited in practice by the fact that every device provides pull-ups and + there's a limit to the amount of current any device can sink. + + TI's link port protocol uses 8-bit bytes transmitted LSB first with no + markers for beginning/end of a byte or parity. To transfer a bit, the + transmitting device pulls down a line (tip for 0, ring for 1), waits + for the receiving device to pull down the other line, releases its + line, and waits for the receiver to release the other line. This + ensures software-based implementations don't drop bits. The 6 MHz + Z80-based calculators can manage up to about 50 kbps. + + In a TI-82, each line is tied to the supply rail via a 10kΩ resistor + in series with a signal diode, and can be pulled low by an NPN + transistor in series with a 470Ω resistor. + + This bus implementation works with logic levels (1 = pulled up, + 0 = driven down). The port device just gives you the drive level from + the opposite side of the port which you need to mix with your device's + output. This makes implementing things like the tee connector easier. + */ + +#ifndef MAME_DEVICES_BUS_TI8X_TI8X_H +#define MAME_DEVICES_BUS_TI8X_TI8X_H + +#pragma once + + + +extern device_type const TI8X_LINK_PORT; + + +#define MCFG_TI8X_LINK_PORT_ADD(tag, slot_intf, def_slot) \ + MCFG_DEVICE_ADD(tag, TI8X_LINK_PORT, 0) \ + MCFG_DEVICE_SLOT_INTERFACE(slot_intf, def_slot, false) + +#define MCFG_TI8X_LINK_TIP_HANDLER(cb) \ + devcb = &ti8x_link_port_device::set_tip_handler(*device, DEVCB_##cb); + +#define MCFG_TI8X_LINK_RING_HANDLER(cb) \ + devcb = &ti8x_link_port_device::set_ring_handler(*device, DEVCB_##cb); + + +class device_ti8x_link_port_interface; + + +class ti8x_link_port_device : public device_t, public device_slot_interface +{ +public: + ti8x_link_port_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock); + + // static configuration helpers + template static devcb_base &set_tip_handler(device_t &device, Object &&cb) + { return downcast(device).m_tip_handler.set_callback(std::forward(cb)); } + template static devcb_base &set_ring_handler(device_t &device, Object &&cb) + { return downcast(device).m_ring_handler.set_callback(std::forward(cb)); } + + DECLARE_WRITE_LINE_MEMBER(tip_w); + DECLARE_WRITE_LINE_MEMBER(ring_w); + + DECLARE_READ_LINE_MEMBER(tip_r) { return m_tip_in ? 1 : 0; } + DECLARE_READ_LINE_MEMBER(ring_r) { return m_ring_in ? 1 : 0; } + +protected: + ti8x_link_port_device( + machine_config const &mconfig, + device_type type, + char const *name, + char const *tag, + device_t *owner, + u32 clock, + char const *shortname, + char const *source); + + virtual void device_start() override; + virtual void device_config_complete() override; + + devcb_write_line m_tip_handler; + devcb_write_line m_ring_handler; + +private: + friend class device_ti8x_link_port_interface; + + device_ti8x_link_port_interface *m_dev; + + bool m_tip_in, m_tip_out, m_ring_in, m_ring_out; +}; + + +class device_ti8x_link_port_interface : public device_slot_card_interface +{ +public: + DECLARE_WRITE_LINE_MEMBER(output_tip) + { if (bool(state) != m_port->m_tip_in) m_port->m_tip_handler((m_port->m_tip_in = bool(state)) ? 1 : 0); } + DECLARE_WRITE_LINE_MEMBER(output_ring) + { if (bool(state) != m_port->m_ring_in) m_port->m_ring_handler((m_port->m_ring_in = bool(state)) ? 1 : 0); } + +protected: + device_ti8x_link_port_interface(machine_config const &mconfig, device_t &device); + + ti8x_link_port_device &port() { return *m_port; } + +private: + virtual DECLARE_WRITE_LINE_MEMBER(input_tip) = 0; + virtual DECLARE_WRITE_LINE_MEMBER(input_ring) = 0; + + friend class ti8x_link_port_device; + + ti8x_link_port_device *m_port; +}; + + +class device_ti8x_link_port_bit_interface : public device_ti8x_link_port_interface +{ +protected: + device_ti8x_link_port_bit_interface(machine_config const &mconfig, device_t &device); + + virtual void interface_pre_start() override; + virtual void interface_pre_reset() override; + + virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr); + + void send_bit(bool data); + void accept_bit(); + +private: + enum + { + TIMER_ID_BIT_TIMEOUT = 20000 // ensure this doesn't clash with device_serial_interface + }; + + enum bit_phase + { + IDLE, + WAIT_ACK_0, + WAIT_ACK_1, + WAIT_REL_0, + WAIT_REL_1, + ACK_0, + ACK_1, + HOLD_0, + HOLD_1, + WAIT_IDLE + }; + + enum bit_buffer + { + EMPTY, + PENDING_0, + PENDING_1 + }; + + virtual DECLARE_WRITE_LINE_MEMBER(input_tip) override; + virtual DECLARE_WRITE_LINE_MEMBER(input_ring) override; + + virtual void bit_collision() = 0; + virtual void bit_send_timeout() = 0; + virtual void bit_receive_timeout() = 0; + virtual void bit_sent() = 0; + virtual void bit_received(bool data) = 0; + + void check_tx_bit_buffer(); + + emu_timer * m_error_timer; + u8 m_bit_phase; + u8 m_tx_bit_buffer; + bool m_tip_in, m_ring_in; +}; + + +class device_ti8x_link_port_byte_interface : public device_ti8x_link_port_bit_interface +{ +protected: + device_ti8x_link_port_byte_interface(machine_config const &mconfig, device_t &device); + + virtual void interface_pre_start() override; + virtual void interface_pre_reset() override; + + void send_byte(u8 data); + void accept_byte(); + +private: + virtual void bit_collision() override; + virtual void bit_send_timeout() override; + virtual void bit_receive_timeout() override; + virtual void bit_sent() override; + virtual void bit_received(bool data) override; + + virtual void byte_collision() = 0; + virtual void byte_send_timeout() = 0; + virtual void byte_receive_timeout() = 0; + virtual void byte_sent() = 0; + virtual void byte_received(u8 data) = 0; + + u16 m_tx_byte_buffer, m_rx_byte_buffer; +}; + + +SLOT_INTERFACE_EXTERN( default_ti8x_link_devices ); + +#endif // MAME_DEVICES_BUS_TI8X_TI8X_H diff --git a/src/devices/bus/ti8x/tispeaker.cpp b/src/devices/bus/ti8x/tispeaker.cpp new file mode 100644 index 00000000000..c62ae827573 --- /dev/null +++ b/src/devices/bus/ti8x/tispeaker.cpp @@ -0,0 +1,117 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb + +#include "tispeaker.h" + +#include "speaker.h" + + +device_type const TI8X_SPEAKER_STEREO = device_creator; +device_type const TI8X_SPEAKER_MONO = device_creator; + + +namespace bus { namespace ti8x { + +namespace { + +MACHINE_CONFIG_FRAGMENT(ti_speaker_stereo) + MCFG_SPEAKER_STANDARD_STEREO("outl", "outr") + + MCFG_SOUND_ADD("lspkr", SPEAKER_SOUND, 0) + MCFG_SOUND_ROUTE(ALL_OUTPUTS, "outl", 0.50) + + MCFG_SOUND_ADD("rspkr", SPEAKER_SOUND, 0) + MCFG_SOUND_ROUTE(ALL_OUTPUTS, "outr", 0.50) +MACHINE_CONFIG_END + + +MACHINE_CONFIG_FRAGMENT(ti_speaker_mono) + MCFG_SPEAKER_STANDARD_MONO("mono") + + MCFG_SOUND_ADD("spkr", SPEAKER_SOUND, 0) + MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.50) +MACHINE_CONFIG_END + +} // anonymous namespace + + +stereo_speaker_device::stereo_speaker_device( + machine_config const &mconfig, + char const *tag, + device_t *owner, + uint32_t clock) + : device_t(mconfig, TI8X_SPEAKER_STEREO, "TI-8x Speaker (Stereo)", tag, owner, clock, "ti8xstspkr", __FILE__) + , device_ti8x_link_port_interface(mconfig, *this) + , m_left_speaker(*this, "lspkr") + , m_right_speaker(*this, "rspkr") +{ +} + + +machine_config_constructor stereo_speaker_device::device_mconfig_additions() const +{ + return MACHINE_CONFIG_NAME(ti_speaker_stereo); +} + + +void stereo_speaker_device::device_start() +{ +} + + +WRITE_LINE_MEMBER(stereo_speaker_device::input_tip) +{ + m_left_speaker->level_w(state); +} + + +WRITE_LINE_MEMBER(stereo_speaker_device::input_ring) +{ + m_right_speaker->level_w(state); +} + + + +mono_speaker_device::mono_speaker_device( + machine_config const &mconfig, + char const *tag, + device_t *owner, + uint32_t clock) + : device_t(mconfig, TI8X_SPEAKER_MONO, "TI-8x Speaker (Mono)", tag, owner, clock, "ti8xmspkr", __FILE__) + , device_ti8x_link_port_interface(mconfig, *this) + , m_speaker(*this, "spkr") + , m_tip_state(true) + , m_ring_state(true) +{ +} + + +machine_config_constructor mono_speaker_device::device_mconfig_additions() const +{ + return MACHINE_CONFIG_NAME(ti_speaker_mono); +} + + +void mono_speaker_device::device_start() +{ + save_item(NAME(m_tip_state)); + save_item(NAME(m_ring_state)); + + m_tip_state = m_ring_state = true; +} + + +WRITE_LINE_MEMBER(mono_speaker_device::input_tip) +{ + m_tip_state = bool(state); + m_speaker->level_w((m_tip_state || m_ring_state) ? 1 : 0); +} + + +WRITE_LINE_MEMBER(mono_speaker_device::input_ring) +{ + m_ring_state = bool(state); + m_speaker->level_w((m_tip_state || m_ring_state) ? 1 : 0); +} + +} } // namespace bus::ti8x diff --git a/src/devices/bus/ti8x/tispeaker.h b/src/devices/bus/ti8x/tispeaker.h new file mode 100644 index 00000000000..308b316b3bf --- /dev/null +++ b/src/devices/bus/ti8x/tispeaker.h @@ -0,0 +1,55 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +#ifndef MAME_DEVICES_BUS_TI8X_TISPEAKER_H +#define MAME_DEVICES_BUS_TI8X_TISPEAKER_H + +#pragma once + +#include "ti8x.h" +#include "sound/spkrdev.h" + + +extern device_type const TI8X_SPEAKER_STEREO; +extern device_type const TI8X_SPEAKER_MONO; + + +namespace bus { namespace ti8x { + +class stereo_speaker_device : public device_t, public device_ti8x_link_port_interface +{ +public: + stereo_speaker_device(machine_config const &mconfig, char const *tag, device_t *owner, uint32_t clock); + +protected: + virtual machine_config_constructor device_mconfig_additions() const override; + virtual void device_start() override; + + virtual DECLARE_WRITE_LINE_MEMBER(input_tip) override; + virtual DECLARE_WRITE_LINE_MEMBER(input_ring) override; + + required_device m_left_speaker; + required_device m_right_speaker; +}; + + +class mono_speaker_device : public device_t, public device_ti8x_link_port_interface +{ +public: + mono_speaker_device(machine_config const &mconfig, char const *tag, device_t *owner, uint32_t clock); + +protected: + virtual machine_config_constructor device_mconfig_additions() const override; + virtual void device_start() override; + + virtual DECLARE_WRITE_LINE_MEMBER(input_tip) override; + virtual DECLARE_WRITE_LINE_MEMBER(input_ring) override; + + required_device m_speaker; + +private: + bool m_tip_state, m_ring_state; +}; + +} } // namespace bus::ti8x + +#endif // MAME_DEVICES_BUS_TI8X_TISPEAKER_H -- cgit v1.2.3-70-g09d2