// license:LGPL-2.1+ // copyright-holders:Angelo Salese, R. Belmont /************************************************************************************ Sega Saturn SMPC - System Manager and Peripheral Control MCU simulation The SMPC is actually a 4-bit Hitachi HD404920FS MCU, labeled with a Sega custom 315-5744 (that needs decapping) MCU simulation by Angelo Salese & R. Belmont TODO: - timings; - fix intback issue with inputs (according to the docs, it should fall in between VBLANK-IN and OUT, for obvious reasons); - clean-ups; *************************************************************************************/ /* SMPC Addresses 00 01 -w Input Register 0 (IREG) 02 03 -w Input Register 1 04 05 -w Input Register 2 06 07 -w Input Register 3 08 09 -w Input Register 4 0a 0b -w Input Register 5 0c 0d -w Input Register 6 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f -w Command Register (COMREG) 20 21 r- Output Register 0 (OREG) 22 23 r- Output Register 1 24 25 r- Output Register 2 26 27 r- Output Register 3 28 29 r- Output Register 4 2a 2b r- Output Register 5 2c 2d r- Output Register 6 2e 2f r- Output Register 7 30 31 r- Output Register 8 32 33 r- Output Register 9 34 35 r- Output Register 10 36 37 r- Output Register 11 38 39 r- Output Register 12 3a 3b r- Output Register 13 3c 3d r- Output Register 14 3e 3f r- Output Register 15 40 41 r- Output Register 16 42 43 r- Output Register 17 44 45 r- Output Register 18 46 47 r- Output Register 19 48 49 r- Output Register 20 4a 4b r- Output Register 21 4c 4d r- Output Register 22 4e 4f r- Output Register 23 50 51 r- Output Register 24 52 53 r- Output Register 25 54 55 r- Output Register 26 56 57 r- Output Register 27 58 59 r- Output Register 28 5a 5b r- Output Register 29 5c 5d r- Output Register 30 5e 5f r- Output Register 31 60 61 r- SR 62 63 rw SF 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 rw PDR1 76 77 rw PDR2 78 79 -w DDR1 7a 7b -w DDR2 7c 7d -w IOSEL2/1 7e 7f -w EXLE2/1 */ #include "emu.h" #include "coreutil.h" #include "includes/saturn.h" #include "machine/smpc.h" #include "machine/eepromser.h" #define LOG_SMPC 0 #define LOG_PAD_CMD 0 /******************************************** * * Bankswitch code for ST-V Multi Cart mode * *******************************************/ void saturn_state::stv_select_game(int gameno) { if (m_prev_bankswitch != gameno) { if (m_cart_reg[gameno] && m_cart_reg[gameno]->base()) memcpy(memregion("abus")->base(), m_cart_reg[gameno]->base(), 0x3000000); else memset(memregion("abus")->base(), 0x00, 0x3000000); // TODO: 1-filled? m_prev_bankswitch = gameno; } } /******************************************** * * Command functions * *******************************************/ void saturn_state::smpc_master_on() { m_maincpu->set_input_line(INPUT_LINE_RESET, CLEAR_LINE); } TIMER_CALLBACK_MEMBER( saturn_state::smpc_slave_enable ) { m_slave->set_input_line(INPUT_LINE_RESET, param ? ASSERT_LINE : CLEAR_LINE); m_smpc.OREG[31] = param + 0x02; //read-back for last command issued m_smpc.SF = 0x00; //clear hand-shake flag m_smpc.slave_on = param; // printf("%d %d\n",machine().first_screen()->hpos(),machine().first_screen()->vpos()); } TIMER_CALLBACK_MEMBER( saturn_state::smpc_sound_enable ) { m_audiocpu->set_input_line(INPUT_LINE_RESET, param ? ASSERT_LINE : CLEAR_LINE); m_en_68k = param ^ 1; m_smpc.OREG[31] = param + 0x06; //read-back for last command issued m_smpc.SF = 0x00; //clear hand-shake flag } TIMER_CALLBACK_MEMBER( saturn_state::smpc_cd_enable ) { m_smpc.OREG[31] = param + 0x08; //read-back for last command issued m_smpc.SF = 0x08; //clear hand-shake flag (TODO: diagnostic wants this to have bit 3 high) } void saturn_state::smpc_system_reset() { /*Only backup ram and SMPC ram are retained after that this command is issued.*/ memset(m_scu_regs.get() ,0x00,0x000100); memset(m_scsp_regs.get(),0x00,0x001000); memset(m_sound_ram,0x00,0x080000); memset(m_workram_h,0x00,0x100000); memset(m_workram_l,0x00,0x100000); memset(m_vdp2_regs.get(),0x00,0x040000); memset(m_vdp2_vram.get(),0x00,0x100000); memset(m_vdp2_cram.get(),0x00,0x080000); memset(m_vdp1_vram.get(),0x00,0x100000); //A-Bus m_maincpu->set_input_line(INPUT_LINE_RESET, PULSE_LINE); } TIMER_CALLBACK_MEMBER( saturn_state::smpc_change_clock ) { UINT32 xtal; if(LOG_SMPC) printf ("Clock change execute at (%d %d)\n",machine().first_screen()->hpos(),machine().first_screen()->vpos()); xtal = param ? MASTER_CLOCK_320 : MASTER_CLOCK_352; machine().device("maincpu")->set_unscaled_clock(xtal/2); machine().device("slave")->set_unscaled_clock(xtal/2); m_vdp2.dotsel = param ^ 1; stv_vdp2_dynamic_res_change(); m_maincpu->set_input_line(INPUT_LINE_HALT, CLEAR_LINE); if(!m_NMI_reset) m_maincpu->set_input_line(INPUT_LINE_NMI, PULSE_LINE); m_slave->set_input_line(INPUT_LINE_HALT, CLEAR_LINE); m_slave->set_input_line(INPUT_LINE_RESET, ASSERT_LINE); m_audiocpu->set_input_line(INPUT_LINE_HALT, CLEAR_LINE); /* put issued command in OREG31 */ m_smpc.OREG[31] = 0x0e + param; /* clear hand-shake flag */ m_smpc.SF = 0x00; /* TODO: VDP1 / VDP2 / SCU / SCSP default power ON values? */ } TIMER_CALLBACK_MEMBER( saturn_state::stv_intback_peripheral ) { if (m_smpc.intback_stage == 2) { m_smpc.SR = (0x80 | m_smpc.pmode); // pad 2, no more data, echo back pad mode set by intback m_smpc.intback_stage = 0; } else { m_smpc.SR = (0xc0 | m_smpc.pmode); // pad 1, more data, echo back pad mode set by intback m_smpc.intback_stage ++; } if(!(m_scu.ism & IRQ_SMPC)) m_maincpu->set_input_line_and_vector(8, HOLD_LINE, 0x47); else m_scu.ist |= (IRQ_SMPC); m_smpc.OREG[31] = 0x10; /* callback for last command issued */ m_smpc.SF = 0x00; /* clear hand-shake flag */ } TIMER_CALLBACK_MEMBER( saturn_state::stv_smpc_intback ) { int i; // printf("%02x %02x %02x\n",m_smpc.intback_buf[0],m_smpc.intback_buf[1],m_smpc.intback_buf[2]); if(m_smpc.intback_buf[0] != 0) { m_smpc.OREG[0] = (0x80) | ((m_NMI_reset & 1) << 6); for(i=0;i<7;i++) m_smpc.OREG[1+i] = m_smpc.rtc_data[i]; m_smpc.OREG[8]=0x00; // CTG0 / CTG1? m_smpc.OREG[9]=0x00; // TODO: system region on Saturn m_smpc.OREG[10]= 0 << 7 | m_vdp2.dotsel << 6 | 1 << 5 | 1 << 4 | 0 << 3 | //MSHNMI 1 << 2 | 0 << 1 | //SYSRES 0 << 0; //SOUNDRES m_smpc.OREG[11]= 0 << 6; //CDRES for(i=0;i<4;i++) m_smpc.OREG[12+i]=m_smpc.SMEM[i]; for(i=0;i<15;i++) m_smpc.OREG[16+i]=0xff; // undefined m_smpc.intback_stage = (m_smpc.intback_buf[1] & 8) >> 3; // first peripheral m_smpc.SR = 0x40 | m_smpc.intback_stage << 5; m_smpc.pmode = m_smpc.intback_buf[0]>>4; // /*This is for RTC,cartridge code and similar stuff...*/ //if(LOG_SMPC) printf ("Interrupt: System Manager (SMPC) at scanline %04x, Vector 0x47 Level 0x08\n",scanline); if(!(m_scu.ism & IRQ_SMPC)) m_maincpu->set_input_line_and_vector(8, HOLD_LINE, 0x47); else m_scu.ist |= (IRQ_SMPC); /* put issued command in OREG31 */ m_smpc.OREG[31] = 0x10; // TODO: doc says 0? /* clear hand-shake flag */ m_smpc.SF = 0x00; } else if(m_smpc.intback_buf[1] & 8) { m_smpc.intback_stage = (m_smpc.intback_buf[1] & 8) >> 3; // first peripheral m_smpc.SR = 0x40; m_smpc.OREG[31] = 0x10; machine().scheduler().timer_set(attotime::from_usec(0), timer_expired_delegate(FUNC(saturn_state::stv_intback_peripheral),this),0); } else { /* Shienryu calls this, it would be plainly illegal on Saturn, I'll just return the command and clear the hs flag for now. */ m_smpc.OREG[31] = 0x10; m_smpc.SF = 0x00; } } /* [0] port status: 0x04 Sega-tap 0x16 Multi-tap 0x2x clock serial peripheral 0xf0 peripheral isn't connected 0xf1 peripheral is connected [1] Peripheral ID (note: lowest four bits determines the size of the input packet) 0x02 digital pad 0x25 (tested by Game Basic?) 0x34 keyboard Lower 4 bits of the port status tell the number of controllers to check for the port Lower 4 bits of the peripheral ID tell the number of registers used by each controller For multitap / segatap, we have implemented the following logic: SMPC reads in sequence - status for port 1 - ID first controller, followed by the number of reads needed by the plugged controller - ID second controller, followed by the number of reads needed by the plugged controller - and so on... until the 4th (for SegaTap) or 6th (for Multitap) controller is read TODO: how does the multitap check if a controller is connected? does it ask for the controller status of each subport? how does this work exactly? currently, there is a small problem in some specific controller config which seems to lose track of one controller. E.g. if I put multitap in port2 with inserted joy1, joy2 and joy4 it does not see joy4 controller, but if I put joy1, joy2, joy4 and joy5 it sees all four of them. The same happens if I skip controllers with id = 0xff... how did a real unit behave in this case? */ TIMER_CALLBACK_MEMBER( saturn_state::intback_peripheral ) { // if (LOG_SMPC) logerror("SMPC: providing PAD data for intback, pad %d\n", intback_stage-2); // doesn't work? //pad_num = m_smpc.intback_stage - 1; if(LOG_PAD_CMD) printf("%d %d %d\n", m_smpc.intback_stage - 1, machine().first_screen()->vpos(), (int)machine().first_screen()->frame_number()); UINT8 status1 = m_ctrl1 ? m_ctrl1->read_status() : 0xf0; UINT8 status2 = m_ctrl2 ? m_ctrl2->read_status() : 0xf0; UINT8 reg_offset = 0; UINT8 ctrl1_offset = 0; // this is used when there is segatap or multitap connected UINT8 ctrl2_offset = 0; // this is used when there is segatap or multitap connected m_smpc.OREG[reg_offset++] = status1; // read ctrl1 for (int i = 0; i < (status1 & 0xf); i++) { UINT8 id = m_ctrl1->read_id(i); m_smpc.OREG[reg_offset++] = id; for (int j = 0; j < (id & 0xf); j++) m_smpc.OREG[reg_offset++] = m_ctrl1->read_ctrl(j + ctrl1_offset); ctrl1_offset += (id & 0xf); } m_smpc.OREG[reg_offset++] = status2; // read ctrl2 for (int i = 0; i < (status2 & 0xf); i++) { UINT8 id = m_ctrl2->read_id(i); m_smpc.OREG[reg_offset++] = id; for (int j = 0; j < (id & 0xf); j++) m_smpc.OREG[reg_offset++] = m_ctrl2->read_ctrl(j + ctrl2_offset); ctrl2_offset += (id & 0xf); } if (m_smpc.intback_stage == 2) { m_smpc.SR = (0x80 | m_smpc.pmode); // pad 2, no more data, echo back pad mode set by intback m_smpc.intback_stage = 0; } else { m_smpc.SR = (0xc0 | m_smpc.pmode); // pad 1, more data, echo back pad mode set by intback m_smpc.intback_stage ++; } if(!(m_scu.ism & IRQ_SMPC)) m_maincpu->set_input_line_and_vector(8, HOLD_LINE, 0x47); else m_scu.ist |= (IRQ_SMPC); m_smpc.OREG[31] = 0x10; /* callback for last command issued */ m_smpc.SF = 0x00; /* clear hand-shake flag */ } TIMER_CALLBACK_MEMBER( saturn_state::saturn_smpc_intback ) { if(m_smpc.intback_buf[0] != 0) { { int i; m_smpc.OREG[0] = (0x80) | ((m_NMI_reset & 1) << 6); // bit 7: SETTIME (RTC isn't setted up properly) for(i=0;i<7;i++) m_smpc.OREG[1+i] = m_smpc.rtc_data[i]; m_smpc.OREG[8]=0x00; //Cartridge code? m_smpc.OREG[9] = m_saturn_region; m_smpc.OREG[10]= 0 << 7 | m_vdp2.dotsel << 6 | 1 << 5 | 1 << 4 | 0 << 3 | //MSHNMI 1 << 2 | 0 << 1 | //SYSRES 0 << 0; //SOUNDRES m_smpc.OREG[11]= 0 << 6; //CDRES for(i=0;i<4;i++) m_smpc.OREG[12+i]=m_smpc.SMEM[i]; for(i=0;i<15;i++) m_smpc.OREG[16+i]=0xff; // undefined } m_smpc.intback_stage = (m_smpc.intback_buf[1] & 8) >> 3; // first peripheral m_smpc.SR = 0x40 | m_smpc.intback_stage << 5; m_smpc.pmode = m_smpc.intback_buf[0]>>4; if(!(m_scu.ism & IRQ_SMPC)) m_maincpu->set_input_line_and_vector(8, HOLD_LINE, 0x47); else m_scu.ist |= (IRQ_SMPC); /* put issued command in OREG31 */ m_smpc.OREG[31] = 0x10; /* clear hand-shake flag */ m_smpc.SF = 0x00; } else if(m_smpc.intback_buf[1] & 8) { m_smpc.intback_stage = (m_smpc.intback_buf[1] & 8) >> 3; // first peripheral m_smpc.SR = 0x40; m_smpc.OREG[31] = 0x10; machine().scheduler().timer_set(attotime::from_usec(0), timer_expired_delegate(FUNC(saturn_state::intback_peripheral),this),0); } else { printf("SMPC intback bogus behaviour called %02x %02x\n",m_smpc.IREG[0],m_smpc.IREG[1]); } } void saturn_state::smpc_rtc_write() { int i; for(i=0;i<7;i++) m_smpc.rtc_data[i] = m_smpc.IREG[i]; } void saturn_state::smpc_memory_setting() { int i; for(i=0;i<4;i++) m_smpc.SMEM[i] = m_smpc.IREG[i]; } void saturn_state::smpc_nmi_req() { /*NMI is unconditionally requested */ m_maincpu->set_input_line(INPUT_LINE_NMI, PULSE_LINE); } TIMER_CALLBACK_MEMBER( saturn_state::smpc_nmi_set ) { // printf("%d %d\n",machine().first_screen()->hpos(),machine().first_screen()->vpos()); m_NMI_reset = param; /* put issued command in OREG31 */ m_smpc.OREG[31] = 0x19 + param; /* clear hand-shake flag */ m_smpc.SF = 0x00; //m_smpc.OREG[0] = (0x80) | ((m_NMI_reset & 1) << 6); } TIMER_CALLBACK_MEMBER( saturn_state::smpc_audio_reset_line_pulse ) { m_audiocpu->set_input_line(INPUT_LINE_RESET, PULSE_LINE); } /******************************************** * * COMREG sub-routine * *******************************************/ void saturn_state::smpc_comreg_exec(address_space &space, UINT8 data, UINT8 is_stv) { switch (data) { case 0x00: if(LOG_SMPC) printf ("SMPC: Master ON\n"); smpc_master_on(); break; //case 0x01: Master OFF? case 0x02: case 0x03: if(LOG_SMPC) printf ("SMPC: Slave %s %d %d\n",(data & 1) ? "off" : "on",machine().first_screen()->hpos(),machine().first_screen()->vpos()); machine().scheduler().timer_set(attotime::from_usec(15), timer_expired_delegate(FUNC(saturn_state::smpc_slave_enable),this),data & 1); break; case 0x06: case 0x07: if(LOG_SMPC) printf ("SMPC: Sound %s\n",(data & 1) ? "off" : "on"); if(!is_stv) machine().scheduler().timer_set(attotime::from_usec(15), timer_expired_delegate(FUNC(saturn_state::smpc_sound_enable),this),data & 1); break; /*CD (SH-1) ON/OFF */ case 0x08: case 0x09: printf ("SMPC: CD %s\n",(data & 1) ? "off" : "on"); machine().scheduler().timer_set(attotime::from_usec(20), timer_expired_delegate(FUNC(saturn_state::smpc_cd_enable),this),data & 1); break; case 0x0a: case 0x0b: popmessage ("SMPC: NETLINK %s, contact MAMEdev",(data & 1) ? "off" : "on"); break; case 0x0d: if(LOG_SMPC) printf ("SMPC: System Reset\n"); smpc_system_reset(); break; case 0x0e: case 0x0f: if(LOG_SMPC) printf ("SMPC: Change Clock to %s (%d %d)\n",data & 1 ? "320" : "352",machine().first_screen()->hpos(),machine().first_screen()->vpos()); /* on ST-V timing of this is pretty fussy, you get 2 credits at start-up otherwise My current theory is that SMPC first stops all CPUs until it executes the whole snippet for this, and restarts them when the screen is again ready for use. I really don't think that the system can do an usable mid-frame clock switching anyway. */ m_maincpu->set_input_line(INPUT_LINE_HALT, ASSERT_LINE); m_slave->set_input_line(INPUT_LINE_HALT, ASSERT_LINE); m_audiocpu->set_input_line(INPUT_LINE_HALT, ASSERT_LINE); machine().scheduler().timer_set(machine().first_screen()->time_until_pos(get_vblank_start_position()*get_ystep_count(), 0), timer_expired_delegate(FUNC(saturn_state::smpc_change_clock),this),data & 1); break; /*"Interrupt Back"*/ case 0x10: if(0) { printf ("SMPC: Status Acquire %02x %02x %02x %d\n",m_smpc.IREG[0],m_smpc.IREG[1],m_smpc.IREG[2],machine().first_screen()->vpos()); } int timing; timing = 8; if(m_smpc.IREG[0] != 0) // non-peripheral data timing += 8; /* TODO: At vblank-out actually ... */ if(m_smpc.IREG[1] & 8) // peripheral data timing += 700; /* TODO: check if IREG[2] is setted to 0xf0 */ { int i; for(i=0;i<3;i++) m_smpc.intback_buf[i] = m_smpc.IREG[i]; } if(is_stv) { machine().scheduler().timer_set(attotime::from_usec(timing), timer_expired_delegate(FUNC(saturn_state::stv_smpc_intback),this),0); //TODO: variable time } else { if(LOG_PAD_CMD) printf("INTBACK %02x %02x %d %d\n",m_smpc.IREG[0],m_smpc.IREG[1],machine().first_screen()->vpos(),(int)machine().first_screen()->frame_number()); machine().scheduler().timer_set(attotime::from_usec(timing), timer_expired_delegate(FUNC(saturn_state::saturn_smpc_intback),this),0); //TODO: is variable time correct? } break; /* RTC write*/ case 0x16: if(LOG_SMPC) printf("SMPC: RTC write\n"); smpc_rtc_write(); break; /* SMPC memory setting*/ case 0x17: if(LOG_SMPC) printf ("SMPC: memory setting\n"); smpc_memory_setting(); break; case 0x18: if(LOG_SMPC) printf ("SMPC: NMI request\n"); smpc_nmi_req(); break; case 0x19: case 0x1a: /* TODO: timing */ if(LOG_SMPC) printf ("SMPC: NMI %sable %d %d\n",data & 1 ? "Dis" : "En",machine().first_screen()->hpos(),machine().first_screen()->vpos()); machine().scheduler().timer_set(attotime::from_usec(100), timer_expired_delegate(FUNC(saturn_state::smpc_nmi_set),this),data & 1); break; default: printf ("cpu '%s' (PC=%08X) SMPC: undocumented Command %02x\n", space.device().tag(), space.device().safe_pc(), data); } } /******************************************** * * ST-V handlers * *******************************************/ READ8_MEMBER( saturn_state::stv_SMPC_r ) { int return_data = 0; if(!(offset & 1)) return 0; if(offset >= 0x21 && offset <= 0x5f) return_data = m_smpc.OREG[(offset-0x21) >> 1]; if (offset == 0x61) // TODO: SR return_data = m_smpc.SR; if (offset == 0x63) return_data = m_smpc.SF; if (offset == 0x75)//PDR1 read return_data = ioport("DSW1")->read(); if (offset == 0x77)//PDR2 read return_data = (0xfe | m_eeprom->do_read()); return return_data; } WRITE8_MEMBER( saturn_state::stv_SMPC_w ) { if (!(offset & 1)) // avoid writing to even bytes return; // if(LOG_SMPC) printf ("8-bit SMPC Write to Offset %02x with Data %02x\n", offset, data); if(offset >= 1 && offset <= 0xd) m_smpc.IREG[offset >> 1] = data; if(offset == 1) //IREG0, check if a BREAK / CONTINUE request for INTBACK command { if(m_smpc.intback_stage) { if(data & 0x40) { if(LOG_PAD_CMD) printf("SMPC: BREAK request\n"); m_smpc.SR &= 0x0f; m_smpc.intback_stage = 0; } else if(data & 0x80) { if(LOG_PAD_CMD) printf("SMPC: CONTINUE request\n"); machine().scheduler().timer_set(attotime::from_usec(700), timer_expired_delegate(FUNC(saturn_state::stv_intback_peripheral),this),0); /* TODO: is timing correct? */ m_smpc.OREG[31] = 0x10; m_smpc.SF = 0x01; //TODO: set hand-shake flag? } } } if (offset == 0x1f) // COMREG { smpc_comreg_exec(space,data,1); // we've processed the command, clear status flag if(data != 0x10 && data != 0x02 && data != 0x03 && data != 0x08 && data != 0x09 && data != 0xe && data != 0xf && data != 0x19 && data != 0x1a) { m_smpc.OREG[31] = data; //read-back command m_smpc.SF = 0x00; } /*TODO:emulate the timing of each command...*/ } if(offset == 0x63) m_smpc.SF = data & 1; if(offset == 0x75) { /* -xx- ---- PDR1 ---x ---- EEPROM write bit ---- x--- EEPROM CLOCK line ---- -x-- EEPROM CS line ---- --xx A-Bus bank bits */ m_eeprom->clk_write((data & 0x08) ? ASSERT_LINE : CLEAR_LINE); m_eeprom->di_write((data >> 4) & 1); m_eeprom->cs_write((data & 0x04) ? ASSERT_LINE : CLEAR_LINE); m_stv_multi_bank = data & 3; stv_select_game(m_stv_multi_bank); m_smpc.PDR1 = (data & 0x60); } if(offset == 0x77) { /* -xx- ---- PDR2 ---x ---- Enable Sound System (ACTIVE LOW) */ //popmessage("PDR2 = %02x",m_smpc_ram[0x77]); if(LOG_SMPC) printf("SMPC: M68k %s\n",(data & 0x10) ? "off" : "on"); //machine().scheduler().timer_set(attotime::from_usec(100), timer_expired_delegate(FUNC(saturn_state::smpc_sound_enable),this),(m_smpc_ram[0x77] & 0x10) >> 4); m_audiocpu->set_input_line(INPUT_LINE_RESET, (data & 0x10) ? ASSERT_LINE : CLEAR_LINE); m_en_68k = ((data & 0x10) >> 4) ^ 1; //if(LOG_SMPC) printf("SMPC: ram [0x77] = %02x\n",data); m_smpc.PDR2 = (data & 0x60); } if(offset == 0x7d) { /* ---- --x- IOSEL2 direct (1) / control mode (0) port select ---- ---x IOSEL1 direct (1) / control mode (0) port select */ m_smpc.IOSEL1 = (data & 1) >> 0; m_smpc.IOSEL2 = (data & 2) >> 1; } if(offset == 0x7f) { //enable PAD irq & VDP2 external latch for port 1/2 m_smpc.EXLE1 = (data & 1) >> 0; m_smpc.EXLE2 = (data & 2) >> 1; } } /******************************************** * * Saturn handlers * *******************************************/ UINT8 saturn_state::smpc_th_control_mode(UINT8 pad_n) { UINT8 res = 0; int th = (pad_n == 0) ? ((m_smpc.PDR1 >> 5) & 3) : ((m_smpc.PDR2 >> 5) & 3); UINT16 ctrl_read = 0; if (m_ctrl1 && pad_n == 0) ctrl_read = m_ctrl1->read_direct(); if (m_ctrl2 && pad_n == 1) ctrl_read = m_ctrl2->read_direct(); if (LOG_SMPC) printf("SMPC: SH-2 TH control mode, returning pad data %d for phase %d\n", pad_n + 1, th); switch (th) { /* TODO: 3D Lemmings bogusly enables TH Control mode, wants this to return the ID, needs HW tests. */ case 3: res = th << 6; res |= 0x14; res |= (ctrl_read & 8); // L break; case 2: res = th << 6; // 1 C B Right Left Down Up // WHP actually has a very specific code at 0x6015f30, doesn't like bits 0-1 active here ... res|= ((ctrl_read >> 4) & 0x30); // C & B res|= ((ctrl_read >> 12) & 0xc); break; case 1: res = th << 6; res |= 0x10; res |= ((ctrl_read >> 4) & 0xf); // R, X, Y, Z break; case 0: res = th << 6; // 0 Start A 0 0 Down Up res |= ((ctrl_read >> 6) & 0x30); // Start & A res |= ((ctrl_read >> 12) & 0x03); // ... and it actually wants bits 2 - 3 active here. res |= 0xc; break; } return res; } UINT8 saturn_state::smpc_direct_mode(UINT8 pad_n) { int hshake = (pad_n == 0) ? ((m_smpc.PDR1 >> 5) & 3) : ((m_smpc.PDR2 >> 5) & 3); const int shift_bit[4] = { 4, 12, 8, 0 }; UINT16 ctrl_read = 0; if (m_ctrl1 && pad_n == 0) ctrl_read = m_ctrl1->read_direct(); if (m_ctrl2 && pad_n == 1) ctrl_read = m_ctrl2->read_direct(); if (LOG_SMPC) logerror("SMPC: SH-2 direct mode, returning data for phase %d\n", hshake); return 0x80 | 0x10 | ((ctrl_read >> shift_bit[hshake]) & 0xf); } READ8_MEMBER( saturn_state::saturn_SMPC_r ) { UINT8 return_data = 0; if (!(offset & 1)) // avoid reading to even bytes (TODO: is it 0s or 1s?) return 0x00; if(offset >= 0x21 && offset <= 0x5f) return_data = m_smpc.OREG[(offset-0x21) >> 1]; if (offset == 0x61) return_data = m_smpc.SR; if (offset == 0x63) { //printf("SF %d %d\n",machine().first_screen()->hpos(),machine().first_screen()->vpos()); return_data = m_smpc.SF; } if (offset == 0x75 || offset == 0x77)//PDR1/2 read { if ((m_smpc.IOSEL1 && offset == 0x75) || (m_smpc.IOSEL2 && offset == 0x77)) { UINT8 cur_ddr; if (((m_ctrl1 && m_ctrl1->read_id(0) != 0x02) || (m_ctrl2 && m_ctrl2->read_id(0) != 0x02)) && !(space.debugger_access())) { popmessage("Warning: read with SH-2 direct mode with a non-pad device"); return 0; } cur_ddr = (offset == 0x75) ? m_smpc.DDR1 : m_smpc.DDR2; switch(cur_ddr & 0x60) { case 0x00: break; // in diag test case 0x40: return_data = smpc_th_control_mode(offset == 0x77); break; case 0x60: return_data = smpc_direct_mode(offset == 0x77); break; default: popmessage("SMPC: unemulated control method %02x, contact MAMEdev",cur_ddr & 0x60); return_data = 0; break; } } } if (LOG_SMPC) logerror ("cpu %s (PC=%08X) SMPC: Read from Byte Offset %02x (%d) Returns %02x\n", space.device().tag(), space.device().safe_pc(), offset, offset>>1, return_data); return return_data; } WRITE8_MEMBER( saturn_state::saturn_SMPC_w ) { if (LOG_SMPC) logerror ("8-bit SMPC Write to Offset %02x (reg %d) with Data %02x\n", offset, offset>>1, data); if (!(offset & 1)) // avoid writing to even bytes return; if(offset >= 1 && offset <= 0xd) m_smpc.IREG[offset >> 1] = data; if(offset == 1) //IREG0, check if a BREAK / CONTINUE request for INTBACK command { if(m_smpc.intback_stage) { if(data & 0x40) { if(LOG_PAD_CMD) printf("SMPC: BREAK request %02x\n",data); m_smpc.SR &= 0x0f; m_smpc.intback_stage = 0; } else if(data & 0x80) { if(LOG_PAD_CMD) printf("SMPC: CONTINUE request %02x\n",data); machine().scheduler().timer_set(attotime::from_usec(700), timer_expired_delegate(FUNC(saturn_state::intback_peripheral),this),0); /* TODO: is timing correct? */ m_smpc.OREG[31] = 0x10; m_smpc.SF = 0x01; //TODO: set hand-shake flag? } } } if (offset == 0x1f) { smpc_comreg_exec(space,data,0); // we've processed the command, clear status flag if(data != 0x10 && data != 2 && data != 3 && data != 6 && data != 7 && data != 0x08 && data != 0x09 && data != 0x0e && data != 0x0f && data != 0x19 && data != 0x1a) { m_smpc.OREG[31] = data; //read-back for last command issued m_smpc.SF = 0x00; //clear hand-shake flag } /*TODO:emulate the timing of each command...*/ } if (offset == 0x63) m_smpc.SF = data & 1; // hand-shake flag if(offset == 0x75) // PDR1 m_smpc.PDR1 = data & 0x7f; if(offset == 0x77) // PDR2 m_smpc.PDR2 = data & 0x7f; if(offset == 0x79) m_smpc.DDR1 = data & 0x7f; if(offset == 0x7b) m_smpc.DDR2 = data & 0x7f; if(offset == 0x7d) { m_smpc.IOSEL1 = data & 1; m_smpc.IOSEL2 = (data & 2) >> 1; } if(offset == 0x7f) { //enable PAD irq & VDP2 external latch for port 1/2 m_smpc.EXLE1 = (data & 1) >> 0; m_smpc.EXLE2 = (data & 2) >> 1; } }