summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/saturn.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/saturn.cpp')
-rw-r--r--src/devices/machine/saturn.cpp1003
1 files changed, 1003 insertions, 0 deletions
diff --git a/src/devices/machine/saturn.cpp b/src/devices/machine/saturn.cpp
new file mode 100644
index 00000000000..cf79fdc26a2
--- /dev/null
+++ b/src/devices/machine/saturn.cpp
@@ -0,0 +1,1003 @@
+// license:LGPL-2.1+
+// copyright-holders:David Haywood, Angelo Salese, Olivier Galibert, Mariusz Wojcieszek, R. Belmont
+/**************************************************************************************
+
+ Sega Saturn (c) 1994 Sega
+
+ @todo List of things that needs to be implemented:
+ - There's definitely an ack mechanism in SCU irqs. This is almost surely done via
+ the ISM register (i.e. going 0->1 to the given bit acks it).
+ - There might be a delay to exactly when SCU irqs happens. This is due to the basic
+ fact that SCU runs at 14-ish MHz, so it needs some time before actually firing the
+ irq.
+ - Vblank-Out actually happens at the last screen line, not at 0.
+ - VDP2 V counter has a similar roll-back as MD correspondent register:
+ vpos line 0 == 0x1ff (Vblank-Out happens here)
+ vpos line 1 == 0
+ ...
+ vpos line 241 == 0xf0 (Vblank-In happens here)
+ vpos line 246 == 0xf5
+ vpos line 247 == 0x1ef (rolls back here)
+ vpos line 263 == 0x1ff again
+ - HBlank bit seems to follow a normal logic instead.
+ - Timer 0 doesn't work if the TENB bit isn't enabled (documentation is a bit fussy
+ over this).
+ - Timer 0 fires at the HBlank-In signal, not before.
+ - VDP2 H Counter actually counts x2 in non Hi-Res mode.
+ - Timer 1 is definitely annoying. Starts from H-Blank signal and starts counting from
+ that position.
+ H counter value 0x282 (642) -> timer 1 fires at setting 1
+ H counter value 0x284 (644) -> 2
+ H counter value 0x2a0 (672) -> 0x10
+ H counter value 0x2c0 (704) -> 0x20
+ H counter value 0x300 (768) -> 0x40
+ H counter value 0x340 (832) -> 0x60
+ H counter value 0x352 (850) -> 0x69
+ H counter value 0x000 (0) -> 0x6a, V counter goes +1 here (max range?)
+ H counter value 0x02c (44) -> 0x80
+ H counter value 0x0ec (236) -> 0xe0
+ H counter value 0x12c (300) -> 0x100
+ - Timer 1 seems to count backwards compared to Timer 0 from setting 0x6b onward.
+ - Yabause claims that if VDP2 DISP bit isn't enabled then vblank irqs (hblank too?)
+ doesn't happen.
+
+**************************************************************************************/
+
+#include "emu.h"
+#include "includes/stv.h"
+#include "cpu/sh2/sh2.h"
+#include "cpu/scudsp/scudsp.h"
+
+/* TODO: do this in a verboselog style */
+#define LOG_CDB 0
+#define LOG_SCU 1
+#define LOG_IRQ 0
+#define LOG_IOGA 0
+
+int saturn_state::DectoBCD(int num)
+{
+ int i, cnt = 0, tmp, res = 0;
+
+ while (num > 0) {
+ tmp = num;
+ while (tmp >= 10) tmp %= 10;
+ for (i=0; i<cnt; i++)
+ tmp *= 16;
+ res += tmp;
+ cnt++;
+ num /= 10;
+ }
+
+ return res;
+}
+
+/**************************************************************************************/
+
+/*
+
+SCU Handling
+
+*/
+
+/**********************************************************************************
+SCU Register Table
+offset,relative address
+Registers are in long words.
+===================================================================================
+0 0000 Level 0 DMA Set Register
+1 0004
+2 0008
+3 000c
+4 0010
+5 0014
+6 0018
+7 001c
+8 0020 Level 1 DMA Set Register
+9 0024
+10 0028
+11 002c
+12 0030
+13 0034
+14 0038
+15 003c
+16 0040 Level 2 DMA Set Register
+17 0044
+18 0048
+19 004c
+20 0050
+21 0054
+22 0058
+23 005c
+24 0060 DMA Forced Stop
+25 0064
+26 0068
+27 006c
+28 0070 <Free>
+29 0074
+30 0078
+31 007c DMA Status Register
+32 0080 DSP Program Control Port
+33 0084 DSP Program RAM Data Port
+34 0088 DSP Data RAM Address Port
+35 008c DSP Data RAM Data Port
+36 0090 Timer 0 Compare Register
+37 0094 Timer 1 Set Data Register
+38 0098 Timer 1 Mode Register
+39 009c <Free>
+40 00a0 Interrupt Mask Register
+41 00a4 Interrupt Status Register
+42 00a8 A-Bus Interrupt Acknowledge
+43 00ac <Free>
+44 00b0 A-Bus Set Register
+45 00b4
+46 00b8 A-Bus Refresh Register
+47 00bc <Free>
+48 00c0
+49 00c4 SCU SDRAM Select Register
+50 00c8 SCU Version Register
+51 00cc <Free>
+52 00cf
+===================================================================================
+DMA Status Register(32-bit):
+xxxx xxxx x--- xx-- xx-- xx-- xx-- xx-- UNUSED
+---- ---- -x-- ---- ---- ---- ---- ---- DMA DSP-Bus access
+---- ---- --x- ---- ---- ---- ---- ---- DMA B-Bus access
+---- ---- ---x ---- ---- ---- ---- ---- DMA A-Bus access
+---- ---- ---- --x- ---- ---- ---- ---- DMA lv 1 interrupt
+---- ---- ---- ---x ---- ---- ---- ---- DMA lv 0 interrupt
+---- ---- ---- ---- --x- ---- ---- ---- DMA lv 2 in stand-by
+---- ---- ---- ---- ---x ---- ---- ---- DMA lv 2 in operation
+---- ---- ---- ---- ---- --x- ---- ---- DMA lv 1 in stand-by
+---- ---- ---- ---- ---- ---x ---- ---- DMA lv 1 in operation
+---- ---- ---- ---- ---- ---- --x- ---- DMA lv 0 in stand-by
+---- ---- ---- ---- ---- ---- ---x ---- DMA lv 0 in operation
+---- ---- ---- ---- ---- ---- ---- --x- DSP side DMA in stand-by
+---- ---- ---- ---- ---- ---- ---- ---x DSP side DMA in operation
+
+**********************************************************************************/
+/*
+DMA TODO:
+-Remove CD transfer DMA hack (tied with CD block bug(s)?)
+-Add timings(but how fast are each DMA?).
+-Add level priority & DMA status register.
+*/
+
+#define DIRECT_MODE(_lv_) (!(m_scu_regs[5+(_lv_*8)] & 0x01000000))
+#define INDIRECT_MODE(_lv_) (m_scu_regs[5+(_lv_*8)] & 0x01000000)
+#define DRUP(_lv_) (m_scu_regs[5+(_lv_*8)] & 0x00010000)
+#define DWUP(_lv_) (m_scu_regs[5+(_lv_*8)] & 0x00000100)
+
+/*These macros sets the various DMA status flags.*/
+#define DnMV_1(_ch_) m_scu.status|=(0x10 << 4 * _ch_)
+#define DnMV_0(_ch_) m_scu.status&=~(0x10 << 4 * _ch_)
+
+/*For area checking*/
+#define BIOS_BUS(var) (var & 0x07f00000) == 0
+#define ABUS(_lv_) ((m_scu.src[_lv_] & 0x07000000) >= 0x02000000) && ((m_scu.src[_lv_] & 0x07000000) <= 0x04000000)
+#define BBUS(_lv_) ((scu_##_lv_ & 0x07ffffff) >= 0x05a00000) && ((scu_##_lv_ & 0x07ffffff) <= 0x05ffffff)
+#define VDP1_REGS(_lv_) ((scu_##_lv_ & 0x07ffffff) >= 0x05d00000) && ((scu_##_lv_ & 0x07ffffff) <= 0x05dfffff)
+#define VDP2(_lv_) ((scu_##_lv_ & 0x07ffffff) >= 0x05e00000) && ((scu_##_lv_ & 0x07ffffff) <= 0x05fdffff)
+#define WORK_RAM_L(_lv_) ((scu_##_lv_ & 0x07ffffff) >= 0x00200000) && ((scu_##_lv_ & 0x07ffffff) <= 0x002fffff)
+#define WORK_RAM_H(var) (var & 0x07000000) == 0x06000000
+#define SOUND_RAM(_lv_) ((scu_##_lv_ & 0x07ffffff) >= 0x05a00000) && ((scu_##_lv_ & 0x07ffffff) <= 0x05afffff)
+
+void saturn_state::scu_do_transfer(UINT8 event)
+{
+ address_space &space = machine().device("maincpu")->memory().space(AS_PROGRAM);
+ int i;
+
+ for(i=0;i<3;i++)
+ {
+ if(m_scu.enable_mask[i] && m_scu.start_factor[i] == event)
+ {
+ if(DIRECT_MODE(i)) { scu_dma_direct(space,i); }
+ else { scu_dma_indirect(space,i); }
+ }
+ }
+}
+
+/* test pending irqs */
+void saturn_state::scu_test_pending_irq()
+{
+ int i;
+ const int irq_level[32] = { 0xf, 0xe, 0xd, 0xc,
+ 0xb, 0xa, 0x9, 0x8,
+ 0x8, 0x6, 0x6, 0x5,
+ 0x3, 0x2, -1, -1,
+ 0x7, 0x7, 0x7, 0x7,
+ 0x4, 0x4, 0x4, 0x4,
+ 0x1, 0x1, 0x1, 0x1,
+ 0x1, 0x1, 0x1, 0x1 };
+
+ for(i=0;i<32;i++)
+ {
+ if((!(m_scu.ism & 1 << i)) && (m_scu.ist & 1 << i))
+ {
+ if(irq_level[i] != -1) /* TODO: cheap check for undefined irqs */
+ {
+ m_maincpu->set_input_line_and_vector(irq_level[i], HOLD_LINE, 0x40 + i);
+ m_scu.ist &= ~(1 << i);
+ return; /* avoid spurious irqs, correct? */
+ }
+ }
+ }
+}
+
+READ32_MEMBER(saturn_state::saturn_scu_r)
+{
+ UINT32 res;
+
+ /*TODO: write only registers must return 0 or open bus */
+ switch(offset)
+ {
+ case 0x5c/4:
+ // Super Major League and Shin Megami Tensei - Akuma Zensho reads from there (undocumented), DMA status mirror?
+ if(LOG_SCU && !space.debugger_access()) logerror("(PC=%08x) DMA status reg read\n",space.device().safe_pc());
+ res = m_scu.status;
+ break;
+ case 0x7c/4:
+ if(LOG_SCU && !space.debugger_access()) logerror("(PC=%08x) DMA status reg read\n",space.device().safe_pc());
+ res = m_scu.status;
+ break;
+ case 0x80/4:
+ res = m_scudsp->program_control_r(space, 0, mem_mask);
+ break;
+ case 0x8c/4:
+ if(LOG_SCU && !space.debugger_access()) logerror( "DSP mem read at %08X\n", m_scu_regs[34]);
+ res = m_scudsp->ram_address_r(space, 0, mem_mask);
+ break;
+ case 0xa0/4:
+ if(LOG_SCU && !space.debugger_access()) logerror("(PC=%08x) IRQ mask reg read %08x MASK=%08x\n",space.device().safe_pc(),mem_mask,m_scu_regs[0xa0/4]);
+ res = m_scu.ism;
+ break;
+ case 0xa4/4:
+ if(LOG_SCU && !space.debugger_access()) logerror("(PC=%08x) IRQ status reg read MASK=%08x IST=%08x | ISM=%08x\n",space.device().safe_pc(),mem_mask,m_scu.ist,m_scu.ism);
+ /* TODO: Bug! trips an HW fault. Basically, it tries to read the IST bit 1 with that irq enabled.
+ Densetsu no Ogre Battle doesn't like this, so it needs investigation ...
+ */
+// res = m_scu.ist | ~m_scu.ism;
+ res = m_scu.ist;
+ break;
+ case 0xc8/4:
+ if(LOG_SCU && !space.debugger_access()) logerror("(PC=%08x) SCU version reg read\n",space.device().safe_pc());
+ res = 0x00000004;/*SCU Version 4, OK? */
+ break;
+ default:
+ if(LOG_SCU && !space.debugger_access()) logerror("(PC=%08x) SCU reg read at %d = %08x\n",space.device().safe_pc(),offset,m_scu_regs[offset]);
+ res = m_scu_regs[offset];
+ break;
+ }
+
+ return res;
+}
+
+#define DMA_CH ((offset & 0x18) / 8)
+
+WRITE32_MEMBER(saturn_state::saturn_scu_w)
+{
+ COMBINE_DATA(&m_scu_regs[offset]);
+
+ switch(offset)
+ {
+ /*LV 0 DMA*/
+ case 0x00/4: case 0x20/4: case 0x40/4: m_scu.src[DMA_CH] = ((m_scu_regs[offset] & 0x07ffffff) >> 0); break;
+ case 0x04/4: case 0x24/4: case 0x44/4: m_scu.dst[DMA_CH] = ((m_scu_regs[offset] & 0x07ffffff) >> 0); break;
+ case 0x08/4: case 0x28/4: case 0x48/4: m_scu.size[DMA_CH] = ((m_scu_regs[offset] & ((offset == 2) ? 0x000fffff : 0xfff)) >> 0); break;
+ case 0x0c/4: case 0x2c/4: case 0x4c/4:
+ m_scu.src_add[DMA_CH] = (m_scu_regs[offset] & 0x100) ? 4 : 0;
+ m_scu.dst_add[DMA_CH] = 1 << (m_scu_regs[offset] & 7);
+ if(m_scu.dst_add[DMA_CH] == 1) { m_scu.dst_add[DMA_CH] = 0; }
+ break;
+ case 0x10/4: case 0x30/4: case 0x50/4:
+ m_scu.enable_mask[DMA_CH] = (data & 0x100) >> 8;
+ if(m_scu.enable_mask[DMA_CH] && m_scu.start_factor[DMA_CH] == 7 && m_scu_regs[offset] & 1)
+ {
+ if(DIRECT_MODE(DMA_CH)) { scu_dma_direct(space,DMA_CH); }
+ else { scu_dma_indirect(space,DMA_CH); }
+ m_scu_regs[offset]&=~1;//disable starting bit.
+ }
+ break;
+ case 0x14/4: case 0x34/4: case 0x54/4:
+ if(INDIRECT_MODE(DMA_CH))
+ {
+ //if(LOG_SCU) logerror("Indirect Mode DMA lv %d set\n",DMA_CH);
+ if(!DWUP(DMA_CH)) m_scu.index[DMA_CH] = m_scu.dst[DMA_CH];
+ }
+
+ m_scu.start_factor[DMA_CH] = m_scu_regs[offset] & 7;
+ break;
+
+ case 0x60/4:
+ if(LOG_SCU) logerror("DMA Forced Stop Register set = %02x\n",m_scu_regs[24]);
+ break;
+ case 0x7c/4: if(LOG_SCU) logerror("Warning: DMA status WRITE! Offset %02x(%d)\n",offset*4,offset); break;
+ /*DSP section*/
+ case 0x80/4:
+ m_scudsp->program_control_w(space, 0, m_scu_regs[offset], mem_mask);
+ if(LOG_SCU) logerror("SCU DSP: Program Control Port Access %08x\n",data);
+ break;
+ case 0x84/4:
+ m_scudsp->program_w(space, 0, m_scu_regs[offset], mem_mask);
+ if(LOG_SCU) logerror("SCU DSP: Program RAM Data Port Access %08x\n",data);
+ break;
+ case 0x88/4:
+ m_scudsp->ram_address_control_w(space, 0,m_scu_regs[offset], mem_mask);
+ if(LOG_SCU) logerror("SCU DSP: Data RAM Address Port Access %08x\n",data);
+ break;
+ case 0x8c/4:
+ m_scudsp->ram_address_w(space, 0, m_scu_regs[offset], mem_mask);
+ if(LOG_SCU) logerror("SCU DSP: Data RAM Data Port Access %08x\n",data);
+ break;
+ case 0x90/4: /*if(LOG_SCU) logerror("timer 0 compare data = %03x\n",m_scu_regs[36]);*/ break;
+ case 0x94/4: /*if(LOG_SCU) logerror("timer 1 set data = %08x\n",m_scu_regs[37]);*/ break;
+ case 0x98/4: /*if(LOG_SCU) logerror("timer 1 mode data = %08x\n",m_scu_regs[38]);*/ break;
+ case 0xa0/4: /* IRQ mask */
+ m_scu.ism = m_scu_regs[0xa0/4];
+ scu_test_pending_irq();
+ break;
+ case 0xa4/4: /* IRQ control */
+ if(LOG_SCU) logerror("PC=%08x IRQ status reg set:%08x %08x\n",space.device().safe_pc(),m_scu_regs[41],mem_mask);
+ m_scu.ist &= m_scu_regs[offset];
+ scu_test_pending_irq();
+ break;
+ case 0xa8/4:
+ /* This sends an irq signal to the extra devices connected to the A-Bus, not really needed for now. */
+ //if(LOG_SCU) logerror("A-Bus IRQ ACK %08x\n",m_scu_regs[42]);
+ break;
+ case 0xc4/4: if(LOG_SCU) logerror("SCU SDRAM set: %02x\n",m_scu_regs[49]); break;
+ default: if(LOG_SCU) logerror("Warning: unused SCU reg set %d = %08x\n",offset,data);
+ }
+}
+
+/*Lv 0 DMA end irq*/
+TIMER_CALLBACK_MEMBER(saturn_state::dma_lv0_ended )
+{
+ if(!(m_scu.ism & IRQ_DMALV0))
+ m_maincpu->set_input_line_and_vector(5, HOLD_LINE, 0x4b);
+ else
+ m_scu.ist |= (IRQ_DMALV0);
+
+ DnMV_0(0);
+}
+
+/*Lv 1 DMA end irq*/
+TIMER_CALLBACK_MEMBER(saturn_state::dma_lv1_ended)
+{
+ if(!(m_scu.ism & IRQ_DMALV1))
+ m_maincpu->set_input_line_and_vector(6, HOLD_LINE, 0x4a);
+ else
+ m_scu.ist |= (IRQ_DMALV1);
+
+ DnMV_0(1);
+}
+
+/*Lv 2 DMA end irq*/
+TIMER_CALLBACK_MEMBER(saturn_state::dma_lv2_ended)
+{
+ if(!(m_scu.ism & IRQ_DMALV2))
+ m_maincpu->set_input_line_and_vector(6, HOLD_LINE, 0x49);
+ else
+ m_scu.ist |= (IRQ_DMALV2);
+
+ DnMV_0(2);
+}
+
+void saturn_state::scu_single_transfer(address_space &space, UINT32 src, UINT32 dst,UINT8 *src_shift)
+{
+ UINT32 src_data;
+
+ if(src & 1)
+ {
+ /* Road Blaster does a work ram h to color ram with offsetted source address, do some data rotation */
+ src_data = ((space.read_dword(src & 0x07fffffc) & 0x00ffffff)<<8);
+ src_data |= ((space.read_dword((src & 0x07fffffc)+4) & 0xff000000) >> 24);
+ src_data >>= (*src_shift)*16;
+ }
+ else
+ src_data = space.read_dword(src & 0x07fffffc) >> (*src_shift)*16;
+
+ space.write_word(dst,src_data);
+
+ *src_shift ^= 1;
+}
+
+void saturn_state::scu_dma_direct(address_space &space, UINT8 dma_ch)
+{
+ UINT32 tmp_src,tmp_dst,total_size;
+ UINT8 cd_transfer_flag;
+
+ if(m_scu.src_add[dma_ch] == 0 || (m_scu.dst_add[dma_ch] != 2 && m_scu.dst_add[dma_ch] != 4))
+ {
+ if(LOG_SCU) printf("DMA lv %d transfer START\n"
+ "Start %08x End %08x Size %04x\n",dma_ch,m_scu.src[dma_ch],m_scu.dst[dma_ch],m_scu.size[dma_ch]);
+ if(LOG_SCU) printf("Start Add %04x Destination Add %04x\n",m_scu.src_add[dma_ch],m_scu.dst_add[dma_ch]);
+ }
+
+ /* Game Basic and World Cup 98 trips this, according to the docs the SCU can't transfer from BIOS area (can't communicate from/to that bus) */
+ if(BIOS_BUS(m_scu.src[dma_ch]))
+ {
+ popmessage("Warning: SCU transfer from BIOS area, contact MAMEdev");
+ if(!(m_scu.ism & IRQ_DMAILL))
+ m_maincpu->set_input_line_and_vector(3, HOLD_LINE, 0x4c);
+ else
+ m_scu.ist |= (IRQ_DMAILL);
+ return;
+ }
+
+ DnMV_1(dma_ch);
+
+ /* max size */
+ if(m_scu.size[dma_ch] == 0) { m_scu.size[dma_ch] = (dma_ch == 0) ? 0x00100000 : 0x1000; }
+
+ tmp_src = tmp_dst = 0;
+
+ total_size = m_scu.size[dma_ch];
+ if(!(DRUP(dma_ch))) tmp_src = m_scu.src[dma_ch];
+ if(!(DWUP(dma_ch))) tmp_dst = m_scu.dst[dma_ch];
+
+ cd_transfer_flag = m_scu.src_add[dma_ch] == 0 && m_scu.src[dma_ch] == 0x05818000;
+
+ /* TODO: Many games directly accesses CD-ROM register 0x05818000, it must be a dword access with current implementation otherwise it won't work */
+ if(cd_transfer_flag)
+ {
+ int i;
+ if(WORK_RAM_H(m_scu.dst[dma_ch]))
+ m_scu.dst_add[dma_ch] = 4;
+ else
+ m_scu.dst_add[dma_ch] <<= 1;
+
+ for (i = 0; i < m_scu.size[dma_ch];i+=m_scu.dst_add[dma_ch])
+ {
+ space.write_dword(m_scu.dst[dma_ch],space.read_dword(m_scu.src[dma_ch]));
+ if(m_scu.dst_add[dma_ch] == 8)
+ space.write_dword(m_scu.dst[dma_ch]+4,space.read_dword(m_scu.src[dma_ch]));
+
+ m_scu.src[dma_ch]+=m_scu.src_add[dma_ch];
+ m_scu.dst[dma_ch]+=m_scu.dst_add[dma_ch];
+ }
+ }
+ else
+ {
+ int i;
+ UINT8 src_shift;
+
+ src_shift = ((m_scu.src[dma_ch] & 2) >> 1) ^ 1;
+
+ for (i = 0; i < m_scu.size[dma_ch];i+=2)
+ {
+ scu_single_transfer(space,m_scu.src[dma_ch],m_scu.dst[dma_ch],&src_shift);
+
+ if(src_shift)
+ m_scu.src[dma_ch]+=m_scu.src_add[dma_ch];
+
+ /* if target is Work RAM H, the add value is fixed, behaviour confirmed by Final Romance 2, Virtual Mahjong and Burning Rangers */
+ m_scu.dst[dma_ch]+=(WORK_RAM_H(m_scu.dst[dma_ch])) ? 2 : m_scu.dst_add[dma_ch];
+ }
+ }
+
+ /* Burning Rangers doesn't agree with this. */
+// m_scu.size[dma_ch] = 0;
+ if(!(DRUP(dma_ch))) m_scu.src[dma_ch] = tmp_src;
+ if(!(DWUP(dma_ch))) m_scu.dst[dma_ch] = tmp_dst;
+
+ {
+ /*TODO: Timing is a guess. */
+ switch(dma_ch)
+ {
+ case 0: machine().scheduler().timer_set(m_maincpu->cycles_to_attotime(total_size/4), timer_expired_delegate(FUNC(saturn_state::dma_lv0_ended),this)); break;
+ case 1: machine().scheduler().timer_set(m_maincpu->cycles_to_attotime(total_size/4), timer_expired_delegate(FUNC(saturn_state::dma_lv1_ended),this)); break;
+ case 2: machine().scheduler().timer_set(m_maincpu->cycles_to_attotime(total_size/4), timer_expired_delegate(FUNC(saturn_state::dma_lv2_ended),this)); break;
+ }
+ }
+}
+
+void saturn_state::scu_dma_indirect(address_space &space,UINT8 dma_ch)
+{
+ /*Helper to get out of the cycle*/
+ UINT8 job_done = 0;
+ /*temporary storage for the transfer data*/
+ UINT32 tmp_src;
+ UINT32 indirect_src,indirect_dst;
+ INT32 indirect_size;
+ UINT32 total_size = 0;
+
+ DnMV_1(dma_ch);
+
+ m_scu.index[dma_ch] = m_scu.dst[dma_ch];
+
+ do{
+ tmp_src = m_scu.index[dma_ch];
+
+ indirect_size = space.read_dword(m_scu.index[dma_ch]);
+ indirect_src = space.read_dword(m_scu.index[dma_ch]+8);
+ indirect_dst = space.read_dword(m_scu.index[dma_ch]+4);
+
+ /*Indirect Mode end factor*/
+ if(indirect_src & 0x80000000)
+ job_done = 1;
+
+ if(m_scu.src_add[dma_ch] == 0 || (m_scu.dst_add[dma_ch] != 2))
+ {
+ if(LOG_SCU) printf("DMA lv %d indirect mode transfer START\n"
+ "Index %08x Start %08x End %08x Size %04x\n",dma_ch,tmp_src,indirect_src,indirect_dst,indirect_size);
+ if(LOG_SCU) printf("Start Add %04x Destination Add %04x\n",m_scu.src_add[dma_ch],m_scu.dst_add[dma_ch]);
+ }
+
+ indirect_src &=0x07ffffff;
+ indirect_dst &=0x07ffffff;
+ indirect_size &= ((dma_ch == 0) ? 0xfffff : 0x3ffff); //TODO: Guardian Heroes sets up a 0x23000 transfer for the FMV?
+
+ if(indirect_size == 0) { indirect_size = (dma_ch == 0) ? 0x00100000 : 0x2000; }
+
+ {
+ int i;
+ UINT8 src_shift;
+
+ src_shift = ((indirect_src & 2) >> 1) ^ 1;
+
+ for (i = 0; i < indirect_size;i+=2)
+ {
+ scu_single_transfer(space,indirect_src,indirect_dst,&src_shift);
+
+ if(src_shift)
+ indirect_src+=m_scu.src_add[dma_ch];
+
+ indirect_dst+= (WORK_RAM_H(indirect_dst)) ? 2 : m_scu.dst_add[dma_ch];
+ }
+ }
+
+ /* Guess: Size + data acquire (1 cycle for src/dst/size) */
+ total_size += indirect_size + 3*4;
+
+ //if(DRUP(0)) space.write_dword(tmp_src+8,m_scu.src[0]|job_done ? 0x80000000 : 0);
+ //if(DWUP(0)) space.write_dword(tmp_src+4,m_scu.dst[0]);
+
+ m_scu.index[dma_ch] = tmp_src+0xc;
+
+ }while(job_done == 0);
+
+ {
+ /*TODO: change DMA into DRQ model. Timing is a guess. */
+ switch(dma_ch)
+ {
+ case 0: machine().scheduler().timer_set(m_maincpu->cycles_to_attotime(total_size/4), timer_expired_delegate(FUNC(saturn_state::dma_lv0_ended),this)); break;
+ case 1: machine().scheduler().timer_set(m_maincpu->cycles_to_attotime(total_size/4), timer_expired_delegate(FUNC(saturn_state::dma_lv1_ended),this)); break;
+ case 2: machine().scheduler().timer_set(m_maincpu->cycles_to_attotime(total_size/4), timer_expired_delegate(FUNC(saturn_state::dma_lv2_ended),this)); break;
+ }
+ }
+}
+
+
+/**************************************************************************************/
+
+WRITE16_MEMBER(saturn_state::saturn_soundram_w)
+{
+ //machine().scheduler().synchronize(); // force resync
+
+ COMBINE_DATA(&m_sound_ram[offset]);
+}
+
+READ16_MEMBER(saturn_state::saturn_soundram_r)
+{
+ //machine().scheduler().synchronize(); // force resync
+
+ return m_sound_ram[offset];
+}
+
+/* communication,SLAVE CPU acquires data from the MASTER CPU and triggers an irq. */
+WRITE32_MEMBER(saturn_state::minit_w)
+{
+ //logerror("cpu %s (PC=%08X) MINIT write = %08x\n", space.device().tag(), space.device().safe_pc(),data);
+ machine().scheduler().boost_interleave(m_minit_boost_timeslice, attotime::from_usec(m_minit_boost));
+ machine().scheduler().trigger(1000);
+ machine().scheduler().synchronize(); // force resync
+ m_slave->sh2_set_frt_input(PULSE_LINE);
+}
+
+WRITE32_MEMBER(saturn_state::sinit_w)
+{
+ //logerror("cpu %s (PC=%08X) SINIT write = %08x\n", space.device().tag(), space.device().safe_pc(),data);
+ machine().scheduler().boost_interleave(m_sinit_boost_timeslice, attotime::from_usec(m_sinit_boost));
+ machine().scheduler().synchronize(); // force resync
+ m_maincpu->sh2_set_frt_input(PULSE_LINE);
+}
+
+/*
+TODO:
+Some games seems to not like either MAME's interleave system and/or SH-2 DRC, causing an hard crash.
+Reported games are:
+Blast Wind (before FMV)
+Choro Q Park (car selection)
+060311E4: MOV.L R14,@-SP ;R14 = 0x60ffba0 / R15 = 0x60ffba0
+060311E6: MOV SP,R14 ;R14 = 0x60ffba0 / R15 = 0x60ffb9c / [0x60ffb9c] <- 0x60ffba0
+060311E8: MOV.L @SP+,R14 ;R14 = 0x60ffb9c / R15 = 0x60ffb9c / [0x60ffb9c] -> R14
+060311EA: RTS ;R14 = 0x60ffba0 / R15 = 0x60ffba0
+060311EC: NOP
+06031734: MULS.W R9, R8 ;R14 = 0x60ffba0 / R15 = 0x60ffba0 / EA = 0x60311E4
+on DRC this becomes:
+R14 0x6031b78 (cause of the crash later on), R15 = 0x60ffba4 and EA = 0
+
+Shinrei Jusatsushi Taromaru (options menu)
+
+*/
+
+WRITE32_MEMBER(saturn_state::saturn_minit_w)
+{
+ //logerror("cpu %s (PC=%08X) MINIT write = %08x\n", space.device().tag(), space.device().safe_pc(),data);
+ if(m_fake_comms->read() & 1)
+ machine().scheduler().synchronize(); // force resync
+ else
+ {
+ machine().scheduler().boost_interleave(m_minit_boost_timeslice, attotime::from_usec(m_minit_boost));
+ machine().scheduler().trigger(1000);
+ }
+
+ m_slave->sh2_set_frt_input(PULSE_LINE);
+}
+
+WRITE32_MEMBER(saturn_state::saturn_sinit_w)
+{
+ //logerror("cpu %s (PC=%08X) SINIT write = %08x\n", space.device().tag(), space.device().safe_pc(),data);
+ if(m_fake_comms->read() & 1)
+ machine().scheduler().synchronize(); // force resync
+ else
+ machine().scheduler().boost_interleave(m_sinit_boost_timeslice, attotime::from_usec(m_sinit_boost));
+
+ m_maincpu->sh2_set_frt_input(PULSE_LINE);
+}
+
+
+READ8_MEMBER(saturn_state::saturn_backupram_r)
+{
+ if(!(offset & 1))
+ return 0; // yes, it makes sure the "holes" are there.
+
+ return m_backupram[offset >> 1] & 0xff;
+}
+
+WRITE8_MEMBER(saturn_state::saturn_backupram_w)
+{
+ if(!(offset & 1))
+ return;
+
+ m_backupram[offset >> 1] = data;
+}
+
+void saturn_state::scu_reset(void)
+{
+ m_scu.ism = 0xbfff;
+ m_scu.ist = 0;
+ m_scu.start_factor[0] = 7;
+ m_scu.start_factor[1] = 7;
+ m_scu.start_factor[2] = 7;
+ m_scu.status = 0;
+}
+
+TIMER_CALLBACK_MEMBER(saturn_state::stv_rtc_increment)
+{
+ static const UINT8 dpm[12] = { 0x31, 0x28, 0x31, 0x30, 0x31, 0x30, 0x31, 0x31, 0x30, 0x31, 0x30, 0x31 };
+ static int year_num, year_count;
+
+ /*
+ m_smpc.rtc_data[0] = DectoBCD(systime.local_time.year /100);
+ m_smpc.rtc_data[1] = DectoBCD(systime.local_time.year %100);
+ m_smpc.rtc_data[2] = (systime.local_time.weekday << 4) | (systime.local_time.month+1);
+ m_smpc.rtc_data[3] = DectoBCD(systime.local_time.mday);
+ m_smpc.rtc_data[4] = DectoBCD(systime.local_time.hour);
+ m_smpc.rtc_data[5] = DectoBCD(systime.local_time.minute);
+ m_smpc.rtc_data[6] = DectoBCD(systime.local_time.second);
+ */
+
+ m_smpc.rtc_data[6]++;
+
+ /* seconds from 9 -> 10*/
+ if((m_smpc.rtc_data[6] & 0x0f) >= 0x0a) { m_smpc.rtc_data[6]+=0x10; m_smpc.rtc_data[6]&=0xf0; }
+ /* seconds from 59 -> 0 */
+ if((m_smpc.rtc_data[6] & 0xf0) >= 0x60) { m_smpc.rtc_data[5]++; m_smpc.rtc_data[6] = 0; }
+ /* minutes from 9 -> 10 */
+ if((m_smpc.rtc_data[5] & 0x0f) >= 0x0a) { m_smpc.rtc_data[5]+=0x10; m_smpc.rtc_data[5]&=0xf0; }
+ /* minutes from 59 -> 0 */
+ if((m_smpc.rtc_data[5] & 0xf0) >= 0x60) { m_smpc.rtc_data[4]++; m_smpc.rtc_data[5] = 0; }
+ /* hours from 9 -> 10 */
+ if((m_smpc.rtc_data[4] & 0x0f) >= 0x0a) { m_smpc.rtc_data[4]+=0x10; m_smpc.rtc_data[4]&=0xf0; }
+ /* hours from 23 -> 0 */
+ if((m_smpc.rtc_data[4] & 0xff) >= 0x24) { m_smpc.rtc_data[3]++; m_smpc.rtc_data[2]+=0x10; m_smpc.rtc_data[4] = 0; }
+ /* week day name sunday -> monday */
+ if((m_smpc.rtc_data[2] & 0xf0) >= 0x70) { m_smpc.rtc_data[2]&=0x0f; }
+ /* day number 9 -> 10 */
+ if((m_smpc.rtc_data[3] & 0x0f) >= 0x0a) { m_smpc.rtc_data[3]+=0x10; m_smpc.rtc_data[3]&=0xf0; }
+
+ // year BCD to dec conversion (for the leap year stuff)
+ {
+ year_num = (m_smpc.rtc_data[1] & 0xf);
+
+ for(year_count = 0; year_count < (m_smpc.rtc_data[1] & 0xf0); year_count += 0x10)
+ year_num += 0xa;
+
+ year_num += (m_smpc.rtc_data[0] & 0xf)*0x64;
+
+ for(year_count = 0; year_count < (m_smpc.rtc_data[0] & 0xf0); year_count += 0x10)
+ year_num += 0x3e8;
+ }
+
+ /* month +1 check */
+ /* the RTC have a range of 1980 - 2100, so we don't actually need to support the leap year special conditions */
+ if(((year_num % 4) == 0) && (m_smpc.rtc_data[2] & 0xf) == 2)
+ {
+ if((m_smpc.rtc_data[3] & 0xff) >= dpm[(m_smpc.rtc_data[2] & 0xf)-1]+1+1)
+ { m_smpc.rtc_data[2]++; m_smpc.rtc_data[3] = 0x01; }
+ }
+ else if((m_smpc.rtc_data[3] & 0xff) >= dpm[(m_smpc.rtc_data[2] & 0xf)-1]+1){ m_smpc.rtc_data[2]++; m_smpc.rtc_data[3] = 0x01; }
+ /* year +1 check */
+ if((m_smpc.rtc_data[2] & 0x0f) > 12) { m_smpc.rtc_data[1]++; m_smpc.rtc_data[2] = (m_smpc.rtc_data[2] & 0xf0) | 0x01; }
+ /* year from 9 -> 10 */
+ if((m_smpc.rtc_data[1] & 0x0f) >= 0x0a) { m_smpc.rtc_data[1]+=0x10; m_smpc.rtc_data[1]&=0xf0; }
+ /* year from 99 -> 100 */
+ if((m_smpc.rtc_data[1] & 0xf0) >= 0xa0) { m_smpc.rtc_data[0]++; m_smpc.rtc_data[1] = 0; }
+
+ // probably not SO precise, here just for reference ...
+ /* year from 999 -> 1000 */
+ //if((m_smpc.rtc_data[0] & 0x0f) >= 0x0a) { m_smpc.rtc_data[0]+=0x10; m_smpc.rtc_data[0]&=0xf0; }
+ /* year from 9999 -> 0 */
+ //if((m_smpc.rtc_data[0] & 0xf0) >= 0xa0) { m_smpc.rtc_data[0] = 0; } //roll over
+}
+
+/* Official documentation says that the "RESET/TAS opcodes aren't supported", but Out Run definitely contradicts with it.
+ Since that m68k can't reset itself via the RESET opcode I suppose that the SMPC actually do it by reading an i/o
+ connected to this opcode. */
+WRITE_LINE_MEMBER(saturn_state::m68k_reset_callback)
+{
+ machine().scheduler().timer_set(attotime::from_usec(100), timer_expired_delegate(FUNC(saturn_state::smpc_audio_reset_line_pulse), this));
+
+ printf("m68k RESET opcode triggered\n");
+}
+
+WRITE8_MEMBER(saturn_state::scsp_irq)
+{
+ // don't bother the 68k if it's off
+ if (!m_en_68k)
+ {
+ return;
+ }
+
+ if (offset != 0)
+ {
+ if (data == ASSERT_LINE) m_scsp_last_line = offset;
+ m_audiocpu->set_input_line(offset, data);
+ }
+ else
+ {
+ m_audiocpu->set_input_line(m_scsp_last_line, data);
+ }
+}
+
+WRITE_LINE_MEMBER(saturn_state::scsp_to_main_irq)
+{
+ if(state)
+ {
+ if(!(m_scu.ism & IRQ_SOUND_REQ))
+ {
+ m_maincpu->set_input_line_and_vector(9, HOLD_LINE, 0x46);
+ scu_do_transfer(5);
+ }
+ else
+ m_scu.ist |= (IRQ_SOUND_REQ);
+ }
+}
+
+
+
+/*
+(Preliminary) explanation about this:
+VBLANK-OUT is used at the start of the vblank period.It also sets the timer zero
+variable to 0.
+If the Timer Compare register is zero too,the Timer 0 irq is triggered.
+
+HBLANK-IN is used at the end of each scanline except when in VBLANK-IN/OUT periods.
+
+The timer 0 is also incremented by one at each HBLANK and checked with the value
+of the Timer Compare register;if equal,the timer 0 irq is triggered here too.
+Notice that the timer 0 compare register can be more than the VBLANK maximum range,in
+this case the timer 0 irq is simply never triggered.This is a known Sega Saturn/ST-V "bug".
+
+VBLANK-IN is used at the end of the vblank period.
+
+SCU register[36] is the timer zero compare register.
+SCU register[40] is for IRQ masking.
+
+TODO:
+- VDP1 timing and CEF emulation isn't accurate at all.
+*/
+
+
+TIMER_DEVICE_CALLBACK_MEMBER(saturn_state::saturn_scanline)
+{
+ int scanline = param;
+ int y_step,vblank_line;
+
+ vblank_line = get_vblank_start_position();
+ y_step = get_ystep_count();
+
+ //popmessage("%08x %d T0 %d T1 %d %08x",m_scu.ism ^ 0xffffffff,max_y,m_scu_regs[36],m_scu_regs[37],m_scu_regs[38]);
+
+ if(scanline == 0*y_step)
+ {
+ if(!(m_scu.ism & IRQ_VBLANK_OUT))
+ {
+ m_maincpu->set_input_line_and_vector(0xe, HOLD_LINE, 0x41);
+ scu_do_transfer(1);
+ }
+ else
+ m_scu.ist |= (IRQ_VBLANK_OUT);
+
+ }
+ else if(scanline == vblank_line*y_step)
+ {
+ if(!(m_scu.ism & IRQ_VBLANK_IN))
+ {
+ m_maincpu->set_input_line_and_vector(0xf, HOLD_LINE ,0x40);
+ scu_do_transfer(0);
+ }
+ else
+ m_scu.ist |= (IRQ_VBLANK_IN);
+
+ /* TODO: when Automatic Draw actually happens? Night Striker S is very fussy on this, and it looks like that VDP1 starts at more or less vblank-in time ... */
+ video_update_vdp1();
+ }
+ else if((scanline % y_step) == 0 && scanline < vblank_line*y_step)
+ {
+ if(!(m_scu.ism & IRQ_HBLANK_IN))
+ {
+ m_maincpu->set_input_line_and_vector(0xd, HOLD_LINE, 0x42);
+ scu_do_transfer(2);
+ }
+ else
+ m_scu.ist |= (IRQ_HBLANK_IN);
+ }
+
+ if(scanline == (vblank_line+1)*y_step)
+ {
+ /* docs mentions that VBE happens one line after vblank-in. */
+ if(STV_VDP1_VBE)
+ m_vdp1.framebuffer_clear_on_next_frame = 1;
+ }
+
+
+ if(scanline == (m_scu_regs[36] & 0x3ff)*y_step)
+ {
+ if(!(m_scu.ism & IRQ_TIMER_0))
+ {
+ m_maincpu->set_input_line_and_vector(0xc, HOLD_LINE, 0x43 );
+ scu_do_transfer(3);
+ }
+ else
+ m_scu.ist |= (IRQ_TIMER_0);
+ }
+
+ /* TODO: this isn't completely correct */
+ if(m_scu_regs[38] & 0x1)
+ {
+ if((!(m_scu_regs[38] & 0x100) && (scanline % y_step) == 0) ||
+ ((m_scu_regs[38] & 0x100) && (scanline == (m_scu_regs[36] & 0x3ff)*y_step)))
+ {
+ if(!(m_scu.ism & IRQ_TIMER_1))
+ {
+ m_maincpu->set_input_line_and_vector(0xb, HOLD_LINE, 0x44 );
+ scu_do_transfer(4);
+ }
+ else
+ m_scu.ist |= (IRQ_TIMER_1);
+ }
+ }
+}
+
+TIMER_DEVICE_CALLBACK_MEMBER(saturn_state::saturn_slave_scanline )
+{
+ int scanline = param;
+ int y_step,vblank_line;
+
+ vblank_line = get_vblank_start_position();
+ y_step = get_ystep_count();
+
+ if(scanline == vblank_line*y_step)
+ m_slave->set_input_line_and_vector(0x6, HOLD_LINE, 0x43);
+ else if((scanline % y_step) == 0 && scanline < vblank_line*y_step)
+ m_slave->set_input_line_and_vector(0x2, HOLD_LINE, 0x41);
+}
+
+static const gfx_layout tiles8x8x4_layout =
+{
+ 8,8,
+ 0x100000/(32*8/8),
+ 4,
+ { 0, 1, 2, 3 },
+ { 0, 4, 8, 12, 16, 20, 24, 28 },
+ { 0*32, 1*32, 2*32, 3*32, 4*32, 5*32, 6*32, 7*32 },
+ 32*8
+};
+
+static const gfx_layout tiles16x16x4_layout =
+{
+ 16,16,
+ 0x100000/(32*32/8),
+ 4,
+ { 0, 1, 2, 3 },
+ { 0, 4, 8, 12, 16, 20, 24, 28,
+ 32*8+0, 32*8+4, 32*8+8, 32*8+12, 32*8+16, 32*8+20, 32*8+24, 32*8+28,
+
+ },
+ { 0*32, 1*32, 2*32, 3*32, 4*32, 5*32, 6*32, 7*32,
+ 32*16, 32*17,32*18, 32*19,32*20,32*21,32*22,32*23
+
+ },
+ 32*32
+};
+
+static const gfx_layout tiles8x8x8_layout =
+{
+ 8,8,
+ 0x100000/(32*8/8),
+ 8,
+ { 0, 1, 2, 3, 4, 5, 6, 7 },
+ { 0, 8, 16, 24, 32, 40, 48, 56 },
+ { 0*64, 1*64, 2*64, 3*64, 4*64, 5*64, 6*64, 7*64 },
+ 32*8 /* really 64*8, but granularity is 32 bytes */
+};
+
+static const gfx_layout tiles16x16x8_layout =
+{
+ 16,16,
+ 0x100000/(64*16/8),
+ 8,
+ { 0, 1, 2, 3, 4, 5, 6, 7 },
+ { 0, 8, 16, 24, 32, 40, 48, 56,
+ 64*8+0, 65*8, 66*8, 67*8, 68*8, 69*8, 70*8, 71*8
+
+ },
+ { 0*64, 1*64, 2*64, 3*64, 4*64, 5*64, 6*64, 7*64,
+ 64*16, 64*17, 64*18, 64*19, 64*20, 64*21, 64*22, 64*23
+ },
+ 64*16 /* really 128*16, but granularity is 32 bytes */
+};
+
+
+
+
+GFXDECODE_START( stv )
+ GFXDECODE_ENTRY( NULL, 0, tiles8x8x4_layout, 0x00, (0x80*(2+1)) )
+ GFXDECODE_ENTRY( NULL, 0, tiles16x16x4_layout, 0x00, (0x80*(2+1)) )
+ GFXDECODE_ENTRY( NULL, 0, tiles8x8x8_layout, 0x00, (0x08*(2+1)) )
+ GFXDECODE_ENTRY( NULL, 0, tiles16x16x8_layout, 0x00, (0x08*(2+1)) )
+GFXDECODE_END
+
+WRITE_LINE_MEMBER(saturn_state::scudsp_end_w)
+{
+ if(state)
+ {
+ if(!(m_scu.ism & IRQ_DSP_END))
+ m_maincpu->set_input_line_and_vector(0xa, HOLD_LINE, 0x45);
+ else
+ m_scu.ist |= (IRQ_DSP_END);
+ }
+}
+
+READ16_MEMBER(saturn_state::scudsp_dma_r)
+{
+ address_space &program = m_maincpu->space(AS_PROGRAM);
+ offs_t addr = offset;
+
+// printf("%08x\n",addr);
+
+ return program.read_word(addr,mem_mask);
+}
+
+
+WRITE16_MEMBER(saturn_state::scudsp_dma_w)
+{
+ address_space &program = m_maincpu->space(AS_PROGRAM);
+ offs_t addr = offset;
+
+// printf("%08x %02x\n",addr,data);
+
+ program.write_word(addr, data,mem_mask);
+}