// license:BSD-3-Clause
// copyright-holders:smf,AJR
/***************************************************************************
NEC µPD4701A 2-Axis Incremental Encoder Counter
***************************************************************************/
#include "emu.h"
#include "upd4701.h"
#define MASK_COUNTER ( 0xfff )
DEFINE_DEVICE_TYPE(UPD4701A, upd4701_device, "upd4701a", "uPD4701A Incremental Encoder")
upd4701_device::upd4701_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: device_t(mconfig, UPD4701A, tag, owner, clock)
, m_cs(true)
, m_xy(false)
, m_ul(false)
, m_resetx(false)
, m_resety(false)
, m_portx(*this, finder_base::DUMMY_TAG)
, m_porty(*this, finder_base::DUMMY_TAG)
, m_latchx(0)
, m_latchy(0)
, m_startx(0)
, m_starty(0)
, m_x(0)
, m_y(0)
, m_switches(0)
, m_latchswitches(0)
, m_cf(true)
, m_cf_cb(*this)
, m_sf_cb(*this)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void upd4701_device::device_start()
{
// resolve callbacks
m_cf_cb.resolve_safe();
m_sf_cb.resolve_safe();
// register state for saving
save_item(NAME(m_cs));
save_item(NAME(m_xy));
save_item(NAME(m_ul));
save_item(NAME(m_resetx));
save_item(NAME(m_resety));
save_item(NAME(m_latchx));
save_item(NAME(m_latchy));
save_item(NAME(m_startx));
save_item(NAME(m_starty));
save_item(NAME(m_x));
save_item(NAME(m_y));
save_item(NAME(m_switches));
save_item(NAME(m_latchswitches));
save_item(NAME(m_cf));
// register special callback for analog inputs
if (m_portx.found() || m_porty.found())
machine().add_notifier(MACHINE_NOTIFY_FRAME, machine_notify_delegate(&upd4701_device::analog_update, this));
}
//-------------------------------------------------
// ul_w - write to counter select line
//-------------------------------------------------
WRITE_LINE_MEMBER(upd4701_device::ul_w)
{
m_ul = state;
}
//-------------------------------------------------
// xy_w - write to byte select line
//-------------------------------------------------
WRITE_LINE_MEMBER(upd4701_device::xy_w)
{
m_xy = state;
}
//-------------------------------------------------
// cs_w - write to chip select line
//-------------------------------------------------
WRITE_LINE_MEMBER(upd4701_device::cs_w)
{
if (m_cs != state)
{
m_cs = state;
if (!m_cs)
{
m_latchx = (m_x - m_startx) & MASK_COUNTER;
m_latchy = (m_y - m_starty) & MASK_COUNTER;
m_latchswitches = m_switches;
if (m_switches != 0)
m_latchswitches |= 8;
if (!m_cf)
{
// CF remains inactive while CS is low
m_cf = true;
m_cf_cb(1);
}
}
}
}
//-------------------------------------------------
// resetx_w - write to X counter reset line
//-------------------------------------------------
WRITE_LINE_MEMBER(upd4701_device::resetx_w)
{
if (m_resetx != state)
{
m_resetx = state;
if (m_resetx)
m_startx = m_x;
}
}
//-------------------------------------------------
// resety_w - write to Y counter reset line
//-------------------------------------------------
WRITE_LINE_MEMBER(upd4701_device::resety_w)
{
if (m_resety != state)
{
m_resety = state;
if (m_resety)
m_starty = m_y;
}
}
//-------------------------------------------------
// reset_x - pulse the X counter reset line
//-------------------------------------------------
READ8_MEMBER(upd4701_device::reset_x_r)
{
if (!machine().side_effects_disabled())
{
resetx_w(1);
resetx_w(0);
}
return space.unmap();
}
WRITE8_MEMBER(upd4701_device::reset_x_w)
{
resetx_w(1);
resetx_w(0);
}
//-------------------------------------------------
// reset_y - pulse the Y counter reset line
//-------------------------------------------------
READ8_MEMBER(upd4701_device::reset_y_r)
{
if (!machine().side_effects_disabled())
{
resety_w(1);
resety_w(0);
}
return space.unmap();
}
WRITE8_MEMBER(upd4701_device::reset_y_w)
{
resety_w(1);
resety_w(0);
}
//-------------------------------------------------
// reset_xy - pulse the counter reset lines
//-------------------------------------------------
READ8_MEMBER(upd4701_device::reset_xy_r)
{
if (!machine().side_effects_disabled())
{
resetx_w(1);
resety_w(1);
resetx_w(0);
resety_w(0);
}
return space.unmap();
}
WRITE8_MEMBER(upd4701_device::reset_xy_w)
{
resetx_w(1);
resety_w(1);
resetx_w(0);
resety_w(0);
}
//-------------------------------------------------
// analog_update - per-frame input update
//-------------------------------------------------
void upd4701_device::analog_update()
{
if (m_portx.found())
x_add(m_portx->read() & MASK_COUNTER);
if (m_porty.found())
y_add(m_porty->read() & MASK_COUNTER);
}
//-------------------------------------------------
// x_add - count X-axis input
//-------------------------------------------------
void upd4701_device::x_add(s16 data)
{
if (!m_resetx && data != 0)
{
m_x += data;
if (m_cs && m_cf)
{
m_cf = false;
m_cf_cb(0);
}
}
}
//-------------------------------------------------
// y_add - count Y-axis input
//-------------------------------------------------
void upd4701_device::y_add(s16 data)
{
if (!m_resety && data != 0)
{
m_y += data;
if (m_cs && m_cf)
{
m_cf = false;
m_cf_cb(0);
}
}
}
//-------------------------------------------------
// switch_update - update one of three switches
//-------------------------------------------------
void upd4701_device::switch_update(u8 mask, bool state)
{
if (!state && (m_switches & mask) == 0)
{
// active low
m_switches |= mask;
// update SF output if other switches were not active
if ((m_switches & ~mask) == 0)
m_sf_cb(0);
}
else if (state && (m_switches & mask) == mask)
{
// inactive high
m_switches &= ~mask;
// update SF output if other switches are also inactive
if ((m_switches & ~mask) == 0)
m_sf_cb(1);
}
}
//-------------------------------------------------
// left_w - update left switch state
//-------------------------------------------------
WRITE_LINE_MEMBER(upd4701_device::left_w)
{
switch_update(4, state);
}
//-------------------------------------------------
// right_w - update right switch state
//-------------------------------------------------
WRITE_LINE_MEMBER(upd4701_device::right_w)
{
switch_update(2, state);
}
//-------------------------------------------------
// middle_w - update middle switch state
//-------------------------------------------------
WRITE_LINE_MEMBER(upd4701_device::middle_w)
{
switch_update(1, state);
}
//-------------------------------------------------
// d_r - read data lines directly
//-------------------------------------------------
READ8_MEMBER(upd4701_device::d_r)
{
if (m_cs)
{
logerror("Read while CS inactive\n");
return space.unmap();
}
u16 data = m_xy ? m_latchy : m_latchx;
data |= m_latchswitches << 12;
if (m_ul)
return data >> 8;
else
return data & 0xff;
}
//-------------------------------------------------
// read_x - read X axis through data/address bus
//-------------------------------------------------
READ8_MEMBER(upd4701_device::read_x)
{
return read_xy(space, (offset & 1) | 0);
}
//-------------------------------------------------
// read_y - read Y axis through data/address bus
//-------------------------------------------------
READ8_MEMBER(upd4701_device::read_y)
{
return read_xy(space, (offset & 1) | 2);
}
//-------------------------------------------------
// read_xy - read either axis through bus
//-------------------------------------------------
READ8_MEMBER(upd4701_device::read_xy)
{
bool old_cs = m_cs;
cs_w(0);
xy_w(BIT(offset, 1));
ul_w(BIT(offset, 0));
u8 result = d_r(space, 0);
cs_w(old_cs);
return result;
}
//-------------------------------------------------
// sf_r - read switch flag
//-------------------------------------------------
READ_LINE_MEMBER(upd4701_device::sf_r)
{
if (m_switches != 0)
return 0;
return 1;
}
//-------------------------------------------------
// cf_r - read counter flag
//-------------------------------------------------
READ_LINE_MEMBER(upd4701_device::cf_r)
{
return m_cf;
}