// license:BSD-3-Clause
// copyright-holders:Sandro Ronco
/**********************************************************************
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 "emu.h"
#include "upd65031.h"
// device type definition
DEFINE_DEVICE_TYPE(UPD65031, upd65031_device, "upd65031", "NEC uPD65031")
//**************************************************************************
// 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_write_int(ASSERT_LINE);
}
else
{
if (LOG) logerror("uPD65031 '%s': clear int\n", tag());
m_write_int(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_t clock) :
device_t(mconfig, UPD65031, tag, owner, clock),
m_read_kb(*this),
m_write_int(*this),
m_write_nmi(*this),
m_write_spkr(*this),
m_screen_update_cb(*this),
m_out_mem_cb(*this)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void upd65031_device::device_start()
{
// resolve callbacks
m_read_kb.resolve_safe(0);
m_write_int.resolve_safe();
m_write_nmi.resolve_safe();
m_write_spkr.resolve_safe();
// bind delegates
m_screen_update_cb.resolve();
m_out_mem_cb.resolve();
// 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;
m_mode = 0;
set_mode(STATE_AWAKE);
if (!m_out_mem_cb.isnull())
{
// reset bankswitch
m_out_mem_cb(0, 0, 0);
m_out_mem_cb(1, 0, 0);
m_out_mem_cb(2, 0, 0);
m_out_mem_cb(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_read_kb(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_write_spkr(m_speaker_state ? 1 : 0);
break;
}
}
//-------------------------------------------------
// screen_update
//-------------------------------------------------
uint32_t upd65031_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
if (!m_screen_update_cb.isnull() && (m_com & COM_LCDON))
m_screen_update_cb(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 -
//-------------------------------------------------
uint8_t upd65031_device::read(offs_t offset)
{
uint8_t 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_t data = m_read_kb(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 -
//-------------------------------------------------
void upd65031_device::write(offs_t offset, uint8_t data)
{
uint8_t 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_write_spkr(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.isnull())
m_out_mem_cb(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.isnull() && m_sr[port & 3] != data)
m_out_mem_cb(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;
}