diff --git a/hash/lnux4004.xml b/hash/lnux4004.xml
new file mode 100644
index 0000000000000..1bf677eb7b547
--- /dev/null
+++ b/hash/lnux4004.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+ uMIPS Linux 4.4.292+
+ 2024
+ Dmitry Grinberg
+
+
+
+
+
+
+
+
diff --git a/scripts/src/machine.lua b/scripts/src/machine.lua
index 19ed1d347b54c..053caa5612909 100644
--- a/scripts/src/machine.lua
+++ b/scripts/src/machine.lua
@@ -3320,6 +3320,17 @@ if (MACHINES["SAA5070"]~=null) then
}
end
+---------------------------------------------------
+--
+--@src/devices/machine/sc16is741.h,MACHINES["SC16IS741"] = true
+---------------------------------------------------
+if (MACHINES["SC16IS741"]~=null) then
+ files {
+ MAME_DIR .. "src/devices/machine/sc16is741.cpp",
+ MAME_DIR .. "src/devices/machine/sc16is741.h",
+ }
+end
+
---------------------------------------------------
--
--@src/devices/machine/scc66470.h,MACHINES["SCC66470"] = true
diff --git a/src/devices/machine/sc16is741.cpp b/src/devices/machine/sc16is741.cpp
new file mode 100644
index 0000000000000..c0a92d8894344
--- /dev/null
+++ b/src/devices/machine/sc16is741.cpp
@@ -0,0 +1,1238 @@
+// license:BSD-3-Clause
+// copyright-holders:Vas Crabb
+/*
+ I²C/SPI UART with 64-byte transmit and receive FIFOs
+
+ _______
+ VDD 1 |* | 16 XTAL1
+ A0 _CS 2 | | 15 XTAL2
+ A1 SI 3 | | 14 _RESET
+ n.c. SO 4 | | 13 RX
+ SCL SCLK 5 | | 12 TX
+ SDA VSS 6 | | 11 _CTS
+ _IRQ 7 | | 10 _RTS
+ I2C _SPI 8 |_______| 9 VSS
+
+ Partially software-compatible with the ubiquitous 16C450.
+
+ TODO:
+ * When are registers considered "read" for side effects?
+ * The rest of the registers
+ * The rest of the interrupts
+ * 9-bit mode
+ * Xon/Xoff handshaking
+ * Special character detect
+ * Loopback
+ * Break detection
+ * IrDA mode
+ * I²C interface
+ * Sleep mode
+ * SC16IS741 differences
+ */
+#include "emu.h"
+#include "sc16is741.h"
+
+//#define VERBOSE 1
+#include "logmacro.h"
+
+
+namespace {
+
+#define IER_CTS_INT() (BIT(m_ier, 7))
+#define IER_RTS_INT() (BIT(m_ier, 6))
+#define IER_XOFF_INT() (BIT(m_ier, 5))
+#define IER_SLEEP_MODE() (BIT(m_ier, 4))
+#define IER_MODEM_STATUS_INT() (BIT(m_ier, 3))
+#define IER_LINE_STATUS_INT() (BIT(m_ier, 2))
+#define IER_THR_INT() (BIT(m_ier, 1))
+#define IER_RHR_INT() (BIT(m_ier, 0))
+
+#define FCR_RX_TRIGGER() (BIT(m_fcr, 6, 2))
+#define FCR_TX_TRIGGER() (BIT(m_fcr, 4, 2))
+#define FCR_FIFO_ENABLE() (BIT(m_fcr, 0))
+
+#define LCR_DL_ENABLE() (BIT(m_lcr, 7))
+#define LCR_BREAK() (BIT(m_lcr, 6))
+#define LCR_SET_PARITY() (BIT(m_lcr, 5))
+#define LCR_EVEN_PARITY() (BIT(m_lcr, 4))
+#define LCR_PARITY_ENABLE() (BIT(m_lcr, 3))
+#define LCR_STOP_BIT() (BIT(m_lcr, 2))
+
+#define MCR_CLOCK_DIV4() (BIT(m_mcr, 7))
+#define MCR_TCR_TLR_ENABLE() (BIT(m_mcr, 2))
+
+#define TCR_LEVEL_RESUME() (BIT(m_tcr, 4, 4))
+#define TCR_LEVEL_HALT() (BIT(m_tcr, 0, 4))
+
+#define EFR_AUTO_CTS() (BIT(m_efr, 7))
+#define EFR_AUTO_RTS() (BIT(m_efr, 6))
+#define EFR_ENHANCED() (BIT(m_efr, 4))
+
+
+constexpr u8 RX_TRIGGER_LEVELS[4] = { 8, 16, 56, 60 };
+constexpr u8 TX_TRIGGER_LEVELS[4] = { 8, 16, 32, 56 };
+
+char const *const SOFT_FLOW_CONTROL_DESC[16] = {
+ "no soft transmit flow control, no soft receive flow control",
+ "no soft transmit flow control, receiver compares Xon2, Xoff2",
+ "no soft transmit flow control, receiver compares Xon1, Xoff1",
+ "no soft transmit flow control, receiver compares Xon1 and Xon2, Xoff1 and Xoff2",
+ "transmit Xon2, Xoff2, no soft receive flow control",
+ "transmit Xon2, Xoff2, receiver compares Xon2, Xoff2",
+ "transmit Xon2, Xoff2, receiver compares Xon1, Xoff1",
+ "transmit Xon2, Xoff2, receiver compares Xon1 or Xon2, Xoff1 or Xoff2",
+ "transmit Xon1, Xoff1, no soft receive flow control",
+ "transmit Xon1, Xoff1, receiver compares Xon2, Xoff2",
+ "transmit Xon1, Xoff1, receiver compares Xon1, Xoff1",
+ "transmit Xon1, Xoff1, receiver compares Xon1 or Xon2, Xoff1 or Xoff2",
+ "transmit Xon1 and Xon2, Xoff1 and Xoff2, no soft receive flow control",
+ "transmit Xon1 and Xon2, Xoff1 and Xoff2, receiver compares Xon2, Xoff2",
+ "transmit Xon1 and Xon2, Xoff1 and Xoff2, receiver compares Xon1, Xoff1",
+ "transmit Xon1 and Xon2, Xoff1 and Xoff2, receiver compares Xon1 and Xon2, Xoff1 and Xoff2" };
+
+} // anonymous namespace
+
+
+DEFINE_DEVICE_TYPE(SC16IS741A, sc16is741a_device, "sc16is741a", "NXP SC16IS741A UART")
+
+
+ALLOW_SAVE_TYPE(sc16is741a_device::phase);
+
+enum class sc16is741a_device::phase : u8
+{
+ IDLE,
+ COMMAND,
+ WRITE,
+ READ
+};
+
+
+enum class sc16is741a_device::parity : u8
+{
+ NONE,
+ ODD,
+ EVEN,
+ MARK,
+ SPACE
+};
+
+
+enum sc16is741a_device::interrupt : u8
+{
+ INTERRUPT_LINE_STATUS = 0x80,
+ INTERRUPT_RX_TIMEOUT = 0x40,
+ INTERRUPT_RHR = 0x20,
+ INTERRUPT_THR = 0x10,
+ INTERRUPT_MODEM_STATUS = 0x08,
+ INTERRUPT_XOFF = 0x04,
+ INTERRUPT_SPECIAL_CHAR = 0x02,
+ INTERRUPT_RTS_CTS = 0x01
+};
+
+
+sc16is741a_device::sc16is741a_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) :
+ device_t(mconfig, SC16IS741A, tag, owner, clock),
+ m_so_cb(*this),
+ m_irq_cb(*this),
+ m_tx_cb(*this),
+ m_rts_cb(*this),
+ m_shift_timer{ nullptr, nullptr },
+ m_rx_timeout_timer(nullptr)
+{
+}
+
+sc16is741a_device::~sc16is741a_device()
+{
+}
+
+
+void sc16is741a_device::sclk_w(int state)
+{
+ if ((phase::COMMAND == m_phase) || (phase::WRITE == m_phase))
+ {
+ if (state && !m_sclk)
+ {
+ m_buffer = (m_buffer << 1) | m_si;
+ if (!--m_bits)
+ {
+ if (phase::COMMAND == m_phase)
+ {
+ m_command = m_buffer;
+ if (BIT(m_buffer, 7))
+ {
+ m_phase = phase::READ;
+ reg_r(true);
+ }
+ else
+ {
+ m_phase = phase::WRITE;
+ }
+ }
+ else
+ {
+ reg_w();
+ }
+ m_bits = 8;
+ }
+ }
+ }
+ else if (phase::READ == m_phase)
+ {
+ if (!state && m_sclk)
+ {
+ m_so_cb(BIT(m_buffer, 7));
+ }
+ else if (state && !m_sclk)
+ {
+ m_buffer = (m_buffer << 1) | (m_buffer >> 7);
+ --m_bits;
+ if (!m_bits)
+ {
+ reg_r(false);
+ m_bits = 8;
+ }
+ else if (7 == m_bits)
+ {
+ if ((BIT(m_command, 3, 4) == 0x00) && ((0xbf == m_lcr) || !LCR_DL_ENABLE()))
+ pop_rx_fifo();
+ }
+ }
+ }
+ m_sclk = state ? 1 : 0;
+}
+
+void sc16is741a_device::cs_w(int state)
+{
+ if (state)
+ {
+ m_phase = phase::IDLE;
+ m_so_cb(1);
+ }
+ else if (m_cs)
+ {
+ m_phase = phase::COMMAND;
+ m_bits = 8;
+ }
+ m_cs = state ? 1 : 0;
+}
+
+void sc16is741a_device::si_w(int state)
+{
+ m_si = state ? 1 : 0;
+}
+
+void sc16is741a_device::rx_w(int state)
+{
+ if (m_divisor) // FIXME: check EFCR[1]
+ {
+ if (!m_rx_remain)
+ {
+ if (m_rx && !state)
+ {
+ // start bit
+ m_rx_remain = m_rx_intervals;
+ m_rx_count = 0;
+ m_shift_timer[0]->adjust(attotime::from_ticks(m_divisor * 16 / 2, clock()));
+ }
+ }
+ else if (!m_rx_count)
+ {
+ if (state)
+ {
+ // false start
+ m_rx_remain = 0;
+ m_shift_timer[0]->reset();
+ }
+ }
+ }
+ m_rx = state ? 1 : 0;
+}
+
+void sc16is741a_device::cts_w(int state)
+{
+ bool const asserted(EFR_AUTO_CTS() && !state && m_cts);
+ if (bool(state) != bool(m_cts))
+ {
+ m_interrupts |= INTERRUPT_MODEM_STATUS;
+ update_irq();
+ }
+ m_cts = state ? 1 : 0;
+ if (asserted)
+ check_tx();
+}
+
+
+void sc16is741a_device::device_resolve_objects()
+{
+ m_tx = 1;
+ m_rx = 1;
+ m_cts = 0;
+ m_sclk = 0;
+ m_cs = 1;
+ m_si = 1;
+ m_bits = 0;
+ m_buffer = 0;
+}
+
+void sc16is741a_device::device_start()
+{
+ m_shift_timer[0] = timer_alloc(FUNC(sc16is741a_device::rx_shift), this);
+ m_shift_timer[1] = timer_alloc(FUNC(sc16is741a_device::tx_shift), this);
+ m_rx_timeout_timer = timer_alloc(FUNC(sc16is741a_device::rx_timeout), this);
+
+ m_spr = 0x00;
+ m_dl = 0x0000;
+ std::fill(std::begin(m_xon_xoff), std::end(m_xon_xoff), 0);
+ m_tx_count = 0;
+ m_rx_count = 0;
+ for (auto &data : m_fifo_data)
+ std::fill(std::begin(data), std::end(data), 0);
+ m_divisor = 0;
+
+ save_item(NAME(m_irq));
+ save_item(NAME(m_tx));
+ save_item(NAME(m_rts));
+ save_item(NAME(m_rx));
+ save_item(NAME(m_cts));
+ save_item(NAME(m_sclk));
+ save_item(NAME(m_cs));
+ save_item(NAME(m_si));
+ save_item(NAME(m_phase));
+ save_item(NAME(m_bits));
+ save_item(NAME(m_buffer));
+ save_item(NAME(m_command));
+ save_item(NAME(m_ier));
+ save_item(NAME(m_fcr));
+ save_item(NAME(m_lcr));
+ save_item(NAME(m_mcr));
+ save_item(NAME(m_spr));
+ save_item(NAME(m_tcr));
+ save_item(NAME(m_tlr));
+ save_item(NAME(m_dl));
+ save_item(NAME(m_efr));
+ save_item(NAME(m_xon_xoff));
+ save_item(NAME(m_shift_reg));
+ save_item(NAME(m_rx_remain));
+ save_item(NAME(m_rx_count));
+ save_item(NAME(m_tx_remain));
+ save_item(NAME(m_tx_count));
+ save_item(NAME(m_fifo_head));
+ save_item(NAME(m_fifo_tail));
+ save_item(NAME(m_fifo_empty));
+ save_item(NAME(m_fifo_data));
+ save_item(NAME(m_fifo_errors));
+ save_item(NAME(m_interrupts));
+}
+
+void sc16is741a_device::device_reset()
+{
+ m_shift_timer[0]->reset();
+ m_shift_timer[1]->reset();
+ m_rx_timeout_timer->reset();
+
+ m_phase = phase::IDLE;
+
+ m_ier = 0x00;
+ m_fcr = 0x00;
+ m_lcr = 0x1d;
+ m_mcr = 0x00;
+ m_tcr = 0x00;
+ m_tlr = 0x00;
+ m_efr = 0x00;
+
+ std::fill(std::begin(m_shift_reg), std::end(m_shift_reg), 0xffff);
+ m_rx_remain = 0;
+ m_tx_remain = 0;
+
+ std::fill(std::begin(m_fifo_tail), std::end(m_fifo_tail), 0);
+ fifo_reset(0);
+ fifo_reset(1);
+ m_fifo_errors = 0;
+
+ m_interrupts = 0x00;
+
+ update_trigger_levels();
+ update_data_frame();
+ update_divisor();
+
+ m_irq_cb(m_irq = CLEAR_LINE);
+ m_tx_cb(m_tx = 1);
+ m_rts_cb(m_rts = 1);
+}
+
+void sc16is741a_device::device_post_load()
+{
+ update_trigger_levels();
+ update_data_frame();
+ update_divisor();
+}
+
+
+inline void sc16is741a_device::update_irq()
+{
+ bool const pending(
+ (IER_LINE_STATUS_INT() && (m_interrupts & INTERRUPT_LINE_STATUS)) ||
+ (IER_MODEM_STATUS_INT() && (m_interrupts & INTERRUPT_MODEM_STATUS)) ||
+ (IER_RHR_INT() && (m_interrupts & (INTERRUPT_RX_TIMEOUT | INTERRUPT_RHR))));
+ if (pending != (ASSERT_LINE == m_irq))
+ {
+ LOG(pending ? "asserting IRQ\n" : "deasserting IRQ\n");
+ m_irq_cb(m_irq = (pending ? ASSERT_LINE : CLEAR_LINE));
+ }
+}
+
+inline void sc16is741a_device::update_tx()
+{
+ u8 const state(LCR_BREAK() ? 0 : BIT(m_shift_reg[1], 0));
+ if (state != m_tx)
+ m_tx_cb(m_tx = state);
+}
+
+inline void sc16is741a_device::set_rts(u8 state)
+{
+ if (state != m_rts)
+ m_rts_cb(m_rts = state);
+}
+
+
+inline void sc16is741a_device::reg_r(bool first)
+{
+ u8 const ch(BIT(m_command, 1, 2));
+ u8 const addr(BIT(m_command, 3, 4));
+
+ // must be zero
+ if (0 != ch)
+ {
+ if (first)
+ logerror("read from unsupported ch %1$u register address 0x%2$02x\n", ch, addr);
+ m_buffer = 0xff;
+ return;
+ }
+
+ switch (addr)
+ {
+ case 0x00:
+ if ((0xbf != m_lcr) && LCR_DL_ENABLE())
+ m_buffer = BIT(m_dl, 0, 8);
+ else
+ m_buffer = m_fifo_data[0][m_fifo_tail[0]];
+ return;
+ case 0x01:
+ if ((0xbf != m_lcr) && LCR_DL_ENABLE())
+ m_buffer = BIT(m_dl, 8, 8);
+ else
+ m_buffer = m_ier;
+ return;
+ case 0x02:
+ if (0xbf == m_lcr)
+ m_buffer = m_efr;
+ else
+ iir_r(first);
+ return;
+ case 0x03:
+ m_buffer = m_lcr;
+ return;
+ case 0x04:
+ if (0xbf == m_lcr)
+ xon_xoff_r(first);
+ else
+ m_buffer = m_mcr;
+ return;
+ case 0x05:
+ if (0xbf == m_lcr)
+ xon_xoff_r(first);
+ else
+ lsr_r(first);
+ return;
+ case 0x06:
+ if (0xbf == m_lcr)
+ xon_xoff_r(first);
+ else if (MCR_TCR_TLR_ENABLE() && EFR_ENHANCED())
+ m_buffer = m_tcr;
+ else
+ msr_r(first);
+ return;
+ case 0x07:
+ if (0xbf == m_lcr)
+ xon_xoff_r(first);
+ else if (MCR_TCR_TLR_ENABLE() && EFR_ENHANCED())
+ m_buffer = m_tcr;
+ else
+ m_buffer = m_spr;
+ return;
+ case 0x08:
+ txlvl_r(first);
+ return;
+ case 0x09:
+ rxlvl_r(first);
+ return;
+ }
+
+ if (first)
+ logerror("read from unimplemented register address 0x%1$02x\n", addr);
+ m_buffer = 0xff;
+}
+
+inline void sc16is741a_device::reg_w()
+{
+ u8 const ch(BIT(m_command, 1, 2));
+ u8 const addr(BIT(m_command, 3, 4));
+
+ // must be zero
+ if (0 != ch)
+ {
+ logerror("write to unsupported ch %1$u register address 0x%2$02x = 0x%3$02x\n", ch, addr, m_buffer);
+ return;
+ }
+
+ switch (addr)
+ {
+ case 0x00:
+ if ((0xbf != m_lcr) && LCR_DL_ENABLE())
+ dl_w();
+ else
+ thr_w();
+ return;
+ case 0x01:
+ if ((0xbf != m_lcr) && LCR_DL_ENABLE())
+ dl_w();
+ else
+ ier_w();
+ return;
+ case 0x02:
+ if (0xbf == m_lcr)
+ efr_w();
+ else
+ fcr_w();
+ return;
+ case 0x03:
+ lcr_w();
+ return;
+ case 0x04:
+ if (0xbf == m_lcr)
+ xon_xoff_w();
+ else
+ mcr_w();
+ return;
+ case 0x05:
+ if (0xbf == m_lcr)
+ xon_xoff_w();
+ else
+ break; // LSR is read-only
+ return;
+ case 0x06:
+ if (0xbf == m_lcr)
+ xon_xoff_w();
+ else if (MCR_TCR_TLR_ENABLE() && EFR_ENHANCED())
+ tcr_w();
+ else
+ break; // MSR is read-only
+ return;
+ case 0x07:
+ if (0xbf == m_lcr)
+ xon_xoff_w();
+ else if (MCR_TCR_TLR_ENABLE() && EFR_ENHANCED())
+ tlr_w();
+ else
+ m_spr = m_buffer;
+ return;
+ case 0x0d:
+ reserved_w();
+ return;
+ case 0x0e:
+ uart_reset_w();
+ return;
+ }
+
+ logerror("write to unimplemented register address 0x%1$02x = 0x%2$02x\n", addr, m_buffer);
+}
+
+
+inline void sc16is741a_device::iir_r(bool first)
+{
+ if (first)
+ {
+ m_buffer = BIT(m_fcr, 0) ? 0xc0 : 0x00;
+ if (!m_irq)
+ {
+ m_buffer |= 0x01;
+ }
+ else if (IER_LINE_STATUS_INT() && (m_interrupts & INTERRUPT_LINE_STATUS))
+ {
+ m_buffer |= 0x06;
+ }
+ else if (IER_RHR_INT() && (m_interrupts & INTERRUPT_RX_TIMEOUT))
+ {
+ m_buffer |= 0x0c;
+ }
+ else if (IER_RHR_INT() && (m_interrupts & INTERRUPT_RHR))
+ {
+ m_buffer |= 0x04;
+ }
+ else if (IER_THR_INT() && (m_interrupts & INTERRUPT_THR))
+ {
+ m_buffer |= 0x02;
+ LOG("clearing THR interrupt\n");
+ m_interrupts &= ~INTERRUPT_THR;
+ }
+ else if (IER_MODEM_STATUS_INT() && (m_interrupts & INTERRUPT_MODEM_STATUS))
+ {
+ m_buffer |= 0x00;
+ }
+
+ LOG("read IIR (0x%1$02x)\n", m_buffer);
+ }
+}
+
+inline void sc16is741a_device::lsr_r(bool first)
+{
+ m_buffer =
+ (m_fifo_errors ? 0x80 : 0x00) |
+ ((m_fifo_empty[1] && !m_tx_remain) ? 0x40 : 0x00) |
+ (m_fifo_empty[1] ? 0x20 : 0x00) |
+ (!m_fifo_empty[0] ? 0x01 : 0x00);
+ if (!m_fifo_empty[0])
+ m_buffer |= m_fifo_data[1][m_fifo_tail[0]];
+
+ if (first)
+ LOG("read LSR (0x%1$02x)\n", m_buffer);
+}
+
+inline void sc16is741a_device::msr_r(bool first)
+{
+ if (first)
+ {
+ m_buffer =
+ (!m_cts ? 0x10 : 0x00) |
+ ((m_interrupts & INTERRUPT_MODEM_STATUS) ? 0x01 : 0x00);
+ m_interrupts &= ~INTERRUPT_MODEM_STATUS;
+
+ LOG("read MSR (0x%1$02x)\n", m_buffer);
+ }
+}
+
+inline void sc16is741a_device::txlvl_r(bool first)
+{
+ m_buffer = fifo_spaces(1);
+
+ if (first)
+ LOG("read TXLVL (0x%1$02x)\n", m_buffer);
+}
+
+inline void sc16is741a_device::rxlvl_r(bool first)
+{
+ m_buffer = fifo_fill_level(0);
+
+ if (first)
+ LOG("read RXLVL (0x%1$02x)\n", m_buffer);
+}
+
+inline void sc16is741a_device::xon_xoff_r(bool first)
+{
+ m_buffer = m_xon_xoff[BIT(m_command, 3, 2)];
+
+ if (first)
+ LOG("read %1$s%2$u (0x%3$02x)\n", BIT(m_command, 4) ? "XOFF" : "XON", BIT(m_command, 3) + 1, m_buffer);
+}
+
+
+inline void sc16is741a_device::thr_w()
+{
+ m_fifo_data[2][fifo_push(1)] = m_buffer;
+
+ if (m_interrupts & INTERRUPT_THR)
+ {
+ LOG("THR written, clearing THR interrupt\n");
+ m_interrupts &= ~INTERRUPT_THR;
+ }
+
+ check_tx();
+ update_irq(); // doing this here avoids a glitch if the FIFO is immediately popped
+}
+
+inline void sc16is741a_device::ier_w()
+{
+ LOG(EFR_ENHANCED()
+ ? "IER = 0x%1$02x (CTS interrupt %2$s, RTS interrupt %3$s, Xoff interrupt %4$s, sleep mode %5$s, modem status interrupt %6$s, RX status interrupt %7$s, THR interrupt %8$s, RHR interrupt %9$s)\n"
+ : "IER = 0x%1$02x (modem status interrupt %6$s, RX status interrupt %7$s, THR interrupt %8$s, RHR interrupt %9$s)\n",
+ m_buffer & (EFR_ENHANCED() ? 0xff : 0x0f),
+ BIT(m_buffer, 7) ? "enabled" : "disabled",
+ BIT(m_buffer, 6) ? "enabled" : "disabled",
+ BIT(m_buffer, 5) ? "enabled" : "disabled",
+ BIT(m_buffer, 4) ? "enabled" : "disabled",
+ BIT(m_buffer, 3) ? "enabled" : "disabled",
+ BIT(m_buffer, 2) ? "enabled" : "disabled",
+ BIT(m_buffer, 1) ? "enabled" : "disabled",
+ BIT(m_buffer, 0) ? "enabled" : "disabled");
+
+ if (EFR_ENHANCED())
+ m_ier = m_buffer;
+ else
+ m_ier = (m_ier & 0xf0) | (m_buffer & 0x0f);
+ update_irq();
+}
+
+inline void sc16is741a_device::fcr_w()
+{
+ LOG(EFR_ENHANCED()
+ ? "FCR = 0x%1$02x (RX trigger %2$u, TX trigger %3$u, reserved %4$u, %5$sTX FIFO reset, %6$sRX FIFO reset, FIFO %7$s)\n"
+ : "FCR = 0x%1$02x (RX trigger %2$u, reserved %4$u, %5$sTX FIFO reset, %6$sRX FIFO reset, FIFO %7$s)\n",
+ m_buffer & (EFR_ENHANCED() ? 0xff : 0xcf),
+ RX_TRIGGER_LEVELS[BIT(m_buffer, 6, 2)],
+ TX_TRIGGER_LEVELS[BIT(m_buffer, 4, 2)],
+ BIT(m_buffer, 3),
+ BIT(m_buffer, 2) ? "" : "no ",
+ BIT(m_buffer, 1) ? "" : "no ",
+ BIT(m_buffer, 0) ? "enabled" : "disabled");
+
+ if (BIT(m_buffer, 3))
+ logerror("reserved bit FCR[3] is set\n");
+
+ if (BIT(m_buffer, 2))
+ fifo_reset(1);
+
+ if (BIT(m_buffer, 1))
+ {
+ fifo_reset(0);
+ m_fifo_errors = 0;
+ m_interrupts &= ~(INTERRUPT_LINE_STATUS | INTERRUPT_RX_TIMEOUT | INTERRUPT_RHR);
+ if (EFR_AUTO_RTS() && m_rts) // FIXME: check EFCR[4]
+ {
+ LOG("RX FIFO reset, asserting RTS\n");
+ set_rts(0);
+ }
+ update_irq();
+ }
+
+ if (EFR_ENHANCED())
+ m_fcr = m_buffer & 0xf9;
+ else
+ m_fcr = (m_fcr & 0x30) | (m_buffer & 0xc9);
+ update_trigger_levels();
+}
+
+inline void sc16is741a_device::lcr_w()
+{
+ LOG("LCR = 0x%1$02x (divisor latch %2$s, %3$sbreak, %4$s parity %5$s, %6$s stop bits, word length %7$u)\n",
+ m_buffer,
+ BIT(m_buffer, 7) ? "enabled" : "disabled",
+ BIT(m_buffer, 6) ? "" : "no ",
+ BIT(m_buffer, 5) ? (BIT(m_buffer, 4) ? "0" : "1") : (BIT(m_buffer, 4) ? "even" : "odd"),
+ BIT(m_buffer, 3) ? "on" : "off",
+ !BIT(m_buffer, 2) ? "1" : !BIT(m_buffer, 0, 2) ? "1.5" : "2",
+ BIT(m_buffer, 0, 2) + 5);
+
+ m_lcr = m_buffer;
+ update_tx();
+ update_data_frame();
+}
+
+inline void sc16is741a_device::mcr_w()
+{
+ LOG(EFR_ENHANCED()
+ ? "MCR = 0x%1$02x (divide-by-%2$u, %3$s mode, Xon Any %4$s, loopback %5$s, reserved %6$u, TCR and TLR %7$s, RTS %8$s, reserved %9$u)\n"
+ : "MCR = 0x%1$02x (loopback %5$s, reserved %6$u, TCR and TLR %7$s, RTS %8$s, reserved %9$u)\n",
+ m_buffer & (EFR_ENHANCED() ? 0xff : 0x1f),
+ BIT(m_buffer, 7) ? 4 : 1,
+ BIT(m_buffer, 6) ? "IrDA" : "normal UART",
+ BIT(m_buffer, 5) ? "enabled" : "disabled",
+ BIT(m_buffer, 4) ? "enabled" : "disabled",
+ BIT(m_buffer, 3),
+ BIT(m_buffer, 2) ? "enabled" : "disabled",
+ BIT(m_buffer, 1) ? "active" : "inactive",
+ BIT(m_buffer, 0));
+
+ if (BIT(m_buffer, 3))
+ logerror("reserved bit MCR[3] is set\n");
+ if (BIT(m_buffer, 0))
+ logerror("reserved bit MCR[0] is set\n");
+
+ if (!EFR_AUTO_RTS()) // FIXME: check EFCR[4]
+ set_rts(BIT(~m_buffer, 1));
+
+ if (EFR_ENHANCED())
+ {
+ m_mcr = m_buffer;
+ update_divisor();
+ }
+ else
+ {
+ m_mcr = (m_mcr & 0xe0) | (m_buffer & 0x1f);
+ }
+}
+
+inline void sc16is741a_device::tcr_w()
+{
+ LOG("TCR = 0x%1$02x (resume transmission at %2$u*4 characters, halt transmission at %3$u*4 characters)\n",
+ m_buffer,
+ BIT(m_buffer, 4, 4),
+ BIT(m_buffer, 0, 4));
+
+ m_tcr = m_buffer;
+}
+
+inline void sc16is741a_device::tlr_w()
+{
+ LOG("TLR = 0x%1$02x (RX FIFO trigger level %2$u*4 characters%3$s, TX FIFO trigger level %4$u*4 spaces%5$s)\n",
+ m_buffer,
+ BIT(m_buffer, 4, 4),
+ BIT(m_buffer, 4, 4) ? "" : " - use FCR[7:6]",
+ BIT(m_buffer, 0, 4),
+ BIT(m_buffer, 0, 4) ? "" : " - use FCR[5:4]");
+
+ m_tlr = m_buffer;
+ update_trigger_levels();
+}
+
+inline void sc16is741a_device::reserved_w()
+{
+ logerror("reserved register address 0x%1$02x = 0x%2$02x\n", BIT(m_command, 3, 4), m_buffer);
+}
+
+inline void sc16is741a_device::uart_reset_w()
+{
+ LOG("UART reset = 0x%1$02x (reserved %2$u, reserved %3$u, reserved %4$u, reserved %5$u, %6$ssoftware reset, reserved %7$u, reserved %8$u, reserved %9$u)\n",
+ m_buffer,
+ BIT(m_buffer, 7),
+ BIT(m_buffer, 6),
+ BIT(m_buffer, 5),
+ BIT(m_buffer, 4),
+ BIT(m_buffer, 3) ? "" : "no ",
+ BIT(m_buffer, 2),
+ BIT(m_buffer, 1),
+ BIT(m_buffer, 0));
+
+ if (BIT(m_buffer, 7))
+ logerror("reserved bit UART reset[7] is set\n");
+ if (BIT(m_buffer, 6))
+ logerror("reserved bit UART reset[6] is set\n");
+ if (BIT(m_buffer, 5))
+ logerror("reserved bit UART reset[5] is set\n");
+ if (BIT(m_buffer, 4))
+ logerror("reserved bit UART reset[4] is set\n");
+ if (BIT(m_buffer, 2))
+ logerror("reserved bit UART reset[2] is set\n");
+ if (BIT(m_buffer, 1))
+ logerror("reserved bit UART reset[1] is set\n");
+ if (BIT(m_buffer, 0))
+ logerror("reserved bit UART reset[0] is set\n");
+
+ // TODO: is this instantaneous reset, or is the reset condition held until the bit is cleared?
+ if (BIT(m_buffer, 3))
+ device_reset();
+}
+
+inline void sc16is741a_device::dl_w()
+{
+ LOG("DL%1$c = 0x%2$02x\n", BIT(m_command, 3) ? 'H' : 'L', m_buffer);
+
+ m_dl = (m_dl & (BIT(m_command, 3) ? 0x00ff : 0xff00)) | (u16(m_buffer) << (BIT(m_command, 3) ? 8 : 0));
+ update_divisor();
+}
+
+inline void sc16is741a_device::efr_w()
+{
+ LOG("EFR = 0x%1$02x (CTS flow control %2$s, RTS flow control %3$s, special character detect %4$s, enhanced functions %5$s, %6$s)\n",
+ m_buffer,
+ BIT(m_buffer, 7) ? "enabled" : "disabled",
+ BIT(m_buffer, 6) ? "enabled" : "disabled",
+ BIT(m_buffer, 5) ? "enabled" : "disabled",
+ BIT(m_buffer, 4) ? "enabled" : "disabled",
+ SOFT_FLOW_CONTROL_DESC[BIT(m_buffer, 0, 4)]);
+
+ if (!BIT(m_buffer, 6)) // FIXME: check EFCR[4]
+ {
+ // auto RTS off, ensure RTS output is up-to-date
+ set_rts(BIT(~m_mcr, 1));
+ }
+ else if (!EFR_AUTO_RTS())
+ {
+ // enabling auto RTS
+ if (FCR_FIFO_ENABLE())
+ {
+ u8 const level(fifo_fill_level(0));
+ set_rts(((level <= (TCR_LEVEL_RESUME() * 4)) || (level < (TCR_LEVEL_HALT() * 4))) ? 0 : 1);
+ }
+ else
+ {
+ set_rts(m_fifo_empty[0] ? 0 : 1);
+ }
+ }
+
+ m_efr = m_buffer;
+ check_tx();
+}
+
+inline void sc16is741a_device::xon_xoff_w()
+{
+ LOG("%1$s%2$u = 0x%3$02x\n", BIT(m_command, 4) ? "XOFF" : "XON", BIT(m_command, 3) + 1, m_buffer);
+
+ m_xon_xoff[BIT(m_command, 3, 2)] = m_buffer;
+}
+
+
+inline void sc16is741a_device::pop_rx_fifo()
+{
+ assert(!m_fifo_empty[0] || !m_fifo_errors);
+
+ if (!m_fifo_empty[0] && m_fifo_data[1][m_fifo_tail[0]])
+ {
+ assert(m_fifo_errors);
+ assert(m_interrupts & INTERRUPT_LINE_STATUS);
+ if (!--m_fifo_errors)
+ {
+ LOG("read last data error, clearing line status interrupt\n");
+ m_interrupts &= ~INTERRUPT_LINE_STATUS;
+ update_irq();
+ }
+ }
+
+ fifo_pop(0);
+ u8 const level(fifo_fill_level(0));
+ if (m_fifo_empty[0])
+ m_rx_timeout_timer->reset();
+ else
+ m_rx_timeout_timer->adjust(attotime::from_ticks(m_divisor * 16 / 2 * 4 * m_rx_intervals, clock()));
+
+ if (m_interrupts & INTERRUPT_RX_TIMEOUT)
+ {
+ LOG("clearing RX timeout interrupt\n");
+ m_interrupts &= ~INTERRUPT_RX_TIMEOUT;
+ update_irq();
+ }
+
+ if (m_interrupts & INTERRUPT_RHR)
+ {
+ if (FCR_FIFO_ENABLE())
+ {
+ if (level < m_rx_trigger)
+ {
+ LOG("RX FIFO level %1$u within %2$u, clearing RHR interrupt\n", level, m_rx_trigger);
+ m_interrupts &= ~INTERRUPT_RHR;
+ update_irq();
+ }
+ }
+ else if (m_fifo_empty[0])
+ {
+ LOG("RHR empty, clearing RHR interrupt\n");
+ m_interrupts &= ~INTERRUPT_RHR;
+ update_irq();
+ }
+ }
+
+ if (EFR_AUTO_RTS() && m_rts) // FIXME: check EFCR[4]
+ {
+ if (FCR_FIFO_ENABLE())
+ {
+ u8 const trigger(TCR_LEVEL_RESUME());
+ if (level <= (trigger * 4))
+ {
+ LOG("RX FIFO level %1$u within %2$u*4, asserting RTS\n", level, trigger);
+ set_rts(0);
+ }
+ }
+ else
+ {
+ LOG("RHR empty, asserting RTS\n");
+ set_rts(0);
+ }
+ }
+}
+
+inline bool sc16is741a_device::check_tx()
+{
+ if (m_tx_remain || m_fifo_empty[1] || (EFR_AUTO_CTS() && m_cts) || !m_divisor) // FIXME: check EFCR[2]
+ return false;
+
+ u16 const data(u16(m_fifo_data[2][fifo_pop(1)] & util::make_bitmask(m_word_length)) << 1);
+ if (parity::NONE == m_parity)
+ {
+ m_shift_reg[1] = ~util::make_bitmask(m_word_length + 1) | data;
+ }
+ else
+ {
+ m_shift_reg[1] = ~util::make_bitmask(m_word_length + 2) | data;
+ switch (m_parity)
+ {
+ case parity::ODD:
+ m_shift_reg[1] |= BIT(~population_count_32(data), 0) << (m_word_length + 1);
+ break;
+ case parity::EVEN:
+ m_shift_reg[1] |= BIT(population_count_32(data), 0) << (m_word_length + 1);
+ break;
+ case parity::MARK:
+ m_shift_reg[1] |= u16(1) << (m_word_length + 1);
+ break;
+ default:
+ break;
+ }
+ }
+ m_tx_remain = m_tx_intervals;
+ m_tx_count = 0;
+ update_tx();
+ m_shift_timer[1]->adjust(attotime::from_ticks(m_divisor * 16 / 2, clock()));
+
+ if (IER_THR_INT() && !(m_interrupts & INTERRUPT_THR))
+ {
+ if (FCR_FIFO_ENABLE())
+ {
+ // TODO: does this only happen at the trigger level, or any time the FIFO is popped above the trigger level?
+ u8 const spaces(fifo_spaces(1));
+ if (spaces >= m_tx_trigger)
+ {
+ LOG("TX FIFO spaces %1$u exceed %2$u, setting THR interrupt\n", spaces, m_tx_trigger);
+ m_interrupts |= INTERRUPT_THR;
+ update_irq();
+ }
+ else
+ {
+ LOG("THR empty, setting THR interrupt\n");
+ m_interrupts |= INTERRUPT_THR;
+ update_irq();
+ }
+ }
+ }
+
+ return true;
+}
+
+
+inline u8 sc16is741a_device::fifo_spaces(unsigned n) const
+{
+ if (m_fifo_empty[n])
+ return FIFO_LENGTH;
+ else
+ return (FIFO_LENGTH - m_fifo_head[n] + m_fifo_tail[n]) % FIFO_LENGTH;
+}
+
+inline u8 sc16is741a_device::fifo_fill_level(unsigned n) const
+{
+ if (!m_fifo_empty[n] && (m_fifo_head[n] == m_fifo_tail[n]))
+ return FIFO_LENGTH;
+ else
+ return (FIFO_LENGTH + m_fifo_head[n] - m_fifo_tail[n]) % FIFO_LENGTH;
+}
+
+inline void sc16is741a_device::fifo_reset(unsigned n)
+{
+ m_fifo_head[n] = m_fifo_tail[n];
+ m_fifo_empty[n] = true;
+}
+
+inline u8 sc16is741a_device::fifo_push(unsigned n)
+{
+ if (!FCR_FIFO_ENABLE())
+ {
+ if (!m_fifo_empty[n])
+ LOG("%1$s FIFO overrun\n", n ? "TX" : "RX");
+ m_fifo_empty[n] = false;
+ return m_fifo_head[n];
+ }
+ else if ((m_fifo_head[n] != m_fifo_tail[n]) || m_fifo_empty[n])
+ {
+ m_fifo_empty[n] = false;
+ return std::exchange(m_fifo_head[n], (m_fifo_head[n] + 1) & 0x3f);
+ }
+ else
+ {
+ LOG("%1$s FIFO overrun\n", n ? "TX" : "RX");
+ return (m_fifo_head[n] - 1) & 0x3f;
+ }
+}
+
+inline u8 sc16is741a_device::fifo_pop(unsigned n)
+{
+ if (m_fifo_empty[n])
+ {
+ assert(m_fifo_head[n] == m_fifo_tail[n]);
+ LOG("%1$s FIFO underrun\n", n ? "TX" : "RX");
+ return m_fifo_tail[n];
+ }
+ else if ((m_fifo_head[n] != m_fifo_tail[n]) || FCR_FIFO_ENABLE())
+ {
+ u8 const result(std::exchange(m_fifo_tail[n], (m_fifo_tail[n] + 1) & 0x3f));
+ if (m_fifo_head[n] == m_fifo_tail[n])
+ m_fifo_empty[n] = true;
+ return result;
+ }
+ else
+ {
+ m_fifo_empty[n] = true;
+ return m_fifo_tail[n];
+ }
+}
+
+
+TIMER_CALLBACK_MEMBER(sc16is741a_device::rx_shift)
+{
+ assert(m_divisor);
+
+ m_shift_reg[0] = (m_shift_reg[0] >> 1) | (u16(m_rx) << 15);
+ --m_rx_remain;
+ ++m_rx_count;
+ if (m_rx_remain)
+ {
+ m_shift_timer[0]->adjust(attotime::from_ticks(m_divisor * 16, clock()));
+ }
+ else
+ {
+ u8 const data(BIT(m_shift_reg[0], 16 + 1 - m_rx_count, m_rx_count - ((parity::NONE == m_parity) ? 2 : 3)));
+ u8 lsr(
+ (BIT(~m_shift_reg[0], 15) ? 0x08 : 0x00) |
+ ((!m_fifo_empty[0] && (!FCR_FIFO_ENABLE() || (m_fifo_head[0] == m_fifo_tail[0]))) ? 0x02 : 0x00));
+ switch (m_parity)
+ {
+ case parity::NONE:
+ break;
+ case parity::ODD:
+ lsr |= BIT(population_count_32(data) ^ BIT(~m_shift_reg[0], 14), 0) << 2;
+ break;
+ case parity::EVEN:
+ lsr |= BIT(population_count_32(data) ^ BIT(m_shift_reg[0], 14), 0) << 2;
+ break;
+ case parity::MARK:
+ lsr |= BIT(~m_shift_reg[0], 14) << 2;
+ break;
+ case parity::SPACE:
+ lsr |= BIT(m_shift_reg[0], 14) << 2;
+ break;
+ }
+ m_shift_reg[0] = 0xffff;
+ u8 const pos(fifo_push(0));
+ if (lsr && (!BIT(lsr, 1) || !m_fifo_data[1][pos]))
+ ++m_fifo_errors;
+ m_fifo_data[0][pos] = data;
+ m_fifo_data[1][pos] = lsr;
+ u8 const level(fifo_fill_level(0));
+ m_rx_timeout_timer->adjust(attotime::from_ticks(m_divisor * 16 / 2 * 4 * m_rx_intervals, clock()));
+
+ if (!(m_interrupts & INTERRUPT_LINE_STATUS))
+ {
+ if (lsr)
+ {
+ assert(1 == m_fifo_errors);
+ LOG("data error, setting line status interrupt\n");
+ m_interrupts |= INTERRUPT_LINE_STATUS;
+ update_irq();
+ }
+ }
+
+ if (!(m_interrupts & INTERRUPT_RHR))
+ {
+ if (FCR_FIFO_ENABLE())
+ {
+ if (level >= m_rx_trigger)
+ {
+ LOG("RX FIFO level %1$u exceeds %2$u, setting RHR interrupt\n", level, m_rx_trigger);
+ m_interrupts |= INTERRUPT_RHR;
+ update_irq();
+ }
+ }
+ else
+ {
+ LOG("RHR full, setting RHR interrupt\n");
+ m_interrupts |= INTERRUPT_RHR;
+ update_irq();
+ }
+ }
+
+ if (EFR_AUTO_RTS() && !m_rts) // FIXME: check EFCR[4]
+ {
+ if (FCR_FIFO_ENABLE())
+ {
+ u8 const trigger(TCR_LEVEL_HALT());
+ if (level >= (trigger * 4))
+ {
+ LOG("RX FIFO level %1$u exceeds %2$u*4, deasserting RTS\n", level, trigger);
+ set_rts(1);
+ }
+ }
+ else
+ {
+ LOG("RHR full, deasserting RTS\n");
+ set_rts(1);
+ }
+ }
+ }
+}
+
+TIMER_CALLBACK_MEMBER(sc16is741a_device::tx_shift)
+{
+ assert(m_divisor);
+
+ if (!BIT(++m_tx_count, 0))
+ {
+ m_shift_reg[1] = (m_shift_reg[1] >> 1) | u16(0x8000);
+ update_tx();
+ }
+
+ if (--m_tx_remain)
+ m_shift_timer[1]->adjust(attotime::from_ticks(m_divisor * 16 / 2, clock()));
+ else if (!check_tx())
+ m_shift_timer[1]->reset();
+}
+
+TIMER_CALLBACK_MEMBER(sc16is741a_device::rx_timeout)
+{
+ if (IER_RHR_INT() && !(m_interrupts & INTERRUPT_RX_TIMEOUT))
+ {
+ LOG("setting RX timeout interrupt\n");
+ m_interrupts |= INTERRUPT_RX_TIMEOUT;
+ update_irq();
+ }
+}
+
+
+inline void sc16is741a_device::update_trigger_levels()
+{
+ u8 const rx_level(BIT(m_tlr, 4, 4));
+ u8 const tx_level(BIT(m_tlr, 0, 4));
+ m_rx_trigger = rx_level ? (rx_level * 4) : RX_TRIGGER_LEVELS[FCR_RX_TRIGGER()];
+ m_tx_trigger = tx_level ? (tx_level * 4) : TX_TRIGGER_LEVELS[FCR_TX_TRIGGER()];
+}
+
+inline void sc16is741a_device::update_data_frame()
+{
+ m_word_length = BIT(m_lcr, 0, 2) + 5;
+ if (!LCR_PARITY_ENABLE())
+ m_parity = parity::NONE;
+ else if (!LCR_SET_PARITY())
+ m_parity = LCR_EVEN_PARITY() ? parity::EVEN : parity::ODD;
+ else
+ m_parity = LCR_EVEN_PARITY() ? parity::SPACE : parity::MARK;
+ u8 const stop(!LCR_STOP_BIT() ? 2 : (5 == m_word_length) ? 3 : 4);
+ m_rx_intervals = m_word_length + ((parity::NONE == m_parity) ? 2 : 3);
+ m_tx_intervals = ((m_word_length + ((parity::NONE == m_parity) ? 1 : 2)) * 2) + stop;
+}
+
+inline void sc16is741a_device::update_divisor()
+{
+ bool const zero(!m_divisor);
+ m_divisor = u32(m_dl) * (MCR_CLOCK_DIV4() ? 4 : 1);
+ if (!zero && !m_divisor)
+ {
+ if (m_rx_remain)
+ {
+ // FIXME: receive shift register immediately transferred to RHR
+ LOG("suspending reception due to zero divisor\n");
+ m_rx_remain = 0;
+ m_shift_timer[0]->reset();
+ }
+
+ if (!m_shift_timer[1]->expire().is_never())
+ {
+ LOG("suspending transmission due to zero divisor\n");
+ m_shift_timer[1]->reset();
+ }
+
+ m_rx_timeout_timer->reset();
+ }
+ else if (zero && m_divisor)
+ {
+ if (m_tx_remain && m_shift_timer[1]->expire().is_never())
+ {
+ LOG("non-zero divisor caused transmission to resume\n");
+ m_shift_timer[1]->adjust(attotime::from_ticks(m_divisor * 16 / 2, clock()));
+ }
+ }
+}
diff --git a/src/devices/machine/sc16is741.h b/src/devices/machine/sc16is741.h
new file mode 100644
index 0000000000000..ff445e68ec7b4
--- /dev/null
+++ b/src/devices/machine/sc16is741.h
@@ -0,0 +1,132 @@
+// license:BSD-3-Clause
+// copyright-holders:Vas Crabb
+// I²C/SPI UART with 64-byte transmit and receive FIFOs
+#ifndef MAME_MACHINE_SC16IS741_H
+#define MAME_MACHINE_SC16IS741_H
+
+#pragma once
+
+
+class sc16is741a_device : public device_t
+{
+public:
+ sc16is741a_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock);
+ virtual ~sc16is741a_device();
+
+ auto so_cb() { return m_so_cb.bind(); }
+ auto irq_cb() { return m_irq_cb.bind(); }
+ auto tx_cb() { return m_tx_cb.bind(); }
+ auto rts_cb() { return m_rts_cb.bind(); }
+
+ void sclk_w(int state);
+ void cs_w(int state);
+ void si_w(int state);
+ void rx_w(int state);
+ void cts_w(int state);
+
+protected:
+ virtual void device_resolve_objects() override ATTR_COLD;
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+ virtual void device_post_load() override ATTR_COLD;
+
+private:
+ static inline constexpr u8 FIFO_LENGTH = 64;
+
+ enum class phase : u8;
+ enum class parity : u8;
+ enum interrupt : u8;
+
+ void update_irq();
+ void update_tx();
+ void set_rts(u8 state);
+
+ void reg_r(bool first);
+ void reg_w();
+
+ void iir_r(bool first);
+ void lsr_r(bool first);
+ void msr_r(bool first);
+ void txlvl_r(bool first);
+ void rxlvl_r(bool first);
+ void xon_xoff_r(bool first);
+
+ void thr_w();
+ void ier_w();
+ void fcr_w();
+ void lcr_w();
+ void mcr_w();
+ void tcr_w();
+ void tlr_w();
+ void reserved_w();
+ void uart_reset_w();
+ void dl_w();
+ void efr_w();
+ void xon_xoff_w();
+
+ void pop_rx_fifo();
+ bool check_tx();
+
+ u8 fifo_spaces(unsigned n) const;
+ u8 fifo_fill_level(unsigned n) const;
+ void fifo_reset(unsigned n);
+ u8 fifo_push(unsigned n);
+ u8 fifo_pop(unsigned n);
+
+ TIMER_CALLBACK_MEMBER(rx_shift);
+ TIMER_CALLBACK_MEMBER(tx_shift);
+ TIMER_CALLBACK_MEMBER(rx_timeout);
+
+ void update_trigger_levels();
+ void update_data_frame();
+ void update_divisor();
+
+ devcb_write_line m_so_cb;
+ devcb_write_line m_irq_cb;
+ devcb_write_line m_tx_cb;
+ devcb_write_line m_rts_cb;
+
+ emu_timer *m_shift_timer[2];
+ emu_timer *m_rx_timeout_timer;
+
+ u8 m_irq, m_tx, m_rts;
+ u8 m_rx, m_cts;
+
+ u8 m_sclk, m_cs, m_si;
+ phase m_phase;
+ u8 m_bits, m_buffer;
+ u8 m_command;
+
+ u8 m_ier;
+ u8 m_fcr;
+ u8 m_lcr;
+ u8 m_mcr;
+ u8 m_spr;
+ u8 m_tcr;
+ u8 m_tlr;
+ u16 m_dl;
+ u8 m_efr;
+ u8 m_xon_xoff[4];
+
+ u16 m_shift_reg[2];
+ u8 m_rx_remain, m_rx_count;
+ u8 m_tx_remain, m_tx_count;
+
+ u8 m_fifo_head[2], m_fifo_tail[2];
+ bool m_fifo_empty[2];
+ u8 m_fifo_data[3][FIFO_LENGTH];
+ u8 m_fifo_errors;
+
+ u8 m_interrupts;
+
+ u32 m_divisor;
+ u8 m_word_length;
+ parity m_parity;
+ u8 m_rx_intervals, m_tx_intervals;
+ u8 m_rx_trigger, m_tx_trigger;
+};
+
+
+DECLARE_DEVICE_TYPE(SC16IS741A, sc16is741a_device)
+
+#endif // MAME_MACHINE_SC16IS741_H
diff --git a/src/devices/machine/spi_psram.cpp b/src/devices/machine/spi_psram.cpp
index 659ae3ad32cd8..b463c8adcfadf 100644
--- a/src/devices/machine/spi_psram.cpp
+++ b/src/devices/machine/spi_psram.cpp
@@ -30,9 +30,13 @@
Example PSRAM:
* AP Memory APS1604L-SQ (2 MiB)
+ * AP Memory APS1604M-SQ (2 MiB)
* AP Memory APS3204L-SQ (4 MiB)
+ * AP Memory APS3204M-SQ (4 MiB)
* AP Memory APS6404L-SQ (8 MiB)
+ * AP Memory APS6404M-SQ (8 MiB)
* AP Memory APS12804L-SQ (16 MiB)
+ * AP Memory APS12804M-SQ (16 MiB)
* ISS IS66WVS1M8 (1 MiB)
* ISS IS66WVS2M8 (2 MiB)
* ISS IS66WVS8M8 (8 MiB)
@@ -54,30 +58,20 @@
#include "spi_psram.h"
-DEFINE_DEVICE_TYPE(SPI_PSRAM, spi_psram_device, "spi_psram", "Generic SPI RAM")
+DEFINE_DEVICE_TYPE(SPI_RAM, spi_ram_device, "spi_ram", "Generic SPI RAM")
+DEFINE_DEVICE_TYPE(SPI_PSRAM, spi_psram_device, "spi_psram", "Generic SPI/QPI Pseudo-SRAM")
-ALLOW_SAVE_TYPE(spi_psram_device::phase)
-
-enum class spi_psram_device::phase : u8
-{
- IDLE,
- COMMAND,
- ADDRESS,
- WAIT,
- READ,
- WRITE
-};
-
-
-enum spi_psram_device::command : u8
+enum spi_ram_device::command : u8
{
COMMAND_READ = 0x03,
COMMAND_FAST_READ = 0x0b, // 8 wait cycles in SPI mode, 4 wait cycles in QPI mode
COMMAND_FAST_READ_QUAD = 0xeb, // 6 wait cycles, always 4-bit address and data
+ COMMAND_WRAPPED_READ = 0x8b, // 8 wait cycles in SPI mode, 5 wait cycles in QPI mode
COMMAND_WRITE = 0x02,
COMMAND_WRITE_QUAD = 0x38, // always 4-bit address and data
+ COMMAND_WRAPPED_WRITE = 0x82,
COMMAND_QPI_ENTER = 0x35,
COMMAND_QPI_EXIT = 0xf5,
@@ -100,20 +94,38 @@ enum spi_psram_device::command : u8
};
-spi_psram_device::spi_psram_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) :
- device_t(mconfig, SPI_PSRAM, tag, owner, clock),
+ALLOW_SAVE_TYPE(spi_ram_device::phase)
+
+enum class spi_ram_device::phase : u8
+{
+ IDLE,
+ COMMAND,
+ ADDRESS,
+ WAIT,
+ READ,
+ WRITE
+};
+
+
+spi_ram_device::spi_ram_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) :
+ spi_ram_device(mconfig, SPI_RAM, tag, owner, clock)
+{
+}
+
+spi_ram_device::spi_ram_device(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) :
+ device_t(mconfig, type, tag, owner, clock),
m_sio_cb(*this),
m_ram(),
m_size(0)
{
}
-spi_psram_device::~spi_psram_device()
+spi_ram_device::~spi_ram_device()
{
}
-void spi_psram_device::ce_w(int state)
+void spi_ram_device::ce_w(int state)
{
if (state)
{
@@ -130,7 +142,7 @@ void spi_psram_device::ce_w(int state)
m_ce = state ? 1 : 0;
}
-void spi_psram_device::sclk_w(int state)
+void spi_ram_device::sclk_w(int state)
{
switch (m_phase)
{
@@ -139,12 +151,13 @@ void spi_psram_device::sclk_w(int state)
case phase::WRITE:
if (state && !m_sclk)
{
- m_buffer = (m_buffer << m_cmd_width) | (m_sio & util::make_bitmask(m_cmd_width));
+ m_buffer = (m_buffer << m_data_width) | (m_sio & util::make_bitmask(m_data_width));
m_bits -= m_data_width;
if (!m_bits)
{
if (phase::COMMAND == m_phase)
{
+ m_phase = phase::IDLE;
m_cmd = u8(m_buffer);
start_command();
}
@@ -187,25 +200,37 @@ void spi_psram_device::sclk_w(int state)
}
break;
+ case phase::WAIT:
+ if (state && !m_sclk)
+ {
+ m_bits -= m_data_width;
+ if (!m_bits)
+ {
+ m_bits = 8;
+ m_phase = m_next_phase;
+ }
+ }
+ break;
+
default:
break;
}
m_sclk = state ? 1 : 0;
}
-void spi_psram_device::sio_w(offs_t offset, u8 data, u8 mem_mask)
+void spi_ram_device::sio_w(offs_t offset, u8 data, u8 mem_mask)
{
m_sio = data & 0xf;
}
-void spi_psram_device::device_validity_check(validity_checker &valid) const
+void spi_ram_device::device_validity_check(validity_checker &valid) const
{
if (!m_size || (m_size & (m_size - 1)) || (m_size > 0x0100'0000))
osd_printf_error("Unsupported size %u (must be a power of 2 not larger than 16M)\n", m_size);
}
-void spi_psram_device::device_resolve_objects()
+void spi_ram_device::device_resolve_objects()
{
m_wrap_mask = util::make_bitmask(10);
m_addr = 0;
@@ -213,15 +238,17 @@ void spi_psram_device::device_resolve_objects()
m_cmd_width = 1;
m_data_width = 1;
m_bits = 0;
+ m_wait = 0;
m_ce = 1;
m_sclk = 0;
m_sio = 0xf;
m_phase = phase::IDLE;
+ m_next_phase = phase::IDLE;
m_cmd = 0;
}
-void spi_psram_device::device_start()
+void spi_ram_device::device_start()
{
if (!m_size || (m_size & (m_size - 1)) || (m_size > 0x0100'0000))
osd_printf_error("%s: Unsupported size %u (must be a power of 2 not larger than 16M)\n", tag(), m_size);
@@ -235,64 +262,77 @@ void spi_psram_device::device_start()
save_item(NAME(m_cmd_width));
save_item(NAME(m_data_width));
save_item(NAME(m_bits));
+ save_item(NAME(m_wait));
save_item(NAME(m_ce));
save_item(NAME(m_sclk));
save_item(NAME(m_sio));
save_item(NAME(m_phase));
+ save_item(NAME(m_next_phase));
save_item(NAME(m_cmd));
}
-void spi_psram_device::start_command()
+void spi_ram_device::start_command()
{
- switch (m_cmd)
+ switch (cmd())
{
case COMMAND_READ:
- // FIXME: AP Memory devices don't support this command in QPI mode
- m_buffer = 0;
- m_data_width = m_cmd_width;
- m_bits = 24;
- m_phase = phase::ADDRESS;
- break;
+ // FIXME: wait cycles depend on mode and device family
+ start_read(m_cmd_width, 0);
+ return;
case COMMAND_WRITE:
- m_buffer = 0;
- m_data_width = m_cmd_width;
- m_bits = 24;
- m_phase = phase::ADDRESS;
- break;
+ start_write(m_cmd_width);
+ return;
default:
- logerror("unimplemented command 0x%02x\n", m_cmd);
- m_phase = phase::IDLE;
+ logerror("unimplemented command 0x%02x in %u-bit mode\n", cmd(), m_cmd_width);
}
}
-void spi_psram_device::address_complete()
+void spi_ram_device::address_complete()
{
- switch (m_cmd)
+ if (m_wait)
{
- case COMMAND_READ:
- // FIXME: wait cycles depend on mode and device family
- m_buffer = m_ram[m_addr];
- m_data_width = m_cmd_width;
+ m_phase = phase::WAIT;
+ m_bits = m_wait;
+ }
+ else
+ {
+ m_buffer = (phase::READ == m_next_phase) ? m_ram[m_addr] : 0;
m_bits = 8;
- m_phase = phase::READ;
- break;
+ m_phase = m_next_phase;
+ }
+}
- case COMMAND_WRITE:
- m_buffer = 0;
- m_data_width = m_cmd_width;
- m_bits = 8;
- m_phase = phase::WRITE;
- break;
- default:
- throw false; // if we get here, there's a bug in the code
- }
+inline void spi_ram_device::set_cmd_width(u8 width)
+{
+ m_cmd_width = width;
}
-inline void spi_psram_device::next_address()
+inline void spi_ram_device::start_read(u8 width, u8 wait)
+{
+ m_buffer = 0;
+ m_data_width = width;
+ m_bits = 24;
+ m_wait = wait * width;
+ m_phase = phase::ADDRESS;
+ m_next_phase = phase::READ;
+}
+
+inline void spi_ram_device::start_write(u8 width)
+{
+ m_buffer = 0;
+ m_data_width = width;
+ m_bits = 24;
+ m_wait = 0;
+ m_phase = phase::ADDRESS;
+ m_next_phase = phase::WRITE;
+}
+
+
+inline void spi_ram_device::next_address()
{
if (m_wrap_mask)
{
@@ -304,3 +344,84 @@ inline void spi_psram_device::next_address()
m_phase = phase::IDLE;
}
}
+
+
+
+spi_psram_device::spi_psram_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) :
+ spi_ram_device(mconfig, SPI_PSRAM, tag, owner, clock)
+{
+}
+
+spi_psram_device::~spi_psram_device()
+{
+}
+
+
+void spi_psram_device::device_resolve_objects()
+{
+ spi_ram_device::device_resolve_objects();
+
+ m_reset_enable = false;
+}
+
+void spi_psram_device::device_start()
+{
+ spi_ram_device::device_start();
+
+ save_item(NAME(m_reset_enable));
+}
+
+
+void spi_psram_device::start_command()
+{
+ bool const reset_enable(std::exchange(m_reset_enable, false));
+ switch (cmd())
+ {
+ case COMMAND_READ:
+ if (cmd_width() == 4)
+ {
+ // FIXME: AP Memory and Vilsion Technology devices don't support command 0x03 in QPI mode
+ start_read(4, 4);
+ return;
+ }
+ break;
+
+ case COMMAND_FAST_READ:
+ start_read(cmd_width(), (cmd_width() == 4) ? 4 : 8);
+ return;
+
+ case COMMAND_FAST_READ_QUAD:
+ start_read(4, 6);
+ return;
+
+ case COMMAND_WRITE_QUAD:
+ start_write(4);
+ return;
+
+ case COMMAND_QPI_ENTER:
+ if (cmd_width() == 1)
+ {
+ set_cmd_width(4);
+ return;
+ }
+ break;
+
+ case COMMAND_QPI_EXIT:
+ if (cmd_width() == 4)
+ {
+ set_cmd_width(1);
+ return;
+ }
+ break;
+
+ case COMMAND_RESET_ENABLE:
+ m_reset_enable = true;
+ return;
+
+ case COMMAND_RESET:
+ if (reset_enable)
+ set_cmd_width(1);
+ return;
+ }
+ spi_ram_device::start_command();
+}
diff --git a/src/devices/machine/spi_psram.h b/src/devices/machine/spi_psram.h
index d2bc592376320..2bbc858b30f34 100644
--- a/src/devices/machine/spi_psram.h
+++ b/src/devices/machine/spi_psram.h
@@ -9,11 +9,11 @@
#include
-class spi_psram_device : public device_t
+class spi_ram_device : public device_t
{
public:
- spi_psram_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock = 0U);
- virtual ~spi_psram_device();
+ spi_ram_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock = 0U);
+ virtual ~spi_ram_device();
void set_size(u32 size) { m_size = size; }
auto sio_cb() { return m_sio_cb.bind(); }
@@ -24,17 +24,28 @@ class spi_psram_device : public device_t
void si_w(int state) { sio_w(0, state ? 0xf : 0xe, 0x1); }
protected:
+ enum command : u8;
+
+ spi_ram_device(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock);
+
virtual void device_validity_check(validity_checker &valid) const override ATTR_COLD;
virtual void device_resolve_objects() override ATTR_COLD;
virtual void device_start() override ATTR_COLD;
- void start_command();
- void address_complete();
- void next_address();
+ virtual void start_command();
+
+ u8 cmd_width() const { return m_cmd_width; }
+ u8 cmd() const { return m_cmd; }
+
+ void set_cmd_width(u8 width);
+ void start_read(u8 width, u8 wait);
+ void start_write(u8 width);
private:
enum class phase : u8;
- enum command : u8;
+
+ void address_complete();
+ void next_address();
devcb_write8 m_sio_cb;
@@ -45,13 +56,32 @@ class spi_psram_device : public device_t
u32 m_buffer;
u8 m_cmd_width, m_data_width;
u8 m_bits;
+ u8 m_wait;
u8 m_ce, m_sclk, m_sio;
- phase m_phase;
+ phase m_phase, m_next_phase;
u8 m_cmd;
};
+class spi_psram_device : public spi_ram_device
+{
+public:
+ spi_psram_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock = 0U);
+ virtual ~spi_psram_device();
+
+protected:
+ virtual void device_resolve_objects() override ATTR_COLD;
+ virtual void device_start() override ATTR_COLD;
+
+ virtual void start_command() override;
+
+private:
+ bool m_reset_enable;
+};
+
+
+DECLARE_DEVICE_TYPE(SPI_RAM, spi_ram_device)
DECLARE_DEVICE_TYPE(SPI_PSRAM, spi_psram_device)
#endif // MAME_MACHINE_SPI_PSRAM_H
diff --git a/src/devices/machine/spi_sdcard.cpp b/src/devices/machine/spi_sdcard.cpp
index ec4267b9ec31a..5fd7526ef2578 100644
--- a/src/devices/machine/spi_sdcard.cpp
+++ b/src/devices/machine/spi_sdcard.cpp
@@ -41,6 +41,7 @@
#include "logmacro.h"
+
namespace {
constexpr u8 DATA_RESPONSE_OK = 0x05;
@@ -152,16 +153,17 @@ spi_sdcard_device::spi_sdcard_device(const machine_config &mconfig, device_type
write_miso(*this),
m_image(*this, "image"),
m_preferred_type(SD_TYPE_V2),
- m_ignore_stop_bit(false),
m_blksize(512),
m_type(SD_TYPE_V2),
m_state(SD_STATE_IDLE),
m_ss(0), m_in_bit(0), m_clk_state(0),
m_in_latch(0), m_out_latch(0xff), m_cur_bit(0),
m_out_delay(0), m_out_count(0), m_out_ptr(0), m_write_ptr(0), m_xferblk(512), m_blknext(0),
+ m_crc_off(true),
m_bACMD(false)
{
std::fill(std::begin(m_csd), std::end(m_csd), 0);
+ std::fill(std::begin(m_cmd), std::end(m_cmd), 0xff);
}
spi_sdcard_device::~spi_sdcard_device()
@@ -187,6 +189,7 @@ void spi_sdcard_device::device_start()
save_item(NAME(m_write_ptr));
save_item(NAME(m_xferblk));
save_item(NAME(m_blknext));
+ save_item(NAME(m_crc_off));
save_item(NAME(m_bACMD));
}
@@ -238,6 +241,7 @@ std::error_condition spi_sdcard_device::image_loaded(device_image_interface &ima
}
m_blksize = m_xferblk = info.sectorbytes;
+ m_crc_off = true;
// set up common CSD fields
m_csd[0] = 0x00; // 127: CSD_STRUCTURE:2 (00b) 0:6
@@ -354,6 +358,26 @@ void spi_sdcard_device::spi_clock_w(int state)
m_clk_state = state;
}
+void spi_sdcard_device::spi_ss_w(int state)
+{
+ if (!m_ss && state)
+ {
+ LOGMASKED(LOG_SPI, "SDCARD: selected\n");
+ std::fill(std::begin(m_cmd), std::end(m_cmd), 0xff);
+ m_state = SD_STATE_IDLE;
+ m_in_latch = 0;
+ m_cur_bit = 0;
+ m_out_latch = 0xff;
+ m_out_delay = 0;
+ m_out_count = 0;
+ }
+ else if (m_ss && !state)
+ {
+ LOGMASKED(LOG_SPI, "SDCARD: deselected\n");
+ }
+ m_ss = state;
+}
+
void spi_sdcard_device::latch_in()
{
m_in_latch &= ~0x01;
@@ -446,7 +470,7 @@ void spi_sdcard_device::shift_out()
void spi_sdcard_device::do_command()
{
- if (((m_cmd[0] & 0xc0) == 0x40) && ((m_cmd[5] & 1) || m_ignore_stop_bit))
+ if (((m_cmd[0] & 0xc0) == 0x40) && ((m_cmd[5] & 1) || m_crc_off))
{
LOGMASKED(LOG_COMMAND, "SDCARD: cmd %02d %02x %02x %02x %02x %02x\n", m_cmd[0] & 0x3f, m_cmd[1], m_cmd[2], m_cmd[3], m_cmd[4], m_cmd[5]);
bool clean_cmd = true;
@@ -682,8 +706,8 @@ void spi_sdcard_device::do_command()
break;
case 59: // CMD59 - CRC_ON_OFF
+ m_crc_off = !BIT(m_cmd[4], 0);
m_data[0] = 0;
- // TODO CRC 1-on, 0-off
send_data(1, SD_STATE_STBY);
break;
diff --git a/src/devices/machine/spi_sdcard.h b/src/devices/machine/spi_sdcard.h
index fc907370972af..8eb9ec90d346c 100644
--- a/src/devices/machine/spi_sdcard.h
+++ b/src/devices/machine/spi_sdcard.h
@@ -18,12 +18,11 @@ class spi_sdcard_device : public device_t
void set_prefer_sd() { m_preferred_type = SD_TYPE_V2; }
void set_prefer_sdhc() { m_preferred_type = SD_TYPE_HC; }
- void set_ignore_stop_bit(bool ignore) { m_ignore_stop_bit = ignore; }
// SPI 4-wire interface
auto spi_miso_callback() { return write_miso.bind(); }
void spi_clock_w(int state);
- void spi_ss_w(int state) { m_ss = state; }
+ void spi_ss_w(int state);
void spi_mosi_w(int state) { m_in_bit = state; }
bool get_card_present() { return m_image->exists(); }
@@ -62,7 +61,6 @@ class spi_sdcard_device : public device_t
// configuration
sd_type m_preferred_type;
- bool m_ignore_stop_bit;
// mounted image info
std::vector m_sectorbuf;
@@ -80,6 +78,7 @@ class spi_sdcard_device : public device_t
u16 m_out_count, m_out_ptr, m_write_ptr;
u16 m_xferblk;
u32 m_blknext;
+ bool m_crc_off;
bool m_bACMD;
};
diff --git a/src/emu/diserial.cpp b/src/emu/diserial.cpp
index 4f31c91ca023a..2d900f2745984 100644
--- a/src/emu/diserial.cpp
+++ b/src/emu/diserial.cpp
@@ -45,21 +45,6 @@ device_serial_interface::device_serial_interface(const machine_config &mconfig,
m_tra_clock_state(false),
m_rcv_clock_state(false)
{
- /* if sum of all bits in the byte is even, then the data
- has even parity, otherwise it has odd parity */
- for (int i=0; i<256; i++)
- {
- int sum = 0;
- int data = i;
-
- for (int b=0; b<8; b++)
- {
- sum+=data & 0x01;
- data = data>>1;
- }
-
- m_serial_parity_table[i] = sum & 0x01;
- }
}
device_serial_interface::~device_serial_interface()
@@ -317,13 +302,13 @@ void device_serial_interface::receive_register_extract()
receive_register_reset();
/* strip off stop bits and parity */
- assert(m_rcv_bit_count >0 && m_rcv_bit_count <= 16);
- data = m_rcv_register_data>>(16-m_rcv_bit_count);
+ assert(m_rcv_bit_count > 0 && m_rcv_bit_count <= 16);
+ data = m_rcv_register_data >> (16 - m_rcv_bit_count);
/* mask off other bits so data byte has 0's in unused bits */
- data &= ~(0xff<adjust(m_tra_rate, 0, m_tra_rate);
m_tra_bit_count_transmitted = 0;
@@ -390,40 +373,35 @@ void device_serial_interface::transmit_register_setup(u8 data_byte)
m_tra_flags &=~TRANSMIT_REGISTER_EMPTY;
/* start bit */
- for (i=0; i>1;
+ transmit_register_add_bit(BIT(transmit_data, 0));
+ transmit_data >>= 1;
}
/* parity */
- if (m_df_parity!=PARITY_NONE)
+ if (m_df_parity != PARITY_NONE)
{
/* odd or even parity */
u8 parity = 0;
switch (m_df_parity)
{
case PARITY_ODD:
-
- /* get parity */
- /* if parity = 0, data has even parity - i.e. there is an even number of one bits in the data */
- /* if parity = 1, data has odd parity - i.e. there is an odd number of one bits in the data */
- parity = serial_helper_get_parity(data_byte) ^ 1;
+ // get parity
+ // if parity[0] = 0, data has even parity - i.e. there is an even number of one bits in the data
+ // if parity[0] = 1, data has odd parity - i.e. there is an odd number of one bits in the data
+ parity = BIT(population_count_32(data_byte), 0) ^ 1;
break;
case PARITY_EVEN:
- parity = serial_helper_get_parity(data_byte);
+ parity = BIT(population_count_32(data_byte), 0);
break;
case PARITY_MARK:
parity = 1;
@@ -436,7 +414,7 @@ void device_serial_interface::transmit_register_setup(u8 data_byte)
}
/* TX stop bit(s) */
- for (i=0; i
+
+#include "lnux4004.lh"
+
+
+namespace {
+
+class linux4004_state : public driver_device
+{
+public:
+ static constexpr feature_type unemulated_features() { return feature::GRAPHICS; }
+
+ linux4004_state(machine_config const &mconfig, device_type type, char const *tag)
+ : driver_device(mconfig, type, tag)
+ , m_cpu(*this, "u1")
+ , m_psram(*this, "u%u", 5U)
+ , m_sdcard(*this, "sd")
+ , m_uart(*this, "u9")
+ , m_memory(*this, "memory")
+ , m_status(*this, "status")
+ , m_rom_bank(*this, "rom")
+ , m_led_pc(*this, "pc%u", 0U)
+ , m_led_sdcard(*this, "storage")
+ {
+ }
+
+ void linux4004(machine_config &config) ATTR_COLD;
+
+protected:
+ virtual void machine_start() override ATTR_COLD;
+ virtual void machine_reset() override ATTR_COLD;
+
+private:
+ template void psram_sio_w(offs_t offset, u8 data, u8 mem_mask);
+ template void miso_w(int state);
+
+ u8 u3_r();
+ void u4002_1_3_w(u8 data);
+ void u4002_1_4_w(u8 data);
+ void u4002_2_3_w(u8 data);
+ template void led_pc_w(offs_t offset, u8 data);
+
+ void umips_rom(address_map &map) ATTR_COLD;
+ void umips_ram(address_map &map) ATTR_COLD;
+ void umips_status(address_map &map) ATTR_COLD;
+ void umips_rom_ports(address_map &map) ATTR_COLD;
+ void umips_ram_ports(address_map &map) ATTR_COLD;
+
+ required_device m_cpu;
+ required_device_array m_psram;
+ required_device m_sdcard;
+ required_device m_uart;
+ required_shared_ptr m_memory;
+ required_shared_ptr m_status;
+ required_memory_bank m_rom_bank;
+
+ output_finder<32> m_led_pc;
+ output_finder<> m_led_sdcard;
+
+ u8 m_psram_so[2];
+ u8 m_u3_in;
+};
+
+
+INPUT_PORTS_START(linux4004)
+ PORT_START("CONF")
+ PORT_CONFNAME(0x03, 0x00, "TLB Entries")
+ PORT_CONFSETTING( 0x03, "4")
+ PORT_CONFSETTING( 0x02, "8")
+ PORT_CONFSETTING( 0x01, "12")
+ PORT_CONFSETTING( 0x00, "16")
+ PORT_CONFNAME(0x04, 0x00, "U4002-2-4 Installed")
+ PORT_CONFSETTING( 0x00, DEF_STR(No))
+ PORT_CONFSETTING( 0x04, DEF_STR(Yes))
+INPUT_PORTS_END
+
+
+void linux4004_state::linux4004(machine_config &config)
+{
+ config.set_default_layout(layout_lnux4004);
+
+ I4004(config, m_cpu, 5.5296_MHz_XTAL / 7);
+ m_cpu->set_rom_map(&linux4004_state::umips_rom);
+ m_cpu->set_ram_memory_map(&linux4004_state::umips_ram);
+ m_cpu->set_ram_status_map(&linux4004_state::umips_status);
+ m_cpu->set_rom_ports_map(&linux4004_state::umips_rom_ports);
+ m_cpu->set_ram_ports_map(&linux4004_state::umips_ram_ports);
+
+ SPI_PSRAM(config, m_psram[0]);
+ m_psram[0]->set_size(8 << 20); // supports 4 MiB to 16 MiB
+ m_psram[0]->sio_cb().set(FUNC(linux4004_state::psram_sio_w<0>));
+
+ SPI_PSRAM(config, m_psram[1]);
+ m_psram[1]->set_size(4 << 20); // supports 128 KiB to 16 MiB
+ m_psram[1]->sio_cb().set(FUNC(linux4004_state::psram_sio_w<1>));
+
+ SPI_SDCARD(config, m_sdcard, 0U);
+ m_sdcard->spi_miso_callback().set(FUNC(linux4004_state::miso_w<3>));
+
+ SC16IS741A(config, m_uart, 3.072_MHz_XTAL);
+ m_uart->so_cb().set(FUNC(linux4004_state::miso_w<2>));
+ m_uart->irq_cb().set_inputline(m_cpu, I4004_TEST_LINE).invert();
+ m_uart->tx_cb().set("rs232", FUNC(rs232_port_device::write_txd));
+ m_uart->rts_cb().set("rs232", FUNC(rs232_port_device::write_rts));
+
+ auto &rs232(RS232_PORT(config, "rs232", default_rs232_devices, "terminal"));
+ rs232.rxd_handler().set(m_uart, FUNC(sc16is741a_device::rx_w));
+ rs232.cts_handler().set(m_uart, FUNC(sc16is741a_device::cts_w));
+
+ SOFTWARE_LIST(config, "sdcard_list").set_original("lnux4004");
+}
+
+
+void linux4004_state::machine_start()
+{
+ m_rom_bank->configure_entries(0, 2, memregion("4004firmware")->base(), 0x1000);
+ m_led_pc.resolve();
+ m_led_sdcard.resolve();
+
+ std::fill(std::begin(m_psram_so), std::end(m_psram_so), 1);
+ m_u3_in = 0;
+
+ save_item(NAME(m_psram_so));
+ save_item(NAME(m_u3_in));
+}
+
+void linux4004_state::machine_reset()
+{
+ ioport_value const conf(ioport("CONF")->read());
+ auto const tlb_empty(BIT(conf, 0, 2));
+ auto const u4002_2_4(BIT(conf, 2));
+
+ m_cpu->space(i4004_cpu_device::AS_RAM_MEMORY).unmap_readwrite(0x0000, 0x02ff);
+ m_cpu->space(i4004_cpu_device::AS_RAM_STATUS).unmap_readwrite(0x00, 0xbf);
+ m_cpu->space(i4004_cpu_device::AS_RAM_PORTS).unmap_readwrite(0x08, 0x0b);
+ m_cpu->space(i4004_cpu_device::AS_RAM_MEMORY).install_ram(0x0000, 0x02ff - (tlb_empty * 0x40), m_memory.target());
+ m_cpu->space(i4004_cpu_device::AS_RAM_STATUS).install_ram(0x00, 0xbf - (tlb_empty * 0x10), m_status.target());
+ m_cpu->space(i4004_cpu_device::AS_RAM_PORTS).install_write_handler(0x08, 0x0b - tlb_empty, emu::rw_delegate(*this, FUNC(linux4004_state::led_pc_w<4>)));
+ if (!u4002_2_4)
+ {
+ m_cpu->space(i4004_cpu_device::AS_RAM_MEMORY).unmap_readwrite(0x01c0, 0x01ff);
+ m_cpu->space(i4004_cpu_device::AS_RAM_STATUS).unmap_readwrite(0x70, 0x7f);
+ }
+
+ std::fill(m_memory.begin(), m_memory.end(), 0);
+ std::fill(m_status.begin(), m_status.end(), 0);
+
+ u4002_1_3_w(0);
+ u4002_1_4_w(0);
+ u4002_2_3_w(0);
+ for (auto &led : m_led_pc)
+ led = 0;
+}
+
+
+template
+void linux4004_state::psram_sio_w(offs_t offset, u8 data, u8 mem_mask)
+{
+ m_psram_so[N] = BIT(data | ~mem_mask, 1);
+ miso_w<0>(m_psram_so[0] & m_psram_so[1]);
+}
+
+template
+void linux4004_state::miso_w(int state)
+{
+ m_u3_in = (m_u3_in & ~(u8(1) << N)) | (u8(state ? 1 : 0) << N);
+}
+
+
+u8 linux4004_state::u3_r()
+{
+ return m_u3_in;
+}
+
+void linux4004_state::u4002_1_3_w(u8 data)
+{
+ m_sdcard->spi_mosi_w(BIT(~data, 0));
+ m_sdcard->spi_clock_w(BIT(~data, 1));
+ m_sdcard->spi_ss_w(BIT(data, 2) ? ASSERT_LINE : CLEAR_LINE);
+ m_led_sdcard = BIT(~data, 2);
+ m_rom_bank->set_entry(BIT(data, 3) ^ 0x01);
+}
+
+void linux4004_state::u4002_1_4_w(u8 data)
+{
+ m_uart->si_w(BIT(~data, 0)); // TODO: also connected to VFD
+ m_uart->sclk_w(BIT(~data, 1)); // TODO: also connected to VFD
+ // VFD_NCS_HV
+ m_uart->cs_w(BIT(~data, 3));
+}
+
+void linux4004_state::u4002_2_3_w(u8 data)
+{
+ m_psram[0]->si_w(BIT(~data, 0));
+ m_psram[1]->si_w(BIT(~data, 0));
+ m_psram[0]->sclk_w(BIT(~data, 1));
+ m_psram[1]->sclk_w(BIT(~data, 1));
+ m_psram[0]->ce_w(BIT(~data, 2));
+ m_psram[1]->ce_w(BIT(~data, 3));
+}
+
+template
+void linux4004_state::led_pc_w(offs_t offset, u8 data)
+{
+ for (unsigned i = 0; 4 > i; ++i)
+ m_led_pc[i | ((N + offset) << 2)] = BIT(data, i);
+}
+
+
+void linux4004_state::umips_rom(address_map &map)
+{
+ map.unmap_value_low();
+ map.global_mask(0x0fff);
+ map(0x0000, 0x0fff).bankr(m_rom_bank);
+}
+
+void linux4004_state::umips_ram(address_map &map)
+{
+ map.unmap_value_low();
+ map(0x0000, 0x02ff).ram().share(m_memory); // up to twelve 4002 chips
+}
+
+void linux4004_state::umips_status(address_map &map)
+{
+ map.unmap_value_low();
+ map(0x00, 0xbf).ram().share(m_status); // up to twelve 4002 chips
+}
+
+void linux4004_state::umips_rom_ports(address_map &map)
+{
+ map.unmap_value_high();
+ map.global_mask(0x0ff);
+ map(0x000, 0x0ff).r(FUNC(linux4004_state::u3_r));
+}
+
+void linux4004_state::umips_ram_ports(address_map &map)
+{
+ map(0x00, 0x03).w(FUNC(linux4004_state::led_pc_w<0>));
+ map(0x04, 0x04).w(FUNC(linux4004_state::u4002_1_3_w));
+ map(0x05, 0x05).w(FUNC(linux4004_state::u4002_1_4_w));
+ map(0x06, 0x06).w(FUNC(linux4004_state::u4002_2_3_w));
+ map(0x08, 0x0b).w(FUNC(linux4004_state::led_pc_w<4>));
+}
+
+
+ROM_START(lnux4004)
+ ROM_REGION(0x2000, "4004firmware", 0)
+ ROM_LOAD("umips.u4", 0x0000, 0x2000, CRC(27dd98c1) SHA1(a9d2b1990e7ae8ce4a53950430c5186d4cf55a01)) // AT28C64B
+ROM_END
+
+} // anonymous namespace
+
+
+SYST( 2024, lnux4004, 0, 0, linux4004, linux4004, linux4004_state, empty_init, "Dmitry Grinberg", "Linux/4004", MACHINE_NO_SOUND_HW | MACHINE_SUPPORTS_SAVE )
diff --git a/src/mame/layout/lnux4004.lay b/src/mame/layout/lnux4004.lay
new file mode 100644
index 0000000000000..6b69528fed131
--- /dev/null
+++ b/src/mame/layout/lnux4004.lay
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/mame/mame.lst b/src/mame/mame.lst
index df67a643bbd16..173d893ca0eac 100644
--- a/src/mame/mame.lst
+++ b/src/mame/mame.lst
@@ -19767,6 +19767,9 @@ gsz80 // Grant Searle's Simple Z-80 Machine
@source:homebrew/homez80.cpp
homez80 //
+@source:homebrew/linux4004.cpp
+lnux4004
+
@source:homebrew/lft_chiptune.cpp
powernin // Power Ninja Action Challenge, by [lft] (2009)
hwchiptn // The Hardware Chiptune Project, by [lft] and kryo (2007)