summaryrefslogtreecommitdiffstatshomepage
path: root/src/mess/machine/upd65031.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mess/machine/upd65031.c')
-rw-r--r--src/mess/machine/upd65031.c637
1 files changed, 637 insertions, 0 deletions
diff --git a/src/mess/machine/upd65031.c b/src/mess/machine/upd65031.c
new file mode 100644
index 00000000000..a048d2f8ef0
--- /dev/null
+++ b/src/mess/machine/upd65031.c
@@ -0,0 +1,637 @@
+/**********************************************************************
+
+ NEC uPD65031 'BLINK' emulation
+
+ the uPD65031 manages almost everything in the Z88:
+ - memory bankswitch
+ - interrupts
+ - RTC
+ - LCD
+ - keyboard
+ - serial
+ - speaker
+
+ TODO:
+ - coma and snooze mode
+ - speaker controlled by txd
+ - EPROM programming
+ - UART
+
+*********************************************************************/
+
+
+#include "upd65031.h"
+
+
+// device type definition
+const device_type UPD65031 = &device_creator<upd65031_device>;
+
+
+//**************************************************************************
+// MACROS / CONSTANTS
+//**************************************************************************
+
+#define LOG 0
+
+#define SPEAKER_ALARM_FREQ attotime::from_hz(3200)
+
+// internal registers
+enum
+{
+ // write registers
+ REG_PB0 = 0x70, // pixel base 0
+ REG_PB1 = 0x71, // pixel base 1
+ REG_PB2 = 0x72, // pixel base 2
+ REG_PB3 = 0x73, // pixel base 3
+ REG_SBR = 0x74, // screen base register
+
+ REG_COM = 0xb0, // command register
+ REG_INT = 0xb1, // interrupt control
+ REG_EPR = 0xb3, // EPROM programming
+ REG_TACK = 0xb4, // RTC acknowledge
+ REG_TMK = 0xb5, // RTC interrupt mask
+ REG_ACK = 0xb6, // interrupt acknowledge
+
+ REG_SR0 = 0xd0, // segment register 0
+ REG_SR1 = 0xd1, // segment register 1
+ REG_SR2 = 0xd2, // segment register 2
+ REG_SR3 = 0xd3, // segment register 3
+
+ REG_RXC = 0xe2, // UART receiver control
+ REG_TXD = 0xe3, // UART transmit data
+ REG_TXC = 0xe4, // UART transmit control
+ REG_UMK = 0xe5, // UART interrupt mask
+ REG_UAK = 0xe6, // UART interrupt acknowledge
+
+
+ // read registers
+ REG_STA = 0xb1, // interrupt status
+ REG_KBD = 0xb2, // keyboard read
+ REG_TSTA = 0xb5, // RTC interrupt status
+
+ REG_TIM0 = 0xd0, // RTC 5ms counter
+ REG_TIM1 = 0xd1, // RTC seconds counter (6 bits)
+ REG_TIM2 = 0xd2, // RTC minutes counter
+ REG_TIM3 = 0xd3, // RTC minutes/256 counter
+ REG_TIM4 = 0xd4, // RTC minutes/65536 counter (5 bits)
+
+ REG_RXD = 0xe0, // UART receive data register
+ REG_RXE = 0xe1, // UART extended receiver data
+ REG_UIT = 0xe5, // UART interrupt status
+};
+
+//mode
+enum
+{
+ STATE_AWAKE = 0,
+ STATE_SNOOZE,
+ STATE_COMA
+};
+
+// interrupt status
+#define STA_FLAPOPEN 0x80
+#define STA_A19 0x40
+#define STA_FLAP 0x20
+#define STA_UART 0x10
+#define STA_BTL 0x08
+#define STA_KEY 0x04
+#define STA_TIME 0x01
+
+// interrupt control
+#define INT_KWAIT 0x80
+#define INT_A19 0x40
+#define INT_FLAP 0x20
+#define INT_UART 0x10
+#define INT_BTL 0x08
+#define INT_KEY 0x04
+#define INT_TIME 0x02
+#define INT_GINT 0x01
+
+// command register
+#define COM_SRUN 0x80
+#define COM_SBIT 0x40
+#define COM_OVERP 0x20
+#define COM_RESTIM 0x10
+#define COM_PROGRAM 0x08
+#define COM_RAMS 0x04
+#define COM_VPPON 0x02
+#define COM_LCDON 0x01
+
+// EPROM programming register
+#define EPR_PD1 0x80
+#define EPR_PD0 0x40
+#define EPR_PGMD 0x20
+#define EPR_EOED 0x10
+#define EPR_SE3D 0x08
+#define EPR_PGMP 0x04
+#define EPR_EOEP 0x02
+#define EPR_SE3P 0x01
+
+// RTC interrupt status
+#define TSTA_MIN 0x04
+#define TSTA_SEC 0x02
+#define TSTA_TICK 0x01
+
+//**************************************************************************
+// INLINE HELPERS
+//**************************************************************************
+
+inline void upd65031_device::interrupt_refresh()
+{
+ if ((m_int & INT_GINT) && ((m_int & m_sta & 0x7c) || ((m_int & INT_TIME) && (m_sta & STA_TIME))))
+ {
+ if (LOG) logerror("uPD65031 '%s': set int\n", tag());
+
+ m_out_int_func(ASSERT_LINE);
+ }
+ else
+ {
+ if (LOG) logerror("uPD65031 '%s': clear int\n", tag());
+
+ m_out_int_func(CLEAR_LINE);
+ }
+}
+
+
+inline void upd65031_device::update_rtc_interrupt()
+{
+ // any ints occurred?
+ if ((m_int & INT_GINT) && (m_int & INT_TIME) && (m_tsta & (TSTA_MIN | TSTA_SEC | TSTA_TICK)))
+ m_sta |= STA_TIME;
+ else
+ m_sta &= ~STA_TIME;
+}
+
+
+inline void upd65031_device::set_mode(int mode)
+{
+ if (m_mode != mode)
+ {
+ m_mode = mode;
+
+ switch(mode)
+ {
+ case STATE_AWAKE:
+ //TODO
+ break;
+ case STATE_SNOOZE:
+ //TODO
+ break;
+ case STATE_COMA:
+ //TODO
+ break;
+ }
+ }
+}
+
+
+//**************************************************************************
+// LIVE DEVICE
+//**************************************************************************
+
+//-------------------------------------------------
+// upd65031_device - constructor
+//-------------------------------------------------
+
+upd65031_device::upd65031_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : device_t(mconfig, UPD65031, "NEC uPD65031", tag, owner, clock)
+{
+}
+
+
+//-------------------------------------------------
+// device_config_complete - perform any
+// operations now that the configuration is
+// complete
+//-------------------------------------------------
+
+void upd65031_device::device_config_complete()
+{
+ // inherit a copy of the static data
+ const upd65031_interface *intf = reinterpret_cast<const upd65031_interface *>(static_config());
+ if (intf != NULL)
+ {
+ *static_cast<upd65031_interface *>(this) = *intf;
+ }
+ // or initialize to defaults if none provided
+ else
+ {
+ m_screen_update_cb = NULL;
+ m_out_mem_cb = NULL;
+ memset(&m_in_kb_cb, 0, sizeof(m_in_kb_cb));
+ memset(&m_out_int_cb, 0, sizeof(m_out_int_cb));
+ memset(&m_out_nmi_cb, 0, sizeof(m_out_nmi_cb));
+ memset(&m_out_spkr_cb, 0, sizeof(m_out_spkr_cb));
+ }
+}
+
+
+//-------------------------------------------------
+// device_start - device-specific startup
+//-------------------------------------------------
+
+void upd65031_device::device_start()
+{
+ // resolve callbacks
+ m_out_int_func.resolve(m_out_int_cb, *this);
+ m_out_nmi_func.resolve(m_out_nmi_cb, *this);
+ m_out_spkr_func.resolve(m_out_spkr_cb, *this);
+ m_int_kb_func.resolve(m_in_kb_cb, *this);
+
+ // allocate timers
+ m_rtc_timer = timer_alloc(TIMER_RTC);
+ m_flash_timer = timer_alloc(TIMER_FLASH);
+ m_speaker_timer = timer_alloc(TIMER_SPEAKER);
+ m_rtc_timer->adjust(attotime::from_msec(5), 0, attotime::from_msec(5));
+ m_flash_timer->adjust(attotime::from_hz(2), 0, attotime::from_hz(2));
+ m_speaker_timer->reset();
+
+ // state saving
+ save_item(NAME(m_mode));
+ save_item(NAME(m_lcd_regs));
+ save_item(NAME(m_tim));
+ save_item(NAME(m_sr));
+ save_item(NAME(m_sta));
+ save_item(NAME(m_int));
+ save_item(NAME(m_ack));
+ save_item(NAME(m_tsta));
+ save_item(NAME(m_tmk));
+ save_item(NAME(m_tack));
+ save_item(NAME(m_com));
+ save_item(NAME(m_flash));
+}
+
+
+//-------------------------------------------------
+// device_reset - device-specific reset
+//-------------------------------------------------
+
+void upd65031_device::device_reset()
+{
+ memset(m_lcd_regs, 0, sizeof(m_lcd_regs));
+ memset(m_tim, 0, sizeof(m_tim));
+ memset(m_sr, 0, sizeof(m_sr));
+ m_sta = 0;
+ m_int = 0;
+ m_ack = 0;
+ m_tsta = 0;
+ m_tmk = TSTA_TICK | TSTA_SEC | TSTA_MIN;
+ m_tack = 0;
+ m_com = 0;
+ m_flash = 0;
+ set_mode(STATE_AWAKE);
+
+ if (m_out_mem_cb)
+ {
+ // reset bankswitch
+ (m_out_mem_cb)(*this, 0, 0, 0);
+ (m_out_mem_cb)(*this, 1, 0, 0);
+ (m_out_mem_cb)(*this, 2, 0, 0);
+ (m_out_mem_cb)(*this, 3, 0, 0);
+ }
+}
+
+
+//-------------------------------------------------
+// device_timer - handler timer events
+//-------------------------------------------------
+
+void upd65031_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
+{
+ switch (id)
+ {
+ case TIMER_RTC:
+
+ // if a key is pressed sets the interrupt
+ if ((m_int & INT_GINT) && (m_int & INT_KEY) && m_int_kb_func(0) != 0xff)
+ {
+ if (LOG) logerror("uPD65031 '%s': Keyboard interrupt!\n", tag());
+
+ // awakes CPU from snooze on key down
+ if (m_mode == STATE_SNOOZE)
+ set_mode(STATE_AWAKE);
+
+ m_sta |= STA_KEY;
+ }
+ else
+ {
+ m_sta &= ~STA_KEY;
+ }
+
+ // hold clock at reset? - in this mode it doesn't update
+ if (!(m_com & COM_RESTIM))
+ {
+ bool irq_change = false;
+
+ // update 5 millisecond counter
+ m_tim[0]++;
+
+ // tick
+ if (m_tim[0] & 1)
+ {
+ // set tick int has occurred
+ if (m_tmk & TSTA_TICK)
+ {
+ m_tsta |= TSTA_TICK;
+ irq_change = true;
+ }
+ }
+
+ if (m_tim[0] == 200)
+ {
+ m_tim[0] = 0;
+
+ // set seconds int has occurred
+ if (m_tmk & TSTA_SEC)
+ {
+ m_tsta |= TSTA_SEC;
+ irq_change = true;
+ }
+
+ m_tim[1]++;
+
+ if (m_tim[1] == 60)
+ {
+ // set minutes int has occurred
+ if (m_tmk & TSTA_MIN)
+ {
+ m_tsta |= TSTA_MIN;
+ irq_change = true;
+ }
+ m_tim[1] = 0;
+ m_tim[2]++;
+
+ if (m_tim[2] == 0) // overflowed from 255
+ {
+ m_tim[3]++;
+
+ if (m_tim[3] == 0) // overflowed from 255
+ {
+ m_tim[4]++;
+
+ if (m_tim[4] == 32)
+ m_tim[4] = 0;
+ }
+ }
+ }
+ }
+
+ if ((m_int & INT_GINT) && (m_int & INT_TIME) && irq_change && !(m_sta & STA_FLAPOPEN))
+ {
+ set_mode(STATE_AWAKE);
+
+ update_rtc_interrupt();
+ }
+
+ // refresh interrupt
+ interrupt_refresh();
+ }
+ break;
+ case TIMER_FLASH:
+ m_flash = !m_flash;
+ break;
+ case TIMER_SPEAKER:
+ m_speaker_state = !m_speaker_state;
+ m_out_spkr_func(m_speaker_state ? 1 : 0);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// screen_update
+//-------------------------------------------------
+
+UINT32 upd65031_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
+{
+ if (m_screen_update_cb && (m_com & COM_LCDON))
+ (m_screen_update_cb)(*this, bitmap, m_lcd_regs[4], m_lcd_regs[2], m_lcd_regs[3], m_lcd_regs[0], m_lcd_regs[1], m_flash);
+ else
+ bitmap.fill(0, cliprect);
+
+ return 0;
+}
+
+//-------------------------------------------------
+// read -
+//-------------------------------------------------
+
+READ8_MEMBER( upd65031_device::read )
+{
+ UINT8 port = offset & 0xff;
+
+ switch (port)
+ {
+ case REG_STA: // read interrupt status
+ return m_sta;
+
+ case REG_KBD:
+ {
+ // if set, reading the keyboard will put into snooze
+ if (m_int & INT_KWAIT)
+ {
+ set_mode(STATE_SNOOZE);
+
+ if (LOG) logerror("uPD65031 '%s': entering snooze!\n", tag());
+ }
+
+ UINT8 data = m_int_kb_func(offset>>8);
+
+ if (LOG) logerror("uPD65031 '%s': key r %02x: %02x\n", tag(), offset>>8, data);
+
+ return data;
+ }
+
+ // read real time clock status
+ case REG_TSTA:
+ if (LOG) logerror("uPD65031 '%s': tsta r: %02x\n", tag(), m_tsta);
+ return m_tsta & 0x07;
+
+ // read real time clock counters
+ case REG_TIM0:
+ if (LOG) logerror("uPD65031 '%s': TIM0 r: %02x\n", tag(), m_tim[0]);
+ return m_tim[0];
+ case REG_TIM1:
+ if (LOG) logerror("uPD65031 '%s': TIM1 r: %02x\n", tag(), m_tim[1]);
+ return m_tim[1];
+ case REG_TIM2:
+ if (LOG) logerror("uPD65031 '%s': TIM2 r: %02x\n", tag(), m_tim[2]);
+ return m_tim[2];
+ case REG_TIM3:
+ if (LOG) logerror("uPD65031 '%s': TIM3 r: %02x\n", tag(), m_tim[3]);
+ return m_tim[3];
+ case REG_TIM4:
+ if (LOG) logerror("uPD65031 '%s': TIM4 r: %02x\n", tag(), m_tim[4]);
+ return m_tim[4];
+
+ // UART
+ case REG_RXD:
+ case REG_RXE:
+ case REG_UIT:
+ // TODO
+ return 0;
+
+ default:
+ logerror("uPD65031 '%s': blink r: %04x\n", tag(), offset);
+ return 0;
+ }
+}
+
+
+//-------------------------------------------------
+// write -
+//-------------------------------------------------
+
+WRITE8_MEMBER( upd65031_device::write )
+{
+ UINT8 port = offset & 0xff;
+
+ switch (port)
+ {
+ // gfx registers
+ case REG_PB0:
+ case REG_PB1:
+ case REG_PB2:
+ case REG_PB3:
+ case REG_SBR:
+ m_lcd_regs[port - REG_PB0] = ((offset & 0xff00) | data);
+ break;
+
+ case REG_COM: // command register
+ if (LOG) logerror("uPD65031 '%s': com w: %02x\n", tag(), data);
+
+ // reset clock?
+ if (data & COM_RESTIM)
+ m_tim[0] = m_tim[1] = m_tim[2] = m_tim[3] = m_tim[4] = 0;
+
+ if ((data & COM_SRUN) && !(data & COM_SBIT))
+ {
+ // constant tone used for keyclick and alarm
+ m_speaker_timer->adjust(SPEAKER_ALARM_FREQ, 0, SPEAKER_ALARM_FREQ);
+ }
+ else
+ {
+ if (!(data & COM_SRUN))
+ {
+ // speaker controlled by SBIT
+ m_speaker_state = BIT(data, 6);
+ m_out_spkr_func(m_speaker_state);
+ }
+ else
+ {
+ // speaker controlled by txd line
+ // TODO
+ }
+
+ m_speaker_timer->reset();
+ }
+
+ // bit 2 controls the lower 8kb of memory
+ if (BIT(m_com^data, 2) && m_out_mem_cb)
+ (m_out_mem_cb)(*this, 0, m_sr[0], BIT(data, 2));
+
+ m_com = data;
+ break;
+
+ case REG_INT: // interrupt control
+ if (LOG) logerror("uPD65031 '%s': int w: %02x\n", tag(), data);
+
+ m_int = data;
+
+ // refresh ints
+ update_rtc_interrupt();
+ interrupt_refresh();
+ break;
+
+ case REG_EPR: // EPROM programming register
+ if (LOG) logerror("uPD65031 '%s': epr w: %02x\n", tag(), data);
+ // TODO
+ break;
+
+ case REG_TACK: // rtc interrupt acknowledge
+ if (LOG) logerror("uPD65031 '%s': tack w: %02x\n", tag(), data);
+
+ // clear ints that have occurred
+ m_tsta &= ~(data & 0x07);
+ m_tack = data;
+
+ // refresh ints
+ update_rtc_interrupt();
+ interrupt_refresh();
+ break;
+
+ case REG_TMK: // write rtc interrupt mask
+ if (LOG) logerror("uPD65031 '%s': tmk w: %02x\n", tag(), data);
+
+ m_tmk = data & 0x07;
+ break;
+
+ case REG_ACK: // acknowledge ints
+ if (LOG) logerror("uPD65031 '%s': ack w: %02x\n", tag(), data);
+
+ m_ack = data;
+ m_sta &= ~(data & 0x7f);
+
+ // refresh ints
+ interrupt_refresh();
+ break;
+
+ // Segment registers
+ case REG_SR0:
+ case REG_SR1:
+ case REG_SR2:
+ case REG_SR3:
+ if (m_out_mem_cb && m_sr[port & 3] != data)
+ (m_out_mem_cb)(*this, port & 3, data, BIT(m_com, 2));
+
+ m_sr[port & 3] = data;
+ break;
+
+ // UART
+ case REG_RXC:
+ case REG_TXD:
+ case REG_TXC:
+ case REG_UMK:
+ case REG_UAK:
+ if (LOG) logerror("uPD65031 '%s': UART w: %02x %02x\n", tag(), port & 7 , data);
+ // TODO
+ break;
+
+ default:
+ logerror("uPD65031 '%s': blink w: %04x %02x\n", tag(), offset, data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// flp line
+//-------------------------------------------------
+
+WRITE_LINE_MEMBER( upd65031_device::flp_w )
+{
+ if (!(m_sta & STA_FLAPOPEN) && state)
+ {
+ // set interrupt on rising edge
+ m_sta |= STA_FLAP;
+
+ interrupt_refresh();
+ }
+
+ if (state)
+ m_sta |= STA_FLAPOPEN;
+ else
+ m_sta &= ~STA_FLAPOPEN;
+}
+
+//-------------------------------------------------
+// battery low line
+//-------------------------------------------------
+
+WRITE_LINE_MEMBER( upd65031_device::btl_w )
+{
+ if (state)
+ m_sta |= STA_BTL;
+ else
+ m_sta &= ~STA_BTL;
+}