// license:BSD-3-Clause
// copyright-holders:Curt Coder
/***************************************************************************
MIOT 6530 emulation
The timer seems to follow these rules:
- When the timer flag changes from 0 to 1 the timer continues to count
down at a 1 cycle rate.
- When the timer is being read or written the timer flag is reset.
- When the timer flag is set and the timer contents are 0, the counting
stops.
From the operation of the KIM1 it expects the irqflag to be set whenever
the unit is reset. This is something that is not clear from the datasheet
and should be verified against real hardware.
***************************************************************************/
#include "emu.h"
#include "mos6530.h"
/***************************************************************************
CONSTANTS
***************************************************************************/
enum
{
TIMER_IDLE,
TIMER_COUNTING,
TIMER_FINISHING
};
#define TIMER_FLAG 0x80
/***************************************************************************
DEVICE INTERFACE
***************************************************************************/
const device_type MOS6530 = &device_creator<mos6530_device>;
mos6530_device::mos6530_device(const machine_config &mconfig, std::string tag, device_t *owner, UINT32 clock)
: device_t(mconfig, MOS6530, "MOS6530 RRIOT", tag, owner, clock, "mos6530", __FILE__),
m_in_pa_cb(*this),
m_out_pa_cb(*this),
m_in_pb_cb(*this),
m_out_pb_cb(*this)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void mos6530_device::device_start()
{
/* set static values */
m_clock = clock();
/* resolve callbacks */
m_in_pa_cb.resolve_safe(0);
m_out_pa_cb.resolve_safe();
m_in_pb_cb.resolve_safe(0);
m_out_pb_cb.resolve_safe();
/* allocate timers */
m_timer = timer_alloc(TIMER_END_CALLBACK);
/* register for save states */
save_item(NAME(m_port[0].m_in));
save_item(NAME(m_port[0].m_out));
save_item(NAME(m_port[0].m_ddr));
save_item(NAME(m_port[1].m_in));
save_item(NAME(m_port[1].m_out));
save_item(NAME(m_port[1].m_ddr));
save_item(NAME(m_irqstate));
save_item(NAME(m_irqenable));
save_item(NAME(m_timershift));
save_item(NAME(m_timerstate));
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void mos6530_device::device_reset()
{
/* reset I/O states */
m_port[0].m_out = 0;
m_port[0].m_ddr = 0;
m_port[1].m_out = 0;
m_port[1].m_ddr = 0;
/* reset IRQ states */
m_irqenable = 0;
m_irqstate = TIMER_FLAG;
update_irqstate();
/* reset timer states */
m_timershift = 0;
m_timerstate = TIMER_IDLE;
m_timer->adjust(attotime::never);
}
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
/*-------------------------------------------------
update_irqstate - update the IRQ state
based on interrupt enables
-------------------------------------------------*/
void mos6530_device::update_irqstate()
{
UINT8 out = m_port[1].m_out;
if (m_irqenable)
out = ((m_irqstate & TIMER_FLAG) ? 0x00 : 0x80) | (out & 0x7F);
m_out_pb_cb((offs_t)0, out);
}
/*-------------------------------------------------
get_timer - return the current timer value
-------------------------------------------------*/
UINT8 mos6530_device::get_timer()
{
/* if idle, return 0 */
if (m_timerstate == TIMER_IDLE)
return 0;
/* if counting, return the number of ticks remaining */
else if (m_timerstate == TIMER_COUNTING)
return m_timer->remaining().as_ticks(m_clock) >> m_timershift;
/* if finishing, return the number of ticks without the shift */
else
return m_timer->remaining().as_ticks(m_clock);
}
/***************************************************************************
INTERNAL FUNCTIONS
***************************************************************************/
/*-------------------------------------------------
timer_end_callback - callback to process the
timer
-------------------------------------------------*/
void mos6530_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch (id)
{
// deferred reset
case TIMER_END_CALLBACK:
assert(m_timerstate != TIMER_IDLE);
/* if we finished counting, switch to the finishing state */
if (m_timerstate == TIMER_COUNTING)
{
m_timerstate = TIMER_FINISHING;
m_timer->adjust(attotime::from_ticks(256, m_clock));
/* signal timer IRQ as well */
m_irqstate |= TIMER_FLAG;
update_irqstate();
}
/* if we finished finishing, switch to the idle state */
else if (m_timerstate == TIMER_FINISHING)
{
m_timerstate = TIMER_IDLE;
m_timer->adjust(attotime::never);
}
break;
}
}
/***************************************************************************
I/O ACCESS
***************************************************************************/
/*-------------------------------------------------
mos6530_w - master I/O write access
-------------------------------------------------*/
WRITE8_MEMBER( mos6530_device::write )
{
/* if A2 == 1, we are writing to the timer */
if (offset & 0x04)
{
static const UINT8 timershift[4] = { 0, 3, 6, 10 };
attotime curtime = space.machine().time();
INT64 target;
/* A0-A1 contain the timer divisor */
m_timershift = timershift[offset & 3];
/* A3 contains the timer IRQ enable */
if (offset & 8)
m_irqenable |= TIMER_FLAG;
else
m_irqenable &= ~TIMER_FLAG;
/* writes here clear the timer flag */
if (m_timerstate != TIMER_FINISHING || get_timer() != 0xff)
m_irqstate &= ~TIMER_FLAG;
update_irqstate();
/* update the timer */
m_timerstate = TIMER_COUNTING;
target = curtime.as_ticks(m_clock) + 1 + (data << m_timershift);
m_timer->adjust(attotime::from_ticks(target, m_clock) - curtime);
}
/* if A2 == 0, we are writing to the I/O section */
else
{
/* A1 selects the port */
mos6530_port *port = &m_port[BIT(offset, 1)];
/* if A0 == 1, we are writing to the port's DDR */
if (offset & 1)
port->m_ddr = data;
/* if A0 == 0, we are writing to the port's output */
else
{
UINT8 olddata = port->m_out;
port->m_out = data;
if ((offset & 2) && m_irqenable)
{
olddata = ((m_irqstate & TIMER_FLAG) ? 0x00 : 0x80) | (olddata & 0x7F);
data = ((m_irqstate & TIMER_FLAG) ? 0x00 : 0x80) | (data & 0x7F);
}
if (!BIT(offset, 1))
m_out_pa_cb((offs_t)0, data);
else
m_out_pb_cb((offs_t)0, data);
}
}
}
/*-------------------------------------------------
mos6530_r - master I/O read access
-------------------------------------------------*/
READ8_MEMBER( mos6530_device::read )
{
UINT8 val;
/* if A2 == 1 and A0 == 1, we are reading interrupt flags */
if ((offset & 0x05) == 0x05)
{
val = m_irqstate;
}
/* if A2 == 1 and A0 == 0, we are reading the timer */
else if ((offset & 0x05) == 0x04)
{
val = get_timer();
/* A3 contains the timer IRQ enable */
if (offset & 8)
m_irqenable |= TIMER_FLAG;
else
m_irqenable &= ~TIMER_FLAG;
/* implicitly clears the timer flag */
if (m_timerstate != TIMER_FINISHING || val != 0xff)
m_irqstate &= ~TIMER_FLAG;
update_irqstate();
}
/* if A2 == 0 and A0 == anything, we are reading from ports */
else
{
/* A1 selects the port */
mos6530_port *port = &m_port[BIT(offset, 1)];
/* if A0 == 1, we are reading the port's DDR */
if (offset & 1)
val = port->m_ddr;
/* if A0 == 0, we are reading the port as an input */
else
{
UINT8 out = port->m_out;
if ((offset & 2) && m_irqenable)
out = ((m_irqstate & TIMER_FLAG) ? 0x00 : 0x80) | (out & 0x7F);
/* call the input callback if it exists */
if (!BIT(offset, 1))
port->m_in = m_in_pa_cb(0);
else
port->m_in = m_in_pb_cb(0);
/* apply the DDR to the result */
val = (out & port->m_ddr) | (port->m_in & ~port->m_ddr);
}
}
return val;
}
/*-------------------------------------------------
mos6530_porta_in_set - set port A input
value
-------------------------------------------------*/
void mos6530_device::porta_in_set(UINT8 data, UINT8 mask)
{
m_port[0].m_in = (m_port[0].m_in & ~mask) | (data & mask);
}
/*-------------------------------------------------
mos6530_portb_in_set - set port B input
value
-------------------------------------------------*/
void mos6530_device::portb_in_set(UINT8 data, UINT8 mask)
{
m_port[1].m_in = (m_port[1].m_in & ~mask) | (data & mask);
}
/*-------------------------------------------------
mos6530_porta_in_get - return port A input
value
-------------------------------------------------*/
UINT8 mos6530_device::porta_in_get()
{
return m_port[0].m_in;
}
/*-------------------------------------------------
mos6530_portb_in_get - return port B input
value
-------------------------------------------------*/
UINT8 mos6530_device::portb_in_get()
{
return m_port[1].m_in;
}
/*-------------------------------------------------
mos6530_porta_in_get - return port A output
value
-------------------------------------------------*/
UINT8 mos6530_device::porta_out_get()
{
return m_port[0].m_out;
}
/*-------------------------------------------------
mos6530_portb_in_get - return port B output
value
-------------------------------------------------*/
UINT8 mos6530_device::portb_out_get()
{
return m_port[1].m_out;
}