// license:BSD-3-Clause // copyright-holders:Curt Coder /*************************************************************************** Mostek MK3801 Serial Timer Interrupt Controller (Z80-STI) emulation ***************************************************************************/ /* TODO: - timers (other than delay mode) - serial I/O - reset behavior */ #include "emu.h" #include "z80sti.h" #include "cpu/z80/z80.h" //#define VERBOSE 1 #include "logmacro.h" // device type definition DEFINE_DEVICE_TYPE(Z80STI, z80sti_device, "z80sti", "Mostek MK3801 STI") //************************************************************************** // CONSTANTS //************************************************************************** // timer C/D control register //const int TCDC_TARS = 0x80; //const int TCDC_TBRS = 0x08; // interrupt vector register //const int PVR_ISE = 0x08; //const int PVR_VR4 = 0x10; // general purpose I/O interrupt levels const int z80sti_device::INT_LEVEL_GPIP[] = { IR_P0, IR_P1, IR_P2, IR_P3, IR_P4, IR_P5, IR_P6, IR_P7 }; // timer interrupt levels const int z80sti_device::INT_LEVEL_TIMER[] = { IR_TA, IR_TB, IR_TC, IR_TD }; // interrupt vectors const uint8_t z80sti_device::INT_VECTOR[] = { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e }; // timer prescaler divisors const int z80sti_device::PRESCALER[] = { 0, 4, 10, 16, 50, 64, 100, 200 }; //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // z80sti_device - constructor //------------------------------------------------- z80sti_device::z80sti_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, Z80STI, tag, owner, clock) , device_serial_interface(mconfig, *this) , device_z80daisy_interface(mconfig, *this) , m_out_int_cb(*this) , m_in_gpio_cb(*this) , m_out_gpio_cb(*this) , m_out_so_cb(*this) , m_out_tao_cb(*this) , m_out_tbo_cb(*this) , m_out_tco_cb(*this) , m_out_tdo_cb(*this) , m_rx_clock(0) , m_tx_clock(0) , m_gpip(0) , m_aer(0) , m_ier(0) , m_ipr(0) , m_isr(0) , m_imr(0) { for (auto & elem : m_int_state) { elem = 0; } } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void z80sti_device::device_start() { // resolve callbacks m_out_int_cb.resolve_safe(); m_in_gpio_cb.resolve_safe(0); m_out_gpio_cb.resolve_safe(); m_out_so_cb.resolve_safe(); m_out_tao_cb.resolve_safe(); m_out_tbo_cb.resolve_safe(); m_out_tco_cb.resolve_safe(); m_out_tdo_cb.resolve_safe(); // create the counter timers m_timer[TIMER_A] = timer_alloc(TIMER_A); m_timer[TIMER_B] = timer_alloc(TIMER_B); m_timer[TIMER_C] = timer_alloc(TIMER_C); m_timer[TIMER_D] = timer_alloc(TIMER_D); // create serial receive clock timer if (m_rx_clock > 0) { set_rcv_rate(m_rx_clock); } // create serial transmit clock timer if (m_tx_clock > 0) { set_tra_rate(m_tx_clock); } // state saving save_item(NAME(m_gpip)); save_item(NAME(m_aer)); save_item(NAME(m_ddr)); save_item(NAME(m_ier)); save_item(NAME(m_ipr)); save_item(NAME(m_isr)); save_item(NAME(m_imr)); save_item(NAME(m_pvr)); save_item(NAME(m_int_state)); save_item(NAME(m_tabc)); save_item(NAME(m_tcdc)); save_item(NAME(m_tdr)); save_item(NAME(m_tmc)); save_item(NAME(m_to)); save_item(NAME(m_scr)); save_item(NAME(m_ucr)); save_item(NAME(m_rsr)); save_item(NAME(m_tsr)); save_item(NAME(m_udr)); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void z80sti_device::device_reset() { memset(m_tmc, 0, sizeof(m_tmc)); memset(m_to, 0, sizeof(m_to)); transmit_register_reset(); receive_register_reset(); } //------------------------------------------------- // device_timer - handler timer events //------------------------------------------------- void z80sti_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { timer_count(id); } //------------------------------------------------- // tra_callback - //------------------------------------------------- void z80sti_device::tra_callback() { m_out_so_cb(transmit_register_get_data_bit()); } //------------------------------------------------- // tra_complete - //------------------------------------------------- void z80sti_device::tra_complete() { // TODO } //------------------------------------------------- // rcv_complete - //------------------------------------------------- void z80sti_device::rcv_complete() { // TODO } //************************************************************************** // DAISY CHAIN INTERFACE //************************************************************************** //------------------------------------------------- // z80daisy_irq_state - get interrupt status //------------------------------------------------- int z80sti_device::z80daisy_irq_state() { int state = 0, i; // loop over all interrupt sources for (i = 15; i >= 0; i--) { // if we're servicing a request, don't indicate more interrupts if (m_int_state[i] & Z80_DAISY_IEO) { state |= Z80_DAISY_IEO; break; } if (BIT(m_imr, i)) { state |= m_int_state[i]; } } LOG("Z80STI Interrupt State: %u\n", state); return state; } //------------------------------------------------- // z80daisy_irq_ack - interrupt acknowledge //------------------------------------------------- int z80sti_device::z80daisy_irq_ack() { int i; // loop over all interrupt sources for (i = 15; i >= 0; i--) { // find the first channel with an interrupt requested if (m_int_state[i] & Z80_DAISY_INT) { uint8_t vector = (m_pvr & 0xe0) | INT_VECTOR[i]; // clear interrupt, switch to the IEO state, and update the IRQs m_int_state[i] = Z80_DAISY_IEO; // clear interrupt pending register bit m_ipr &= ~(1 << i); // set interrupt in-service register bit m_isr |= (1 << i); check_interrupts(); LOG("Z80STI Interrupt Acknowledge Vector: %02x\n", vector); return vector; } } logerror("z80sti_irq_ack: failed to find an interrupt to ack!\n"); return 0; } //------------------------------------------------- // z80daisy_irq_reti - return from interrupt //------------------------------------------------- void z80sti_device::z80daisy_irq_reti() { int i; LOG("Z80STI Return from Interrupt\n"); // loop over all interrupt sources for (i = 15; i >= 0; i--) { // find the first channel with an IEO pending if (m_int_state[i] & Z80_DAISY_IEO) { // clear the IEO state and update the IRQs m_int_state[i] &= ~Z80_DAISY_IEO; // clear interrupt in-service register bit m_isr &= ~(1 << i); check_interrupts(); return; } } logerror("z80sti_irq_reti: failed to find an interrupt to clear IEO on!\n"); } //************************************************************************** // IMPLEMENTATION //************************************************************************** //------------------------------------------------- // check_interrupts - set the interrupt request // line state //------------------------------------------------- void z80sti_device::check_interrupts() { if (m_ipr & m_imr) { m_out_int_cb(ASSERT_LINE); } else { m_out_int_cb(CLEAR_LINE); } } //------------------------------------------------- // take_interrupt - mark an interrupt pending //------------------------------------------------- void z80sti_device::take_interrupt(int level) { // set interrupt pending register bit m_ipr |= 1 << level; // trigger interrupt m_int_state[level] |= Z80_DAISY_INT; check_interrupts(); } //------------------------------------------------- // read - register read //------------------------------------------------- READ8_MEMBER( z80sti_device::read ) { uint8_t data = 0; switch (offset & 0x0f) { case REGISTER_IR: switch (m_pvr & 0x07) { case REGISTER_IR_SCR: data = m_scr; break; case REGISTER_IR_TDDR: data = m_tmc[TIMER_D]; break; case REGISTER_IR_TCDR: data = m_tmc[TIMER_C]; break; case REGISTER_IR_AER: data = m_aer; break; case REGISTER_IR_IERB: data = m_ier & 0xff; break; case REGISTER_IR_IERA: data = m_ier >> 8; break; case REGISTER_IR_DDR: data = m_ddr; break; case REGISTER_IR_TCDC: data = m_tcdc; break; } break; case REGISTER_GPIP: m_gpip = (m_in_gpio_cb(0) & ~m_ddr) | (m_gpip & m_ddr); data = m_gpip; break; case REGISTER_IPRB: data = m_ipr & 0xff; break; case REGISTER_IPRA: data = m_ipr >> 8; break; case REGISTER_ISRB: data = m_isr & 0xff; break; case REGISTER_ISRA: data = m_isr >> 8; break; case REGISTER_IMRB: data = m_imr & 0xff; break; case REGISTER_IMRA: data = m_imr >> 8; break; case REGISTER_PVR: data = m_pvr; break; case REGISTER_TABC: data = m_tabc; break; case REGISTER_TBDR: data = m_tmc[TIMER_B]; break; case REGISTER_TADR: data = m_tmc[TIMER_A]; break; case REGISTER_UCR: data = m_ucr; break; case REGISTER_RSR: data = m_rsr; break; case REGISTER_TSR: data = m_tsr; break; case REGISTER_UDR: data = m_udr; break; } return data; } //------------------------------------------------- // write - register write //------------------------------------------------- WRITE8_MEMBER( z80sti_device::write ) { switch (offset & 0x0f) { case REGISTER_IR: switch (m_pvr & 0x07) { case REGISTER_IR_SCR: LOG("Z80STI Sync Character Register: %x\n", data); m_scr = data; break; case REGISTER_IR_TDDR: LOG("Z80STI Timer D Data Register: %x\n", data); m_tdr[TIMER_D] = data; break; case REGISTER_IR_TCDR: LOG("Z80STI Timer C Data Register: %x\n", data); m_tdr[TIMER_C] = data; break; case REGISTER_IR_AER: LOG("Z80STI Active Edge Register: %x\n", data); m_aer = data; break; case REGISTER_IR_IERB: LOG("Z80STI Interrupt Enable Register B: %x\n", data); m_ier = (m_ier & 0xff00) | data; check_interrupts(); break; case REGISTER_IR_IERA: LOG("Z80STI Interrupt Enable Register A: %x\n", data); m_ier = (data << 8) | (m_ier & 0xff); check_interrupts(); break; case REGISTER_IR_DDR: LOG("Z80STI Data Direction Register: %x\n", data); m_ddr = data; break; case REGISTER_IR_TCDC: { int tcc = PRESCALER[(data >> 4) & 0x07]; int tdc = PRESCALER[data & 0x07]; m_tcdc = data; LOG("Z80STI Timer C Prescaler: %u\n", tcc); LOG("Z80STI Timer D Prescaler: %u\n", tdc); if (tcc) m_timer[TIMER_C]->adjust(attotime::from_hz(clock() / tcc), TIMER_C, attotime::from_hz(clock() / tcc)); else m_timer[TIMER_C]->enable(false); if (tdc) m_timer[TIMER_D]->adjust(attotime::from_hz(clock() / tdc), TIMER_D, attotime::from_hz(clock() / tdc)); else m_timer[TIMER_D]->enable(false); if (BIT(data, 7)) { LOG("Z80STI Timer A Reset\n"); m_to[TIMER_A] = 0; m_out_tao_cb(m_to[TIMER_A]); } if (BIT(data, 3)) { LOG("Z80STI Timer B Reset\n"); m_to[TIMER_B] = 0; m_out_tbo_cb(m_to[TIMER_B]); } } break; } break; case REGISTER_GPIP: LOG("Z80STI General Purpose I/O Register: %x\n", data); m_gpip = data & m_ddr; m_out_gpio_cb((offs_t)0, m_gpip); break; case REGISTER_IPRB: { int i; LOG("Z80STI Interrupt Pending Register B: %x\n", data); m_ipr &= (m_ipr & 0xff00) | data; for (i = 0; i < 16; i++) { if (!BIT(m_ipr, i) && (m_int_state[i] == Z80_DAISY_INT)) m_int_state[i] = 0; } check_interrupts(); } break; case REGISTER_IPRA: { int i; LOG("Z80STI Interrupt Pending Register A: %x\n", data); m_ipr &= (data << 8) | (m_ipr & 0xff); for (i = 0; i < 16; i++) { if (!BIT(m_ipr, i) && (m_int_state[i] == Z80_DAISY_INT)) m_int_state[i] = 0; } check_interrupts(); } break; case REGISTER_ISRB: LOG("Z80STI Interrupt In-Service Register B: %x\n", data); m_isr &= (m_isr & 0xff00) | data; break; case REGISTER_ISRA: LOG("Z80STI Interrupt In-Service Register A: %x\n", data); m_isr &= (data << 8) | (m_isr & 0xff); break; case REGISTER_IMRB: LOG("Z80STI Interrupt Mask Register B: %x\n", data); m_imr = (m_imr & 0xff00) | data; m_isr &= m_imr; check_interrupts(); break; case REGISTER_IMRA: LOG("Z80STI Interrupt Mask Register A: %x\n", data); m_imr = (data << 8) | (m_imr & 0xff); m_isr &= m_imr; check_interrupts(); break; case REGISTER_PVR: LOG("Z80STI Interrupt Vector: %02x\n", data & 0xe0); LOG("Z80STI IR Address: %01x\n", data & 0x07); m_pvr = data; break; case REGISTER_TABC: { int tac = PRESCALER[(data >> 4) & 0x07]; int tbc = PRESCALER[data & 0x07]; m_tabc = data; LOG("Z80STI Timer A Prescaler: %u\n", tac); LOG("Z80STI Timer B Prescaler: %u\n", tbc); if (tac) m_timer[TIMER_A]->adjust(attotime::from_hz(clock() / tac), TIMER_A, attotime::from_hz(clock() / tac)); else m_timer[TIMER_A]->enable(false); if (tbc) m_timer[TIMER_B]->adjust(attotime::from_hz(clock() / tbc), TIMER_B, attotime::from_hz(clock() / tbc)); else m_timer[TIMER_B]->enable(false); } break; case REGISTER_TBDR: LOG("Z80STI Timer B Data Register: %x\n", tag(), data); m_tdr[TIMER_B] = data; m_tmc[TIMER_B] = data; break; case REGISTER_TADR: LOG("Z80STI Timer A Data Register: %x\n", tag(), data); m_tdr[TIMER_A] = data; m_tmc[TIMER_A] = data; break; case REGISTER_UCR: LOG("Z80STI USART Control Register: %x\n", tag(), data); m_ucr = data; break; case REGISTER_RSR: LOG("Z80STI Receiver Status Register: %x\n", tag(), data); m_rsr = data; break; case REGISTER_TSR: LOG("Z80STI Transmitter Status Register: %x\n", tag(), data); m_tsr = data; break; case REGISTER_UDR: LOG("Z80STI USART Data Register: %x\n", tag(), data); m_udr = data; break; } } //------------------------------------------------- // timer_count - timer count down //------------------------------------------------- void z80sti_device::timer_count(int index) { if (m_tmc[index] == 0x01) { //LOG("Z80STI Timer %c Expired\n", 'A' + index); // toggle timer output signal m_to[index] = !m_to[index]; switch (index) { case TIMER_A: m_out_tao_cb(m_to[index]); break; case TIMER_B: m_out_tbo_cb(m_to[index]); break; case TIMER_C: m_out_tco_cb(m_to[index]); break; case TIMER_D: m_out_tdo_cb(m_to[index]); break; } if (m_ier & (1 << INT_LEVEL_TIMER[index])) { LOG("Z80STI for Timer %c\n", 'A' + index); // signal timer elapsed interrupt take_interrupt(INT_LEVEL_TIMER[index]); } // load timer main counter m_tmc[index] = m_tdr[index]; } else { // count down m_tmc[index]--; } } //------------------------------------------------- // gpip_input - GPIP input line write //------------------------------------------------- void z80sti_device::gpip_input(int bit, int state) { int aer = BIT(m_aer, bit); int old_state = BIT(m_gpip, bit); if ((old_state ^ aer) && !(state ^ aer)) { LOG("Z80STI Edge Transition Detected on Bit: %u\n", bit); if (m_ier & (1 << INT_LEVEL_GPIP[bit])) { LOG("Z80STI Interrupt Pending for P%u\n", bit); take_interrupt(INT_LEVEL_GPIP[bit]); } } m_gpip = (m_gpip & ~(1 << bit)) | (state << bit); } WRITE_LINE_MEMBER( z80sti_device::i0_w ) { gpip_input(0, state); } WRITE_LINE_MEMBER( z80sti_device::i1_w ) { gpip_input(1, state); } WRITE_LINE_MEMBER( z80sti_device::i2_w ) { gpip_input(2, state); } WRITE_LINE_MEMBER( z80sti_device::i3_w ) { gpip_input(3, state); } WRITE_LINE_MEMBER( z80sti_device::i4_w ) { gpip_input(4, state); } WRITE_LINE_MEMBER( z80sti_device::i5_w ) { gpip_input(5, state); } WRITE_LINE_MEMBER( z80sti_device::i6_w ) { gpip_input(6, state); } WRITE_LINE_MEMBER( z80sti_device::i7_w ) { gpip_input(7, state); } //------------------------------------------------- // rc_w - receiver clock //------------------------------------------------- WRITE_LINE_MEMBER( z80sti_device::rc_w ) { rx_clock_w(state); } //------------------------------------------------- // tc_w - transmitter clock //------------------------------------------------- WRITE_LINE_MEMBER( z80sti_device::tc_w ) { tx_clock_w(state); }