/**********************************************************************

    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 "memconv.h"
#include "z80dma.h"
#include "cpu/z80/z80daisy.h"


//**************************************************************************
//  CONSTANTS
//**************************************************************************

enum
{
	INT_RDY = 0,
	INT_MATCH,
	INT_END_OF_BLOCK,
	INT_MATCH_END_OF_BLOCK
};

const int COMMAND_RESET							= 0xc3;
const int COMMAND_RESET_PORT_A_TIMING			= 0xc7;
const int COMMAND_RESET_PORT_B_TIMING			= 0xcb;
const int COMMAND_LOAD							= 0xcf;
const int COMMAND_CONTINUE						= 0xd3;
const int COMMAND_DISABLE_INTERRUPTS			= 0xaf;
const int COMMAND_ENABLE_INTERRUPTS				= 0xab;
const int COMMAND_RESET_AND_DISABLE_INTERRUPTS	= 0xa3;
const int COMMAND_ENABLE_AFTER_RETI				= 0xb7;
const int COMMAND_READ_STATUS_BYTE				= 0xbf;
const int COMMAND_REINITIALIZE_STATUS_BYTE		= 0x8b;
const int COMMAND_INITIATE_READ_SEQUENCE		= 0xa7;
const int COMMAND_FORCE_READY					= 0xb3;
const int COMMAND_ENABLE_DMA					= 0x87;
const int COMMAND_DISABLE_DMA					= 0x83;
const int COMMAND_READ_MASK_FOLLOWS				= 0xbb;

const int TM_TRANSFER			= 0x01;
const int TM_SEARCH				= 0x02;
const int TM_SEARCH_TRANSFER	= 0x03;



//**************************************************************************
//  MACROS
//**************************************************************************

#define LOG 0
#define DMA_LOG 0

#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 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
const device_type Z80DMA = &device_creator<z80dma_device>;

//-------------------------------------------------
//  z80dma_device - constructor
//-------------------------------------------------

z80dma_device::z80dma_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: device_t(mconfig, Z80DMA, "Z8410", tag, owner, clock),
	  device_z80daisy_interface(mconfig, *this)
{
}


//-------------------------------------------------
//  device_config_complete - perform any
//  operations now that the configuration is
//  complete
//-------------------------------------------------

void z80dma_device::device_config_complete()
{
	// inherit a copy of the static data
	const z80dma_interface *intf = reinterpret_cast<const z80dma_interface *>(static_config());
	if (intf != NULL)
		*static_cast<z80dma_interface *>(this) = *intf;

	// or initialize to defaults if none provided
	else
	{
		memset(&m_out_busreq_cb, 0, sizeof(m_out_busreq_cb));
		memset(&m_out_int_cb, 0, sizeof(m_out_int_cb));
		memset(&m_out_bao_cb, 0, sizeof(m_out_bao_cb));
		memset(&m_in_mreq_cb, 0, sizeof(m_in_mreq_cb));
		memset(&m_out_mreq_cb, 0, sizeof(m_out_mreq_cb));
		memset(&m_in_iorq_cb, 0, sizeof(m_in_iorq_cb));
		memset(&m_out_iorq_cb, 0, sizeof(m_out_iorq_cb));
	}
}


//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void z80dma_device::device_start()
{
	// resolve callbacks
	m_out_busreq_func.resolve(m_out_busreq_cb, *this);
	m_out_int_func.resolve(m_out_int_cb, *this);
	m_out_bao_func.resolve(m_out_bao_cb, *this);
	m_in_mreq_func.resolve(m_in_mreq_cb, *this);
	m_out_mreq_func.resolve(m_out_mreq_cb, *this);
	m_in_iorq_func.resolve(m_in_iorq_cb, *this);
	m_out_iorq_func.resolve(m_out_iorq_cb, *this);

	// allocate timer
	m_timer = machine().scheduler().timer_alloc(FUNC(static_timerproc), (void *)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;

	// 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;
	}

	if (LOG) logerror("Z80DMA '%s' Interrupt State: %u\n", tag(), state);

	return state;
}


//-------------------------------------------------
//  z80daisy_irq_ack - acknowledge an IRQ and
//  return the appropriate vector
//-------------------------------------------------

int z80dma_device::z80daisy_irq_ack()
{
	if (m_ip)
	{
	    if (LOG) logerror("Z80DMA '%s' Interrupt Acknowledge\n", tag());

		// clear interrupt pending flag
		m_ip = 0;
	    interrupt_check();

		// set interrupt under service flag
		m_ius = 1;

		// disable DMA
		m_dma_enabled = 0;

		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)
	{
	    if (LOG) logerror("Z80DMA '%s' Return from Interrupt\n", tag());

		// 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_func(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;

		if (LOG) logerror("Z80DMA '%s' Interrupt Pending\n", tag());

		interrupt_check();
	}
}


//-------------------------------------------------
//  do_read - perform DMA read
//-------------------------------------------------

void z80dma_device::do_read()
{
	UINT8 mode;

	mode = TRANSFER_MODE;
	switch(mode) {
		case TM_TRANSFER:
		case TM_SEARCH:
			if (PORTA_IS_SOURCE)
			{
				if (PORTA_MEMORY)
					m_latch = m_in_mreq_func(m_addressA);
				else
					m_latch = m_in_iorq_func(m_addressA);

				if (DMA_LOG) logerror("Z80DMA '%s' A src: %04x %s -> data: %02x\n", tag(), m_addressA, PORTA_MEMORY ? "mem" : "i/o", m_latch);
			}
			else
			{
				if (PORTB_MEMORY)
					m_latch = m_in_mreq_func(m_addressB);
				else
					m_latch = m_in_iorq_func(m_addressB);

				if (DMA_LOG) logerror("Z80DMA '%s' B src: %04x %s -> data: %02x\n", tag(), m_addressB, PORTB_MEMORY ? "mem" : "i/o", m_latch);
			}
			break;
		case TM_SEARCH_TRANSFER:
			fatalerror("z80dma_do_operation: unhandled search & transfer mode !\n");
			break;
		default:
			logerror("z80dma_do_operation: invalid mode %d!\n", mode);
			break;
	}
}


//-------------------------------------------------
//  do_write - perform DMA write
//-------------------------------------------------

int z80dma_device::do_write()
{
	int done = 0;
	UINT8 mode;

	mode = TRANSFER_MODE;
	if (m_count == 0x0000)
	{
		//FIXME: Any signal here
	}
	switch(mode) {
		case TM_TRANSFER:
			if (PORTA_IS_SOURCE)
			{
				if (PORTB_MEMORY)
					m_out_mreq_func(m_addressB, m_latch);
				else
					m_out_iorq_func(m_addressB, m_latch);

				if (DMA_LOG) logerror("Z80DMA '%s' B dst: %04x %s\n", tag(), m_addressB, PORTB_MEMORY ? "mem" : "i/o");
			}
			else
			{
				if (PORTA_MEMORY)
					m_out_mreq_func(m_addressA, m_latch);
				else
					m_out_iorq_func(m_addressA, m_latch);

				if (DMA_LOG) logerror("Z80DMA '%s' A dst: %04x %s\n", tag(), m_addressA, PORTA_MEMORY ? "mem" : "i/o");
			}

			break;

		case TM_SEARCH:
			{
				UINT8 load_byte,match_byte;
				load_byte = m_latch | MASK_BYTE;
				match_byte = MATCH_BYTE | MASK_BYTE;
				//if (LOG) logerror("%02x %02x\n",load_byte,match_byte));
				if (load_byte == match_byte)
				{
					if (INT_ON_MATCH)
					{
						trigger_interrupt(INT_MATCH);
					}
				}

			}
			break;

		case TM_SEARCH_TRANSFER:
			fatalerror("z80dma_do_operation: unhandled search & transfer mode !\n");
			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
//-------------------------------------------------

void 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();
		if (LOG) logerror("Z80DMA '%s' End of Block\n", tag());

		if (INT_ON_END_OF_BLOCK)
        {
			trigger_interrupt(INT_END_OF_BLOCK);
        }
	}
}


//-------------------------------------------------
//  update_status - update DMA status
//-------------------------------------------------

void z80dma_device::update_status()
{
	UINT16 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_func(pending_transfer ? ASSERT_LINE : CLEAR_LINE);
}



//**************************************************************************
//  READ/WRITE INTERFACES
//**************************************************************************

//-------------------------------------------------
//  read - register read
//-------------------------------------------------

UINT8 z80dma_device::read()
{
	UINT8 res;

	if(m_read_num_follow == 0) /* TODO: should return the status, but let me know WHAT uses this first */
		fatalerror("Z80DMA '%s' Read without anything setted into stack", tag());

	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;

	if (LOG) logerror("Z80DMA '%s' Read %02x\n", tag(), res);

	return res;
}


//-------------------------------------------------
//  write - register write
//-------------------------------------------------

void z80dma_device::write(UINT8 data)
{
	if (LOG) logerror("Z80DMA '%s' Write %02x\n", tag(), data);

	if (m_num_follow == 0)
	{
		m_reset_pointer = 0;

		if ((data & 0x87) == 0) // WR2
		{
			WR2 = data;
			if (data & 0x40)
				m_regs_follow[m_num_follow++] = GET_REGNUM(PORTB_TIMING);
		}
		else if ((data & 0x87) == 0x04) // WR1
		{
			WR1 = data;
			if (data & 0x40)
				m_regs_follow[m_num_follow++] = GET_REGNUM(PORTA_TIMING);
		}
		else if ((data & 0x80) == 0) // WR0
		{
			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
		{
			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
		{
			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
		{
			WR5 = data;
		}
		else if ((data & 0x83) == 0x83) // WR6
		{
			m_dma_enabled = 0;

			WR6 = data;

			switch (data)
			{
				case COMMAND_ENABLE_AFTER_RETI:
					fatalerror("Z80DMA '%s' Unimplemented WR6 command %02x", tag(), data);
					break;
				case COMMAND_READ_STATUS_BYTE:
					if (LOG) logerror("Z80DMA '%s' CMD Read status Byte\n", tag());
        			READ_MASK = 1;
        			m_read_regs_follow[m_read_num_follow++] = 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:
					if (LOG) logerror("Z80DMA '%s' Initiate Read Sequence\n", tag());
					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:
					if (LOG) logerror("Z80DMA '%s' Reset\n", tag());
					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 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;

					if (LOG) logerror("Z80DMA '%s' Load A: %x B: %x N: %x\n", tag(), m_addressA, m_addressB, m_count);
					break;
				case COMMAND_DISABLE_DMA:
					if (LOG) logerror("Z80DMA '%s' Disable DMA\n", tag());
					m_dma_enabled = 0;
					break;
				case COMMAND_ENABLE_DMA:
					if (LOG) logerror("Z80DMA '%s' Enable DMA\n", tag());
					m_dma_enabled = 1;
					update_status();
					break;
				case COMMAND_READ_MASK_FOLLOWS:
					if (LOG) logerror("Z80DMA '%s' Set Read Mask\n", tag());
					m_regs_follow[m_num_follow++] = GET_REGNUM(READ_MASK);
					break;
				case COMMAND_CONTINUE:
					if (LOG) logerror("Z80DMA '%s' Continue\n", tag());
					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:
					if (LOG) logerror("Z80DMA '%s' Reset Port A Timing\n", tag());
					PORTA_TIMING = 0;
					break;
				case COMMAND_RESET_PORT_B_TIMING:
					if (LOG) logerror("Z80DMA '%s' Reset Port B Timing\n", tag());
					PORTB_TIMING = 0;
					break;
				case COMMAND_FORCE_READY:
					if (LOG) logerror("Z80DMA '%s' Force Ready\n", tag());
					m_force_ready = 1;
					update_status();
					break;
				case COMMAND_ENABLE_INTERRUPTS:
					if (LOG) logerror("Z80DMA '%s' Enable IRQ\n", tag());
					WR3 |= 0x20;
					break;
				case COMMAND_DISABLE_INTERRUPTS:
					if (LOG) logerror("Z80DMA '%s' Disable IRQ\n", tag());
					WR3 &= ~0x20;
					break;
				case COMMAND_REINITIALIZE_STATUS_BYTE:
					if (LOG) logerror("Z80DMA '%s' Reinitialize status byte\n", tag());
					m_status |= 0x30;
					m_ip = 0;
					break;
				case 0xFB:
					if (LOG) logerror("Z80DMA '%s' undocumented command triggered 0x%02X!\n", tag(), data);
					break;
				default:
					fatalerror("Z80DMA '%s' Unknown WR6 command %02x", tag(), data);
			}
		}
		else if(data == 0x8e) //newtype on Sharp X1, unknown purpose
			printf("Z80DMA '%s' Unknown base register %02x\n", tag(), data);
		else
			fatalerror("Z80DMA '%s' Unknown base register %02x", tag(), data);
		m_cur_follow = 0;
	}
	else
	{
		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
//-------------------------------------------------

void z80dma_device::rdy_write_callback(int state)
{
	// normalize state
	m_rdy = state;
	m_status = (m_status & 0xFD) | (!is_ready() << 1);

	update_status();

	if (is_ready() && INT_ON_READY)
    {
		trigger_interrupt(INT_RDY);
    }
}


//-------------------------------------------------
//  rdy_w - ready input
//-------------------------------------------------

void z80dma_device::rdy_w(int state)
{
	if (LOG) logerror("Z80DMA '%s' RDY: %d Active High: %d\n", tag(), state, READY_ACTIVE_HIGH);
	machine().scheduler().synchronize(FUNC(static_rdy_write_callback), state, (void *)this);
}


//-------------------------------------------------
//  wait_w - wait input
//-------------------------------------------------

void z80dma_device::wait_w(int state)
{
}


//-------------------------------------------------
//  bai_w - bus acknowledge input
//-------------------------------------------------

void z80dma_device::bai_w(int state)
{
}



//**************************************************************************
//  GLOBAL STUBS
//**************************************************************************

READ8_DEVICE_HANDLER( z80dma_r ) { return downcast<z80dma_device *>(device)->read(); }
WRITE8_DEVICE_HANDLER( z80dma_w ) { downcast<z80dma_device *>(device)->write(data); }

WRITE_LINE_DEVICE_HANDLER( z80dma_rdy_w ) { downcast<z80dma_device *>(device)->rdy_w(state); }
WRITE_LINE_DEVICE_HANDLER( z80dma_wait_w ) { downcast<z80dma_device *>(device)->wait_w(state); }
WRITE_LINE_DEVICE_HANDLER( z80dma_bai_w ) { downcast<z80dma_device *>(device)->bai_w(state); }
