// license: BSD-3-Clause
// copyright-holders: Dirk Best
/***************************************************************************
Data East IRQ Controller
TODO:
- Lightgun support is only used by Locked 'n Loaded.
irq is likely an h AND v beam trigger, game counts via raster irqs until
it finds one of these, and where gun trigger x position is the H pixel
latch.
Two problems here:
1. (assuming being correct) default calibration looks way offset.
2. We currently give minmax defaults to Y latches to order to avoid
making the game to crash. These limits doesn't seem very sane,
for example lower limit is / 3 the full screen height (248 / 3 = 82).
Being an highly timing dependant behaviour ARM core is probably at
fault here.
- Understand if there's an additional switch for player 1 and player 2
wrt lightgun trigger.
***************************************************************************/
#include "emu.h"
#include "deco_irq.h"
//**************************************************************************
// DEVICE DEFINITIONS
//**************************************************************************
DEFINE_DEVICE_TYPE(DECO_IRQ, deco_irq_device, "deco_irq", "Data East IRQ Controller")
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// deco_irq_device - constructor
//-------------------------------------------------
deco_irq_device::deco_irq_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
device_t(mconfig, DECO_IRQ, tag, owner, clock),
m_screen(*this, finder_base::DUMMY_TAG),
m_scanline_timer(nullptr),
m_lightgun1_cb(*this), m_lightgun2_cb(*this),
m_lightgun_irq_cb(*this),
m_raster1_irq_cb(*this), m_raster2_irq_cb(*this),
m_vblank_irq_cb(*this),
m_lightgun_irq(false), m_raster_irq(false), m_vblank_irq(false),
m_raster_irq_target(RASTER1_IRQ), m_raster_irq_masked(true),
m_raster_irq_scanline(0),
m_lightgun_latch(0)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void deco_irq_device::device_start()
{
// make sure our screen is started
if (!m_screen->started())
throw device_missing_dependencies();
// resolve callbacks
m_lightgun1_cb.resolve_safe(0);
m_lightgun2_cb.resolve_safe(0);
m_lightgun_irq_cb.resolve_safe();
m_raster1_irq_cb.resolve_safe();
m_raster2_irq_cb.resolve_safe();
m_vblank_irq_cb.resolve_safe();
// allocate scanline timer and start it
m_scanline_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(deco_irq_device::scanline_callback), this));
m_scanline_timer->adjust(m_screen->time_until_pos(0));
// register for save states
save_item(NAME(m_lightgun_irq));
save_item(NAME(m_raster_irq));
save_item(NAME(m_vblank_irq));
save_item(NAME(m_raster_irq_target));
save_item(NAME(m_raster_irq_masked));
save_item(NAME(m_raster_irq_scanline));
save_item(NAME(m_lightgun_latch));
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void deco_irq_device::device_reset()
{
}
//-------------------------------------------------
// scanline_callback - called on each scanline
//-------------------------------------------------
TIMER_CALLBACK_MEMBER( deco_irq_device::scanline_callback )
{
const rectangle visible = m_screen->visible_area();
const u16 y = m_screen->vpos();
// raster irq?
if (m_raster_irq_scanline >= visible.top() && m_raster_irq_scanline <= visible.bottom() && y == m_raster_irq_scanline)
{
if (!m_raster_irq_masked)
{
m_raster_irq = true;
switch (m_raster_irq_target)
{
case RASTER1_IRQ: m_raster1_irq_cb(ASSERT_LINE); break;
case RASTER2_IRQ: m_raster2_irq_cb(ASSERT_LINE); break;
}
}
}
// lightgun?
if (m_lightgun_latch >= visible.top() && m_lightgun_latch <= visible.bottom() && y == m_lightgun_latch)
{
m_lightgun_irq = true;
m_lightgun_irq_cb(ASSERT_LINE);
}
// vblank-in?
if (y == (visible.bottom() + 1))
{
m_vblank_irq = true;
m_vblank_irq_cb(ASSERT_LINE);
}
// wait for next line
m_scanline_timer->adjust(m_screen->time_until_pos(y + 1));
}
//**************************************************************************
// INTERFACE
//**************************************************************************
void deco_irq_device::raster_irq_ack()
{
m_raster_irq = false;
m_raster1_irq_cb(CLEAR_LINE);
m_raster2_irq_cb(CLEAR_LINE);
}
void deco_irq_device::map(address_map &map)
{
map(0x0, 0x0).w(FUNC(deco_irq_device::control_w));
map(0x1, 0x1).rw(FUNC(deco_irq_device::scanline_r), FUNC(deco_irq_device::scanline_w));
map(0x2, 0x2).rw(FUNC(deco_irq_device::raster_irq_ack_r), FUNC(deco_irq_device::vblank_irq_ack_w));
map(0x3, 0x3).r(FUNC(deco_irq_device::status_r));
}
void deco_irq_device::control_w(u8 data)
{
// 765----- unused?
// ---4---- raster irq target
// ----3--- unused?
// -----2-- unknown
// ------1- raster irq mask
// -------0 unused?
m_raster_irq_target = BIT(data, 4);
m_raster_irq_masked = bool(BIT(data, 1));
if (m_raster_irq_masked)
raster_irq_ack();
}
u8 deco_irq_device::scanline_r()
{
return m_raster_irq_scanline;
}
void deco_irq_device::scanline_w(u8 data)
{
m_raster_irq_scanline = data;
}
u8 deco_irq_device::raster_irq_ack_r()
{
if (!machine().side_effects_disabled())
raster_irq_ack();
return 0xff;
}
void deco_irq_device::vblank_irq_ack_w(u8 data)
{
m_vblank_irq = false;
m_vblank_irq_cb(CLEAR_LINE);
}
u8 deco_irq_device::status_r()
{
u8 data = 0;
// 7------- unknown
// -6------ lightgun irq
// --5----- raster irq
// ---4---- vblank irq
// ----32-- unknown
// ------1- vblank
// -------0 hblank?
data |= 1 << 7;
data |= (m_lightgun_irq ? 1 : 0) << 6;
data |= (m_raster_irq ? 1 : 0) << 5;
data |= (m_vblank_irq ? 1 : 0) << 4;
data |= 0 << 3;
data |= 0 << 2;
data |= m_screen->vblank() << 1;
// data |= (m_screen->hblank() & m_screen->vblank()) << 0;
data |= m_screen->hblank() << 0;
return data;
}
WRITE_LINE_MEMBER( deco_irq_device::lightgun1_trigger_w )
{
if (state)
m_lightgun_latch = m_lightgun1_cb();
}
WRITE_LINE_MEMBER( deco_irq_device::lightgun2_trigger_w )
{
if (state)
m_lightgun_latch = m_lightgun2_cb();
}
WRITE_LINE_MEMBER( deco_irq_device::lightgun_irq_ack_w )
{
m_lightgun_irq = false;
m_lightgun_irq_cb(CLEAR_LINE);
}