// license:BSD-3-Clause
// copyright-holders:R. Belmont
/*****************************************************************************
 *
 *   sh2common.c
 *
 *   SH-2 non-specific components
 *
 *****************************************************************************/

#include "emu.h"
#include "sh2.h"
#include "sh2comn.h"

#include "debugger.h"

//#define VERBOSE 1
#include "logmacro.h"

static const int div_tab[4] = { 3, 5, 7, 0 };
static const int wdtclk_tab[8] = { 1, 6, 7, 8, 9, 10, 12, 13 };


void sh2_device::sh2_timer_resync()
{
	// TODO: setting 3 is "External clock: count on rising edge"
	int divider = div_tab[m_frc_tcr & 3];
	uint64_t cur_time = total_cycles();
	uint64_t add = (cur_time - m_frc_base) >> divider;

	if (add > 0)
	{
		if(divider)
			m_frc += add;

		m_frc_base = cur_time;
	}
}

void sh2_device::sh2_timer_activate()
{
	int max_delta = 0xfffff;
	uint16_t frc;

	m_timer->adjust(attotime::never);

	frc = m_frc;
	if(!(m_ftcsr & OCFA)) {
		uint16_t delta = m_ocra - frc;
		if(delta < max_delta)
			max_delta = delta;
	}

	if(!(m_ftcsr & OCFB) && (m_ocra <= m_ocrb || !(m_ftcsr & CCLRA))) {
		uint16_t delta = m_ocrb - frc;
		if(delta < max_delta)
			max_delta = delta;
	}

	if(!(m_ftcsr & OVF) && !(m_ftcsr & CCLRA)) {
		int delta = 0x10000 - frc;
		if(delta < max_delta)
			max_delta = delta;
	}

	if(max_delta != 0xfffff) {
		int divider = div_tab[m_frc_tcr & 3];
		if(divider) {
			max_delta <<= divider;
			m_frc_base = total_cycles();
			m_timer->adjust(cycles_to_attotime(max_delta));
		} else {
			logerror("SH2.%s: Timer event in %d cycles of external clock", tag(), max_delta);
		}
	}
}

TIMER_CALLBACK_MEMBER( sh2_device::sh2_timer_callback )
{
	uint16_t frc;

	sh2_timer_resync();

	frc = m_frc;

	if(frc == m_ocrb)
		m_ftcsr |= OCFB;

	if(frc == 0x0000)
		m_ftcsr |= OVF;

	if(frc == m_ocra)
	{
		m_ftcsr |= OCFA;

		if(m_ftcsr & CCLRA)
			m_frc = 0;
	}

	sh2_recalc_irq();
	sh2_timer_activate();
}

void sh2_device::sh2_wtcnt_recalc()
{
	if (m_wdtimer->expire() != attotime::never)
		m_wtcnt = 0x100 - (attotime_to_cycles(m_wdtimer->remaining()) >> wdtclk_tab[m_wtcsr & 7]);
}

void sh2_device::sh2_wdt_activate()
{
	m_wdtimer->adjust(cycles_to_attotime((0x100 - m_wtcnt) << wdtclk_tab[m_wtcsr & 7]));
}

TIMER_CALLBACK_MEMBER(sh2_device::sh2_wdtimer_callback)
{
	m_wtcnt = 0;
	if (!(m_wtcsr & 0x40))  // timer mode
	{
		m_wtcsr |= 0x80;
		sh2_recalc_irq();
		sh2_wdt_activate();
	}
	else // watchdog mode
	{
		m_rstcsr |= 0x80;
		// TODO reset and /WDTOVF out
	}
}

/*
  We have to do DMA on a timer (or at least, in chunks) due to the way some systems use it.
  The 32x is a difficult case, they set the SOURCE of the DMA to a FIFO buffer, which at most
  can have 8 words in it.  Attempting to do an 'instant DMA' in this scenario is impossible
  because the game is expecting the 68k of the system to feed data into the FIFO at the same
  time as the SH2 is transfering it out via DMA

  There are two ways we can do this

  a) with a high frequency timer (more accurate, but a large performance hit)

  or

  b) in the CPU_EXECUTE loop


  we're currently doing a)

  b) causes problems with ST-V games

*/



void sh2_device::sh2_notify_dma_data_available()
{
	//printf("call notify\n");

	for (int dmach=0;dmach<2;dmach++)
	{
		//printf("m_dma_timer_active[dmach] %04x\n",m_dma_timer_active[dmach]);

		if (m_dma_timer_active[dmach]==2) // 2 = stalled
		{
		//  printf("resuming stalled dma\n");
			m_dma_timer_active[dmach]=1;
			m_dma_current_active_timer[dmach]->adjust(attotime::zero, dmach);
		}
	}

}

void sh2_device::sh2_do_dma(int dmach)
{
	uint32_t dmadata;

	uint32_t tempsrc, tempdst;

	if (m_active_dma_count[dmach] > 0)
	{
		// process current DMA
		switch(m_active_dma_size[dmach])
		{
		case 0:
			{
				// we need to know the src / dest ahead of time without changing them
				// to allow for the callback to check if we can process the DMA at this
				// time (we need to know where we're reading / writing to/from)

				if(m_active_dma_incs[dmach] == 2)
					tempsrc = m_active_dma_src[dmach] - 1;
				else
					tempsrc = m_active_dma_src[dmach];

				if(m_active_dma_incd[dmach] == 2)
					tempdst = m_active_dma_dst[dmach] - 1;
				else
					tempdst = m_active_dma_dst[dmach];

				if (!m_dma_fifo_data_available_cb.isnull())
				{
					int available = m_dma_fifo_data_available_cb(tempsrc, tempdst, 0, m_active_dma_size[dmach]);

					if (!available)
					{
						//printf("dma stalled\n");
						m_dma_timer_active[dmach]=2;// mark as stalled
						return;
					}
				}

				//schedule next DMA callback
				m_dma_current_active_timer[dmach]->adjust(cycles_to_attotime(2), dmach);

				dmadata = m_program->read_byte(tempsrc);
				if (!m_dma_kludge_cb.isnull()) dmadata = m_dma_kludge_cb(tempsrc, tempdst, dmadata, m_active_dma_size[dmach]);
				m_program->write_byte(tempdst, dmadata);

				if(m_active_dma_incs[dmach] == 2)
					m_active_dma_src[dmach] --;
				if(m_active_dma_incd[dmach] == 2)
					m_active_dma_dst[dmach] --;


				if(m_active_dma_incs[dmach] == 1)
					m_active_dma_src[dmach] ++;
				if(m_active_dma_incd[dmach] == 1)
					m_active_dma_dst[dmach] ++;

				m_active_dma_count[dmach] --;
			}
			break;
		case 1:
			{
				if(m_active_dma_incs[dmach] == 2)
					tempsrc = m_active_dma_src[dmach] - 2;
				else
					tempsrc = m_active_dma_src[dmach];

				if(m_active_dma_incd[dmach] == 2)
					tempdst = m_active_dma_dst[dmach] - 2;
				else
					tempdst = m_active_dma_dst[dmach];

				if (!m_dma_fifo_data_available_cb.isnull())
				{
					int available = m_dma_fifo_data_available_cb(tempsrc, tempdst, 0, m_active_dma_size[dmach]);

					if (!available)
					{
						//printf("dma stalled\n");
						m_dma_timer_active[dmach]=2;// mark as stalled
						return;
					}
				}

				//schedule next DMA callback
				m_dma_current_active_timer[dmach]->adjust(cycles_to_attotime(2), dmach);

				// check: should this really be using read_word_32 / write_word_32?
				dmadata = m_program->read_word(tempsrc);
				if (!m_dma_kludge_cb.isnull()) dmadata = m_dma_kludge_cb(tempsrc, tempdst, dmadata, m_active_dma_size[dmach]);
				m_program->write_word(tempdst, dmadata);

				if(m_active_dma_incs[dmach] == 2)
					m_active_dma_src[dmach] -= 2;
				if(m_active_dma_incd[dmach] == 2)
					m_active_dma_dst[dmach] -= 2;

				if(m_active_dma_incs[dmach] == 1)
					m_active_dma_src[dmach] += 2;
				if(m_active_dma_incd[dmach] == 1)
					m_active_dma_dst[dmach] += 2;

				m_active_dma_count[dmach] --;
			}
			break;
		case 2:
			{
				if(m_active_dma_incs[dmach] == 2)
					tempsrc = m_active_dma_src[dmach] - 4;
				else
					tempsrc = m_active_dma_src[dmach];

				if(m_active_dma_incd[dmach] == 2)
					tempdst = m_active_dma_dst[dmach] - 4;
				else
					tempdst = m_active_dma_dst[dmach];

				if (!m_dma_fifo_data_available_cb.isnull())
				{
					int available = m_dma_fifo_data_available_cb(tempsrc, tempdst, 0, m_active_dma_size[dmach]);

					if (!available)
					{
						//printf("dma stalled\n");
						m_dma_timer_active[dmach]=2;// mark as stalled
						return;
					}
				}

				//schedule next DMA callback
				m_dma_current_active_timer[dmach]->adjust(cycles_to_attotime(2), dmach);

				dmadata = m_program->read_dword(tempsrc);
				if (!m_dma_kludge_cb.isnull()) dmadata = m_dma_kludge_cb(tempsrc, tempdst, dmadata, m_active_dma_size[dmach]);
				m_program->write_dword(tempdst, dmadata);

				if(m_active_dma_incs[dmach] == 2)
					m_active_dma_src[dmach] -= 4;
				if(m_active_dma_incd[dmach] == 2)
					m_active_dma_dst[dmach] -= 4;

				if(m_active_dma_incs[dmach] == 1)
					m_active_dma_src[dmach] += 4;
				if(m_active_dma_incd[dmach] == 1)
					m_active_dma_dst[dmach] += 4;

				m_active_dma_count[dmach] --;
			}
			break;
		case 3:
			{
				// shouldn't this really be 4 calls here instead?

				tempsrc = m_active_dma_src[dmach];

				if(m_active_dma_incd[dmach] == 2)
					tempdst = m_active_dma_dst[dmach] - 16;
				else
					tempdst = m_active_dma_dst[dmach];

				if (!m_dma_fifo_data_available_cb.isnull())
				{
					int available = m_dma_fifo_data_available_cb(tempsrc, tempdst, 0, m_active_dma_size[dmach]);

					if (!available)
					{
						//printf("dma stalled\n");
						m_dma_timer_active[dmach]=2;// mark as stalled
						fatalerror("SH2 dma_callback_fifo_data_available == 0 in unsupported mode\n");
					}
				}

				//schedule next DMA callback
				m_dma_current_active_timer[dmach]->adjust(cycles_to_attotime(2), dmach);

				dmadata = m_program->read_dword(tempsrc);
				if (!m_dma_kludge_cb.isnull()) dmadata = m_dma_kludge_cb(tempsrc, tempdst, dmadata, m_active_dma_size[dmach]);
				m_program->write_dword(tempdst, dmadata);

				dmadata = m_program->read_dword(tempsrc+4);
				if (!m_dma_kludge_cb.isnull()) dmadata = m_dma_kludge_cb(tempsrc, tempdst, dmadata, m_active_dma_size[dmach]);
				m_program->write_dword(tempdst+4, dmadata);

				dmadata = m_program->read_dword(tempsrc+8);
				if (!m_dma_kludge_cb.isnull()) dmadata = m_dma_kludge_cb(tempsrc, tempdst, dmadata, m_active_dma_size[dmach]);
				m_program->write_dword(tempdst+8, dmadata);

				dmadata = m_program->read_dword(tempsrc+12);
				if (!m_dma_kludge_cb.isnull()) dmadata = m_dma_kludge_cb(tempsrc, tempdst, dmadata, m_active_dma_size[dmach]);
				m_program->write_dword(tempdst+12, dmadata);

				if(m_active_dma_incd[dmach] == 2)
					m_active_dma_dst[dmach] -= 16;

				m_active_dma_src[dmach] += 16;
				if(m_active_dma_incd[dmach] == 1)
					m_active_dma_dst[dmach] += 16;

				m_active_dma_count[dmach]-=4;
			}
			break;
		}
	}
	else // the dma is complete
	{
	//  int dma = param & 1;

		// fever soccer uses cycle-stealing mode, resume the CPU now DMA has finished
		if (m_active_dma_steal[dmach])
		{
			resume(SUSPEND_REASON_HALT );
		}


		LOG("SH2: DMA %d complete\n", dmach);
		m_dmac[dmach].tcr = 0;
		m_dmac[dmach].chcr |= 2;
		m_dma_timer_active[dmach] = 0;
		m_dma_irq[dmach] |= 1;
		sh2_recalc_irq();

	}
}

TIMER_CALLBACK_MEMBER( sh2_device::sh2_dma_current_active_callback )
{
	int dma = param & 1;

	sh2_do_dma(dma);
}


void sh2_device::sh2_dmac_check(int dmach)
{
	if(m_dmac[dmach].chcr & m_dmaor & 1)
	{
		if(!m_dma_timer_active[dmach] && !(m_dmac[dmach].chcr & 2))
		{
			m_active_dma_incd[dmach] = (m_dmac[dmach].chcr >> 14) & 3;
			m_active_dma_incs[dmach] = (m_dmac[dmach].chcr >> 12) & 3;
			m_active_dma_size[dmach] = (m_dmac[dmach].chcr >> 10) & 3;
			m_active_dma_steal[dmach] = (m_dmac[dmach].chcr & 0x10);

			if(m_active_dma_incd[dmach] == 3 || m_active_dma_incs[dmach] == 3)
			{
				logerror("SH2: DMA: bad increment values (%d, %d, %d, %04x)\n", m_active_dma_incd[dmach], m_active_dma_incs[dmach], m_active_dma_size[dmach], m_dmac[dmach].chcr);
				return;
			}
			m_active_dma_src[dmach]   = m_dmac[dmach].sar;
			m_active_dma_dst[dmach]   = m_dmac[dmach].dar;
			m_active_dma_count[dmach] = m_dmac[dmach].tcr;
			if(!m_active_dma_count[dmach])
				m_active_dma_count[dmach] = 0x1000000;

			LOG("SH2: DMA %d start %x, %x, %x, %04x, %d, %d, %d\n", dmach, m_active_dma_src[dmach], m_active_dma_dst[dmach], m_active_dma_count[dmach], m_dmac[dmach].chcr, m_active_dma_incs[dmach], m_active_dma_incd[dmach], m_active_dma_size[dmach]);

			m_dma_timer_active[dmach] = 1;

			m_active_dma_src[dmach] &= SH12_AM;
			m_active_dma_dst[dmach] &= SH12_AM;

			switch(m_active_dma_size[dmach])
			{
			case 0:
				break;
			case 1:
				m_active_dma_src[dmach] &= ~1;
				m_active_dma_dst[dmach] &= ~1;
				break;
			case 2:
				m_active_dma_src[dmach] &= ~3;
				m_active_dma_dst[dmach] &= ~3;
				break;
			case 3:
				m_active_dma_src[dmach] &= ~3;
				m_active_dma_dst[dmach] &= ~3;
				m_active_dma_count[dmach] &= ~3;
				break;
			}

			// start DMA timer

			// fever soccer uses cycle-stealing mode, requiring the CPU to be halted
			if (m_active_dma_steal[dmach])
			{
				//printf("cycle stealing DMA\n");
				suspend(SUSPEND_REASON_HALT, 1 );
			}

			m_dma_current_active_timer[dmach]->adjust(cycles_to_attotime(2), dmach);
		}
	}
	else
	{
		if(m_dma_timer_active[dmach])
		{
			logerror("SH2: DMA %d cancelled in-flight\n", dmach);
			//m_dma_complete_timer[dmach]->adjust(attotime::never);
			m_dma_current_active_timer[dmach]->adjust(attotime::never, dmach);

			m_dma_timer_active[dmach] = 0;
		}
	}
}

/*
 * SCI
 */
// TODO: identical to H8 counterpart

uint8_t sh2_device::smr_r()
{
	return m_smr;
}

void sh2_device::smr_w(uint8_t data)
{
	m_smr = data;
}

uint8_t sh2_device::brr_r()
{
	return m_brr;
}

void sh2_device::brr_w(uint8_t data)
{
	m_brr = data;
}

uint8_t sh2_device::scr_r()
{
	return m_scr;
}

void sh2_device::scr_w(uint8_t data)
{
	m_scr = data;
}

uint8_t sh2_device::tdr_r()
{
	return m_tdr;
}

void sh2_device::tdr_w(uint8_t data)
{
	m_tdr = data;
	// printf("%c",data & 0xff);
}

uint8_t sh2_device::ssr_r()
{
	// 0x84 is needed by EGWord on Saturn to make it to boot for some reason.
	return m_ssr | 0x84;
}

void sh2_device::ssr_w(uint8_t data)
{
	m_ssr = data;
}

uint8_t sh2_device::rdr_r()
{
	return 0;
}

/*
 * FRC
 */

uint8_t sh2_device::tier_r()
{
	return m_tier;
}

void sh2_device::tier_w(uint8_t data)
{
	sh2_timer_resync();
	m_tier = data;
	sh2_timer_activate();
	sh2_recalc_irq();
}

uint8_t sh2_device::ftcsr_r()
{
	// TODO: to be tested
	if (!m_ftcsr_read_cb.isnull())
		m_ftcsr_read_cb((((m_tier<<24) | (m_ftcsr<<16)) & 0xffff0000) | m_frc);

	return m_ftcsr;
}

void sh2_device::ftcsr_w(uint8_t data)
{
	uint8_t old;
	old = m_ftcsr;

	m_ftcsr = data;
	sh2_timer_resync();
	m_ftcsr = (m_ftcsr & ~(ICF|OCFA|OCFB|OVF)) | (old & m_ftcsr & (ICF|OCFA|OCFB|OVF));
	sh2_timer_activate();
	sh2_recalc_irq();
}

uint16_t sh2_device::frc_r()
{
	sh2_timer_resync();
	return m_frc;
}

void sh2_device::frc_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	sh2_timer_resync();
	COMBINE_DATA(&m_frc);
	sh2_timer_activate();
	sh2_recalc_irq();
}

uint16_t sh2_device::ocra_b_r()
{
	return (m_tocr & 0x10) ? m_ocrb : m_ocra;
}

void sh2_device::ocra_b_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	sh2_timer_resync();
	if(m_tocr & 0x10)
		m_ocrb = (m_ocrb & (~mem_mask)) | (data & mem_mask);
	else
		m_ocra = (m_ocra & (~mem_mask)) | (data & mem_mask);
	sh2_timer_activate();
	sh2_recalc_irq();
}

uint8_t sh2_device::frc_tcr_r()
{
	return m_frc_tcr & 0x83;
}

void sh2_device::frc_tcr_w(uint8_t data)
{
	sh2_timer_resync();
	m_frc_tcr = data & 0x83;
	sh2_timer_activate();
	sh2_recalc_irq();
}

uint8_t sh2_device::tocr_r()
{
	return (m_tocr & 0x13) | 0xe0;
}

void sh2_device::tocr_w(uint8_t data)
{
	sh2_timer_resync();
	// TODO: output levels A/B (bits 1-0)
	m_tocr = data & 0x13;
	sh2_timer_activate();
	sh2_recalc_irq();
}

uint16_t sh2_device::frc_icr_r()
{
	return m_frc_icr;
}

/*
 * INTC
 */

uint16_t sh2_device::intc_icr_r()
{
	// TODO: flip meaning based off NMI edge select bit (NMIE)
	uint16_t nmilv = m_nmi_line_state == ASSERT_LINE ? 0 : 0x8000;
	return (nmilv) | (m_intc_icr & 0x0101);
}

void sh2_device::intc_icr_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	COMBINE_DATA(&m_intc_icr);
	m_nmie = bool(BIT(m_intc_icr, 8));
	m_vecmd = bool(BIT(m_intc_icr, 0));
}

uint16_t sh2_device::ipra_r()
{
	return m_ipra & 0xfff0;
}

void sh2_device::ipra_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	COMBINE_DATA(&m_ipra);
	m_irq_level.divu = (m_ipra >> 12) & 0xf;
	m_irq_level.dmac = (m_ipra >> 8) & 0xf;
	m_irq_level.wdt = (m_ipra >> 4) & 0xf;
	sh2_recalc_irq();
}

uint16_t sh2_device::iprb_r()
{
	return m_iprb & 0xff00;
}

void sh2_device::iprb_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	COMBINE_DATA(&m_iprb);
	m_irq_level.sci = (m_iprb >> 12) & 0xf;
	m_irq_level.frc = (m_iprb >> 8) & 0xf;
	sh2_recalc_irq();
}

uint16_t sh2_device::vcra_r()
{
	return m_vcra & 0x7f7f;
}

void sh2_device::vcra_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	COMBINE_DATA(&m_vcra);
	// ...
	sh2_recalc_irq();
}

uint16_t sh2_device::vcrb_r()
{
	return m_vcrb;
}

void sh2_device::vcrb_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	COMBINE_DATA(&m_vcrb);
	// ...
	sh2_recalc_irq();
}

uint16_t sh2_device::vcrc_r()
{
	return m_vcrc & 0x7f7f;
}

void sh2_device::vcrc_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	COMBINE_DATA(&m_vcrc);
	m_irq_vector.fic = (m_vcrc >> 8) & 0x7f;
	m_irq_vector.foc = (m_vcrc >> 0) & 0x7f;
	sh2_recalc_irq();
}

uint16_t sh2_device::vcrd_r()
{
	return m_vcrd & 0x7f00;
}

void sh2_device::vcrd_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	COMBINE_DATA(&m_vcrd);
	m_irq_vector.fov = (m_vcrc >> 8) & 0x7f;
	sh2_recalc_irq();
}

uint16_t sh2_device::vcrwdt_r()
{
	return m_vcrwdt & 0x7f7f;
}

void sh2_device::vcrwdt_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	COMBINE_DATA(&m_vcrwdt);
	// ...
	sh2_recalc_irq();
}

uint32_t sh2_device::vcrdiv_r()
{
	return m_vcrdiv & 0x7f;
}

void sh2_device::vcrdiv_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	COMBINE_DATA(&m_vcrdiv);
	// TODO: unemulated, level is seemingly not documented/settable?
	m_irq_vector.divu = data & 0x7f;
	sh2_recalc_irq();
}

/*
 * DIVU
 */

uint32_t sh2_device::dvcr_r()
{
	return (m_divu_ovfie == true ? 2 : 0) | (m_divu_ovf == true ? 1 : 0);
}

void sh2_device::dvcr_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	if(ACCESSING_BITS_0_7)
	{
		if (data & 1)
			m_divu_ovf = false;
		if (data & 2)
		{
			m_divu_ovfie = bool(BIT(data, 1));
			if (m_divu_ovfie == true)
				logerror("SH2: unemulated DIVU OVF interrupt enable\n");
		}
		sh2_recalc_irq();
	}
}

uint32_t sh2_device::dvsr_r()
{
	return m_dvsr;
}

void sh2_device::dvsr_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	COMBINE_DATA(&m_dvsr);
}

uint32_t sh2_device::dvdnt_r()
{
	return m_dvdntl;
}

void sh2_device::dvdnt_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	COMBINE_DATA(&m_dvdntl);
	int32_t a = m_dvdntl;
	int32_t b = m_dvsr;
	LOG("SH2 div32+mod %d/%d\n", a, b);
	if (b)
	{
		m_dvdntl = a / b;
		m_dvdnth = a % b;
	}
	else
	{
		m_divu_ovf = true;
		m_dvdntl = 0x7fffffff;
		m_dvdnth = 0x7fffffff;
		sh2_recalc_irq();
	}
}

uint32_t sh2_device::dvdnth_r()
{
	return m_dvdnth;
}

uint32_t sh2_device::dvdntl_r()
{
	return m_dvdntl;
}

void sh2_device::dvdnth_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	COMBINE_DATA(&m_dvdnth);
}

void sh2_device::dvdntl_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	COMBINE_DATA(&m_dvdntl);
	int64_t a = m_dvdntl | ((uint64_t)(m_dvdnth) << 32);
	int64_t b = (int32_t)m_dvsr;
	LOG("SH2 div64+mod %d/%d\n", a, b);
	if (b)
	{
		int64_t q = a / b;
		if (q != (int32_t)q)
		{
			m_divu_ovf = true;
			m_dvdntl = 0x7fffffff;
			m_dvdnth = 0x7fffffff;
			sh2_recalc_irq();
		}
		else
		{
			m_dvdntl = q;
			m_dvdnth = a % b;
		}
	}
	else
	{
		m_divu_ovf = true;
		m_dvdntl = 0x7fffffff;
		m_dvdnth = 0x7fffffff;
		sh2_recalc_irq();
	}
}

/*
 * WTC
 */

uint16_t sh2_device::wtcnt_r()
{
	sh2_wtcnt_recalc();
	return ((m_wtcsr | 0x18) << 8) | (m_wtcnt & 0xff);
}

uint16_t sh2_device::rstcsr_r()
{
	return (m_rstcsr & 0xe0) | 0x1f;
}

void sh2_device::wtcnt_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	COMBINE_DATA(&m_wtcw[0]);
	switch (m_wtcw[0] & 0xff00)
	{
		case 0x5a00:
			m_wtcnt = m_wtcw[0] & 0xff;
			if (m_wtcsr & 0x20)
				sh2_wdt_activate();
			break;
		case 0xa500:
			/*
			WTCSR
			x--- ---- Overflow in IT mode
			-x-- ---- Timer mode (0: IT 1: watchdog)
			--x- ---- Timer enable
			---1 1---
			---- -xxx Clock select
			*/
			sh2_wtcnt_recalc();
			m_wtcsr &= m_wtcw[0] & 0x80;
			m_wtcsr |= m_wtcw[0] & 0x7f;
			if (m_wtcsr & 0x20)
				sh2_wdt_activate();
			else
			{
				m_wtcnt = 0;
				m_wdtimer->adjust(attotime::never);
			}
			sh2_recalc_irq();
			break;
	}
}

void sh2_device::rstcsr_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	COMBINE_DATA(&m_wtcw[1]);
	switch (m_wtcw[1] & 0xff00)
	{
		case 0xa500:
			// clear WOVF flag
			if ((m_wtcw[1] & 0x80) == 0)
				m_rstcsr &= 0x7f;
			break;
		case 0x5a00:
			m_rstcsr = (m_rstcsr & 0x80) | (m_wtcw[1] & 0x60);
			break;
	}
}

uint16_t sh2_device::fmr_sbycr_r()
{
	return m_sbycr;
}

void sh2_device::fmr_sbycr_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	switch (mem_mask)
	{
	case 0xff00: // FMR 8bit
		logerror("SH2 set clock multiplier x%d\n", 1 << ((data >> 8) & 3));
		break;
	case 0xffff: // FMR 16bit
		// SH7604 docs says FMR register must be set using 8-bit write, however at practice 16-bit works too.
		// has been verified for CPS3 custom SH2, SH7604 and SH7095 (clock multiplier feature is not officially documented for SH7095).
		logerror("SH2 set clock multiplier x%d\n", 1 << (data & 3));
		break;
	case 0x00ff: // SBYCR
		m_sbycr = data;
		if (data & 0x1f)
			logerror("SH2 module stop selected %02x\n", data);
		break;
	}
}

uint8_t sh2_device::ccr_r()
{
	return m_ccr & ~0x30;
}

void sh2_device::ccr_w(uint8_t data)
{
	/*
	    xx-- ---- Way 0/1
	    ---x ---- Cache Purge (CP), write only
	    ---- x--- Two-Way Mode (TW)
	    ---- -x-- Data Replacement Disable (OD)
	    ---- --x- Instruction Replacement Disable (ID)
	    ---- ---x Cache Enable (CE)
	*/
	m_ccr = data;
}

uint32_t sh2_device::bcr1_r()
{
	return (m_bcr1 & ~0xe008) | (m_is_slave ? 0x8000 : 0);
}

void sh2_device::bcr1_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	COMBINE_DATA(&m_bcr1);
}

uint32_t sh2_device::bcr2_r()
{
	return m_bcr2;
}

void sh2_device::bcr2_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	COMBINE_DATA(&m_bcr2);
}

uint32_t sh2_device::wcr_r()
{
	return m_wcr;
}

void sh2_device::wcr_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	COMBINE_DATA(&m_wcr);
}

uint32_t sh2_device::mcr_r()
{
	return m_mcr & ~0x103;
}

void sh2_device::mcr_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	COMBINE_DATA(&m_mcr);
}

uint32_t sh2_device::rtcsr_r()
{
	return m_rtcsr & 0xf8;
}

void sh2_device::rtcsr_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	COMBINE_DATA(&m_rtcsr);
}

uint32_t sh2_device::rtcnt_r()
{
	return m_rtcnt & 0xff;
}

void sh2_device::rtcnt_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	COMBINE_DATA(&m_rtcnt);
	m_rtcnt &= 0xff;
}

uint32_t sh2_device::rtcor_r()
{
	return m_rtcor & 0xff;
}

void sh2_device::rtcor_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	COMBINE_DATA(&m_rtcor);
	m_rtcor &= 0xff;
}

void sh2_device::set_frt_input(int state)
{
	if(m_frt_input == state) {
		return;
	}

	m_frt_input = state;

	if(m_frc_tcr & 0x80) {
		if(state == CLEAR_LINE) {
			return;
		}
	} else {
		if(state == ASSERT_LINE) {
			return;
		}
	}

	sh2_timer_resync();
	m_frc_icr = m_frc;
	m_ftcsr |= ICF;
	//logerror("SH2.%s: ICF activated (%x)\n", tag(), m_sh2_state->pc & AM);
	sh2_recalc_irq();
}

void sh2_device::sh2_recalc_irq()
{
	int irq = 0, vector = -1;
	int  level;

	// Timer irqs
	if (m_tier & m_ftcsr & (ICF|OCFA|OCFB|OVF))
	{
		level = (m_irq_level.frc & 15);
		if (level > irq)
		{
			int mask = m_tier & m_ftcsr;
			irq = level;
			if(mask & ICF)
				vector = m_irq_vector.fic & 0x7f;
			else if(mask & (OCFA|OCFB))
				vector = m_irq_vector.foc & 0x7f;
			else
				vector = m_irq_vector.fov & 0x7f;
		}
	}

	// WDT irqs
	if (m_wtcsr & 0x80)
	{
		level = m_irq_level.wdt & 15;
		if (level > irq)
		{
			irq = level;
			vector = (m_vcrwdt >> 8) & 0x7f;
		}
	}

	// DMA irqs
	if((m_dmac[0].chcr & 6) == 6 && m_dma_irq[0]) {
		level = m_irq_level.dmac & 15;
		if(level > irq) {
			irq = level;
			m_dma_irq[0] &= ~1;
			vector = m_irq_vector.dmac[0] & 0x7f;
		}
	}
	else if((m_dmac[1].chcr & 6) == 6 && m_dma_irq[1]) {
		level = m_irq_level.dmac & 15;
		if(level > irq) {
			irq = level;
			m_dma_irq[1] &= ~1;
			vector = m_irq_vector.dmac[1] & 0x7f;
		}
	}

	m_sh2_state->internal_irq_level = irq;
	m_internal_irq_vector = vector;
	m_test_irq = 1;
}

/*
 SH-7021 on-chip device
 */

void sh2a_device::sh7032_dma_exec(int ch)
{
	const short dma_word_size[4] = { 0, +1, -1, 0 };
	uint8_t rs = (m_dma[ch].chcr >> 8) & 0xf; /**< Resource Select bits */
	if(rs != 0xc) // Auto-Request
	{
		logerror("Warning: SH7032 DMA enables non auto-request transfer\n");
		return;
	}

	// channel enable & master enable
	if((m_dma[ch].chcr & 1) == 0 || (m_dmaor & 1) == 0)
		return;

	printf("%08x %08x %04x\n",m_dma[ch].sar,m_dma[ch].dar,m_dma[ch].chcr);
	uint8_t dm = (m_dma[ch].chcr >> 14) & 3;  /**< Destination Address Mode bits */
	uint8_t sm = (m_dma[ch].chcr >> 12) & 3;  /**< Source Address Mode bits */
	bool ts = (m_dma[ch].chcr & 8);         /**< Transfer Size bit */
	int src_word_size = dma_word_size[sm] * ((ts == true) ? 2 : 1);
	int dst_word_size = dma_word_size[dm] * ((ts == true) ? 2 : 1);
	uint32_t src_addr = m_dma[ch].sar;
	uint32_t dst_addr = m_dma[ch].dar;
	uint32_t size_index = m_dma[ch].tcr;
	if(size_index == 0)
		size_index = 0x10000;

	if(ts == false)
		logerror("SH7032: DMA byte mode check\n");

	for(int index = size_index;index>-1;index--)
	{
		if(ts == true)
			m_program->write_word(dst_addr,m_program->read_word(src_addr));
		else
			m_program->write_byte(dst_addr,m_program->read_byte(src_addr));

		src_addr += src_word_size;
		dst_addr += dst_word_size;
	}

	m_dma[ch].chcr &= ~1; /**< @todo non-instant DMA */
	printf("%02x %02x %02x %1d\n",sm,dm,rs,ts);
}

uint32_t sh2a_device::dma_sar0_r()
{
	return m_dma[0].sar;
}

void sh2a_device::dma_sar0_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	COMBINE_DATA(&m_dma[0].sar);
}

uint32_t sh2a_device::dma_dar0_r()
{
	return m_dma[0].dar;
}

void sh2a_device::dma_dar0_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	COMBINE_DATA(&m_dma[0].dar);
}

uint16_t sh2a_device::dma_tcr0_r()
{
	return m_dma[0].tcr;
}

void sh2a_device::dma_tcr0_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	//printf("%04x\n",data);
	COMBINE_DATA(&m_dma[0].tcr);
}

uint16_t sh2a_device::dma_chcr0_r()
{
	return m_dma[0].chcr;
}

void sh2a_device::dma_chcr0_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	//printf("%04x CHCR0\n",data);
	COMBINE_DATA(&m_dma[0].chcr);
	sh7032_dma_exec(0);
}

uint16_t sh2a_device::dmaor_r()
{
	return m_dmaor;
}

void sh2a_device::dmaor_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	COMBINE_DATA(&m_dmaor);
	sh7032_dma_exec(0);
}

/*!
  @brief Dummy debug interface
  */
uint16_t sh1_device::sh7032_r(offs_t offset)
{
	return m_sh7032_regs[offset];
}

/*!
  @brief Dummy debug interface
 */
void sh1_device::sh7032_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	COMBINE_DATA(&m_sh7032_regs[offset]);
}

uint16_t sh2a_device::sh7021_r(offs_t offset)
{
	return m_sh7021_regs[offset];
}

/*!
  @brief Dummy debug interface
 */
void sh2a_device::sh7021_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	COMBINE_DATA(&m_sh7021_regs[offset]);
}
