/***************************************************************************
Z80 PIO implementation
based on original version (c) 1997, Tatsuyuki Satoh
Copyright Nicola Salmoria and the MAME Team.
Visit http://mamedev.org for licensing and usage restrictions.
***************************************************************************/
#include "driver.h"
#include "deprecat.h"
#include "z80pio.h"
#include "cpu/z80/z80.h"
#include "cpu/z80/z80daisy.h"
/***************************************************************************
DEBUGGING
***************************************************************************/
#define VERBOSE 0
#define VPRINTF(x) do { if (VERBOSE) logerror x; } while (0)
/***************************************************************************
CONSTANTS
***************************************************************************/
#define PIO_MODE0 0x00 /* output mode */
#define PIO_MODE1 0x01 /* input mode */
#define PIO_MODE2 0x02 /* i/o mode */
#define PIO_MODE3 0x03 /* bit mode */
/* pio controll port operation (bit 0-3) */
#define PIO_OP_MODE 0x0f /* mode select */
#define PIO_OP_INTC 0x07 /* interrupt controll */
#define PIO_OP_INTE 0x03 /* interrupt enable */
/* pio interrupt controll nit */
#define PIO_INT_ENABLE 0x80 /* ENABLE : 0=disable , 1=enable */
#define PIO_INT_AND 0x40 /* LOGIC : 0=OR , 1=AND */
#define PIO_INT_HIGH 0x20 /* LEVEL : 0=low , 1=high */
#define PIO_INT_MASK 0x10 /* MASK : 0=off , 1=on */
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
struct _z80pio
{
UINT8 vector[2]; /* interrupt vector */
void (*intr)(int which); /* interrupt callbacks */
void (*rdyr[2])(int data); /* RDY active callback */
read8_machine_func port_read[2]; /* port read callbacks */
write8_machine_func port_write[2]; /* port write callbacks */
UINT8 mode[2]; /* mode 00=in,01=out,02=i/o,03=bit*/
UINT8 enable[2]; /* interrupt enable */
UINT8 mask[2]; /* mask folowers */
UINT8 dir[2]; /* direction (bit mode) */
UINT8 rdy[2]; /* ready pin level */
UINT8 in[2]; /* input port data */
UINT8 out[2]; /* output port */
UINT8 strobe[2]; /* strobe inputs */
UINT8 int_state[2]; /* interrupt status (daisy chain) */
};
typedef struct _z80pio z80pio;
/***************************************************************************
GLOBAL VARIABLES
***************************************************************************/
static z80pio pios[MAX_PIO];
/***************************************************************************
INTERNAL STATE MANAGEMENT
***************************************************************************/
static void set_rdy(z80pio *pio, int ch, int state)
{
/* set state */
pio->rdy[ch] = state;
/* call callback with state */
if (pio->rdyr[ch])
(*pio->rdyr[ch])(pio->rdy[ch]);
}
static void interrupt_check(int which)
{
z80pio *pio = pios + which;
/* if we have a callback, update it with the current state */
if (pio->intr)
(*pio->intr)((z80pio_irq_state(which) & Z80_DAISY_INT) ? ASSERT_LINE : CLEAR_LINE);
}
static void update_irq_state(z80pio *pio, int ch)
{
int old_state = pio->int_state[ch];
int irq = 0;
int data;
if (pio->mode[ch] == 0x13 || (pio->enable[ch] & PIO_INT_MASK)) return;
/* only check if interrupts are enabled */
if (pio->enable[ch] & PIO_INT_ENABLE)
{
/* in mode 3, interrupts are tricky */
if (pio->mode[ch] == PIO_MODE3)
{
/* fetch input data (ignore output lines) */
data = pio->in[ch] & pio->dir[ch];
/* keep only relevant bits */
data &= ~pio->mask[ch];
/* if active low, invert the bits */
if (!(pio->enable[ch] & PIO_INT_HIGH))
data ^= pio->mask[ch];
/* if AND logic, interrupt if all bits are set */
if (pio->enable[ch] & PIO_INT_AND)
irq = (data == pio->mask[ch]);
/* otherwise, interrupt if at least one bit is set */
else
irq = (data != 0);
/* if portB, portA mode 2 check */
if (ch && (pio->mode[0] == PIO_MODE2))
{
if (pio->rdy[ch] == 0)
irq = 1;
}
}
/* otherwise, just interrupt when ready is cleared */
else
irq = (pio->rdy[ch] == 0);
}
if (irq)
pio->int_state[ch] |= Z80_DAISY_INT;
else
pio->int_state[ch] &= ~Z80_DAISY_INT;
if (old_state != pio->int_state[ch])
interrupt_check(pio - pios);
}
/***************************************************************************
INITIALIZATION/CONFIGURATION
***************************************************************************/
void z80pio_init(int which, const z80pio_interface *intf)
{
z80pio *pio = pios + which;
assert(which < MAX_PIO);
memset(pio, 0, sizeof(*pio));
pio->intr = intf->intr;
pio->port_read[0] = intf->portAread;
pio->port_read[1] = intf->portBread;
pio->port_write[0] = intf->portAwrite;
pio->port_write[1] = intf->portBwrite;
pio->rdyr[0] = intf->rdyA;
pio->rdyr[1] = intf->rdyB;
z80pio_reset(which);
state_save_register_item_array("z80pio", which, pio->vector);
state_save_register_item_array("z80pio", which, pio->mode);
state_save_register_item_array("z80pio", which, pio->enable);
state_save_register_item_array("z80pio", which, pio->mask);
state_save_register_item_array("z80pio", which, pio->dir);
state_save_register_item_array("z80pio", which, pio->rdy);
state_save_register_item_array("z80pio", which, pio->in);
state_save_register_item_array("z80pio", which, pio->out);
state_save_register_item_array("z80pio", which, pio->strobe);
state_save_register_item_array("z80pio", which, pio->int_state);
}
void z80pio_reset(int which)
{
z80pio *pio = pios + which;
int i;
for (i = 0; i < 2; i++)
{
pio->mask[i] = 0xff; /* mask all on */
pio->enable[i] = 0x00; /* disable */
pio->mode[i] = 0x01; /* mode input */
pio->dir[i] = 0x01; /* dir input */
set_rdy(pio, i, 0); /* RDY = low */
pio->out[i] = 0x00; /* outdata = 0 */
pio->int_state[i] = 0;
pio->strobe[i] = 0;
}
interrupt_check(which);
}
/***************************************************************************
CONTROL REGISTER READ/WRITE
***************************************************************************/
void z80pio_c_w(int which, int ch, UINT8 data)
{
z80pio *pio = pios + which;
/* load direction phase ? */
if (pio->mode[ch] == 0x13)
{
VPRINTF(("PIO-%c Bits %02x\n", 'A' + ch, data));
pio->dir[ch] = data;
pio->mode[ch] = 0x03;
return;
}
/* load mask folows phase ? */
if (pio->enable[ch] & PIO_INT_MASK)
{
/* load mask folows */
pio->mask[ch] = data;
pio->enable[ch] &= ~PIO_INT_MASK;
VPRINTF(("PIO-%c interrupt mask %02x\n",'A'+ch,data ));
return;
}
switch (data & 0x0f)
{
case PIO_OP_MODE: /* mode select 0=out,1=in,2=i/o,3=bit */
pio->mode[ch] = (data >> 6);
VPRINTF(("PIO-%c Mode %x\n", 'A' + ch, pio->mode[ch]));
if (pio->mode[ch] == 0x03)
pio->mode[ch] = 0x13;
return;
case PIO_OP_INTC: /* interrupt control */
pio->enable[ch] = data & 0xf0;
pio->mask[ch] = 0x00;
/* when interrupt enable , set vector request flag */
VPRINTF(("PIO-%c Controll %02x\n", 'A' + ch, data));
break;
case PIO_OP_INTE: /* interrupt enable controll */
pio->enable[ch] &= ~PIO_INT_ENABLE;
pio->enable[ch] |= (data & PIO_INT_ENABLE);
VPRINTF(("PIO-%c enable %02x\n", 'A' + ch, data & 0x80));
break;
default:
if (!(data & 1))
{
pio->vector[ch] = data;
VPRINTF(("PIO-%c vector %02x\n", 'A' + ch, data));
}
else
VPRINTF(("PIO-%c illegal command %02x\n", 'A' + ch, data));
break;
}
/* interrupt check */
update_irq_state(pio, ch);
}
UINT8 z80pio_c_r(int which, int ch)
{
VPRINTF(("PIO-%c controll read\n", 'A' + ch ));
return 0;
}
/***************************************************************************
DATA REGISTER READ/WRITE
***************************************************************************/
void z80pio_d_w(int which, int ch, UINT8 data)
{
z80pio *pio = pios + which;
pio->out[ch] = data; /* latch out data */
if(pio->port_write[ch])
pio->port_write[ch](Machine, 0, data);
switch (pio->mode[ch])
{
case PIO_MODE0: /* mode 0 output */
case PIO_MODE2: /* mode 2 i/o */
set_rdy(pio, ch, 1); /* ready = H */
update_irq_state(pio, ch);
return;
case PIO_MODE1: /* mode 1 input */
case PIO_MODE3: /* mode 0 bit */
return;
default:
VPRINTF(("PIO-%c data write,bad mode\n",'A'+ch ));
}
}
UINT8 z80pio_d_r(int which, int ch)
{
z80pio *pio = pios + which;
switch (pio->mode[ch])
{
case PIO_MODE0: /* mode 0 output */
return pio->out[ch];
case PIO_MODE1: /* mode 1 input */
set_rdy(pio, ch, 1); /* ready = H */
if(pio->port_read[ch])
pio->in[ch] = pio->port_read[ch](Machine, 0);
update_irq_state(pio, ch);
return pio->in[ch];
case PIO_MODE2: /* mode 2 i/o */
if (ch) VPRINTF(("PIO-B mode 2 \n"));
set_rdy(pio, 1, 1); /* brdy = H */
if(pio->port_read[ch])
pio->in[ch] = pio->port_read[ch](Machine, 0);
update_irq_state(pio, ch);
return pio->in[ch];
case PIO_MODE3: /* mode 3 bit */
if(pio->port_read[ch])
pio->in[ch] = pio->port_read[ch](Machine, 0);
return (pio->in[ch] & pio->dir[ch]) | (pio->out[ch] & ~pio->dir[ch]);
}
VPRINTF(("PIO-%c data read,bad mode\n",'A'+ch ));
return 0;
}
/***************************************************************************
PORT I/O
***************************************************************************/
void z80pio_p_w(int which, UINT8 ch, UINT8 data)
{
z80pio *pio = pios + which;
pio->in[ch] = data;
switch (pio->mode[ch])
{
case PIO_MODE0:
VPRINTF(("PIO-%c OUTPUT mode and data write\n",'A'+ch ));
break;
case PIO_MODE2: /* only port A */
ch = 1; /* handshake and IRQ is use portB */
case PIO_MODE1:
set_rdy(pio, ch, 0);
update_irq_state(pio, ch);
break;
case PIO_MODE3:
/* irq check */
update_irq_state(pio, ch);
break;
}
}
int z80pio_p_r(int which, UINT8 ch)
{
z80pio *pio = pios + which;
switch (pio->mode[ch])
{
case PIO_MODE2: /* port A only */
case PIO_MODE0:
set_rdy(pio, ch, 0);
update_irq_state(pio, ch);
break;
case PIO_MODE1:
VPRINTF(("PIO-%c INPUT mode and data read\n",'A'+ch ));
break;
case PIO_MODE3:
return (pio->in[ch] & pio->dir[ch]) | (pio->out[ch] & ~pio->dir[ch]);
}
return pio->out[ch];
}
WRITE8_HANDLER( z80pioA_0_p_w ) { z80pio_p_w(0, 0, data); }
WRITE8_HANDLER( z80pioB_0_p_w ) { z80pio_p_w(0, 1, data); }
READ8_HANDLER( z80pioA_0_p_r ) { return z80pio_p_r(0, 0); }
READ8_HANDLER( z80pioB_0_p_r ) { return z80pio_p_r(0, 1); }
WRITE8_HANDLER( z80pioA_1_p_w ) { z80pio_p_w(1, 0, data); }
WRITE8_HANDLER( z80pioB_1_p_w ) { z80pio_p_w(1, 1, data); }
READ8_HANDLER( z80pioA_1_p_r ) { return z80pio_p_r(1, 0); }
READ8_HANDLER( z80pioB_1_p_r ) { return z80pio_p_r(1, 1); }
/***************************************************************************
STROBE STATE MANAGEMENT
***************************************************************************/
static void z80pio_update_strobe(int which, int ch, int state)
{
z80pio *pio = pios + which;
switch (pio->mode[ch])
{
/* output mode: a positive edge is used by peripheral to acknowledge
the receipt of data */
case PIO_MODE0:
{
/* ensure valid */
state = state & 0x01;
/* strobe changed state? */
if ((pio->strobe[ch] ^ state) != 0)
{
/* yes */
if (state != 0)
{
/* positive edge */
VPRINTF(("PIO-%c positive strobe\n",'A' + ch));
/* ready is now inactive */
set_rdy(pio, ch, 0);
/* int enabled? */
if (pio->enable[ch] & PIO_INT_ENABLE)
{
/* trigger an int request */
pio->int_state[ch] |= Z80_DAISY_INT;
}
}
}
/* store strobe state */
pio->strobe[ch] = state;
/* check interrupt */
interrupt_check(which);
}
break;
/* input mode: strobe is used by peripheral to load data from the peripheral
into port a input register, data loaded into pio when signal is active */
default:
break;
}
}
void z80pio_astb_w(int which, int state) { z80pio_update_strobe(which, 0, state); }
void z80pio_bstb_w(int which, int state) { z80pio_update_strobe(which, 1, state); }
/***************************************************************************
DAISY CHAIN INTERFACE
***************************************************************************/
int z80pio_irq_state(int which)
{
z80pio *pio = pios + which;
int state = 0;
int ch;
/* loop over all channels */
for (ch = 0; ch < 2; ch++)
{
/* if we're servicing a request, don't indicate more interrupts */
if (pio->int_state[ch] & Z80_DAISY_IEO)
{
state |= Z80_DAISY_IEO;
break;
}
state |= pio->int_state[ch];
}
return state;
}
int z80pio_irq_ack(int which)
{
z80pio *pio = pios + which;
int ch;
/* loop over all channels */
for (ch = 0; ch < 2; ch++)
/* find the first channel with an interrupt requested */
if (pio->int_state[ch] & Z80_DAISY_INT)
{
/* clear interrupt, switch to the IEO state, and update the IRQs */
pio->int_state[ch] = Z80_DAISY_IEO;
interrupt_check(which);
return pio->vector[ch];
}
VPRINTF(("z80pio_irq_ack: failed to find an interrupt to ack!"));
return pio->vector[0];
}
void z80pio_irq_reti(int which)
{
z80pio *pio = pios + which;
int ch;
/* loop over all channels */
for (ch = 0; ch < 2; ch++)
/* find the first channel with an IEO pending */
if (pio->int_state[ch] & Z80_DAISY_IEO)
{
/* clear the IEO state and update the IRQs */
pio->int_state[ch] &= ~Z80_DAISY_IEO;
interrupt_check(which);
return;
}
VPRINTF(("z80pio_irq_reti: failed to find an interrupt to clear IEO on!"));
}
/***************************************************************************
READ/WRITE HANDLERS
***************************************************************************/
READ8_HANDLER(z80pio_0_r)
{
return (offset & 2) ? z80pio_c_r(0, offset & 1) : z80pio_d_r(0, offset & 1);
}
WRITE8_HANDLER(z80pio_0_w)
{
if (offset & 2)
z80pio_c_w(0, offset & 1, data);
else
z80pio_d_w(0, offset & 1, data);
}
READ8_HANDLER(z80pio_1_r)
{
return (offset & 2) ? z80pio_c_r(1, offset & 1) : z80pio_d_r(1, offset & 1);
}
WRITE8_HANDLER(z80pio_1_w)
{
if (offset & 2)
z80pio_c_w(1, offset & 1, data);
else
z80pio_d_w(1, offset & 1, data);
}