text: // license:BSD-3-Clause
  // copyright-holders:Wilbert Pol,Carl
  
  //TODO: rotating priority, cascade
  
  #include "emu.h"
  #include "machine/am9519.h"
  
  #define LOG_GENERAL (1U << 0)
  
  //#define VERBOSE (LOG_GENERAL)
  #include "logmacro.h"
  
  void am9519_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
  {
  	if(!BIT(m_mode, 7)) // chip disabled
  		return;
  
  	/* check the various IRQs */
  	for(int n = 0, irq = m_prio; n < 8; n++, irq = (irq + 1) & 7)
  	{
  		u8 mask = 1 << irq;
  
  		/* is this IRQ in service */
  		if(m_isr & mask)
  		{
  			LOG("am9519_timerproc(): UIC IRQ #%d still in service\n", irq);
  			break;
  		}
  
  		/* is this IRQ pending and enabled? */
  		if((m_irr & mask) && !(m_imr & mask))
  		{
  			LOG("am9519_timerproc(): UIC triggering IRQ #%d\n", irq);
  			if(!BIT(m_mode, 2))
  				m_out_int_func(1);
  			return;
  		}
  	}
  	m_out_int_func(0);
  }
  
  
  void am9519_device::set_irq_line(int irq, int state)
  {
  	u8 mask = (1 << irq);
  	bool active = !((state == ASSERT_LINE ? 1 : 0) ^ BIT(m_mode, 4));
  
  	if(active)
  	{
  		/* setting IRQ line */
  		LOG("am9519_set_irq_line(): UIC set IRQ line #%d\n", irq);
  
  		if(!(m_irq_lines & mask)) // edge trig only
  			m_irr |= mask;
  		m_irq_lines |= mask;
  	}
  	else
  	{
  		/* clearing IRQ line */
  		LOG("am9519_device::set_irq_line(): UIC cleared IRQ line #%d\n", irq);
  
  		m_irq_lines &= ~mask;
  		m_irr &= ~mask;
  	}
  	set_timer();
  }
  
  
  u32 am9519_device::acknowledge()
  {
  	for(int n = 0, irq = m_prio; n < 8; n++, irq = (irq + 1) & 7)
  	{
  		u8 mask = 1 << irq;
  
  		/* is this IRQ pending and enabled? */
  		if((m_irr & mask) && !(m_imr & mask))
  		{
  			LOG("am9519_acknowledge(): UIC acknowledge IRQ #%d\n", irq);
  
  			if(!(mask & m_aclear))
  				m_isr |= mask;
  
  			m_irr &= ~mask;
  			set_timer();
  
  			u32 ret = 0;
  			int irqr = BIT(m_mode, 1) ? 0 : irq;
  			for(int i = 0; i < m_count[irqr]; i++)
  				ret = (ret << 8) | m_resp[irqr][i];
  
  			return ret;
  		}
  	}
  	logerror("Spurious IRQ\n");
  	return 0;
  }
  
  
  IRQ_CALLBACK_MEMBER(am9519_device::iack_cb)
  {
  	return acknowledge();
  }
  
  
  u8 am9519_device::stat_r()
  {
  	u8 stat = 0;
  	for(int n = 0, irq = m_prio; n < 8; n++, irq = (irq + 1) & 7)
  	{
  		u8 mask = 1 << irq;
  		if((m_irr & mask) && !(m_imr & mask))
  		{
  			stat = 0x80 | irq;
  			break;
  		}
  	}
  	stat |= BIT(m_mode, 7) << 3;
  	stat |= BIT(m_mode, 2) << 4;
  	stat |= BIT(m_mode, 0) << 5;
  	return stat;
  }
  
  u8 am9519_device::data_r()
  {
  	switch((m_mode & 0x60) >> 5)
  	{
  		case 0:
  			return m_isr;
  		case 1:
  			return m_imr;
  		case 2:
  			return m_irr;
  		case 3:
  			return m_aclear;
  	}
  	return 0;
  }
  
  void am9519_device::cmd_w(u8 data)
  {
  	m_cmd = data;
  	switch(data >> 3)
  	{
  		case 0:
  			reset();
  			break;
  		case 2:
  			m_irr = m_imr = 0;
  			break;
  		case 3:
  			m_irr &= ~(1 << (data & 7));
  			m_imr &= ~(1 << (data & 7));
  			break;
  		case 4:
  			m_imr = 0;
  			break;
  		case 5:
  			m_imr &= ~(1 << (data & 7));
  			break;
  		case 6:
  			m_imr = 0xff;
  			break;
  		case 7:
  			m_imr |= 1 << (data & 7);
  			break;
  		case 8:
  			m_irr = 0;
  			break;
  		case 9:
  			m_irr &= ~(1 << (data & 7));
  			break;
  		case 10:
  			m_irr = 0xff;
  			break;
  		case 11:
  			m_irr |= 1 << (data & 7);
  			break;
  		case 12:
  		case 13:
  		{
  			for(int n = 0, irq = m_prio; n < 8; n++, irq = (irq + 1) & 7)
  			{
  				if(m_isr & (1 << irq))
  					m_isr &= ~(1 << irq);
  			}
  			break;
  		}
  		case 14:
  			m_isr = 0;
  			break;
  		case 15:
  			m_isr &= ~(1 << (data & 7));
  			break;
  		case 16:
  		case 17:
  		case 18:
  		case 19:
  			m_mode = (m_mode & 0xe0) | (data & 0x1f);
  			break;
  		case 20:
  		case 21:
  			m_mode = (m_mode & 0x9f) | (data & 0x60);
  			switch(data & 3)
  			{
  				case 1:
  					m_mode |= 0x80;
  					break;
  				case 2:
  					m_mode &= 0x7f;
  					break;
  			}
  			break;
  		case 28:
  		case 29:
  		case 30:
  		case 31:
  			m_count[m_cmd & 7] = ((data >> 3) & 3) + 1;
  			m_curcnt = 0;
  			break;
  	}
  	set_timer();
  }
  
  void am9519_device::data_w(u8 data)
  {
  	if((m_cmd & 0xf0) >= 0xb0)
  	{
  		switch(m_cmd >> 4)
  		{
  			case 11:
  				m_imr = data;
  				break;
  			case 12:
  				m_aclear = data;
  				break;
  			case 14:
  			case 15:
  				if(m_curcnt < m_count[m_cmd & 7])
  				{
  					m_resp[m_cmd & 7][m_curcnt] = data;
  					m_curcnt++;
  					return;
  				}
  				break;
  		}
  		m_cmd = 0;
  	}
  	set_timer();
  }
  
  //-------------------------------------------------
  //  device_start - device-specific startup
  //-------------------------------------------------
  
  void am9519_device::device_start()
  {
  	// resolve callbacks
  	m_out_int_func.resolve_safe();
  
  	// Register save state items
  	save_item(NAME(m_isr));
  	save_item(NAME(m_irr));
  	save_item(NAME(m_prio));
  	save_item(NAME(m_imr));
  	save_item(NAME(m_irq_lines));
  	save_item(NAME(m_mode));
  	save_item(NAME(m_count));
  	save_item(NAME(m_resp));
  	save_item(NAME(m_aclear));
  	save_item(NAME(m_cmd));
  }
  
  
  //-------------------------------------------------
  //  device_reset - device-specific reset
  //-------------------------------------------------
  
  void am9519_device::device_reset()
  {
  	m_isr = 0;
  	m_irr = 0;
  	m_irq_lines = 0;
  	m_prio = 0;
  	m_imr = 0xff;
  	m_mode = 0;
  	m_aclear = 0;
  	m_cmd = 0;
  	m_curcnt = 0;
  	for(int i = 0; i < 8; i++)
  	{
  		m_count[i] = 0;
  		m_resp[i][0] = 0;
  		m_resp[i][1] = 0;
  		m_resp[i][2] = 0;
  		m_resp[i][3] = 0;
  	}
  }
  
  DEFINE_DEVICE_TYPE(AM9519, am9519_device, "am9519", "AMD AM9519 Universal Interrupt Controller")
  
  am9519_device::am9519_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
  	: device_t(mconfig, AM9519, tag, owner, clock)
  	, m_out_int_func(*this)
  	, m_irr(0)
  	, m_irq_lines(0)
  {
  }
  