summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/cpu/m6502/st2xxx.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/cpu/m6502/st2xxx.cpp')
-rw-r--r--src/devices/cpu/m6502/st2xxx.cpp914
1 files changed, 914 insertions, 0 deletions
diff --git a/src/devices/cpu/m6502/st2xxx.cpp b/src/devices/cpu/m6502/st2xxx.cpp
new file mode 100644
index 00000000000..4dbaf96b3b3
--- /dev/null
+++ b/src/devices/cpu/m6502/st2xxx.cpp
@@ -0,0 +1,914 @@
+// license:BSD-3-Clause
+// copyright-holders:AJR
+/**********************************************************************
+
+ Sitronix ST2XXX LCD MCUs
+
+ This extended SoC family combines a W65C02S 8-bit CPU core
+ (including the Rockwell bit opcodes) with a wide variety of on-
+ chip peripherals. Common features besides internal RAM and ROM are
+ parallel ports, internal timers, a vectored interrupt controller,
+ LCD controllers (of varying degrees of sophistication), R/C/slow
+ XTAL clock generators, power management and PSG channels for
+ speaker output. Each MCU also has numerous pins dedicated to LCD
+ segment drivers (ST20XX, ST2104, ST2108), an external bus capable
+ of addressing several MB of off-chip memory using multiple chip
+ select signals (ST2100, ST22XX), or both (ST25XX, ST26XX). The
+ later ST23XX series, targeted mostly at digital greeting card
+ applications, eliminated on-chip LCD control but retained other
+ typical ST2XXX features.
+
+ On all ST2XXX MCUs but the smallest single-chip ST20XX models,
+ 4000–7FFF (nominally program memory) and 8000–FFFF (nominally
+ data memory) are bankswitched over all internal and external ROM,
+ and interrupt vectors are read from 7Fxx rather than FFxx. The
+ ST22XX and ST26XX series use a separate, auto-incrementing bank
+ register for DMA reads from the 8000–FFFF area, and will also
+ switch 4000–7FFF to a different bank during interrupt service if
+ the IRREN bit in the SYS register is set.
+
+ At some time between 2010 and 2012, Sitronix spun off all of its
+ SoC product line to mCore Technology Corporation.
+
+**********************************************************************/
+
+#include "emu.h"
+#include "st2xxx.h"
+
+#define LOG_IRQ (1U << 1)
+#define LOG_BT (1U << 2)
+#define LOG_LCDC (1U << 3)
+#define VERBOSE LOG_IRQ
+//#define VERBOSE (LOG_IRQ | LOG_BT | LOG_LCDC)
+#include "logmacro.h"
+
+st2xxx_device::st2xxx_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, address_map_constructor internal_map, int data_bits, bool has_banked_ram)
+ : w65c02s_device(mconfig, type, tag, owner, clock)
+ , m_data_config("data", ENDIANNESS_LITTLE, 8, data_bits, 0)
+ , m_in_port_cb(*this, 0xff)
+ , m_out_port_cb(*this)
+ , m_prr_mask(data_bits <= 14 ? 0 : ((u16(1) << (data_bits - 14)) - 1) | (has_banked_ram ? 0x8000 : 0))
+ , m_drr_mask(data_bits <= 15 ? 0 : ((u16(1) << (data_bits - 15)) - 1) | (has_banked_ram ? 0x8000 : 0))
+ , m_pdata{0}
+ , m_pctrl{0}
+ , m_psel{0}
+ , m_pfun{0}
+ , m_pmcr(0)
+ , m_bten(0)
+ , m_btsr(0)
+ , m_bt_mask(0)
+ , m_bt_ireq(0)
+ , m_pres_base(0)
+ , m_pres_started(attotime::zero)
+ , m_prs(0)
+ , m_sys(0)
+ , m_misc(0)
+ , m_ireq(0)
+ , m_iena(0)
+ , m_irq_level(0xff)
+ , m_lssa(0)
+ , m_lvpw(0)
+ , m_lxmax(0)
+ , m_lymax(0)
+ , m_lpan(0)
+ , m_lctr(0)
+ , m_lckr(0)
+ , m_lfra(0)
+ , m_lac(0)
+ , m_lpwm(0)
+ , m_lcd_ireq(0)
+ , m_lcd_timer(nullptr)
+ , m_sctr(0)
+ , m_sckr(0)
+ , m_ssr(0)
+ , m_smod(0)
+ , m_uctr(0)
+ , m_usr(0)
+ , m_irctr(0)
+ , m_bctr(0)
+{
+ program_config.m_internal_map = std::move(internal_map);
+}
+
+device_memory_interface::space_config_vector st2xxx_device::memory_space_config() const
+{
+ return space_config_vector {
+ std::make_pair(AS_PROGRAM, &program_config),
+ std::make_pair(AS_DATA, &m_data_config)
+ };
+}
+
+TIMER_CALLBACK_MEMBER(st2xxx_device::bt_interrupt)
+{
+ // BTSR must be cleared each time the interrupt is serviced
+ bool interrupt = (m_btsr == 0);
+
+ m_btsr |= 1 << param;
+
+ unsigned div = st2xxx_bt_divider(param);
+ assert(div != 0);
+ m_base_timer[param]->adjust(attotime::from_ticks(div, 32768), param);
+
+ if (interrupt)
+ {
+ LOGMASKED(LOG_BT, "Interrupt caused by %.1f Hz base timer\n", 32768.0 / div);
+ m_ireq |= m_bt_ireq;
+ update_irq_state();
+ }
+}
+
+void st2xxx_device::init_base_timer(u16 ireq)
+{
+ m_bt_ireq = ireq;
+
+ for (int n = 0; n < 8; n++)
+ {
+ if (st2xxx_bt_divider(n) != 0)
+ {
+ m_bt_mask |= 1 << n;
+ m_base_timer[n] = timer_alloc(FUNC(st2xxx_device::bt_interrupt), this);
+ }
+ }
+
+ assert(m_bt_mask != 0);
+ assert(m_bt_ireq != 0);
+}
+
+TIMER_CALLBACK_MEMBER(st2xxx_device::lcd_interrupt)
+{
+ m_ireq |= m_lcd_ireq;
+ update_irq_state();
+}
+
+void st2xxx_device::init_lcd_timer(u16 ireq)
+{
+ m_lcd_ireq = ireq;
+ assert(m_lcd_ireq != 0);
+
+ m_lcd_timer = timer_alloc(FUNC(st2xxx_device::lcd_interrupt), this);
+}
+
+void st2xxx_device::save_common_registers()
+{
+ mi_st2xxx *intf = downcast<mi_st2xxx *>(mintf.get());
+
+ save_item(NAME(m_pdata));
+ save_item(NAME(m_pctrl));
+ save_item(NAME(m_psel));
+ save_item(NAME(m_pfun));
+ save_item(NAME(m_pmcr));
+ if (m_prr_mask != 0)
+ {
+ if (BIT(st2xxx_sys_mask(), 1))
+ {
+ save_item(NAME(intf->irq_service));
+ save_item(NAME(intf->irr_enable));
+ save_item(NAME(intf->irr));
+ }
+ save_item(NAME(intf->prr));
+ }
+ if (m_drr_mask != 0)
+ save_item(NAME(intf->drr));
+ if (m_bt_mask != 0)
+ {
+ save_item(NAME(m_bten));
+ save_item(NAME(m_btsr));
+ }
+ save_item(NAME(m_pres_base));
+ save_item(NAME(m_pres_started));
+ save_item(NAME(m_prs));
+ save_item(NAME(m_sys));
+ if (st2xxx_misc_mask() != 0)
+ save_item(NAME(m_misc));
+ save_item(NAME(m_ireq));
+ save_item(NAME(m_iena));
+ save_item(NAME(m_irq_level));
+ if (st2xxx_lctr_mask() != 0)
+ {
+ save_item(NAME(m_lssa));
+ save_item(NAME(m_lvpw));
+ save_item(NAME(m_lxmax));
+ save_item(NAME(m_lymax));
+ if (st2xxx_lpan_mask() != 0)
+ save_item(NAME(m_lpan));
+ save_item(NAME(m_lctr));
+ save_item(NAME(m_lckr));
+ save_item(NAME(m_lfra));
+ save_item(NAME(m_lac));
+ save_item(NAME(m_lpwm));
+ }
+ if (st2xxx_has_spi())
+ {
+ save_item(NAME(m_sctr));
+ save_item(NAME(m_sckr));
+ save_item(NAME(m_ssr));
+ if (st2xxx_spi_iis())
+ save_item(NAME(m_smod));
+ }
+ if (st2xxx_uctr_mask() != 0)
+ {
+ save_item(NAME(m_uctr));
+ save_item(NAME(m_usr));
+ save_item(NAME(m_irctr));
+ save_item(NAME(m_bctr));
+ save_item(NAME(m_brs));
+ save_item(NAME(m_bdiv));
+ }
+}
+
+void st2xxx_device::device_reset()
+{
+ m6502_device::device_reset();
+
+ // reset port registers
+ std::fill(std::begin(m_pdata), std::end(m_pdata), 0xff);
+ std::fill(std::begin(m_pctrl), std::end(m_pctrl), 0);
+ std::fill(std::begin(m_psel), std::end(m_psel), 0xff);
+ std::fill(std::begin(m_pfun), std::end(m_pfun), 0);
+ for (auto &cb : m_out_port_cb)
+ cb(0xff);
+ m_pmcr = 0x80;
+
+ // reset bank registers
+ mi_st2xxx &m = downcast<mi_st2xxx &>(*mintf);
+ m.irr_enable = false;
+ m.irr = 0;
+ m.prr = 0;
+ m.drr = 0;
+
+ // reset interrupt registers
+ m_ireq = 0;
+ m_iena = 0;
+ update_irq_state();
+
+ // reset base timer
+ bten_w(0);
+ m_btsr = 0;
+
+ // reset prescaler
+ prs_w(0x80);
+
+ // reset miscellaneous registers
+ m_sys = 0;
+ m_misc = st2xxx_wdten_on_reset() ? 0x0c : 0;
+
+ // reset LCDC registers
+ m_lssa = 0;
+ m_lvpw = 0;
+ m_lxmax = 0;
+ m_lymax = 0;
+ m_lpan = 0;
+ m_lctr = 0x80;
+ m_lckr = 0;
+ m_lfra = 0;
+ m_lac = 0;
+ m_lpwm = 0;
+ m_lcd_timer->adjust(attotime::never);
+
+ // reset SPI
+ m_sctr = 0;
+ m_sckr = 0;
+ m_ssr = 0;
+ m_smod = 0;
+
+ // reset UART and BRG
+ m_uctr = 0;
+ m_usr = BIT(st2xxx_uctr_mask(), 4) ? 0x01 : 0;
+ m_irctr = 0;
+ m_bctr = 0;
+}
+
+u8 st2xxx_device::active_irq_level() const
+{
+ // IREQH interrupts have priority over IREQL interrupts
+ u16 ireq_active = swapendian_int16(m_ireq & m_iena);
+ if (ireq_active != 0)
+ return 31 - (8 ^ count_leading_zeros_32(ireq_active & -ireq_active));
+ else
+ return 0xff;
+}
+
+u8 st2xxx_device::read_vector(u16 adr)
+{
+ if (adr >= 0xfffe)
+ {
+ if (adr == 0xfffe)
+ {
+ set_irq_service(true);
+
+ // Make sure this doesn't change in between vector pull cycles
+ m_irq_level = irq_taken ? active_irq_level() : 0xff;
+ }
+
+ if (m_irq_level != 0xff)
+ {
+ adr -= (m_irq_level + 3) << 1;
+
+ LOGMASKED(LOG_IRQ, "Acknowledging %s interrupt (PC = $%04X, IREQ = $%04X, IENA = $%04X, vector pull from $%04X)\n",
+ st2xxx_irq_name(m_irq_level),
+ PPC,
+ m_ireq,
+ m_iena,
+ adr & 0x7fff);
+
+ if (BIT(adr, 0))
+ {
+ m_ireq &= ~(1 << m_irq_level);
+ update_irq_state();
+ }
+ }
+ }
+ return downcast<mi_st2xxx &>(*mintf).read_vector(adr);
+}
+
+u8 st2xxx_device::pdata_r(offs_t offset)
+{
+ u8 pdata = m_pdata[offset];
+ u8 pinmask = ~m_pctrl[offset] | (pdata & ~m_psel[offset]);
+ if (pinmask != 0)
+ pdata = (pdata & ~pinmask) | (m_in_port_cb[offset](0, pinmask) & pinmask);
+ return pdata;
+}
+
+void st2xxx_device::pdata_w(offs_t offset, u8 data)
+{
+ // Set output state (CMOS or open drain) or activate/deactive pullups for input pins
+ if (data != m_pdata[offset])
+ {
+ m_pdata[offset] = data;
+ m_out_port_cb[offset](0, data, m_pctrl[offset]);
+ }
+}
+
+u8 st2xxx_device::pctrl_r(offs_t offset)
+{
+ return m_pctrl[offset];
+}
+
+void st2xxx_device::pctrl_w(offs_t offset, u8 data)
+{
+ if (data != m_pctrl[offset])
+ {
+ m_pctrl[offset] = data;
+ m_out_port_cb[offset](0, m_pdata[offset], data);
+ }
+}
+
+u8 st2xxx_device::pfc_r()
+{
+ return m_pfun[0];
+}
+
+void st2xxx_device::pfc_w(u8 data)
+{
+ m_pfun[0] = data;
+}
+
+u8 st2xxx_device::pfd_r()
+{
+ return m_pfun[1];
+}
+
+void st2xxx_device::pfd_w(u8 data)
+{
+ m_pfun[1] = data;
+}
+
+u8 st2xxx_device::pl_r()
+{
+ return pdata_r(6);
+}
+
+void st2xxx_device::pl_w(u8 data)
+{
+ pdata_w(6, data);
+}
+
+u8 st2xxx_device::psc_r()
+{
+ return m_psel[2];
+}
+
+void st2xxx_device::psc_w(u8 data)
+{
+ m_psel[2] = data;
+}
+
+u8 st2xxx_device::pse_r()
+{
+ return m_psel[4];
+}
+
+void st2xxx_device::pse_w(u8 data)
+{
+ m_psel[4] = data;
+}
+
+u8 st2xxx_device::pcl_r()
+{
+ return pctrl_r(6);
+}
+
+void st2xxx_device::pcl_w(u8 data)
+{
+ pctrl_w(6, data);
+}
+
+u8 st2xxx_device::pmcr_r()
+{
+ return m_pmcr;
+}
+
+void st2xxx_device::pmcr_w(u8 data)
+{
+ m_pmcr = data & st2xxx_pmcr_mask();
+}
+
+u8 st2xxx_device::bten_r()
+{
+ return m_bten;
+}
+
+void st2xxx_device::bten_w(u8 data)
+{
+ data &= m_bt_mask;
+
+ for (int n = 0; n < 8; n++)
+ {
+ if (BIT(data, n) && !BIT(m_bten, n))
+ {
+ unsigned div = st2xxx_bt_divider(n);
+ assert(div != 0);
+ assert(m_base_timer[n] != nullptr);
+ m_base_timer[n]->adjust(attotime::from_ticks(div, 32768), n);
+ LOGMASKED(LOG_BT, "Base timer %d enabled at %.1f Hz (PC = $%04X)\n", n, 32768.0 / div, PPC);
+ }
+ else if (!BIT(data, n) && BIT(m_bten, n))
+ {
+ m_base_timer[n]->adjust(attotime::never);
+ LOGMASKED(LOG_BT, "Base timer %d disabled (PC = $%04X)\n", n, PPC);
+ }
+ }
+
+ m_bten = data;
+}
+
+u8 st2xxx_device::btsr_r()
+{
+ return m_btsr;
+}
+
+void st2xxx_device::btclr_w(u8 data)
+{
+ // Write 1 to clear each individual bit
+ m_btsr &= ~data;
+}
+
+void st2xxx_device::btclr_all_w(u8 data)
+{
+ // Only bit 7 has any effect
+ if (BIT(data, 7))
+ m_btsr = 0;
+}
+
+u16 st2xxx_device::pres_count() const
+{
+ return (m_pres_base + ((m_prs & 0x60) == 0x40 ? attotime_to_cycles(machine().time() - m_pres_started) : 0));
+}
+
+u8 st2xxx_device::prs_r()
+{
+ return pres_count() & 0xff;
+}
+
+void st2xxx_device::prs_w(u8 data)
+{
+ data &= st2xxx_prs_mask();
+
+ // Bit 7 produces prescaler reset pulse
+ if (BIT(data, 7))
+ {
+ st2xxx_tclk_stop();
+ m_pres_base = 0;
+ if ((m_prs & 0x60) == 0x40)
+ {
+ m_pres_started = machine().time();
+ st2xxx_tclk_start();
+ }
+ data &= 0x7f;
+ }
+
+ // Bit 6 enables prescaler; bit 5 selects clock source
+ if ((data & 0x60) == 0x40 && (m_prs & 0x60) != 0x40)
+ {
+ m_pres_started = machine().time();
+ st2xxx_tclk_start();
+ }
+ else if ((data & 0x60) == 0x40 && (m_prs & 0x60) != 0x40)
+ {
+ st2xxx_tclk_stop();
+ m_pres_base += attotime_to_cycles(machine().time() - m_pres_started);
+ }
+
+ m_prs = data;
+}
+
+u8 st2xxx_device::sys_r()
+{
+ return m_sys | 0x01;
+}
+
+void st2xxx_device::sys_w(u8 data)
+{
+ u8 mask = st2xxx_sys_mask();
+ m_sys = data & mask;
+ if (BIT(mask, 1))
+ downcast<mi_st2xxx &>(*mintf).irr_enable = BIT(data, 1);
+}
+
+u8 st2xxx_device::misc_r()
+{
+ return m_misc;
+}
+
+void st2xxx_device::misc_w(u8 data)
+{
+ m_misc = data & st2xxx_misc_mask();
+}
+
+u8 st2xxx_device::irrl_r()
+{
+ return downcast<mi_st2xxx &>(*mintf).irr & 0xff;
+}
+
+void st2xxx_device::irrl_w(u8 data)
+{
+ u16 &irr = downcast<mi_st2xxx &>(*mintf).irr;
+ irr = (data & m_prr_mask) | (irr & 0xff00);
+}
+
+u8 st2xxx_device::irrh_r()
+{
+ return downcast<mi_st2xxx &>(*mintf).irr >> 8;
+}
+
+void st2xxx_device::irrh_w(u8 data)
+{
+ u16 &irr = downcast<mi_st2xxx &>(*mintf).irr;
+ irr = ((u16(data) << 8) & m_prr_mask) | (irr & 0x00ff);
+}
+
+u8 st2xxx_device::prrl_r()
+{
+ return downcast<mi_st2xxx &>(*mintf).prr & 0xff;
+}
+
+void st2xxx_device::prrl_w(u8 data)
+{
+ u16 &prr = downcast<mi_st2xxx &>(*mintf).prr;
+ prr = (data & m_prr_mask) | (prr & 0xff00);
+}
+
+u8 st2xxx_device::prrh_r()
+{
+ return downcast<mi_st2xxx &>(*mintf).prr >> 8;
+}
+
+void st2xxx_device::prrh_w(u8 data)
+{
+ u16 &prr = downcast<mi_st2xxx &>(*mintf).prr;
+ prr = ((u16(data) << 8) & m_prr_mask) | (prr & 0x00ff);
+}
+
+u8 st2xxx_device::drrl_r()
+{
+ return downcast<mi_st2xxx &>(*mintf).drr & 0xff;
+}
+
+void st2xxx_device::drrl_w(u8 data)
+{
+ u16 &drr = downcast<mi_st2xxx &>(*mintf).drr;
+ drr = (data & m_drr_mask) | (drr & 0xff00);
+}
+
+u8 st2xxx_device::drrh_r()
+{
+ return downcast<mi_st2xxx &>(*mintf).drr >> 8;
+}
+
+void st2xxx_device::drrh_w(u8 data)
+{
+ u16 &drr = downcast<mi_st2xxx &>(*mintf).drr;
+ drr = ((u16(data) << 8) & m_drr_mask) | (drr & 0x00ff);
+}
+
+u8 st2xxx_device::ireql_r()
+{
+ return m_ireq & 0x00ff;
+}
+
+void st2xxx_device::ireql_w(u8 data)
+{
+ if ((m_ireq & ~data & 0x00ff) != 0)
+ {
+ for (int i = 0; i < 8; i++)
+ {
+ if (!BIT(data, i) && BIT(m_ireq, i))
+ LOGMASKED(LOG_IRQ, "%s interrupt cleared (PC = $%04X)\n", st2xxx_irq_name(i), PPC);
+ }
+ m_ireq &= data | 0xff00;
+ update_irq_state();
+ }
+}
+
+u8 st2xxx_device::ireqh_r()
+{
+ return m_ireq >> 8;
+}
+
+void st2xxx_device::ireqh_w(u8 data)
+{
+ if ((m_ireq & ~(u16(data) << 8) & 0xff00) != 0)
+ {
+ for (int i = 0; i < 8; i++)
+ {
+ if (!BIT(data, i) && BIT(m_ireq, i + 8))
+ LOGMASKED(LOG_IRQ, "%s interrupt cleared (PC = $%04X)\n", st2xxx_irq_name(i + 8), PPC);
+ }
+ m_ireq &= u16(data) << 8 | 0x00ff;
+ update_irq_state();
+ }
+}
+
+u8 st2xxx_device::ienal_r()
+{
+ return m_iena & 0x00ff;
+}
+
+void st2xxx_device::ienal_w(u8 data)
+{
+ data &= st2xxx_ireq_mask();
+ if (data != (m_iena & 0x00ff))
+ {
+ for (int i = 0; i < 8; i++)
+ {
+ if (BIT(data, i) != BIT(m_iena, i))
+ LOGMASKED(LOG_IRQ, "%s interrupt %sabled (PC = $%04X)\n",
+ st2xxx_irq_name(i),
+ BIT(data, i) ? "en" : "dis",
+ PPC);
+ }
+ m_iena = (m_iena & 0xff00) | data;
+ update_irq_state();
+ }
+}
+
+u8 st2xxx_device::ienah_r()
+{
+ return m_iena >> 8;
+}
+
+void st2xxx_device::ienah_w(u8 data)
+{
+ data &= st2xxx_ireq_mask() >> 8;
+ if (data != (m_iena >> 8))
+ {
+ for (int i = 0; i < 8; i++)
+ {
+ if (BIT(data, i) != BIT(m_iena, i + 8))
+ LOGMASKED(LOG_IRQ, "%s interrupt %sabled (PC = $%04X)\n",
+ st2xxx_irq_name(i + 8),
+ BIT(data, i) ? "en" : "dis",
+ PPC);
+ }
+ m_iena = (m_iena & 0x00ff) | (u16(data) << 8);
+ update_irq_state();
+ }
+}
+
+void st2xxx_device::lssal_w(u8 data)
+{
+ m_lssa = (m_lssa & 0xff00) | data;
+}
+
+void st2xxx_device::lssah_w(u8 data)
+{
+ m_lssa = (m_lssa & 0x00ff) | (u16(data) << 8);
+}
+
+void st2xxx_device::lvpw_w(u8 data)
+{
+ m_lvpw = data;
+}
+
+u8 st2xxx_device::lxmax_r()
+{
+ return m_lxmax;
+}
+
+void st2xxx_device::lxmax_w(u8 data)
+{
+ m_lxmax = data;
+ lfr_recalculate_period();
+}
+
+u8 st2xxx_device::lymax_r()
+{
+ return m_lymax;
+}
+
+void st2xxx_device::lymax_w(u8 data)
+{
+ m_lymax = data;
+ lfr_recalculate_period();
+}
+
+u8 st2xxx_device::lpan_r()
+{
+ return m_lpan;
+}
+
+void st2xxx_device::lpan_w(u8 data)
+{
+ m_lpan = data & st2xxx_lpan_mask();
+}
+
+u8 st2xxx_device::lctr_r()
+{
+ return m_lctr;
+}
+
+void st2xxx_device::lctr_w(u8 data)
+{
+ data &= st2xxx_lctr_mask();
+ u8 old_lctr = std::exchange(m_lctr, data);
+
+ if ((old_lctr & 0xbf) != (m_lctr & 0xbf))
+ lfr_recalculate_period();
+}
+
+void st2xxx_device::lckr_w(u8 data)
+{
+ m_lckr = data & st2xxx_lckr_mask();
+ lfr_recalculate_period();
+}
+
+void st2xxx_device::lfra_w(u8 data)
+{
+ m_lfra = data & 0x3f;
+ lfr_recalculate_period();
+}
+
+void st2xxx_device::lfr_recalculate_period()
+{
+ if (!BIT(m_lctr, 7))
+ {
+ unsigned clocks = st2xxx_lfr_clocks();
+ assert(clocks != 0);
+ attotime period = cycles_to_attotime(clocks);
+ LOGMASKED(LOG_LCDC, "LCD frame rate = %f Hz (PC = $%04X)\n", period.as_hz(), PPC);
+ m_lcd_timer->adjust(period, 0, period);
+ }
+ else
+ m_lcd_timer->adjust(attotime::never);
+}
+
+u8 st2xxx_device::lac_r()
+{
+ return m_lac | 0xe0;
+}
+
+void st2xxx_device::lac_w(u8 data)
+{
+ m_lac = data & 0x1f;
+}
+
+u8 st2xxx_device::lpwm_r()
+{
+ return m_lpwm | ~st2xxx_lpwm_mask();
+}
+
+void st2xxx_device::lpwm_w(u8 data)
+{
+ m_lpwm = data & st2xxx_lpwm_mask();
+}
+
+u8 st2xxx_device::sctr_r()
+{
+ return m_sctr;
+}
+
+void st2xxx_device::sctr_w(u8 data)
+{
+ // TXEMP on wakeup?
+ if (!BIT(m_sctr, 7) && BIT(data, 7))
+ m_ssr |= 0x20;
+
+ m_sctr = data;
+}
+
+u8 st2xxx_device::sckr_r()
+{
+ return m_sckr | 0x80;
+}
+
+void st2xxx_device::sckr_w(u8 data)
+{
+ m_sckr = data & 0x7f;
+}
+
+u8 st2xxx_device::ssr_r()
+{
+ return m_ssr | 0x88;
+}
+
+void st2xxx_device::ssr_w(u8 data)
+{
+ // Write any value to clear
+ m_ssr = 0;
+}
+
+u8 st2xxx_device::smod_r()
+{
+ return m_smod | 0xf0;
+}
+
+void st2xxx_device::smod_w(u8 data)
+{
+ m_smod = data & 0x0f;
+}
+
+u8 st2xxx_device::uctr_r()
+{
+ return m_uctr | ~st2xxx_uctr_mask();
+}
+
+void st2xxx_device::uctr_w(u8 data)
+{
+ m_uctr = data & st2xxx_uctr_mask();
+}
+
+u8 st2xxx_device::usr_r()
+{
+ return m_usr | 0x80;
+}
+
+void st2xxx_device::ustr_trg_w(u8 data)
+{
+ m_usr = (m_usr & 0x7a) | (data & 0x05);
+}
+
+void st2xxx_device::usr_clr_w(u8 data)
+{
+ m_usr &= ~data;
+}
+
+u8 st2xxx_device::irctr_r()
+{
+ return m_irctr | 0x3c;
+}
+
+void st2xxx_device::irctr_w(u8 data)
+{
+ m_irctr = data & 0xc7;
+}
+
+u8 st2xxx_device::udata_r()
+{
+ return 0;
+}
+
+void st2xxx_device::udata_w(u8 data)
+{
+ logerror("Writing %02X to UART transmitter (PC = %04X)\n", data, PPC);
+}
+
+u8 st2xxx_device::bctr_r()
+{
+ return m_bctr | ~st2xxx_bctr_mask();
+}
+
+void st2xxx_device::bctr_w(u8 data)
+{
+ m_bctr = data & st2xxx_bctr_mask();
+}
+
+u8 st2xxx_device::brs_r()
+{
+ return m_brs;
+}
+
+void st2xxx_device::brs_w(u8 data)
+{
+ m_brs = data;
+}
+
+u8 st2xxx_device::bdiv_r()
+{
+ return m_bdiv;
+}
+
+void st2xxx_device::bdiv_w(u8 data)
+{
+ m_bdiv = data;
+}