// license:BSD-3-Clause
// copyright-holders:David Haywood
#include "emu.h"
#include "elan_eu3a05commonsys.h"
/*
Both the Elan EU3A05 and EU3A14 CPU types implement some kind of custom interrupt handling
It isn't clear if this is a completely new addition to the CPU, or just an interface / controller
sitting on top of the existing NMI or IRQ support in the core providing custom vectors.
The interrupt handlers are 16 4-byte entries starting at 0xffb0 in memory space
*/
/*
-----------------------
Custom Interrupt purposes
-----------------------
TETRIS (enables 5007 : 0a, 5008: 0f)
ffb0 (enabled)
nothing of note?
ffb4 (enabled)
stuff with 500e, 500c and 500d
ffb8 (enabled)
stuff with 50a4 / 50a5 / 50a6 and memory address e2
ffbc (enabled)
stuff with 50a4 / 50a5 / 50a6 and memory address e2 (similar to above, different bits)
ffc0 - doesn't exist
ffc4 - doesn't exist
ffc8 - doesn't exist
ffd0 - doesn't exist
ffd4 (enabled)
main irq?
ffd8
jumps straight to an rti
ffdc (enabled) - probably P2 input related? ADC interrupt?
accesses 501d / 501b
-----------------------
SPACE INVADERS
ffb0
rti
ffb4
rti
ffb8 (enabled by phoenix)
rti
ffbc (enabled by phoenix)
decreases 301 bit 02
stuff with 50a5
ffc0 (enabled by phoenix)
decreases 302
stuff with 50a5 bit 04
ffc4 (enabled by phoenix)
decreases 303
stuff with 50a5 bit 08
ffc8 (enabled by phoenix)
decreases 304
stuff with 50a5 bit 10
ffcc (enabled by phoenix)
uses 307
stuff with 50a5 bit 20
ffd0
dead loop
ffd4 (enabled by all games)
main interrupt
ffd8
dead loop
ffdc
dead loop
ffe0
dead loop
ffe4
rti
ffe8
dead loop
ffec
dead loop
-----------------------
AIR BLASTER JOYSTICK
all these 60xx jumps expect bank 00 or 0e or 3a or 7d to be active, so IRQs must be masked
ffb0: jmp to 6000 (ends up jumping to pointer from RAM)
ffb4: jmp to e08e (stuff with 500c/500d/506e etc.)
ffb8: jmp to 601c (stub handler) (has function in bank 0e - writes 00 then 01 to 50a5)
ffbc: jmp to 602a (stub handler)
ffc0: jmp to 6038 (stub handler)
ffc4: jmp to 6046 (stub handler)
ffc8: jmp to 6054 (stub handler)
ffcc: jmp to 6062 (stub handler)
ffd0: jmp to 6070 (stub handler)
ffd4: jmp to 607e (valid code - main IRQ?)
ffd8: jmp to 608c (stub handler)
ffdc: jmp to 609a (stub handler)
ffe0: jmp to 60a8 (stub handler)
ffe4: jmp to 60b6 (stub handler)
ffe8: jmp to 60c4 (stub handler)
ffec: jmp to 60d2 (stub handler)
fff0: 7d
fffa: e0 60 (60e0 vector) (stub handler)
fffc: 88 e1 (e188 startup vector)
fffe: 02 e0 (e002 vector)
-----------------------
GOLDEN TEE HOME
ffb0 rti
ffb4 rti
ffb8 rti
ffbc rti
ffc0 rti
ffc4 rti
ffc8 rti
ffcc rti
ffd0 rti
ffd4 main irq?
ffd8 rti
ffdc rti
ffe0 something with 5045 bit 0x08 and 9d in ram (increase or decrease) (ADC interrupt)
ffe4 something with 5045 bit 0x20 and 9c in ram (increase of decrease) (ADC interrupt)
ffe8 rti
ffec rti
regular NMI (e3f0 - jump to ($19e2) which seems to point to rti, but could move..)
regular IRQ (e3f3 - points to rti)
*/
DEFINE_DEVICE_TYPE(ELAN_EU3A05_COMMONSYS, elan_eu3a05commonsys_device, "elan_eu3a05commonsys", "Elan EU3A05/EU3A14 Common System")
elan_eu3a05commonsys_device::elan_eu3a05commonsys_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) :
device_t(mconfig, type, tag, owner, clock),
m_cpu(*this, finder_base::DUMMY_TAG),
m_bank(*this, finder_base::DUMMY_TAG),
m_is_pal(false),
m_allow_timer_irq(true)
{
}
elan_eu3a05commonsys_device::elan_eu3a05commonsys_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
elan_eu3a05commonsys_device(mconfig, ELAN_EU3A05_COMMONSYS, tag, owner, clock)
{
}
/*
rad_bb3 plays with address 0x5009 in this way, but never sets it, something else must set it (and 1->0 is 'activate' or 'acknowledge')
lda $5009
and #$ef
sta $5009
lda $5009
and #$df
sta $5009
0x5006 looks interesting too, again just seems to be masking out bits
(as one block of code)
lda $5006
and #$f0
sta $5006
lda $5006
and $#8f
sta $5006
todo: investigate rad_hnt3 which polls this address
*/
void elan_eu3a05commonsys_device::map(address_map &map)
{
map(0x00, 0x00).ram();
map(0x01, 0x01).ram();
// 5002
map(0x03, 0x03).r(FUNC(elan_eu3a05commonsys_device::elan_eu3a05_5003_r));
map(0x04, 0x04).ram();
// 5005
map(0x06, 0x06).ram();
map(0x07, 0x08).rw(FUNC(elan_eu3a05commonsys_device::intmask_r), FUNC(elan_eu3a05commonsys_device::intmask_w));
map(0x09, 0x09).r(FUNC(elan_eu3a05commonsys_device::radica_5009_unk_r)); // rad_hnt3 polls this on startup
map(0x0a, 0x0a).ram();
map(0x0b, 0x0b).rw(FUNC(elan_eu3a05commonsys_device::elan_eu3a05_pal_ntsc_r), FUNC(elan_eu3a05commonsys_device::elan_eu3a05_500b_unk_w)); // PAL / NTSC flag at least
map(0x0c, 0x0d).rw(FUNC(elan_eu3a05commonsys_device::elan_eu3a05_rombank_r), FUNC(elan_eu3a05commonsys_device::elan_eu3a05_rombank_w));
// 0e
}
void elan_eu3a05commonsys_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch (id)
{
case TIMER_UNK:
{
// rad_bb3 unmasks the interrupt, but the jumps use pointers in RAM, which haven't been set up at the time
// of unmasking, so we need to find some kind of global enable / disable, or timer enable.
if (m_allow_timer_irq)
generate_custom_interrupt(0);
break;
}
}
}
void elan_eu3a05commonsys_device::device_start()
{
save_item(NAME(m_rombank_lo));
save_item(NAME(m_rombank_hi));
save_item(NAME(m_intmask));
save_item(NAME(m_custom_irq));
save_item(NAME(m_custom_nmi));
save_item(NAME(m_custom_irq_vector));
save_item(NAME(m_custom_nmi_vector));
m_unk_timer = timer_alloc(TIMER_UNK);
m_unk_timer->adjust(attotime::never);
}
void elan_eu3a05commonsys_device::device_reset()
{
// all interrupts disabled?
m_intmask[0] = 0x00;
m_intmask[1] = 0x00;
m_custom_irq = 0;
m_custom_irq_vector = 0x0000;
m_custom_nmi = 0;
m_custom_nmi_vector = 0x0000;
m_rombank_lo = 0x7f;
m_rombank_hi = 0x00;
m_bank->set_bank(0x7f);
// generate at a fixed frequency for now, but can probably be configured. drives 3D stages in Air Blaster Joystick
m_unk_timer->adjust(attotime::from_hz(4096), 0, attotime::from_hz(2048));
}
READ8_MEMBER(elan_eu3a05commonsys_device::intmask_r)
{
return m_intmask[offset];
}
WRITE8_MEMBER(elan_eu3a05commonsys_device::intmask_w)
{
m_intmask[offset] = data;
}
void elan_eu3a05commonsys_device::generate_custom_interrupt(int level)
{
// Air Blaster uses brk in the code, which is problematic for custom IRQs
// m_custom_irq = 1;
// m_custom_irq_vector = 0xffd4;
// m_maincpu->set_input_line(INPUT_LINE_IRQ0,HOLD_LINE);
// 5007 5008
// --ee --v- ssss ss-t
// 10 5432 10
// vector = 0xffb0 + 4 * bit position from right
// each bit seems to relate to an IRQ level
// v = vbl interrupt bit (vector 0xffd4)
// t = used for object movement (enemies / bullets) on air blaster chase levels (vector 0xffb0) needs to be frequent, timer? or hbl?
// s = sound channel related? (air blaster enables s1)
// e = 'event' interrupts (rad_gtg)
// vbl irq masking is important for air blaster or it will suffer from random crashes
uint16_t intmask = (m_intmask[0] << 8) | m_intmask[1];
if (intmask & (1 << level))
{
m_custom_nmi = 1;
m_custom_nmi_vector = 0xffb0 + level * 4;
m_cpu->pulse_input_line(INPUT_LINE_NMI, attotime::zero);
}
}
READ8_MEMBER(elan_eu3a05commonsys_device::nmi_vector_r)
{
if (m_custom_nmi)
{
return m_custom_nmi_vector >> (offset*8);
}
else
{
if(machine().side_effects_disabled())
return 0x00;
fatalerror("NMI without custom vector!");
}
}
// not currently used
READ8_MEMBER(elan_eu3a05commonsys_device::irq_vector_r)
{
if(machine().side_effects_disabled())
return 0x00;
if (m_custom_irq)
{
return m_custom_irq_vector >> (offset*8);
}
else
{
if(machine().side_effects_disabled())
return 0x00;
fatalerror("IRQ without custom vector!");
}
}
WRITE8_MEMBER(elan_eu3a05commonsys_device::elan_eu3a05_rombank_w)
{
if (offset == 0x00)
{
// written with the banking?
//logerror("%s: elan_eu3a05_rombank_hi_w (set ROM bank) %02x\n", machine().describe_context(), data);
m_rombank_hi = data;
m_bank->set_bank(m_rombank_lo | (m_rombank_hi << 8));
}
else
{
//logerror("%s: elan_eu3a05_rombank_lo_w (select ROM bank) %02x\n", machine().describe_context(), data);
m_rombank_lo = data;
}
}
READ8_MEMBER(elan_eu3a05commonsys_device::elan_eu3a05_rombank_r)
{
if (offset == 0x00)
{
return m_rombank_hi;
}
else
{
return m_rombank_lo;
}
}
READ8_MEMBER(elan_eu3a05commonsys_device::elan_eu3a05_pal_ntsc_r)
{
// the text under the radica logo differs between regions, sometimes the titles too
logerror("%s: elan_eu3a05_pal_ntsc_r (region + more?)\n", machine().describe_context());
if (!m_is_pal) return 0xff; // NTSC
else return 0x00; // PAL
}
READ8_MEMBER(elan_eu3a05commonsys_device::elan_eu3a05_5003_r)
{
/* masked with 0x0f, 0x01 or 0x03 depending on situation..
I think it might just be an RNG because if you return 0x00
your shots can never hit the stage 3 enemies in Phoenix and
if you return 0xff they always hit. On the real hardware it
seems to be random. Could also just be a crude frame counter
used for the same purpose.
*/
logerror("%s: elan_eu3a05_5003_r (RNG?)\n", machine().describe_context());
return machine().rand();
}
WRITE8_MEMBER(elan_eu3a05commonsys_device::elan_eu3a05_500b_unk_w)
{
// this is the PAL / NTSC flag when read, so what are writes?
logerror("%s: elan_eu3a05_500b_unk_w %02x\n", machine().describe_context(), data);
}