mirror of
https://github.com/holub/mame
synced 2025-06-01 02:21:48 +03:00
639 lines
14 KiB
C
639 lines
14 KiB
C
/**********************************************************************
|
|
|
|
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_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 clock)
|
|
: device_t(mconfig, UPD65031, "NEC uPD65031", tag, owner, clock, "upd65031", __FILE__),
|
|
m_read_kb(*this),
|
|
m_write_int(*this),
|
|
m_write_nmi(*this),
|
|
m_write_spkr(*this)
|
|
{
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// 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();
|
|
|
|
// 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)
|
|
{
|
|
// 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_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 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_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 -
|
|
//-------------------------------------------------
|
|
|
|
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_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)
|
|
(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;
|
|
}
|