// license:BSD-3-Clause // copyright-holders:Couriersud /********************************************************************** Z80 DMA interface and emulation For datasheet http://www.zilog.com/docs/z80/ps0179.pdf 2008/01 couriersud - architecture copied from 8257 DMA - significant changes to implementation - This is only a minimum implementation to support dkong3 and mario drivers - Only memory to memory is tested! TODO: - reset command (C3) is handled improperly - rewrite to match documentation - implement missing features - implement more asserts - implement a INPUT_LINE_BUSREQ for Z80. As a workaround, HALT is used. This implies burst mode. **********************************************************************/ #include "emu.h" #include "z80dma.h" #define LOG_GENERAL (1U << 0) #define LOG_DMA (1U << 1) //#define VERBOSE (LOG_GENERAL | LOG_DMA) #include "logmacro.h" #define LOGDMA(...) LOGMASKED(LOG_DMA, __VA_ARGS__) //************************************************************************** // CONSTANTS //************************************************************************** enum { INT_RDY = 0, INT_MATCH, INT_END_OF_BLOCK, INT_MATCH_END_OF_BLOCK }; constexpr int COMMAND_RESET = 0xc3; constexpr int COMMAND_RESET_PORT_A_TIMING = 0xc7; constexpr int COMMAND_RESET_PORT_B_TIMING = 0xcb; constexpr int COMMAND_LOAD = 0xcf; constexpr int COMMAND_CONTINUE = 0xd3; constexpr int COMMAND_DISABLE_INTERRUPTS = 0xaf; constexpr int COMMAND_ENABLE_INTERRUPTS = 0xab; constexpr int COMMAND_RESET_AND_DISABLE_INTERRUPTS = 0xa3; constexpr int COMMAND_ENABLE_AFTER_RETI = 0xb7; constexpr int COMMAND_READ_STATUS_BYTE = 0xbf; constexpr int COMMAND_REINITIALIZE_STATUS_BYTE = 0x8b; constexpr int COMMAND_INITIATE_READ_SEQUENCE = 0xa7; constexpr int COMMAND_FORCE_READY = 0xb3; constexpr int COMMAND_ENABLE_DMA = 0x87; constexpr int COMMAND_DISABLE_DMA = 0x83; constexpr int COMMAND_READ_MASK_FOLLOWS = 0xbb; constexpr int TM_TRANSFER = 0x01; constexpr int TM_SEARCH = 0x02; constexpr int TM_SEARCH_TRANSFER = 0x03; //************************************************************************** // MACROS //************************************************************************** #define REGNUM(_m, _s) (((_m)<<3) + (_s)) #define GET_REGNUM(_r) (&(_r) - &(WR0)) #define REG(_m, _s) m_regs[REGNUM(_m,_s)] #define WR0 REG(0, 0) #define WR1 REG(1, 0) #define WR2 REG(2, 0) #define WR3 REG(3, 0) #define WR4 REG(4, 0) #define WR5 REG(5, 0) #define WR6 REG(6, 0) #define PORTA_ADDRESS_L REG(0,1) #define PORTA_ADDRESS_H REG(0,2) #define BLOCKLEN_L REG(0,3) #define BLOCKLEN_H REG(0,4) #define PORTA_TIMING REG(1,1) #define PORTB_TIMING REG(2,1) #define MASK_BYTE REG(3,1) #define MATCH_BYTE REG(3,2) #define PORTB_ADDRESS_L REG(4,1) #define PORTB_ADDRESS_H REG(4,2) #define INTERRUPT_CTRL REG(4,3) #define INTERRUPT_VECTOR REG(4,4) #define PULSE_CTRL REG(4,5) #define READ_MASK REG(6,1) #define PORTA_ADDRESS ((PORTA_ADDRESS_H<<8) | PORTA_ADDRESS_L) #define PORTB_ADDRESS ((PORTB_ADDRESS_H<<8) | PORTB_ADDRESS_L) #define BLOCKLEN ((BLOCKLEN_H<<8) | BLOCKLEN_L) #define PORTA_INC (WR1 & 0x10) #define PORTB_INC (WR2 & 0x10) #define PORTA_FIXED (((WR1 >> 4) & 0x02) == 0x02) #define PORTB_FIXED (((WR2 >> 4) & 0x02) == 0x02) #define PORTA_MEMORY (((WR1 >> 3) & 0x01) == 0x00) #define PORTB_MEMORY (((WR2 >> 3) & 0x01) == 0x00) #define PORTA_CYCLE_LEN (4-(PORTA_TIMING & 0x03)) #define PORTB_CYCLE_LEN (4-(PORTB_TIMING & 0x03)) #define PORTA_IS_SOURCE ((WR0 >> 2) & 0x01) #define PORTB_IS_SOURCE (!PORTA_IS_SOURCE) #define TRANSFER_MODE (WR0 & 0x03) #define MATCH_F_SET (m_status &= ~0x10) #define MATCH_F_CLEAR (m_status |= 0x10) #define EOB_F_SET (m_status &= ~0x20) #define EOB_F_CLEAR (m_status |= 0x20) #define READY_ACTIVE_HIGH ((WR5>>3) & 0x01) #define AUTO_RESTART ((WR5>>5) & 0x01) #define INTERRUPT_ENABLE (WR3 & 0x20) #define INT_ON_MATCH (INTERRUPT_CTRL & 0x01) #define INT_ON_END_OF_BLOCK (INTERRUPT_CTRL & 0x02) #define INT_ON_READY (INTERRUPT_CTRL & 0x40) #define STATUS_AFFECTS_VECTOR (INTERRUPT_CTRL & 0x20) //************************************************************************** // LIVE DEVICE //************************************************************************** // device type definition DEFINE_DEVICE_TYPE(Z80DMA, z80dma_device, "z80dma", "Z80 DMA Controller") //------------------------------------------------- // z80dma_device - constructor //------------------------------------------------- z80dma_device::z80dma_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, Z80DMA, tag, owner, clock) , device_z80daisy_interface(mconfig, *this) , m_out_busreq_cb(*this) , m_out_int_cb(*this) , m_out_bao_cb(*this) , m_in_mreq_cb(*this) , m_out_mreq_cb(*this) , m_in_iorq_cb(*this) , m_out_iorq_cb(*this) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void z80dma_device::device_start() { // resolve callbacks m_out_busreq_cb.resolve_safe(); m_out_int_cb.resolve_safe(); m_out_bao_cb.resolve_safe(); m_in_mreq_cb.resolve_safe(0); m_out_mreq_cb.resolve_safe(); m_in_iorq_cb.resolve_safe(0); m_out_iorq_cb.resolve_safe(); // allocate timer m_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(z80dma_device::timerproc), this)); // register for state saving save_item(NAME(m_regs)); save_item(NAME(m_regs_follow)); save_item(NAME(m_num_follow)); save_item(NAME(m_cur_follow)); save_item(NAME(m_status)); save_item(NAME(m_dma_enabled)); save_item(NAME(m_vector)); save_item(NAME(m_ip)); save_item(NAME(m_ius)); save_item(NAME(m_addressA)); save_item(NAME(m_addressB)); save_item(NAME(m_count)); save_item(NAME(m_rdy)); save_item(NAME(m_force_ready)); save_item(NAME(m_is_read)); save_item(NAME(m_cur_cycle)); save_item(NAME(m_latch)); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void z80dma_device::device_reset() { m_status = 0; m_rdy = 0; m_force_ready = 0; m_num_follow = 0; m_dma_enabled = 0; m_read_num_follow = m_read_cur_follow = 0; m_reset_pointer = 0; m_is_read = false; memset(m_regs, 0, sizeof(m_regs)); memset(m_regs_follow, 0, sizeof(m_regs_follow)); // disable interrupts WR3 &= ~0x20; m_ip = 0; m_ius = 0; m_vector = 0; update_status(); } //************************************************************************** // DAISY CHAIN INTERFACE //************************************************************************** //------------------------------------------------- // z80daisy_irq_state - return the overall IRQ // state for this device //------------------------------------------------- int z80dma_device::z80daisy_irq_state() { int state = 0; if (m_ip) { // interrupt pending state = Z80_DAISY_INT; } else if (m_ius) { // interrupt under service state = Z80_DAISY_IEO; } LOG("Z80DMA Interrupt State: %u\n", state); return state; } //------------------------------------------------- // z80daisy_irq_ack - acknowledge an IRQ and // return the appropriate vector //------------------------------------------------- int z80dma_device::z80daisy_irq_ack() { if (m_ip) { LOG("Z80DMA Interrupt Acknowledge\n"); // clear interrupt pending flag m_ip = 0; interrupt_check(); // set interrupt under service flag m_ius = 1; return m_vector; } //logerror("z80dma_irq_ack: failed to find an interrupt to ack!\n"); return 0; } //------------------------------------------------- // z80daisy_irq_reti - clear the interrupt // pending state to allow other interrupts through //------------------------------------------------- void z80dma_device::z80daisy_irq_reti() { if (m_ius) { LOG("Z80DMA Return from Interrupt\n"); // clear interrupt under service flag m_ius = 0; interrupt_check(); return; } //logerror("z80dma_irq_reti: failed to find an interrupt to clear IEO on!\n"); } //************************************************************************** // INTERNAL STATE MANAGEMENT //************************************************************************** //------------------------------------------------- // is_ready - ready for DMA transfer? //------------------------------------------------- int z80dma_device::is_ready() { return (m_force_ready) || (m_rdy == READY_ACTIVE_HIGH); } //------------------------------------------------- // interrupt_check - update IRQ line state //------------------------------------------------- void z80dma_device::interrupt_check() { m_out_int_cb(m_ip ? ASSERT_LINE : CLEAR_LINE); } //------------------------------------------------- // trigger_interrupt - trigger DMA interrupt //------------------------------------------------- void z80dma_device::trigger_interrupt(int level) { if (!m_ius && INTERRUPT_ENABLE) { // set interrupt pending flag m_ip = 1; // set interrupt vector if (STATUS_AFFECTS_VECTOR) { m_vector = (INTERRUPT_VECTOR & 0xf9) | (level << 1); } else { m_vector = INTERRUPT_VECTOR; } m_status &= ~0x08; LOG("Z80DMA Interrupt Pending\n"); interrupt_check(); } } //------------------------------------------------- // do_read - perform DMA read //------------------------------------------------- void z80dma_device::do_read() { uint8_t mode; mode = TRANSFER_MODE; switch(mode) { case TM_TRANSFER: case TM_SEARCH: case TM_SEARCH_TRANSFER: if (PORTA_IS_SOURCE) { if (PORTA_MEMORY) m_latch = m_in_mreq_cb(m_addressA); else m_latch = m_in_iorq_cb(m_addressA); LOGDMA("Z80DMA A src: %04x %s -> data: %02x\n", m_addressA, PORTA_MEMORY ? "mem" : "i/o", m_latch); } else { if (PORTB_MEMORY) m_latch = m_in_mreq_cb(m_addressB); else m_latch = m_in_iorq_cb(m_addressB); LOGDMA("Z80DMA B src: %04x %s -> data: %02x\n", m_addressB, PORTB_MEMORY ? "mem" : "i/o", m_latch); } break; default: logerror("z80dma_do_operation: invalid mode %d!\n", mode); break; } } //------------------------------------------------- // do_write - perform DMA write //------------------------------------------------- void z80dma_device::do_transfer_write() { if (PORTA_IS_SOURCE) { if (PORTB_MEMORY) m_out_mreq_cb((offs_t)m_addressB, m_latch); else m_out_iorq_cb((offs_t)m_addressB, m_latch); LOGDMA("Z80DMA B dst: %04x %s\n", m_addressB, PORTB_MEMORY ? "mem" : "i/o"); } else { if (PORTA_MEMORY) m_out_mreq_cb((offs_t)m_addressA, m_latch); else m_out_iorq_cb((offs_t)m_addressA, m_latch); LOGDMA("Z80DMA A dst: %04x %s\n", m_addressA, PORTA_MEMORY ? "mem" : "i/o"); } } void z80dma_device::do_search() { uint8_t load_byte,match_byte; load_byte = m_latch | MASK_BYTE; match_byte = MATCH_BYTE | MASK_BYTE; //LOG("%02x %02x\n",load_byte,match_byte)); if (load_byte == match_byte) { if (INT_ON_MATCH) { trigger_interrupt(INT_MATCH); } } } int z80dma_device::do_write() { int done; uint8_t mode; mode = TRANSFER_MODE; if (m_count == 0x0000) { //FIXME: Any signal here } switch(mode) { case TM_TRANSFER: do_transfer_write(); break; case TM_SEARCH: do_search(); break; case TM_SEARCH_TRANSFER: do_transfer_write(); do_search(); break; default: logerror("z80dma_do_operation: invalid mode %d!\n", mode); break; } m_addressA += PORTA_FIXED ? 0 : PORTA_INC ? 1 : -1; m_addressB += PORTB_FIXED ? 0 : PORTB_INC ? 1 : -1; m_count--; done = (m_count == 0xFFFF); //correct? if (done) { //FIXME: interrupt ? } return done; } //------------------------------------------------- // timerproc //------------------------------------------------- TIMER_CALLBACK_MEMBER(z80dma_device::timerproc) { int done; if (--m_cur_cycle) { return; } if (m_is_read && !is_ready()) return; if (m_is_read) { /* TODO: there's a nasty recursion bug with Alpha for Sharp X1 Turbo on the transfers with this function! */ do_read(); done = 0; m_is_read = false; m_cur_cycle = (PORTA_IS_SOURCE ? PORTA_CYCLE_LEN : PORTB_CYCLE_LEN); } else { done = do_write(); m_is_read = true; m_cur_cycle = (PORTB_IS_SOURCE ? PORTA_CYCLE_LEN : PORTB_CYCLE_LEN); } if (done) { m_dma_enabled = 0; //FIXME: Correct? m_status = 0x09; m_status |= !is_ready() << 1; // ready line status if(TRANSFER_MODE == TM_TRANSFER) m_status |= 0x10; // no match found update_status(); LOG("Z80DMA End of Block\n"); if (INT_ON_END_OF_BLOCK) { trigger_interrupt(INT_END_OF_BLOCK); } if (AUTO_RESTART) { LOG("Z80DMA Auto Restart\n"); m_dma_enabled = 1; m_addressA = PORTA_ADDRESS; m_addressB = PORTB_ADDRESS; m_count = BLOCKLEN; m_status |= 0x30; } } } //------------------------------------------------- // update_status - update DMA status //------------------------------------------------- void z80dma_device::update_status() { uint16_t pending_transfer; attotime next; // no transfer is active right now; is there a transfer pending right now? pending_transfer = is_ready() & m_dma_enabled; if (pending_transfer) { m_is_read = true; m_cur_cycle = (PORTA_IS_SOURCE ? PORTA_CYCLE_LEN : PORTB_CYCLE_LEN); next = attotime::from_hz(clock()); m_timer->adjust( attotime::zero, 0, // 1 byte transferred in 4 clock cycles next); } else { if (m_is_read) { // no transfers active right now m_timer->reset(); } } // set the busreq line m_out_busreq_cb(pending_transfer ? ASSERT_LINE : CLEAR_LINE); } //************************************************************************** // READ/WRITE INTERFACES //************************************************************************** //------------------------------------------------- // read - register read //------------------------------------------------- uint8_t z80dma_device::read() { uint8_t res; if(m_read_num_follow == 0) // special case: Legend of Kage on X1 Turbo res = m_status; else { res = m_read_regs_follow[m_read_cur_follow]; } m_read_cur_follow++; if(m_read_cur_follow >= m_read_num_follow) m_read_cur_follow = 0; LOG("Z80DMA Read %02x\n", res); return res; } //------------------------------------------------- // write - register write //------------------------------------------------- void z80dma_device::write(uint8_t data) { if (m_num_follow == 0) { m_reset_pointer = 0; if ((data & 0x87) == 0) // WR2 { LOG("Z80DMA WR2 %02x\n", data); WR2 = data; if (data & 0x40) m_regs_follow[m_num_follow++] = GET_REGNUM(PORTB_TIMING); } else if ((data & 0x87) == 0x04) // WR1 { LOG("Z80DMA WR1 %02x\n", data); WR1 = data; if (data & 0x40) m_regs_follow[m_num_follow++] = GET_REGNUM(PORTA_TIMING); } else if ((data & 0x80) == 0) // WR0 { LOG("Z80DMA WR0 %02x\n", data); WR0 = data; if (data & 0x08) m_regs_follow[m_num_follow++] = GET_REGNUM(PORTA_ADDRESS_L); if (data & 0x10) m_regs_follow[m_num_follow++] = GET_REGNUM(PORTA_ADDRESS_H); if (data & 0x20) m_regs_follow[m_num_follow++] = GET_REGNUM(BLOCKLEN_L); if (data & 0x40) m_regs_follow[m_num_follow++] = GET_REGNUM(BLOCKLEN_H); } else if ((data & 0x83) == 0x80) // WR3 { LOG("Z80DMA WR3 %02x\n", data); WR3 = data; if (data & 0x08) m_regs_follow[m_num_follow++] = GET_REGNUM(MASK_BYTE); if (data & 0x10) m_regs_follow[m_num_follow++] = GET_REGNUM(MATCH_BYTE); } else if ((data & 0x83) == 0x81) // WR4 { LOG("Z80DMA WR4 %02x\n", data); WR4 = data; if (data & 0x04) m_regs_follow[m_num_follow++] = GET_REGNUM(PORTB_ADDRESS_L); if (data & 0x08) m_regs_follow[m_num_follow++] = GET_REGNUM(PORTB_ADDRESS_H); if (data & 0x10) m_regs_follow[m_num_follow++] = GET_REGNUM(INTERRUPT_CTRL); } else if ((data & 0xC7) == 0x82) // WR5 { LOG("Z80DMA WR5 %02x\n", data); WR5 = data; } else if ((data & 0x83) == 0x83) // WR6 { LOG("Z80DMA WR6 %02x\n", data); m_dma_enabled = 0; WR6 = data; switch (data) { case COMMAND_ENABLE_AFTER_RETI: fatalerror("Z80DMA '%s' Unimplemented WR6 command %02x\n", tag(), data); case COMMAND_READ_STATUS_BYTE: LOG("Z80DMA CMD Read status Byte\n"); READ_MASK = 1; m_read_regs_follow[0] = m_status; break; case COMMAND_RESET_AND_DISABLE_INTERRUPTS: WR3 &= ~0x20; m_ip = 0; m_ius = 0; m_force_ready = 0; m_status |= 0x08; break; case COMMAND_INITIATE_READ_SEQUENCE: LOG("Z80DMA Initiate Read Sequence\n"); m_read_cur_follow = m_read_num_follow = 0; if(READ_MASK & 0x01) { m_read_regs_follow[m_read_num_follow++] = m_status; } if(READ_MASK & 0x02) { m_read_regs_follow[m_read_num_follow++] = m_count & 0xff; } //byte counter (low) if(READ_MASK & 0x04) { m_read_regs_follow[m_read_num_follow++] = m_count >> 8; } //byte counter (high) if(READ_MASK & 0x08) { m_read_regs_follow[m_read_num_follow++] = m_addressA & 0xff; } //port A address (low) if(READ_MASK & 0x10) { m_read_regs_follow[m_read_num_follow++] = m_addressA >> 8; } //port A address (high) if(READ_MASK & 0x20) { m_read_regs_follow[m_read_num_follow++] = m_addressB & 0xff; } //port B address (low) if(READ_MASK & 0x40) { m_read_regs_follow[m_read_num_follow++] = m_addressB >> 8; } //port B address (high) break; case COMMAND_RESET: LOG("Z80DMA Reset\n"); m_dma_enabled = 0; m_force_ready = 0; m_ip = 0; m_ius = 0; interrupt_check(); // Needs six reset commands to reset the DMA { uint8_t WRi; for(WRi=0;WRi<7;WRi++) REG(WRi,m_reset_pointer) = 0; m_reset_pointer++; if(m_reset_pointer >= 6) { m_reset_pointer = 0; } } m_status = 0x38; break; case COMMAND_LOAD: m_force_ready = 0; m_addressA = PORTA_ADDRESS; m_addressB = PORTB_ADDRESS; m_count = BLOCKLEN; m_status |= 0x30; LOG("Z80DMA Load A: %x B: %x N: %x\n", m_addressA, m_addressB, m_count); break; case COMMAND_DISABLE_DMA: LOG("Z80DMA Disable DMA\n"); m_dma_enabled = 0; break; case COMMAND_ENABLE_DMA: LOG("Z80DMA Enable DMA\n"); m_dma_enabled = 1; update_status(); break; case COMMAND_READ_MASK_FOLLOWS: LOG("Z80DMA Set Read Mask\n"); m_regs_follow[m_num_follow++] = GET_REGNUM(READ_MASK); break; case COMMAND_CONTINUE: LOG("Z80DMA Continue\n"); m_count = BLOCKLEN; m_dma_enabled = 1; //"match not found" & "end of block" status flags zeroed here m_status |= 0x30; break; case COMMAND_RESET_PORT_A_TIMING: LOG("Z80DMA Reset Port A Timing\n"); PORTA_TIMING = 0; break; case COMMAND_RESET_PORT_B_TIMING: LOG("Z80DMA Reset Port B Timing\n"); PORTB_TIMING = 0; break; case COMMAND_FORCE_READY: LOG("Z80DMA Force Ready\n"); m_force_ready = 1; update_status(); break; case COMMAND_ENABLE_INTERRUPTS: LOG("Z80DMA Enable IRQ\n"); WR3 |= 0x20; break; case COMMAND_DISABLE_INTERRUPTS: LOG("Z80DMA Disable IRQ\n"); WR3 &= ~0x20; break; case COMMAND_REINITIALIZE_STATUS_BYTE: LOG("Z80DMA Reinitialize status byte\n"); m_status |= 0x30; m_ip = 0; break; case 0xFB: case 0xFF: // TODO: p8k triggers this, it probably crashed. LOG("Z80DMA undocumented command triggered 0x%02X!\n", data); break; default: logerror("Z80DMA Unknown WR6 command %02x\n", data); } } else if(data == 0x8e) //newtype on Sharp X1, unknown purpose logerror("Z80DMA Unknown base register %02x\n", data); else fatalerror("Z80DMA '%s' Unknown base register %02x\n", tag(), data); m_cur_follow = 0; } else { LOG("Z80DMA Write %02x\n", data); int nreg = m_regs_follow[m_cur_follow]; m_regs[nreg] = data; m_cur_follow++; if (m_cur_follow>=m_num_follow) m_num_follow = 0; if (nreg == REGNUM(4,3)) { m_num_follow=0; if (data & 0x08) m_regs_follow[m_num_follow++] = GET_REGNUM(PULSE_CTRL); if (data & 0x10) m_regs_follow[m_num_follow++] = GET_REGNUM(INTERRUPT_VECTOR); m_cur_follow = 0; } else if(m_regs_follow[m_num_follow] == GET_REGNUM(READ_MASK)) { m_read_cur_follow = m_read_num_follow = 0; if(READ_MASK & 0x01) { m_read_regs_follow[m_read_num_follow++] = m_status; } if(READ_MASK & 0x02) { m_read_regs_follow[m_read_num_follow++] = m_count & 0xff; } //byte counter (low) if(READ_MASK & 0x04) { m_read_regs_follow[m_read_num_follow++] = m_count >> 8; } //byte counter (high) if(READ_MASK & 0x08) { m_read_regs_follow[m_read_num_follow++] = m_addressA & 0xff; } //port A address (low) if(READ_MASK & 0x10) { m_read_regs_follow[m_read_num_follow++] = m_addressA >> 8; } //port A address (high) if(READ_MASK & 0x20) { m_read_regs_follow[m_read_num_follow++] = m_addressB & 0xff; } //port B address (low) if(READ_MASK & 0x40) { m_read_regs_follow[m_read_num_follow++] = m_addressB >> 8; } //port B address (high) } m_reset_pointer++; if(m_reset_pointer >= 6) { m_reset_pointer = 0; } } } //------------------------------------------------- // rdy_write_callback - deferred RDY signal write //------------------------------------------------- TIMER_CALLBACK_MEMBER(z80dma_device::rdy_write_callback) { // normalize state m_rdy = param; m_status = (m_status & 0xFD) | (!is_ready() << 1); update_status(); if (is_ready() && INT_ON_READY) { trigger_interrupt(INT_RDY); } } //------------------------------------------------- // rdy_w - ready input //------------------------------------------------- WRITE_LINE_MEMBER(z80dma_device::rdy_w) { LOG("Z80DMA RDY: %d Active High: %d\n", state, READY_ACTIVE_HIGH); machine().scheduler().synchronize(timer_expired_delegate(FUNC(z80dma_device::rdy_write_callback),this), state); } //------------------------------------------------- // wait_w - wait input //------------------------------------------------- WRITE_LINE_MEMBER(z80dma_device::wait_w) { } //------------------------------------------------- // bai_w - bus acknowledge input //------------------------------------------------- WRITE_LINE_MEMBER(z80dma_device::bai_w) { }