// license:BSD-3-Clause // copyright-holders:Angelo Salese, Alex Marshall /****************************************************************************** Jaleco MS32 System Control Unit A simple system controller for late 90s Jaleco HWs Known features: - CRTC & screen(s?) control; - dot clock control; - irq/reset controller; - programmable timer; - watchdog; First use in MS32, then their later (?) 68k revision. TODO: - pinpoint exact timing generation for programmable irq (free counter or based on host screen beams) - interface with multiple screens is a mystery, cfr. dual screen bnstars, stepping stage HW. Most likely former effectively controls both screens in demux while latter has no way to set the other screen(s)? - watchdog timing; - upper address line seems unconnected by 68k, and is it a mystery how watchdog is supposed to route here and assuming it is and not actually disabled by pin. - network/COPROs irq connections, specifically for f1superb; - actual chip name; BTANBs: - in p47aces v1.0 (p47acesa) code messes up the prg irq timer setup, causing SFX overloads by using Spitfire ship with 30 Hz autofire and shooting at point blank range over walls/enemies. This has been fixed in v1.1 *******************************************************************************/ #include "emu.h" #include "jaleco_ms32_sysctrl.h" //***************************************************************************** // GLOBAL VARIABLES //***************************************************************************** // device type definition DEFINE_DEVICE_TYPE(JALECO_MS32_SYSCTRL, jaleco_ms32_sysctrl_device, "jaleco_ms32_sysctrl", "Jaleco MS32 System Control Unit") //***************************************************************************** // LIVE DEVICE //***************************************************************************** //------------------------------------------------- // jaleco_ms32_sysctrl_device - constructor //------------------------------------------------- jaleco_ms32_sysctrl_device::jaleco_ms32_sysctrl_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, JALECO_MS32_SYSCTRL, tag, owner, clock) , m_screen(*this, finder_base::DUMMY_TAG) , m_flip_screen_cb(*this) , m_vblank_cb(*this) , m_field_cb(*this) , m_prg_timer_cb(*this) , m_sound_ack_cb(*this) , m_sound_reset_cb(*this) , m_invert_vblank_lines(false) { } void jaleco_ms32_sysctrl_device::amap(address_map& map) { // 0xba0000 in 68k, 0xfce00000 in MS32 mapped at lower 16-bits mask map(0x00, 0x01).w(FUNC(jaleco_ms32_sysctrl_device::control_w)); map(0x02, 0x03).w(FUNC(jaleco_ms32_sysctrl_device::hblank_w)); map(0x04, 0x05).w(FUNC(jaleco_ms32_sysctrl_device::hdisplay_w)); map(0x06, 0x07).w(FUNC(jaleco_ms32_sysctrl_device::hbp_w)); map(0x08, 0x09).w(FUNC(jaleco_ms32_sysctrl_device::hfp_w)); map(0x0a, 0x0b).w(FUNC(jaleco_ms32_sysctrl_device::vblank_w)); map(0x0c, 0x0d).w(FUNC(jaleco_ms32_sysctrl_device::vdisplay_w)); map(0x0e, 0x0f).w(FUNC(jaleco_ms32_sysctrl_device::vbp_w)); map(0x10, 0x11).w(FUNC(jaleco_ms32_sysctrl_device::vfp_w)); map(0x18, 0x19).w(FUNC(jaleco_ms32_sysctrl_device::timer_interval_w)); map(0x1a, 0x1b).w(FUNC(jaleco_ms32_sysctrl_device::timer_ack_w)); map(0x1c, 0x1d).w(FUNC(jaleco_ms32_sysctrl_device::sound_reset_w)); map(0x1e, 0x1f).w(FUNC(jaleco_ms32_sysctrl_device::irq_ack_w)); // map(0x24, 0x27).w // sound comms bidirectional acks? map(0x26, 0x27).w(FUNC(jaleco_ms32_sysctrl_device::sound_ack_w)); map(0x28, 0x29).nopw(); // watchdog on MS32 map(0x2c, 0x2d).w(FUNC(jaleco_ms32_sysctrl_device::field_ack_w)); map(0x2e, 0x2f).w(FUNC(jaleco_ms32_sysctrl_device::vblank_ack_w)); } //------------------------------------------------- // device_add_mconfig - device-specific machine // configuration additions //------------------------------------------------- void jaleco_ms32_sysctrl_device::device_add_mconfig(machine_config &config) { // TIMER(config, "scantimer").configure_scanline(FUNC(jaleco_ms32_sysctrl_device::scanline_cb), m_screen, 0, 1); // TODO: watchdog } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void jaleco_ms32_sysctrl_device::device_resolve_objects() { m_flip_screen_cb.resolve(); m_vblank_cb.resolve(); m_field_cb.resolve(); m_prg_timer_cb.resolve(); m_sound_reset_cb.resolve(); m_sound_ack_cb.resolve(); } void jaleco_ms32_sysctrl_device::device_start() { save_item(NAME(m_dotclock_setting)); save_item(NAME(m_crtc.horz_blank)); save_item(NAME(m_crtc.horz_display)); save_item(NAME(m_crtc.vert_blank)); save_item(NAME(m_crtc.vert_display)); save_item(NAME(m_flip_screen_state)); save_item(NAME(m_timer.irq_enable)); save_item(NAME(m_timer.interval)); m_timer.prg_irq = timer_alloc(PRG_TIMER); m_timer_scanline = timer_alloc(SCANLINE_TIMER); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void jaleco_ms32_sysctrl_device::device_reset() { m_dotclock_setting = 0; m_crtc.horz_blank = 64; m_crtc.horz_display = 320; m_crtc.vert_blank = 39; m_crtc.vert_display = 224; m_timer.irq_enable = false; m_timer.interval = 1; flush_prg_timer(); // bnstars1 never ever set up the CRTC at boot, causing no irq. // we currently compensate by basically giving one frame of time, // ofc on the real thing the first vblank is really when screen sync occurs. flush_scanline_timer(m_crtc.vert_display-1); // m_timer_scanline->adjust(attotime::never); // put flipping in a default state m_flip_screen_state = false; m_flip_screen_cb(0); } void jaleco_ms32_sysctrl_device::flush_prg_timer() { attotime step; if (m_timer.irq_enable) { //const u16 htotal = m_crtc.horz_blank + m_crtc.horz_display; // TODO: unknown actual timings, with interval = 2 it should fire an irq every 16 scanlines in p47aces v1.1 // (excluding times where it is disabled) -> ~1000 Hz? step = attotime::from_nsec(500000) * m_timer.interval; } else { step = attotime::never; m_prg_timer_cb(0); } m_timer.prg_irq->adjust(step); } void jaleco_ms32_sysctrl_device::flush_scanline_timer(int current_scanline) { // in typical Jaleco fashion (cfr. mega system 1), both irqs are somehow configurable (a pin?). // Examples are tp2ms32 and wpksocv2, wanting vblank as vector 9 and field as 10 otherwise they runs // at half speed, but then their config can't possibly work with p47aces (i.e. wants 10 and 9 respectively), // plus bnstars that locks up off the bat if the wrong irq runs at 60 Hz. // We currently hardwire via an init time setter here, making the irq acks to trigger properly as well. if (current_scanline == m_crtc.vert_display) m_invert_vblank_lines ? m_field_cb(1) : m_vblank_cb(1); // 30 Hz irq // TODO: unknown mechanics where this happens, is it even tied to scanline? if (current_scanline == 0 && m_screen->frame_number() & 1) m_invert_vblank_lines ? m_vblank_cb(1) : m_field_cb(1); uint32_t next_scanline = (current_scanline + 1) % crtc_vtotal(); m_timer_scanline->adjust(m_screen->time_until_pos(next_scanline), next_scanline); } void jaleco_ms32_sysctrl_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { case SCANLINE_TIMER: flush_scanline_timer(param); break; case PRG_TIMER: m_prg_timer_cb(1); flush_prg_timer(); break; } } //***************************************************************************** // READ/WRITE HANDLERS //***************************************************************************** static constexpr u16 clamp_to_12bits_neg(u16 raw_data) { // each write has a 12 bit resolution, for both CRTC and timer interval // TODO: nndmseal: on POST it sets up a 0x1000 for vblank, possibly for screen disable? // TODO: rockn2: on POST it sets up a vertical size of 487, is it trying to setup an interlace setting? return 0x1000 - (raw_data & 0xfff); } // ============================================================================ // CRTC // ============================================================================ inline u32 jaleco_ms32_sysctrl_device::get_dotclock_frequency() { const u8 dot_divider = m_dotclock_setting & 1 ? 6 : 8; return clock() / dot_divider; } inline u16 jaleco_ms32_sysctrl_device::crtc_vtotal() { return m_crtc.vert_blank + m_crtc.vert_display; } inline void jaleco_ms32_sysctrl_device::crtc_refresh_screen_params() { rectangle visarea; const u16 htotal = m_crtc.horz_blank + m_crtc.horz_display; const u16 vtotal = crtc_vtotal(); const attoseconds_t refresh = HZ_TO_ATTOSECONDS(get_dotclock_frequency()) * htotal * vtotal; visarea.set(0, m_crtc.horz_display - 1, 0, m_crtc.vert_display - 1); logerror("%s: CRTC setup total: %d x %d display: %d x %d\n", this->tag(), htotal, vtotal, m_crtc.horz_display, m_crtc.vert_display); m_screen->configure(htotal, vtotal, visarea, refresh); m_timer_scanline->adjust(m_screen->time_until_pos(vtotal), vtotal); } void jaleco_ms32_sysctrl_device::control_w(u16 data) { /* * ---- x--- programmable irq timer enable * ---- -x-- used by f1superb, stepstag, bnstars * ---- --x- flip screen * ---- ---x dotclock select (1) 8 MHz (0) 6 MHz */ if (BIT(data, 0) != m_dotclock_setting) { m_dotclock_setting = BIT(data, 0); crtc_refresh_screen_params(); } const bool current_flip = bool(BIT(data, 1)); if (current_flip != m_flip_screen_state) { m_flip_screen_state = current_flip; m_flip_screen_cb(m_flip_screen_state ? ASSERT_LINE : CLEAR_LINE); } if (data & 0xf4) logerror("%s: enabled unknown bit in control_w %02x\n", this->tag(), data & 0xf4); m_timer.irq_enable = bool(BIT(data, 3)); flush_prg_timer(); } void jaleco_ms32_sysctrl_device::hblank_w(u16 data) { m_crtc.horz_blank = clamp_to_12bits_neg(data); crtc_refresh_screen_params(); } void jaleco_ms32_sysctrl_device::hdisplay_w(u16 data) { m_crtc.horz_display = clamp_to_12bits_neg(data); crtc_refresh_screen_params(); } void jaleco_ms32_sysctrl_device::hbp_w(u16 data) { logerror("%s: HSYNC back porch %d\n", this->tag(), 0x1000 - data); } void jaleco_ms32_sysctrl_device::hfp_w(u16 data) { logerror("%s: HSYNC front porch %d\n", this->tag(), 0x1000 - data); } void jaleco_ms32_sysctrl_device::vblank_w(u16 data) { m_crtc.vert_blank = clamp_to_12bits_neg(data); crtc_refresh_screen_params(); } void jaleco_ms32_sysctrl_device::vdisplay_w(u16 data) { m_crtc.vert_display = clamp_to_12bits_neg(data); crtc_refresh_screen_params(); } void jaleco_ms32_sysctrl_device::vbp_w(u16 data) { logerror("%s: VSYNC back porch %d\n", this->tag(), clamp_to_12bits_neg(data)); } void jaleco_ms32_sysctrl_device::vfp_w(u16 data) { logerror("%s: VSYNC front porch %d\n", this->tag(), clamp_to_12bits_neg(data)); } // ============================================================================ // Timer // ============================================================================ void jaleco_ms32_sysctrl_device::timer_interval_w(u16 data) { m_timer.interval = clamp_to_12bits_neg(data); flush_prg_timer(); } void jaleco_ms32_sysctrl_device::timer_ack_w(u16 data) { m_prg_timer_cb(0); } // ============================================================================ // ACK lines // ============================================================================ void jaleco_ms32_sysctrl_device::sound_reset_w(u16 data) { m_sound_reset_cb(data & 1); logerror("%s: sound_reset_w %02x\n", this->tag(), data); } void jaleco_ms32_sysctrl_device::vblank_ack_w(u16 data) { m_vblank_cb(0); } void jaleco_ms32_sysctrl_device::field_ack_w(u16 data) { m_field_cb(0); } void jaleco_ms32_sysctrl_device::sound_ack_w(u16 data) { m_sound_ack_cb(1); } void jaleco_ms32_sysctrl_device::irq_ack_w(u16 data) { // guess: 68k games calls this in vblank routine instead of // the designated line, maybe it's a 68k version difference // or maybe this is right m_vblank_cb(0); m_field_cb(0); // TODO: f1superb definitely clears comms irq with this }