// license:GPL-2.0+
// copyright-holders:Dirk Best,Carl
/***************************************************************************

    Intel 8089 I/O Processor

    I/O channel

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

#include "emu.h"
#include "i8089_channel.h"

#include "i8089.h"

#include "debugger.h"


//**************************************************************************
//  MACROS/CONSTANTS
//**************************************************************************

#define VERBOSE      0
#define VERBOSE_DMA  0

// channel control register fields
#define CC_TMC     ((m_r[CC].w >>  0) & 0x07)   // terminate on masked compare
#define CC_TBC     ((m_r[CC].w >>  3) & 0x03)   // terminate on byte count
#define CC_TX      ((m_r[CC].w >>  5) & 0x03)   // terminate on external signal
#define CC_TS      ((m_r[CC].w >>  7) & 0x01)   // terminate on single transfer
#define CC_CHAIN   ((m_r[CC].w >>  8) & 0x01)   // chaining
#define CC_LOCK    ((m_r[CC].w >>  9) & 0x01)   // actuate lock
#define CC_SOURCE  ((m_r[CC].w >> 10) & 0x01)   // source register
#define CC_SYNC    ((m_r[CC].w >> 11) & 0x03)   // synchronization
#define CC_TRANS   ((m_r[CC].w >> 13) & 0x01)   // translation
#define CC_FUNC    ((m_r[CC].w >> 14) & 0x03)   // function


//**************************************************************************
//  DEVICE DEFINITIONS
//**************************************************************************

DEFINE_DEVICE_TYPE(I8089_CHANNEL, i8089_channel_device, "i8089_channel", "Intel 8089 I/O Channel")


//**************************************************************************
//  LIVE DEVICE
//**************************************************************************

//-------------------------------------------------
//  i8089_channel_device - constructor
//-------------------------------------------------

i8089_channel_device::i8089_channel_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
	device_t(mconfig, I8089_CHANNEL, tag, owner, clock),
	m_write_sintr(*this),
	m_iop(nullptr),
	m_icount(0),
	m_xfer_pending(false),
	m_dma_value(0),
	m_dma_state(DMA_IDLE),
	m_drq(0),
	m_prio(PRIO_IDLE)
{
}

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

void i8089_channel_device::device_start()
{
	// get parent device
	m_iop = downcast<i8089_device *>(owner());

	// resolve callbacks
	m_write_sintr.resolve_safe();

	// register for save states
	save_item(NAME(m_xfer_pending));
	save_item(NAME(m_dma_value));
	save_item(NAME(m_dma_state));
	save_item(NAME(m_drq));
	save_item(NAME(m_prio));

	save_item(STRUCT_MEMBER(m_r, w));
	save_item(STRUCT_MEMBER(m_r, t));
}

//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void i8089_channel_device::device_reset()
{
	m_xfer_pending = false;

	// initialize registers
	for (auto & elem : m_r)
	{
		elem.w = 0;
		elem.t = 0;
	}
	m_prio = PRIO_IDLE;
	m_load_hi = false;
	m_store_hi = false;
}


//**************************************************************************
//  IMPLEMENTATION
//**************************************************************************

void i8089_channel_device::set_reg(int reg, uint32_t value, int tag)
{
	if((reg == BC) || (reg == IX) || (reg == CC) || (reg == MC))
	{
		m_r[reg].w = value & 0xffff;
		if((reg == CC) && executing())
			m_prio = chained() ? PRIO_PROG_CHAIN : PRIO_PROG;
		return;
	}
	m_r[reg].w = value & 0xfffff;

	if(reg == PP)
	{
		m_r[PP].t = 0;
		return;
	}

	if (tag != -1)
		m_r[reg].t = tag;

	if (reg == TP)
		m_iop->m_current_tp = value;
}

// channel status
bool i8089_channel_device::executing()    { return BIT(m_r[PSW].w, 2); }
bool i8089_channel_device::transferring() { return BIT(m_r[PSW].w, 6); }
bool i8089_channel_device::priority()     { return BIT(m_r[PSW].w, 7); }
int  i8089_channel_device::chan_prio()    { return m_prio; }
bool i8089_channel_device::chained()      { return CC_CHAIN; }
bool i8089_channel_device::lock()         { return CC_LOCK; }

int16_t i8089_channel_device::displacement(int wb)
{
	int16_t displacement = 0;

	if (wb == 1)
	{
		displacement = (int16_t)((int8_t)m_iop->read_byte(m_r[TP].t, m_r[TP].w));
		set_reg(TP, m_r[TP].w + 1);
	}
	else if (wb == 2)
	{
		displacement = (int16_t)m_iop->read_word(m_r[TP].t, m_r[TP].w);
		set_reg(TP, m_r[TP].w + 2);
	}

	return displacement;
}

uint32_t i8089_channel_device::offset(int aa, int mm, int w)
{
	uint32_t offset = 0;
	switch(aa)
	{
		case 0:
			offset = m_r[mm].w;
			break;
		case 1:
			offset = m_r[mm].w + m_iop->read_byte(m_r[TP].t, m_r[TP].w);
			set_reg(TP, m_r[TP].w + 1);
			break;
		case 2:
			offset = m_r[mm].w + m_r[IX].w;
			break;
		case 3:
			offset = m_r[mm].w + m_r[IX].w;
			set_reg(IX, m_r[IX].w + (w ? 2 : 1));
			break;
	}
	return offset & 0xfffff;
}

int8_t i8089_channel_device::imm8()
{
	int8_t imm8 = (int8_t)m_iop->read_byte(m_r[TP].t, m_r[TP].w);
	set_reg(TP, m_r[TP].w + 1);
	return imm8;
}

int16_t i8089_channel_device::imm16()
{
	int16_t imm16 = (int16_t)m_iop->read_word(m_r[TP].t, m_r[TP].w);
	set_reg(TP, m_r[TP].w + 2);
	return imm16;
}

// adjust task pointer and continue execution
void i8089_channel_device::terminate_dma(int offset)
{
	if (VERBOSE)
		logerror("%s('%s'): terminating dma transfer\n", shortname(), tag());

	set_reg(TP, m_r[TP].w + offset);
	m_r[PSW].w |= 1 << 2;
	m_r[PSW].w &= ~(1 << 6);
	m_dma_state = DMA_IDLE;
}

int i8089_channel_device::execute_run()
{
	m_icount = 0;

	if (chan_prio() == PRIO_CHAN_ATTN)
	{
		attention();
		return m_icount++;
	}

	// active transfer?
	if (transferring())
	{
		// new transfer?
		if (executing())
		{
			// we are no longer executing task blocks
			m_r[PSW].w &= ~(1 << 2);
			m_xfer_pending = false;
			m_load_hi = false;
			m_store_hi = false;

			if (VERBOSE)
			{
				logerror("%s('%s'): ---- starting dma transfer ----\n", shortname(), tag());
				logerror("%s('%s'): ga = %06x, gb = %06x, gc = %06x\n", shortname(), tag(), m_r[GA].w, m_r[GB].w, m_r[GC].w);
				logerror("%s('%s'): bc = %04x, cc = %04x, mc = %04x\n", shortname(), tag(), m_r[BC].w, m_r[CC].w, m_r[MC].w);
			}
		}

		switch (m_dma_state)
		{
		case DMA_IDLE:
			if (VERBOSE_DMA)
				logerror("%s('%s'): entering state: DMA_IDLE (bc = %04x)\n", shortname(), tag(), m_r[BC].w);

			// synchronize on source?
			if (CC_SYNC == 0x01)
				m_dma_state = DMA_WAIT_FOR_SOURCE_DRQ;
			else
				m_dma_state = DMA_FETCH;
			break;

		case DMA_WAIT_FOR_SOURCE_DRQ:
			if (m_drq)
				m_dma_state = DMA_FETCH;
			break;

		case DMA_FETCH:
			if (VERBOSE_DMA)
				logerror("%s('%s'): entering state: DMA_FETCH\n", shortname(), tag());

			// source is 16-bit?
			if (BIT(m_r[PSW].w, 1))
			{
				m_dma_value = m_iop->read_word(m_r[GA + CC_SOURCE].t, m_r[GA + CC_SOURCE].w);
				if(CC_FUNC & 1)
					m_r[GA + CC_SOURCE].w += 2;
				m_r[BC].w -= 2;
			}
			// destination is 16-bit, low byte
			else if (BIT(m_r[PSW].w, 0) && !m_load_hi)
			{
				m_dma_value = m_iop->read_byte(m_r[GA + CC_SOURCE].t, m_r[GA + CC_SOURCE].w);
				if(CC_FUNC & 1)
					m_r[GA + CC_SOURCE].w++;
				if(--m_r[BC].w)
					m_load_hi = true;
			}
			// destination is 16-bit, high byte
			else if (BIT(m_r[PSW].w, 0) && m_load_hi)
			{
				m_dma_value |= m_iop->read_byte(m_r[GA + CC_SOURCE].t, m_r[GA + CC_SOURCE].w) << 8;
				if(CC_FUNC & 1)
					m_r[GA + CC_SOURCE].w++;
				m_r[BC].w--;
				m_load_hi = false;
			}
			// 8-bit transfer
			else
			{
				m_dma_value = m_iop->read_byte(m_r[GA + CC_SOURCE].t, m_r[GA + CC_SOURCE].w);
				if(CC_FUNC & 1)
					m_r[GA + CC_SOURCE].w++;
				m_r[BC].w--;
			}

			m_r[BC].w &= 0xffff;
			m_r[GA + CC_SOURCE].w &= 0xfffff;

			if (VERBOSE_DMA)
				logerror("[ %04x ]\n", m_dma_value);

			if (BIT(m_r[PSW].w, 0) && m_load_hi)
				m_dma_state = DMA_FETCH;
			else if (CC_TRANS)
				m_dma_state = DMA_TRANSLATE;
			else if (CC_SYNC == 0x02)
				m_dma_state = DMA_WAIT_FOR_DEST_DRQ;
			else
				m_dma_state = DMA_STORE;

			break;

		case DMA_TRANSLATE:
			fatalerror("%s('%s'): dma translate requested\n", shortname(), tag());

		case DMA_WAIT_FOR_DEST_DRQ:
			if (m_drq)
			{
				if(m_store_hi)
					m_dma_state = DMA_STORE_BYTE_HIGH;
				else
					m_dma_state = DMA_STORE;
			}
			break;

		case DMA_STORE:
			if (VERBOSE_DMA)
				logerror("%s('%s'): entering state: DMA_STORE", shortname(), tag());

			// destination is 16-bit?
			if (BIT(m_r[PSW].w, 0))
			{
				m_iop->write_word(m_r[GB - CC_SOURCE].t, m_r[GB - CC_SOURCE].w, m_dma_value);
				if(CC_FUNC & 2)
					m_r[GB - CC_SOURCE].w += 2;

				if (VERBOSE_DMA)
					logerror("[ %04x ]\n", m_dma_value);
			}
			// destination is 8-bit
			else
			{
				m_iop->write_byte(m_r[GB - CC_SOURCE].t, m_r[GB - CC_SOURCE].w, m_dma_value & 0xff);
				if(CC_FUNC & 2)
					m_r[GB - CC_SOURCE].w++;

				if (VERBOSE_DMA)
					logerror("[ %02x ]\n", m_dma_value & 0xff);
			}

			m_r[GB - CC_SOURCE].w &= 0xfffff;

			if (CC_TMC & 0x03)
				m_dma_state = DMA_COMPARE;
			else
				m_dma_state = DMA_TERMINATE;

			break;

		case DMA_COMPARE:
			fatalerror("%s('%s'): dma compare requested\n", shortname(), tag());

		case DMA_TERMINATE:
			if (VERBOSE_DMA)
				logerror("%s('%s'): entering state: DMA_TERMINATE\n", shortname(), tag());

			// do we need to read another byte?
			if (BIT(m_r[PSW].w, 1) && !BIT(m_r[PSW].w, 0) && !m_store_hi)
			{
				m_store_hi = true;

				if (CC_SYNC == 0x02)
					m_dma_state = DMA_WAIT_FOR_DEST_DRQ;
				else
					m_dma_state = DMA_STORE_BYTE_HIGH;
			}

			// terminate on masked compare?
			else if (CC_TMC & 0x03)
				fatalerror("%s('%s'): terminate on masked compare not supported\n", shortname(), tag());

			// terminate on byte count?
			else if (CC_TBC && m_r[BC].w == 0)
				terminate_dma((CC_TBC - 1) * 4);

			// terminate on single transfer
			else if (CC_TS)
				fatalerror("%s('%s'): terminate on single transfer not supported\n", shortname(), tag());

			else
			{
				m_store_hi = false;
				m_dma_state = DMA_IDLE;
			}

			break;

		case DMA_STORE_BYTE_HIGH:
			if (VERBOSE_DMA)
				logerror("%s('%s'): entering state: DMA_STORE_BYTE_HIGH[ %02x ]\n", shortname(), tag(), (m_dma_value >> 8) & 0xff);

			m_iop->write_byte(m_r[GB - CC_SOURCE].t, m_r[GB - CC_SOURCE].w, (m_dma_value >> 8) & 0xff);
			if(CC_FUNC & 2)
				m_r[GB - CC_SOURCE].w++;
			m_dma_state = DMA_TERMINATE;

			break;
		}

		m_icount++;
	}

	// executing task block instructions?
	else if (executing())
	{
		// call debugger
		m_iop->debugger_instruction_hook(m_iop->m_current_tp);

		// dma transfer pending?
		if (m_xfer_pending)
		{
			m_r[PSW].w |= 1 << 6;
			m_prio = PRIO_DMA;
		}
		else
			m_prio = chained() ? PRIO_PROG_CHAIN : PRIO_PROG;

		// fetch first two instruction bytes
		uint16_t op = m_iop->read_word(m_r[TP].t, m_r[TP].w);
		set_reg(TP, m_r[TP].w + 2);

		// extract parameters
		uint8_t params = op & 0xff;
		uint8_t opcode = (op >> 8) & 0xff;

		int brp = (params >> 5) & 0x07;
		int wb  = (params >> 3) & 0x03;
		int aa  = (params >> 1) & 0x03;
		int w   = (params >> 0) & 0x01;
		int opc = (opcode >> 2) & 0x3f;
		int mm  = (opcode >> 0) & 0x03;

		// fix-up so we can use our register array
		if (mm == BC) mm = PP;

		uint32_t o;
		uint16_t off, seg;

		switch (opc)
		{
		case 0x00: // control
			switch (brp)
			{
			case 0: nop(); break;
			case 1: invalid(opc); break;
			case 2: do_sintr(); break;
			case 3: xfer(); break;
			default: wid(BIT(brp, 1), BIT(brp, 0));
			}
			break;

		case 0x02: // lpdi
			off = (uint16_t)imm16();
			seg = (uint16_t)imm16();
			lpdi(brp, seg, off);
			break;

		case 0x08: // add(b)i r, i
			if (w) addi_ri(brp, imm16());
			else   addbi_ri(brp, imm8());
			break;

		case 0x09: // or(b)i r, i
			if (w) ori_ri(brp, imm16());
			else   orbi_ri(brp, imm8());
			break;

		case 0x0a: // and(b)i r, i
			if (w) andi_ri(brp, imm16());
			else   andbi_ri(brp, imm8());
			break;

		case 0x0b: // not r
			not_r(brp);
			break;

		case 0x0c: // mov(b)i r, i
			if (w) movi_ri(brp, imm16());
			else   movbi_ri(brp, imm8());
			break;

		case 0x0e: // inc r
			inc_r(brp);
			break;

		case 0x0f: // dec r
			dec_r(brp);
			break;

		case 0x10: // jnz r
			jnz_r(brp, displacement(wb));
			break;

		case 0x11: // jz r
			jz_r(brp, displacement(wb));
			break;

		case 0x12: // hlt
			if (BIT(brp, 0)) hlt();
			else             invalid(opc);
			break;

		case 0x13: // mov(b)i m, i
			o = offset(aa, mm, w);
			if (w) movi_mi(mm, imm16(), o);
			else   movbi_mi(mm, imm8(), o);
			break;

		case 0x20: // mov(b) r, m
			if (w) mov_rm(brp, mm, offset(aa, mm, w));
			else   movb_rm(brp, mm, offset(aa, mm, w));
			break;

		case 0x21: // mov(b) m, r
			if (w) mov_mr(mm, brp, offset(aa, mm, w));
			else   movb_mr(mm, brp, offset(aa, mm, w));
			break;

		case 0x22: // lpd
			o = offset(aa, mm, w);
			lpd(brp, mm, o);
			break;

		case 0x23: // movp p, m
			movp_pm(brp, mm, offset(aa, mm, w));
			break;

		case 0x24: // mov(b) m, m
		{
			o = offset(aa, mm, w);
			uint16_t op2 = m_iop->read_word(m_r[TP].t, m_r[TP].w);
			set_reg(TP, m_r[TP].w + 2);
			int mm2 = (op2 >> 8) & 0x03;

			if (w) mov_mm(mm, mm2, o, offset((op2 >> 1) & 0x03, mm2, w));
			else   movb_mm(mm, mm2, o, offset((op2 >> 1) & 0x03, mm2, w));
			break;
		}

		case 0x25: // tsl m, i, d
		{
			o = offset(aa, mm, w);
			int8_t i = imm8();
			tsl(mm, i, imm8(), o);
			break;
		}

		case 0x26: // movp m, p
			movp_mp(mm, brp, offset(aa, mm, w));
			break;

		case 0x27: // call
			o = offset(aa, mm, w);
			call(mm, displacement(wb), o);
			break;

		case 0x28: // add(b) r, m
			if (w) add_rm(brp, mm, offset(aa, mm, w));
			else   addb_rm(brp, mm, offset(aa, mm, w));
			break;

		case 0x29: // or(b) r, m
			if (w) or_rm(brp, mm, offset(aa, mm, w));
			else   orb_rm(brp, mm, offset(aa, mm, w));
			break;

		case 0x2a: // and(b) r, m
			if (w) and_rm(brp, mm, offset(aa, mm, w));
			else   andb_rm(brp, mm, offset(aa, mm, w));
			break;

		case 0x2b: // not(b) r, m
			if (w) not_rm(brp, mm, offset(aa, mm, w));
			else   notb_rm(brp, mm, offset(aa, mm, w));
			break;

		case 0x2c: // jmce m, d
			o = offset(aa, mm, w);
			jmce(mm, displacement(wb), o);
			break;

		case 0x2d: // jmcne m, d
			o = offset(aa, mm, w);
			jmcne(mm, displacement(wb), o);
			break;

		case 0x2e: // jnbt m, b, d
			o = offset(aa, mm, w);
			jnbt(mm, brp, displacement(wb), o);
			break;

		case 0x2f: // jbt m, b, d
			o = offset(aa, mm, w);
			jbt(mm, brp, displacement(wb), o);
			break;

		case 0x30: // add(b)i m, i
			o = offset(aa, mm, w);
			if (w) addi_mi(mm, imm16(), o);
			else   addbi_mi(mm, imm8(), o);
			break;

		case 0x31: // or(b)i m, i
			o = offset(aa, mm, w);
			if (w) ori_mi(mm, imm16(), o);
			else   orbi_mi(mm, imm8(), o);
			break;

		case 0x32: // and(b)i m, i
			o = offset(aa, mm, w);
			if (w) andi_mi(mm, imm16(), o);
			else   andbi_mi(mm, imm8(), o);
			break;

		case 0x34: // add(b) m, r
			if (w) add_mr(mm, brp, offset(aa, mm, w));
			else   addb_mr(mm, brp, offset(aa, mm, w));
			break;

		case 0x35: // or(b) m, r
			if (w) or_mr(mm, brp, offset(aa, mm, w));
			else   orb_mr(mm, brp, offset(aa, mm, w));
			break;

		case 0x36: // and(b) m, r
			if (w) and_mr(mm, brp, offset(aa, mm, w));
			else   andb_mr(mm, brp, offset(aa, mm, w));
			break;

		case 0x37: // not(b) m
			if (w) not_m(mm, offset(aa, mm, w));
			else   notb_m(mm, offset(aa, mm, w));
			break;

		case 0x38: // jnz m
			o = offset(aa, mm, w);
			if(w) jnz_m(mm, displacement(wb), o);
			else jnzb(mm, displacement(wb), o);
			break;

		case 0x39: // jz m
			o = offset(aa, mm, w);
			if(w) jz_m(mm, displacement(wb), o);
			else jzb(mm, displacement(wb), o);
			break;

		case 0x3a: // inc(b) m
			if (w) inc_m(mm, offset(aa, mm, w));
			else   incb(mm, offset(aa, mm, w));
			break;

		case 0x3b: // dec(b) m
			if (w) dec_m(mm, offset(aa, mm, w));
			else   decb(mm, offset(aa, mm, w));
			break;

		case 0x3d: // setb
			setb(mm, brp, offset(aa, mm, w));
			break;

		case 0x3e: // clr
			clr(mm, brp, offset(aa, mm, w));
			break;

		default:
			invalid(opc);
		}

		m_icount++;
	}

	// nothing to do
	else
	{
		m_icount++;
	}

	return m_icount;
}

void i8089_channel_device::examine_ccw(uint8_t ccw)
{
	// priority and bus load limit, bit 7 and 5
	m_r[PSW].w = (m_r[PSW].w & 0x5f) | (ccw & 0xa0);

	// acknowledge interrupt
	if (BIT(ccw, 3))
	{
		m_write_sintr(0);
		m_r[PSW].w &= ~(1 << 5);
	}

	// interrupt enable
	if (BIT(ccw, 4))
	{
		if (BIT(ccw, 3))
			m_r[PSW].w &= ~(1 << 4);
		else
			m_r[PSW].w |= 1 << 4;
	}
}

void i8089_channel_device::attention()
{
	// examine control byte
	uint8_t ccw = m_iop->read_byte(m_r[CP].t, m_r[CP].w);

	switch (ccw & 0x07)
	{
	// no channel command
	case 0:
		if (VERBOSE)
			logerror("%s('%s'): command received: update psw\n", shortname(), tag());

		if(executing())
			m_prio = chained() ? PRIO_PROG_CHAIN : PRIO_PROG;
		else if(transferring())
			m_prio = PRIO_DMA;
		else
			m_prio = PRIO_IDLE;

		examine_ccw(ccw);
		break;

	// start channel, tb in local space
	case 1:
		if (VERBOSE)
			logerror("%s('%s'): command received: start channel in local space\n", shortname(), tag());

		examine_ccw(ccw);

		lpd(PP, CP, m_r[CP].w + 2);
		movp_pm(TP, PP, m_r[PP].w);
		movbi_mi(CP, (int8_t) 0xff, m_r[CP].w + 1);
		m_r[TP].t = 1;

		m_r[PSW].w |= 1 << 2;
		m_prio = chained() ? PRIO_PROG_CHAIN : PRIO_PROG;

		if (VERBOSE)
		{
			logerror("%s('%s'): ---- starting channel ----\n", shortname(), tag());
			logerror("%s('%s'): parameter block address: %06x\n", shortname(), tag(), m_r[PP].w);
			logerror("%s('%s'): task pointer: %04x\n", shortname(), tag(), m_r[TP].w);
		}

		break;

	// reserved
	case 2:
		if (VERBOSE)
			logerror("%s('%s'): command received: invalid command 010\n", shortname(), tag());
		m_prio = PRIO_IDLE;

		break;

	// start channel, tb in system space
	case 3:
		if (VERBOSE)
			logerror("%s('%s'): command received: start channel in system space\n", shortname(), tag());

		examine_ccw(ccw);

		lpd(PP, CP, m_r[CP].w + 2);
		lpd(TP, PP, m_r[PP].w);
		movbi_mi(CP, (int8_t) 0xff, m_r[CP].w + 1);

		m_r[PSW].w |= 1 << 2;
		m_prio = chained() ? PRIO_PROG_CHAIN : PRIO_PROG;

		if (VERBOSE)
		{
			logerror("%s('%s'): ---- starting channel ----\n", shortname(), tag());
			logerror("%s('%s'): parameter block address: %06x\n", shortname(), tag(), m_r[PP].w);
			logerror("%s('%s'): task pointer: %06x\n", shortname(), tag(), m_r[TP].w);
		}

		break;

	case 4:
		if (VERBOSE)
			logerror("%s('%s'): command received: invalid command 100\n", shortname(), tag());
		m_prio = PRIO_IDLE;

		break;

	// continue channel processing
	case 5:
		if (VERBOSE)
			logerror("%s('%s'): command received: continue channel processing\n", shortname(), tag());

		// restore task pointer and parameter block
		movp_pm(TP, PP, m_r[PP].w);
		movb_rm(PSW, PP, m_r[PP].w + 3);
		movbi_mi(CP, (int8_t) 0xff, m_r[CP].w + 1);

		m_r[PSW].w |= 1 << 2;
		m_prio = chained() ? PRIO_PROG_CHAIN : PRIO_PROG;

		if (VERBOSE)
		{
			logerror("%s('%s'): ---- continuing channel ----\n", shortname(), tag());
			logerror("%s('%s'): task pointer: %06x\n", shortname(), tag(), m_r[TP].w);
		}

		break;

	// halt channel, save tp
	case 6:
		if (VERBOSE)
			logerror("%s('%s'): command received: halt channel and save tp\n", shortname(), tag());

		// save task pointer and psw to parameter block
		movp_mp(PP, TP, m_r[TP].w);
		movb_mr(PP, PSW, m_r[PP].w + 3);
		hlt();

		break;

	// halt channel, don't save tp
	case 7:
		if (VERBOSE)
			logerror("%s('%s'): command received: halt channel\n", shortname(), tag());

		hlt();

		break;
	}
}

void i8089_channel_device::ca()
{
	m_prio = PRIO_CHAN_ATTN;
}

WRITE_LINE_MEMBER( i8089_channel_device::ext_w )
{
	if (VERBOSE)
		logerror("%s('%s'): ext_w: %d\n", shortname(), tag(), state);

	if (transferring() && state)
		terminate_dma((CC_TX - 1) * 4);
}

WRITE_LINE_MEMBER( i8089_channel_device::drq_w )
{
	if (VERBOSE_DMA)
		logerror("%s('%s'): drq_w: %d\n", shortname(), tag(), state);

	m_drq = state;
}
