mame/src/emu/machine/z80pio.c

625 lines
17 KiB
C

/***************************************************************************
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
***************************************************************************/
typedef struct _z80pio z80pio_t;
struct _z80pio
{
UINT8 vector[2]; /* interrupt vector */
void (*intr)(const device_config *, int which); /* interrupt callbacks */
void (*rdyr[2])(int data); /* RDY active callback */
read8_device_func port_read[2]; /* port read callbacks */
write8_device_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) */
};
/***************************************************************************
FUNCTION PROTOTYPES
***************************************************************************/
static int z80pio_irq_state(const device_config *device);
static int z80pio_irq_ack(const device_config *device);
static void z80pio_irq_reti(const device_config *device);
/***************************************************************************
INLINE FUNCTIONS
***************************************************************************/
INLINE z80pio_t *get_safe_token(const device_config *device)
{
assert(device != NULL);
assert(device->token != NULL);
assert(device->type == Z80PIO);
return (z80pio_t *)device->token;
}
/***************************************************************************
INTERNAL STATE MANAGEMENT
***************************************************************************/
INLINE void set_rdy(const device_config *device, int ch, int state)
{
z80pio_t *z80pio = get_safe_token( device );
/* set state */
z80pio->rdy[ch] = state;
/* call callback with state */
if (z80pio->rdyr[ch])
z80pio->rdyr[ch](z80pio->rdy[ch]);
}
INLINE void interrupt_check(const device_config *device)
{
z80pio_t *z80pio = get_safe_token( device );
/* if we have a callback, update it with the current state */
if (z80pio->intr)
z80pio->intr(device, (z80pio_irq_state(device) & Z80_DAISY_INT) ? ASSERT_LINE : CLEAR_LINE);
}
static void update_irq_state(const device_config *device, int ch)
{
z80pio_t *z80pio = get_safe_token( device );
int old_state = z80pio->int_state[ch];
int irq = 0;
int data;
if (z80pio->mode[ch] == 0x13 || (z80pio->enable[ch] & PIO_INT_MASK)) return;
/* only check if interrupts are enabled */
if (z80pio->enable[ch] & PIO_INT_ENABLE)
{
/* in mode 3, interrupts are tricky */
if (z80pio->mode[ch] == PIO_MODE3)
{
/* fetch input data (ignore output lines) */
data = z80pio->in[ch] & z80pio->dir[ch];
/* keep only relevant bits */
data &= ~z80pio->mask[ch];
/* if active low, invert the bits */
if (!(z80pio->enable[ch] & PIO_INT_HIGH))
data ^= z80pio->mask[ch];
/* if AND logic, interrupt if all bits are set */
if (z80pio->enable[ch] & PIO_INT_AND)
irq = (data == z80pio->mask[ch]);
/* otherwise, interrupt if at least one bit is set */
else
irq = (data != 0);
/* if portB, portA mode 2 check */
if (ch && (z80pio->mode[0] == PIO_MODE2))
{
if (z80pio->rdy[ch] == 0)
irq = 1;
}
}
/* otherwise, just interrupt when ready is cleared */
else
irq = (z80pio->rdy[ch] == 0);
}
if (irq)
z80pio->int_state[ch] |= Z80_DAISY_INT;
else
z80pio->int_state[ch] &= ~Z80_DAISY_INT;
if (old_state != z80pio->int_state[ch])
interrupt_check(device);
}
/***************************************************************************
CONTROL REGISTER READ/WRITE
***************************************************************************/
WRITE8_DEVICE_HANDLER( z80pio_c_w )
{
z80pio_t *z80pio = get_safe_token( device );
/* There are only 2 channels */
offset &= 0x01;
/* load direction phase ? */
if (z80pio->mode[offset] == 0x13)
{
VPRINTF(("PIO-%c Bits %02x\n", 'A' + offset, data));
z80pio->dir[offset] = data;
z80pio->mode[offset] = 0x03;
return;
}
/* load mask folows phase ? */
if (z80pio->enable[offset] & PIO_INT_MASK)
{
/* load mask folows */
z80pio->mask[offset] = data;
z80pio->enable[offset] &= ~PIO_INT_MASK;
VPRINTF(("PIO-%c interrupt mask %02x\n",'A'+offset,data ));
return;
}
switch (data & 0x0f)
{
case PIO_OP_MODE: /* mode select 0=out,1=in,2=i/o,3=bit */
z80pio->mode[offset] = (data >> 6);
VPRINTF(("PIO-%c Mode %x\n", 'A' + offset, z80pio->mode[offset]));
if (z80pio->mode[offset] == 0x03)
z80pio->mode[offset] = 0x13;
return;
case PIO_OP_INTC: /* interrupt control */
z80pio->enable[offset] = data & 0xf0;
z80pio->mask[offset] = 0x00;
/* when interrupt enable , set vector request flag */
VPRINTF(("PIO-%c Controll %02x\n", 'A' + offset, data));
break;
case PIO_OP_INTE: /* interrupt enable controll */
z80pio->enable[offset] &= ~PIO_INT_ENABLE;
z80pio->enable[offset] |= (data & PIO_INT_ENABLE);
VPRINTF(("PIO-%c enable %02x\n", 'A' + offset, data & 0x80));
break;
default:
if (!(data & 1))
{
z80pio->vector[offset] = data;
VPRINTF(("PIO-%c vector %02x\n", 'A' + offset, data));
}
else
VPRINTF(("PIO-%c illegal command %02x\n", 'A' + offset, data));
break;
}
/* interrupt check */
update_irq_state(device, offset);
}
READ8_DEVICE_HANDLER( z80pio_c_r )
{
/* There are only 2 channels */
offset &= 0x01;
VPRINTF(("PIO-%c controll read\n", 'A' + offset ));
return 0;
}
/***************************************************************************
DATA REGISTER READ/WRITE
***************************************************************************/
WRITE8_DEVICE_HANDLER( z80pio_d_w )
{
z80pio_t *z80pio = get_safe_token( device );
/* There are only 2 channels */
offset &= 0x01;
z80pio->out[offset] = data; /* latch out data */
if(z80pio->port_write[offset])
z80pio->port_write[offset](device, 0, data);
switch (z80pio->mode[offset])
{
case PIO_MODE0: /* mode 0 output */
case PIO_MODE2: /* mode 2 i/o */
set_rdy(device, offset, 1); /* ready = H */
update_irq_state(device, offset);
return;
case PIO_MODE1: /* mode 1 input */
case PIO_MODE3: /* mode 0 bit */
return;
default:
VPRINTF(("PIO-%c data write,bad mode\n",'A'+offset ));
}
}
READ8_DEVICE_HANDLER( z80pio_d_r )
{
z80pio_t *z80pio = get_safe_token( device );
/* There are only 2 channels */
offset &= 0x01;
switch (z80pio->mode[offset])
{
case PIO_MODE0: /* mode 0 output */
return z80pio->out[offset];
case PIO_MODE1: /* mode 1 input */
set_rdy(device, offset, 1); /* ready = H */
if(z80pio->port_read[offset])
z80pio->in[offset] = z80pio->port_read[offset](device, 0);
update_irq_state(device, offset);
return z80pio->in[offset];
case PIO_MODE2: /* mode 2 i/o */
if (offset) VPRINTF(("PIO-B mode 2 \n"));
set_rdy(device, 1, 1); /* brdy = H */
if(z80pio->port_read[offset])
z80pio->in[offset] = z80pio->port_read[offset](device, 0);
update_irq_state(device, offset);
return z80pio->in[offset];
case PIO_MODE3: /* mode 3 bit */
if(z80pio->port_read[offset])
z80pio->in[offset] = z80pio->port_read[offset](device, 0);
return (z80pio->in[offset] & z80pio->dir[offset]) | (z80pio->out[offset] & ~z80pio->dir[offset]);
}
VPRINTF(("PIO-%c data read,bad mode\n",'A'+offset ));
return 0;
}
/***************************************************************************
PORT I/O
***************************************************************************/
WRITE8_DEVICE_HANDLER( z80pio_p_w )
{
z80pio_t *z80pio = get_safe_token( device );
/* There are only 2 channels */
offset &= 0x01;
z80pio->in[offset] = data;
switch (z80pio->mode[offset])
{
case PIO_MODE0:
VPRINTF(("PIO-%c OUTPUT mode and data write\n",'A'+offset ));
break;
case PIO_MODE2: /* only port A */
offset = 1; /* handshake and IRQ is use portB */
case PIO_MODE1:
set_rdy(device, offset, 0);
update_irq_state(device, offset);
break;
case PIO_MODE3:
/* irq check */
update_irq_state(device, offset);
break;
}
}
READ8_DEVICE_HANDLER( z80pio_p_r )
{
z80pio_t *z80pio = get_safe_token( device );
/* There are only 2 channels */
offset &= 0x01;
switch (z80pio->mode[offset])
{
case PIO_MODE2: /* port A only */
case PIO_MODE0:
set_rdy(device, offset, 0);
update_irq_state(device, offset);
break;
case PIO_MODE1:
VPRINTF(("PIO-%c INPUT mode and data read\n",'A'+offset ));
break;
case PIO_MODE3:
return (z80pio->in[offset] & z80pio->dir[offset]) | (z80pio->out[offset] & ~z80pio->dir[offset]);
}
return z80pio->out[offset];
}
/***************************************************************************
STROBE STATE MANAGEMENT
***************************************************************************/
static void z80pio_update_strobe(const device_config *device, int ch, int state)
{
z80pio_t *z80pio = get_safe_token( device );
switch (z80pio->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 ((z80pio->strobe[ch] ^ state) != 0)
{
/* yes */
if (state != 0)
{
/* positive edge */
VPRINTF(("PIO-%c positive strobe\n",'A' + ch));
/* ready is now inactive */
set_rdy(device, ch, 0);
/* int enabled? */
if (z80pio->enable[ch] & PIO_INT_ENABLE)
{
/* trigger an int request */
z80pio->int_state[ch] |= Z80_DAISY_INT;
}
}
}
/* store strobe state */
z80pio->strobe[ch] = state;
/* check interrupt */
interrupt_check(device);
}
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(const device_config *device, int state) { z80pio_update_strobe(device, 0, state); }
void z80pio_bstb_w(const device_config *device, int state) { z80pio_update_strobe(device, 1, state); }
/***************************************************************************
DAISY CHAIN INTERFACE
***************************************************************************/
static int z80pio_irq_state(const device_config *device)
{
z80pio_t *z80pio = get_safe_token( device );
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 (z80pio->int_state[ch] & Z80_DAISY_IEO)
{
state |= Z80_DAISY_IEO;
break;
}
state |= z80pio->int_state[ch];
}
return state;
}
static int z80pio_irq_ack(const device_config *device)
{
z80pio_t *z80pio = get_safe_token( device );
int ch;
/* loop over all channels */
for (ch = 0; ch < 2; ch++)
/* find the first channel with an interrupt requested */
if (z80pio->int_state[ch] & Z80_DAISY_INT)
{
/* clear interrupt, switch to the IEO state, and update the IRQs */
z80pio->int_state[ch] = Z80_DAISY_IEO;
interrupt_check(device);
return z80pio->vector[ch];
}
VPRINTF(("z80pio_irq_ack: failed to find an interrupt to ack!"));
return z80pio->vector[0];
}
static void z80pio_irq_reti(const device_config *device)
{
z80pio_t *z80pio = get_safe_token( device );
int ch;
/* loop over all channels */
for (ch = 0; ch < 2; ch++)
/* find the first channel with an IEO pending */
if (z80pio->int_state[ch] & Z80_DAISY_IEO)
{
/* clear the IEO state and update the IRQs */
z80pio->int_state[ch] &= ~Z80_DAISY_IEO;
interrupt_check(device);
return;
}
VPRINTF(("z80pio_irq_reti: failed to find an interrupt to clear IEO on!"));
}
/***************************************************************************
READ/WRITE HANDLERS
***************************************************************************/
READ8_DEVICE_HANDLER(z80pio_r)
{
return (offset & 2) ? z80pio_c_r(device, offset & 1) : z80pio_d_r(device, offset & 1);
}
WRITE8_DEVICE_HANDLER(z80pio_w)
{
if (offset & 2)
z80pio_c_w(device, offset & 1, data);
else
z80pio_d_w(device, offset & 1, data);
}
static DEVICE_START( z80pio )
{
const z80pio_interface *intf = device->static_config;
z80pio_t *z80pio = get_safe_token( device );
char unique_tag[30];
z80pio->intr = intf->intr;
z80pio->port_read[0] = intf->portAread;
z80pio->port_read[1] = intf->portBread;
z80pio->port_write[0] = intf->portAwrite;
z80pio->port_write[1] = intf->portBwrite;
z80pio->rdyr[0] = intf->rdyA;
z80pio->rdyr[1] = intf->rdyB;
/* register for save states */
state_save_combine_module_and_tag(unique_tag, "z80pio", device->tag);
state_save_register_item_array(unique_tag, 0, z80pio->vector);
state_save_register_item_array(unique_tag, 0, z80pio->mode);
state_save_register_item_array(unique_tag, 0, z80pio->enable);
state_save_register_item_array(unique_tag, 0, z80pio->mask);
state_save_register_item_array(unique_tag, 0, z80pio->dir);
state_save_register_item_array(unique_tag, 0, z80pio->rdy);
state_save_register_item_array(unique_tag, 0, z80pio->in);
state_save_register_item_array(unique_tag, 0, z80pio->out);
state_save_register_item_array(unique_tag, 0, z80pio->strobe);
state_save_register_item_array(unique_tag, 0, z80pio->int_state);
return DEVICE_START_OK;
}
static DEVICE_RESET( z80pio )
{
z80pio_t *z80pio = get_safe_token( device );
int i;
for (i = 0; i < 2; i++)
{
z80pio->mask[i] = 0xff; /* mask all on */
z80pio->enable[i] = 0x00; /* disable */
z80pio->mode[i] = 0x01; /* mode input */
z80pio->dir[i] = 0x01; /* dir input */
set_rdy(device, i, 0); /* RDY = low */
z80pio->out[i] = 0x00; /* outdata = 0 */
z80pio->int_state[i] = 0;
z80pio->strobe[i] = 0;
}
interrupt_check(device);
}
static DEVICE_SET_INFO( z80pio )
{
switch (state)
{
/* no parameters to set */
}
}
DEVICE_GET_INFO( z80pio )
{
switch (state)
{
/* --- the following bits of info are returned as 64-bit signed integers --- */
case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(z80pio_t); break;
case DEVINFO_INT_INLINE_CONFIG_BYTES: info->i = 0; break;
case DEVINFO_INT_CLASS: info->i = DEVICE_CLASS_PERIPHERAL; break;
/* --- the following bits of info are returned as pointers to data or functions --- */
case DEVINFO_FCT_SET_INFO: info->set_info = DEVICE_SET_INFO_NAME(z80pio); break;
case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(z80pio);break;
case DEVINFO_FCT_STOP: /* Nothing */ break;
case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME(z80pio);break;
case DEVINFO_FCT_IRQ_STATE: info->f = (genf *)z80pio_irq_state; break;
case DEVINFO_FCT_IRQ_ACK: info->f = (genf *)z80pio_irq_ack; break;
case DEVINFO_FCT_IRQ_RETI: info->f = (genf *)z80pio_irq_reti; break;
/* --- the following bits of info are returned as NULL-terminated strings --- */
case DEVINFO_STR_NAME: info->s = "Zilog Z80 PIO"; break;
case DEVINFO_STR_FAMILY: info->s = "Z80"; break;
case DEVINFO_STR_VERSION: info->s = "1.0"; break;
case DEVINFO_STR_SOURCE_FILE: info->s = __FILE__; break;
case DEVINFO_STR_CREDITS: info->s = "Copyright Nicola Salmoria and the MAME Team"; break;
}
}