// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** Bally/Sente SAC-1 system driver by Aaron Giles ***************************************************************************/ #include "emu.h" #include "cpu/m6809/m6809.h" #include "includes/balsente.h" /************************************* * * Interrupt handling * *************************************/ TIMER_CALLBACK_MEMBER(balsente_state::irq_off) { m_maincpu->set_input_line(M6809_IRQ_LINE, CLEAR_LINE); } TIMER_DEVICE_CALLBACK_MEMBER(balsente_state::interrupt_timer) { /* next interrupt after scanline 256 is scanline 64 */ if (param == 256) m_scanline_timer->adjust(m_screen->time_until_pos(64), 64); else m_scanline_timer->adjust(m_screen->time_until_pos(param + 64), param + 64); /* IRQ starts on scanline 0, 64, 128, etc. */ m_maincpu->set_input_line(M6809_IRQ_LINE, ASSERT_LINE); /* it will turn off on the next HBLANK */ machine().scheduler().timer_set(m_screen->time_until_pos(param, BALSENTE_HBSTART), timer_expired_delegate(FUNC(balsente_state::irq_off),this)); /* if this is Grudge Match, update the steering */ if (m_grudge_steering_result & 0x80) update_grudge_steering(); /* if we're a shooter, we do a little more work */ if (m_shooter) { uint8_t tempx, tempy; /* we latch the beam values on the first interrupt after VBLANK */ if (param == 64) { m_shooter_x = ioport("FAKEX")->read(); m_shooter_y = ioport("FAKEY")->read(); } /* which bits get returned depends on which scanline we're at */ tempx = m_shooter_x << ((param - 64) / 64); tempy = m_shooter_y << ((param - 64) / 64); m_nstocker_bits = ((tempx >> 4) & 0x08) | ((tempx >> 1) & 0x04) | ((tempy >> 6) & 0x02) | ((tempy >> 3) & 0x01); } } void balsente_state::machine_start() { if (m_acia.found()) { m_acia->write_cts(0); m_acia->write_dcd(0); } save_item(NAME(m_nstocker_bits)); save_item(NAME(m_spiker_expand_color)); save_item(NAME(m_spiker_expand_bgcolor)); save_item(NAME(m_spiker_expand_bits)); save_item(NAME(m_grudge_steering_result)); save_item(NAME(m_grudge_last_steering)); } void balsente_state::machine_reset() { /* create the polynomial tables */ poly17_init(); /* reset the ADC states */ m_adc_value = 0; /* reset game-specific states */ m_grudge_steering_result = 0; /* point the banks to bank 0 */ m_bankab->set_entry(0); m_bankcd->set_entry(0); m_bankef->set_entry(0); m_maincpu->reset(); /* start a timer to generate interrupts */ m_scanline_timer->adjust(m_screen->time_until_pos(0)); } /************************************* * * Hardware random numbers * * NOTE: this is stolen straight from * POKEY.c * *************************************/ void balsente_state::poly17_init() { uint32_t i, x = 0; uint8_t *r; /* allocate memory */ r = m_rand17; /* generate the polynomial */ for (i = 0; i < POLY17_SIZE; i++) { /* store new values */ *r++ = x >> 3; /* calculate next bit */ x = ((x << POLY17_SHL) + (x >> POLY17_SHR) + POLY17_ADD) & POLY17_SIZE; } } void balsente_state::random_reset_w(uint8_t data) { /* reset random number generator */ } uint8_t balsente_state::random_num_r() { /* CPU runs at 1.25MHz, noise source at 100kHz --> multiply by 12.5 */ uint32_t cc = m_maincpu->total_cycles(); /* 12.5 = 8 + 4 + 0.5 */ cc = (cc << 3) + (cc << 2) + (cc >> 1); return m_rand17[cc & POLY17_SIZE]; } /************************************* * * ROM banking * *************************************/ void balsente_state::rombank_select_w(uint8_t data) { /* the bank number comes from bits 4-6 */ m_bankab->set_entry((data >> 4) & 7); m_bankcd->set_entry((data >> 4) & 7); m_bankef->set_entry(0); } void balsente_state::rombank2_select_w(uint8_t data) { /* Night Stocker and Name that Tune only so far.... */ int bank = data & 7; /* top bit controls which half of the ROMs to use (Name that Tune only) */ if (memregion("maincpu")->bytes() > 0x20000) bank |= (data >> 4) & 8; /* when they set the AB bank, it appears as though the CD bank is reset */ if (data & 0x20) { m_bankab->set_entry(bank); m_bankcd->set_entry(6); m_bankef->set_entry(0); } /* set both banks */ else { m_bankab->set_entry(bank); m_bankcd->set_entry(bank); m_bankef->set_entry(BIT(bank, 3)); } } /************************************* * * Special outputs * *************************************/ WRITE_LINE_MEMBER(balsente_state::out0_w) { // output().set_led_value(0, state); } WRITE_LINE_MEMBER(balsente_state::out1_w) { // output().set_led_value(1, state); } WRITE_LINE_MEMBER(balsente_state::out2_w) { // output().set_led_value(2, state); } WRITE_LINE_MEMBER(balsente_state::out3_w) { // output().set_led_value(3, state); } WRITE_LINE_MEMBER(balsente_state::out4_w) { // output().set_led_value(4, state); } WRITE_LINE_MEMBER(balsente_state::out5_w) { // output().set_led_value(5, state); } WRITE_LINE_MEMBER(balsente_state::out6_w) { // output().set_led_value(6, state); } WRITE_LINE_MEMBER(balsente_state::nvrecall_w) { m_novram[0]->recall(!state); m_novram[1]->recall(!state); } uint8_t balsente_state::novram_8bit_r(address_space &space, offs_t offset) { return (m_novram[0]->read(space, offset) & 0x0f) | (m_novram[1]->read(space, offset) << 4); } void balsente_state::novram_8bit_w(offs_t offset, uint8_t data) { m_novram[0]->write(offset, data & 0x0f); m_novram[1]->write(offset, data >> 4); } /************************************* * * ADC handlers * *************************************/ INTERRUPT_GEN_MEMBER(balsente_state::update_analog_inputs) { int i; static const char *const analog[] = { "AN0", "AN1", "AN2", "AN3" }; /* the analog input system helpfully scales the value read by the percentage of time */ /* into the current frame we are; unfortunately, this is bad for us, since the analog */ /* ports are read once a frame, just at varying intervals. To get around this, we */ /* read all the analog inputs at VBLANK time and just return the cached values. */ for (i = 0; i < 4; i++) m_analog_input_data[i] = ioport(analog[i])->read(); } TIMER_CALLBACK_MEMBER(balsente_state::adc_finished) { int which = param; /* analog controls are read in two pieces; the lower port returns the sign */ /* and the upper port returns the absolute value of the magnitude */ int val = m_analog_input_data[which / 2] << m_adc_shift; /* special case for Stompin'/Shrike Avenger */ if (m_adc_shift == 32) { m_adc_value = m_analog_input_data[which]; return; } /* push everything out a little bit extra; most games seem to have a dead */ /* zone in the middle that feels unnatural with the mouse */ if (val < 0) val -= 8; else if (val > 0) val += 8; /* clip to 0xff maximum magnitude */ if (val < -0xff) val = -0xff; else if (val > 0xff) val = 0xff; /* return the sign */ if (!(which & 1)) m_adc_value = (val < 0) ? 0xff : 0x00; /* return the magnitude */ else m_adc_value = (val < 0) ? -val : val; } uint8_t balsente_state::adc_data_r() { /* just return the last value read */ return m_adc_value; } void balsente_state::adc_select_w(offs_t offset, uint8_t data) { /* set a timer to go off and read the value after 50us */ /* it's important that we do this for Mini Golf */ logerror("adc_select %d\n", offset & 7); machine().scheduler().timer_set(attotime::from_usec(50), timer_expired_delegate(FUNC(balsente_state::adc_finished),this), offset & 7); } uint8_t balsente_state::teamht_extra_r() { return m_teamht_input; } void balsente_state::teamht_multiplex_select_w(offs_t offset, uint8_t data) { logerror("multiplex_select %d\n", offset & 7); switch (offset & 7) { case 0x04: m_teamht_input = ioport("EX0")->read(); break; case 0x05: m_teamht_input = ioport("EX1")->read(); break; case 0x06: m_teamht_input = ioport("EX2")->read(); break; case 0x07: m_teamht_input = ioport("EX3")->read(); break; default: logerror("(unhandled)\n"); break; } } /************************************* * * Game-specific handlers * *************************************/ CUSTOM_INPUT_MEMBER(balsente_state::nstocker_bits_r) { return m_nstocker_bits; } void balsente_state::spiker_expand_w(offs_t offset, uint8_t data) { /* offset 0 is the bit pattern */ if (offset == 0) m_spiker_expand_bits = data; /* offset 1 is the background color (cleared on each read) */ else if (offset == 1) m_spiker_expand_bgcolor = data; /* offset 2 is the color */ else if (offset == 2) m_spiker_expand_color = data; } uint8_t balsente_state::spiker_expand_r() { uint8_t left, right; /* first rotate each nibble */ m_spiker_expand_bits = ((m_spiker_expand_bits << 1) & 0xee) | ((m_spiker_expand_bits >> 3) & 0x11); /* compute left and right pixels */ left = (m_spiker_expand_bits & 0x10) ? m_spiker_expand_color : m_spiker_expand_bgcolor; right = (m_spiker_expand_bits & 0x01) ? m_spiker_expand_color : m_spiker_expand_bgcolor; /* reset the background color */ m_spiker_expand_bgcolor = 0; /* return the combined result */ return (left & 0xf0) | (right & 0x0f); } void balsente_state::update_grudge_steering() { uint8_t wheel[3]; int8_t diff[3]; /* read the current steering values */ wheel[0] = ioport("AN0")->read(); wheel[1] = ioport("AN1")->read(); wheel[2] = ioport("AN2")->read(); /* diff the values */ diff[0] = wheel[0] - m_grudge_last_steering[0]; diff[1] = wheel[1] - m_grudge_last_steering[1]; diff[2] = wheel[2] - m_grudge_last_steering[2]; /* update the last values */ m_grudge_last_steering[0] += diff[0]; m_grudge_last_steering[1] += diff[1]; m_grudge_last_steering[2] += diff[2]; /* compute the result */ m_grudge_steering_result = 0xff; if (diff[0]) { m_grudge_steering_result ^= 0x01; if (diff[0] > 0) m_grudge_steering_result ^= 0x02; } if (diff[1]) { m_grudge_steering_result ^= 0x04; if (diff[1] > 0) m_grudge_steering_result ^= 0x08; } if (diff[2]) { m_grudge_steering_result ^= 0x10; if (diff[2] > 0) m_grudge_steering_result ^= 0x20; } logerror("Recomputed steering\n"); } uint8_t balsente_state::grudge_steering_r() { logerror("%s:grudge_steering_r(@%d)\n", machine().describe_context(), m_screen->vpos()); m_grudge_steering_result |= 0x80; return m_grudge_steering_result; } /************************************* * * Shrike Avenger CPU memory handlers * *************************************/ uint8_t balsente_state::shrike_shared_6809_r(offs_t offset) { uint16_t mem_mask_int = offset & 1 ? 0xff00 : 0x00ff; switch( offset ) { case 6: // return OK for 68k status register until motors hooked up return 0; default: return ( m_shrike_shared[offset >> 1] & ~mem_mask_int ) >> ( mem_mask_int & 8 ); } } void balsente_state::shrike_shared_6809_w(offs_t offset, uint8_t data) { uint16_t mem_mask_int = offset & 1 ? 0xff00 : 0x00ff; m_shrike_shared[offset >> 1] = ( m_shrike_shared[offset >> 1] & mem_mask_int ) | ( data << ( mem_mask_int & 0x8 ) ); } // uses movep, so writes even 8 bit addresses to odd 16 bit addresses, reads as 16 bit from odd addresses // i.e. write 0xdeadbeef to 10000, read 0xde from 10001, 0xad from 10003, 0xbe from 10005... void balsente_state::shrike_io_68k_w(offs_t offset, uint16_t data, uint16_t mem_mask) { COMBINE_DATA( &m_shrike_io[offset] ); } uint16_t balsente_state::shrike_io_68k_r(offs_t offset, uint16_t mem_mask) { return ( m_shrike_io[offset] & mem_mask ) >> ( 8 & ~mem_mask ); }