diff options
Diffstat (limited to 'src/devices/cpu/sh/sh7014_sci.cpp')
-rw-r--r-- | src/devices/cpu/sh/sh7014_sci.cpp | 380 |
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; + } +} |