summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/cpu/sh/sh7014_sci.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/cpu/sh/sh7014_sci.cpp')
-rw-r--r--src/devices/cpu/sh/sh7014_sci.cpp380
1 files changed, 380 insertions, 0 deletions
diff --git a/src/devices/cpu/sh/sh7014_sci.cpp b/src/devices/cpu/sh/sh7014_sci.cpp
new file mode 100644
index 00000000000..ffa19247382
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014_sci.cpp
@@ -0,0 +1,380 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH7014 SCI Controller
+
+ TODO list (not comprehensive):
+ - RX is untested
+ - Multiprocessor bit is not handled at all
+
+***************************************************************************/
+
+#include "emu.h"
+#include "sh7014_sci.h"
+
+#define LOG_REGISTERS (1U << 1)
+#define LOG_TXRX (1U << 2)
+#define LOG_CLOCK (1U << 3)
+
+// #define VERBOSE (LOG_GENERAL | LOG_REGISTERS | LOG_TXRX | LOG_CLOCK)
+
+#include "logmacro.h"
+
+DEFINE_DEVICE_TYPE(SH7014_SCI, sh7014_sci_device, "sh7014sci", "SH7014 SCI Controller")
+
+sh7014_sci_device::sh7014_sci_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, SH7014_SCI, tag, owner, clock)
+ , device_serial_interface(mconfig, *this)
+ , m_intc(*this, finder_base::DUMMY_TAG)
+ , m_sci_tx_cb(*this)
+ , m_hack_set_full_data_transmit_on_sync(false)
+{
+ m_external_clock_period = attotime::never;
+}
+
+void sh7014_sci_device::device_start()
+{
+ save_item(NAME(m_smr));
+ save_item(NAME(m_brr));
+ save_item(NAME(m_scr));
+ save_item(NAME(m_tdr));
+ save_item(NAME(m_ssr));
+ save_item(NAME(m_rdr));
+ save_item(NAME(m_is_dma_source_tx));
+ save_item(NAME(m_is_dma_source_rx));
+ save_item(NAME(m_clock_speed));
+ save_item(NAME(m_external_clock_period));
+}
+
+void sh7014_sci_device::device_reset()
+{
+ m_smr = 0;
+ m_brr = 0xff;
+ m_scr = 0;
+ m_tdr = 0xff;
+ m_ssr = SSR_TDRE | SSR_TEND;
+ m_rdr = 0;
+ m_is_dma_source_tx = m_is_dma_source_rx = false;
+ m_clock_speed = attotime::never;
+
+ update_data_format();
+ update_clock();
+}
+
+void sh7014_sci_device::map(address_map &map)
+{
+ map(0x00, 0x00).rw(FUNC(sh7014_sci_device::smr_r), FUNC(sh7014_sci_device::smr_w));
+ map(0x01, 0x01).rw(FUNC(sh7014_sci_device::brr_r), FUNC(sh7014_sci_device::brr_w));
+ map(0x02, 0x02).rw(FUNC(sh7014_sci_device::scr_r), FUNC(sh7014_sci_device::scr_w));
+ map(0x03, 0x03).rw(FUNC(sh7014_sci_device::tdr_r), FUNC(sh7014_sci_device::tdr_w));
+ map(0x04, 0x04).rw(FUNC(sh7014_sci_device::ssr_r), FUNC(sh7014_sci_device::ssr_w));
+ map(0x05, 0x05).r(FUNC(sh7014_sci_device::rdr_r));
+}
+
+///
+
+void sh7014_sci_device::set_send_full_data_transmit_on_sync_hack(bool enabled)
+{
+ // Synchronous clock mode forces a fixed 8-bit transmissions with no start, stop, parity, or multiprocessor bits.
+ // This flag makes it so that all 8 bits in the transmit register will be transferred at the start of a transmission
+ // instead of transmitting 1 bit at a time when in synchronous clock mode, allowing the transmit clock to be set to
+ // 1/8th of its normal speed.
+ m_hack_set_full_data_transmit_on_sync = enabled;
+}
+
+void sh7014_sci_device::set_external_clock_period(const attotime &period)
+{
+ m_external_clock_period = period;
+
+ // Update clock again if it's being used
+ if (m_scr & SCR_CKE1)
+ update_clock();
+}
+
+uint8_t sh7014_sci_device::smr_r()
+{
+ return m_smr;
+}
+
+void sh7014_sci_device::smr_w(uint8_t data)
+{
+ LOGMASKED(LOG_REGISTERS, "smr_w %02x %s %c%c%c%s /%d\n", data,
+ data & SMR_CA ? "sync" : "async",
+ data & SMR_CHR ? '7' : '8',
+ data & SMR_PE ? data & SMR_OE ? 'o' : 'e' : 'n',
+ data & SMR_STOP ? '2' : '1',
+ data & SMR_MP ? " mp" : "",
+ 1 << (2 * (data & SMR_CKS)));
+
+ bool do_clock_update = (data & (SMR_CKS | SMR_CA)) != (m_smr & (SMR_CKS | SMR_CA));
+ bool do_format_update = false;
+
+ if (!(data & SMR_CA))
+ do_format_update = (data & ~SMR_CKS) != (m_smr & ~SMR_CKS);
+ else
+ do_format_update = (data & SMR_CA) != (m_smr & SMR_CA);
+
+ m_smr = data;
+
+ if (do_format_update)
+ update_data_format();
+
+ if (do_clock_update)
+ update_clock();
+}
+
+uint8_t sh7014_sci_device::scr_r()
+{
+ return m_scr;
+}
+
+void sh7014_sci_device::scr_w(uint8_t data)
+{
+ const auto old = m_scr;
+
+ LOGMASKED(LOG_REGISTERS, "scr_w %02x%s%s%s%s%s%s clk=%d\n", data,
+ (data & SCR_TIE) ? " txi" : "",
+ (data & SCR_RIE) ? " rxi" : "",
+ (data & SCR_TE) ? " tx" : "",
+ (data & SCR_RE) ? " rx" : "",
+ (data & SCR_MPIE) ? " mpi" : "",
+ (data & SCR_TEIE) ? " tei" : "",
+ data & SCR_CKE);
+
+ if ((m_scr & SCR_TE) && !(data & SCR_TE))
+ m_ssr |= SSR_TEND | SSR_TDRE;
+
+ if ((m_scr & SCR_TIE) && !(data & SCR_TIE))
+ m_intc->set_interrupt(m_txi_int, CLEAR_LINE);
+
+ if ((m_scr & SCR_TEIE) && !(data & SCR_TEIE))
+ m_intc->set_interrupt(m_tei_int, CLEAR_LINE);
+
+ if ((m_scr & SCR_RIE) && !(data & SCR_RIE)) {
+ m_intc->set_interrupt(m_rxi_int, CLEAR_LINE);
+ m_intc->set_interrupt(m_eri_int, CLEAR_LINE);
+ }
+
+ m_scr = data;
+
+ if ((data & SCR_CKE) != (old & SCR_CKE))
+ update_clock();
+}
+
+uint8_t sh7014_sci_device::ssr_r()
+{
+ LOGMASKED(LOG_REGISTERS, "ssr_r %02x\n", m_ssr);
+ return m_ssr;
+}
+
+void sh7014_sci_device::ssr_w(uint8_t data)
+{
+ const auto old = m_ssr;
+ bool do_tx_update = false;
+
+ m_ssr = (data & (m_ssr & (SSR_TDRE | SSR_RDRF | SSR_ORER | SSR_FER | SSR_PER)))
+ | (m_ssr & (SSR_TEND | SSR_MPB))
+ | (data & SSR_MPBT);
+
+ if (!(m_scr & SCR_TE)) {
+ m_ssr |= SSR_TEND | SSR_TDRE;
+ } else if ((old & SSR_TDRE) || !(m_scr & SSR_TDRE)) {
+ do_tx_update = true;
+ m_ssr &= ~(SSR_TEND | SSR_TDRE);
+ }
+
+ if ((old & (SSR_ORER | SSR_FER | SSR_PER)) && !(data & (SSR_ORER | SSR_FER | SSR_PER)))
+ m_intc->set_interrupt(m_eri_int, CLEAR_LINE);
+
+ LOGMASKED(LOG_REGISTERS, "ssr_w %02x | %02x -> %02x\n", data, old, m_ssr);
+
+ if (do_tx_update)
+ update_tx_state();
+}
+
+uint8_t sh7014_sci_device::brr_r()
+{
+ return m_brr;
+}
+
+void sh7014_sci_device::brr_w(uint8_t data)
+{
+ LOGMASKED(LOG_REGISTERS, "brr_w %02x\n", data);
+ m_brr = data;
+ update_clock();
+}
+
+uint8_t sh7014_sci_device::tdr_r()
+{
+ LOGMASKED(LOG_TXRX, "tdr_r %02x\n", m_tdr);
+ return m_tdr;
+}
+
+void sh7014_sci_device::tdr_w(uint8_t data)
+{
+ LOGMASKED(LOG_TXRX, "tdr_w%s %02x %d %d\n", m_is_dma_source_tx ? " (dma)" : "", data, is_transmit_register_empty(), (m_scr & SCR_TE) != 0);
+
+ m_tdr = data;
+
+ if (!(m_scr & SCR_TE))
+ return;
+
+ if (m_is_dma_source_tx) {
+ // Normally this would happen with a write to SCR but DMAs can only write to TDR,
+ // so these flags are handled here as a special case for DMA writes only
+ m_ssr &= ~(SSR_TEND | SSR_TDRE);
+ m_is_dma_source_tx = false;
+
+ update_tx_state();
+ }
+}
+
+void sh7014_sci_device::tra_callback()
+{
+ if (!is_transmit_register_empty())
+ m_sci_tx_cb(transmit_register_get_data_bit());
+
+ if ((m_smr & SMR_CA) && m_hack_set_full_data_transmit_on_sync) {
+ while (!is_transmit_register_empty())
+ m_sci_tx_cb(transmit_register_get_data_bit());
+ }
+}
+
+void sh7014_sci_device::tra_complete()
+{
+ if (!(m_ssr & SSR_TDRE)) {
+ update_tx_state();
+ return;
+ }
+
+ LOGMASKED(LOG_TXRX, "transmit ended\n");
+
+ m_ssr |= SSR_TEND;
+ if (m_scr & SCR_TEIE)
+ m_intc->set_interrupt(m_tei_int, ASSERT_LINE);
+}
+
+void sh7014_sci_device::update_tx_state()
+{
+ if (!(m_scr & SCR_TE) || (m_ssr & SSR_TDRE) || !is_transmit_register_empty())
+ return;
+
+ LOGMASKED(LOG_TXRX, "transmitting %02x\n", m_tdr);
+
+ transmit_register_setup(m_tdr);
+
+ m_ssr = (m_ssr & ~SSR_TEND) | SSR_TDRE;
+
+ if (m_scr & SCR_TIE)
+ m_intc->set_interrupt(m_txi_int, ASSERT_LINE);
+}
+
+uint8_t sh7014_sci_device::rdr_r()
+{
+ auto r = m_rdr;
+
+ LOGMASKED(LOG_TXRX, "rdr_r%s %02x\n", m_is_dma_source_rx ? " (dma)" : "", m_rdr);
+
+ // DMA reads cause RDRF to be cleared
+ if (m_is_dma_source_rx)
+ m_ssr &= ~SSR_RDRF;
+
+ return r;
+}
+
+void sh7014_sci_device::rcv_complete()
+{
+ receive_register_extract();
+
+ if (!(m_scr & SCR_RE))
+ return;
+
+ if (is_receive_framing_error())
+ m_ssr |= SSR_FER;
+ if (is_receive_parity_error())
+ m_ssr |= SSR_PER;
+
+ if (!(m_ssr & SSR_RDRF)) {
+ m_rdr = get_received_char();
+ m_ssr |= SSR_RDRF;
+
+ if (m_scr & SCR_RIE)
+ m_intc->set_interrupt(m_rxi_int, ASSERT_LINE);
+
+ if ((m_ssr & SSR_FER) | (m_ssr & SSR_PER) || (m_ssr & SSR_ORER))
+ m_intc->set_interrupt(m_eri_int, ASSERT_LINE);
+ } else {
+ m_ssr |= SSR_ORER;
+ }
+}
+
+void sh7014_sci_device::update_data_format()
+{
+ if (m_smr & SMR_CA) {
+ // Synchronous clock is a fixed 8-bit data length transmission
+ set_data_frame(0, 8, PARITY_NONE, STOP_BITS_0);
+ return;
+ }
+
+ // Async
+ set_data_frame(
+ 1,
+ (m_smr & SMR_CHR) ? 8 : 7,
+ (m_smr & SMR_MP) ? PARITY_NONE : ((m_smr & SMR_PE) ? PARITY_ODD : PARITY_EVEN), // Multiprocessor mode does not use parity
+ (m_smr & SMR_STOP) ? STOP_BITS_1 : STOP_BITS_2
+ );
+}
+
+void sh7014_sci_device::update_clock()
+{
+ auto clock_mode = INTERNAL_SYNC_OUT;
+ if (m_smr & SMR_CA) {
+ if (m_scr & SCR_CKE1)
+ clock_mode = EXTERNAL_SYNC;
+ else
+ clock_mode = INTERNAL_SYNC_OUT;
+ } else {
+ if (m_scr & SCR_CKE1)
+ clock_mode = EXTERNAL_ASYNC;
+ else if (m_scr & SCR_CKE0)
+ clock_mode = INTERNAL_ASYNC_OUT;
+ else
+ clock_mode = INTERNAL_ASYNC;
+ }
+
+ if (clock_mode == EXTERNAL_ASYNC && !m_external_clock_period.is_never())
+ clock_mode = EXTERNAL_RATE_ASYNC;
+ else if (clock_mode == EXTERNAL_SYNC && !m_external_clock_period.is_never())
+ clock_mode = EXTERNAL_RATE_SYNC;
+
+ auto clock_speed = attotime::zero;
+ switch (clock_mode) {
+ case INTERNAL_ASYNC:
+ case INTERNAL_ASYNC_OUT:
+ case INTERNAL_SYNC_OUT:
+ {
+ int divider = (1 << (2 * (m_smr & SMR_CKS))) * (m_brr + 1);
+ clock_speed = attotime::from_ticks(divider, clock());
+ }
+ break;
+
+ case EXTERNAL_ASYNC:
+ case EXTERNAL_RATE_ASYNC:
+ case EXTERNAL_SYNC:
+ case EXTERNAL_RATE_SYNC:
+ clock_speed = m_external_clock_period;
+ break;
+ }
+
+ if (clock_speed != m_clock_speed && !clock_speed.is_never()) {
+ LOGMASKED(LOG_CLOCK, "Changing SCI ch %d rate set to %lf (%d %04x %04x)\n", m_channel_id, clock_speed.as_hz(), clock_mode, m_brr, m_smr);
+
+ if (m_hack_set_full_data_transmit_on_sync && (m_smr & SMR_CA))
+ set_rate(clock_speed * 8);
+ else
+ set_rate(clock_speed);
+
+ m_clock_speed = clock_speed;
+ }
+}