// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************
atarigen.c
General functions for Atari games.
***************************************************************************/
#include "emu.h"
#include "cpu/m6502/m6502.h"
#include "video/atarimo.h"
#include "atarigen.h"
/***************************************************************************
CONSTANTS
***************************************************************************/
#define SOUND_TIMER_RATE attotime::from_usec(5)
#define SOUND_TIMER_BOOST attotime::from_usec(1000)
/***************************************************************************
INLINE FUNCTIONS
***************************************************************************/
inline const atarigen_screen_timer *get_screen_timer(screen_device &screen)
{
atarigen_state *state = screen.machine().driver_data<atarigen_state>();
int i;
// find the index of the timer that matches the screen
for (i = 0; i < ARRAY_LENGTH(state->m_screen_timer); i++)
if (state->m_screen_timer[i].screen == &screen)
return &state->m_screen_timer[i];
fatalerror("Unexpected: no atarivc_eof_update_timer for screen '%s'\n", screen.tag());
return nullptr;
}
//**************************************************************************
// SOUND COMMUNICATIONS DEVICE
//**************************************************************************
// device type definition
const device_type ATARI_SOUND_COMM = &device_creator<atari_sound_comm_device>;
//-------------------------------------------------
// atari_sound_comm_device - constructor
//-------------------------------------------------
atari_sound_comm_device::atari_sound_comm_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, ATARI_SOUND_COMM, "Atari Sound Communications", tag, owner, clock, "atarscom", __FILE__),
m_sound_cpu_tag(nullptr),
m_main_int_cb(*this),
m_sound_cpu(nullptr),
m_main_to_sound_ready(false),
m_sound_to_main_ready(false),
m_main_to_sound_data(0),
m_sound_to_main_data(0),
m_timed_int(0),
m_ym2151_int(0)
{
}
//-------------------------------------------------
// static_set_sound_cpu: Set the tag of the
// sound CPU
//-------------------------------------------------
void atari_sound_comm_device::static_set_sound_cpu(device_t &device, const char *cputag)
{
downcast<atari_sound_comm_device &>(device).m_sound_cpu_tag = cputag;
}
//-------------------------------------------------
// device_start: Start up the device
//-------------------------------------------------
void atari_sound_comm_device::device_start()
{
// find the sound CPU
if (m_sound_cpu_tag == nullptr)
throw emu_fatalerror("No sound CPU specified!");
m_sound_cpu = siblingdevice<m6502_device>(m_sound_cpu_tag);
if (m_sound_cpu == nullptr)
throw emu_fatalerror("Sound CPU '%s' not found!", m_sound_cpu_tag);
// resolve callbacks
m_main_int_cb.resolve_safe();
// register for save states
save_item(NAME(m_main_to_sound_ready));
save_item(NAME(m_sound_to_main_ready));
save_item(NAME(m_main_to_sound_data));
save_item(NAME(m_sound_to_main_data));
save_item(NAME(m_timed_int));
save_item(NAME(m_ym2151_int));
}
//-------------------------------------------------
// device_reset: Handle a device reset by
// clearing the interrupt lines and states
//-------------------------------------------------
void atari_sound_comm_device::device_reset()
{
// reset the internal interrupts states
m_timed_int = m_ym2151_int = 0;
// reset the sound I/O states
m_main_to_sound_data = m_sound_to_main_data = 0;
m_main_to_sound_ready = m_sound_to_main_ready = false;
}
//-------------------------------------------------
// device_timer: Handle device-specific timer
// calbacks
//-------------------------------------------------
void atari_sound_comm_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch (id)
{
case TID_SOUND_RESET:
delayed_sound_reset(param);
break;
case TID_SOUND_WRITE:
delayed_sound_write(param);
break;
case TID_6502_WRITE:
delayed_6502_write(param);
break;
}
}
//-------------------------------------------------
// sound_irq_gen: Generates an IRQ signal to the
// 6502 sound processor.
//-------------------------------------------------
INTERRUPT_GEN_MEMBER(atari_sound_comm_device::sound_irq_gen)
{
m_timed_int = 1;
update_sound_irq();
}
//-------------------------------------------------
// sound_irq_ack_r: Resets the IRQ signal to the
// 6502 sound processor. Both reads and writes
// can be used.
//-------------------------------------------------
READ8_MEMBER(atari_sound_comm_device::sound_irq_ack_r)
{
m_timed_int = 0;
update_sound_irq();
return 0;
}
WRITE8_MEMBER(atari_sound_comm_device::sound_irq_ack_w)
{
m_timed_int = 0;
update_sound_irq();
}
//-------------------------------------------------
// atarigen_ym2151_irq_gen: Sets the state of the
// YM2151's IRQ line.
//-------------------------------------------------
WRITE_LINE_MEMBER(atari_sound_comm_device::ym2151_irq_gen)
{
m_ym2151_int = state;
update_sound_irq();
}
//-------------------------------------------------
// sound_reset_w: Write handler which resets the
// sound CPU in response.
//-------------------------------------------------
WRITE16_MEMBER(atari_sound_comm_device::sound_reset_w)
{
synchronize(TID_SOUND_RESET);
}
//-------------------------------------------------
// main_command_w: Handles communication from the main CPU
// to the sound CPU. Two versions are provided, one with the
// data byte in the low 8 bits, and one with the data byte in
// the upper 8 bits.
//-------------------------------------------------
WRITE8_MEMBER(atari_sound_comm_device::main_command_w)
{
synchronize(TID_SOUND_WRITE, data);
}
//-------------------------------------------------
// main_response_r: Handles reading data communicated from the
// sound CPU to the main CPU. Two versions are provided, one
// with the data byte in the low 8 bits, and one with the data
// byte in the upper 8 bits.
//-------------------------------------------------
READ8_MEMBER(atari_sound_comm_device::main_response_r)
{
m_sound_to_main_ready = false;
m_main_int_cb(CLEAR_LINE);
return m_sound_to_main_data;
}
//-------------------------------------------------
// sound_response_w: Handles communication from the
// sound CPU to the main CPU.
//-------------------------------------------------
WRITE8_MEMBER(atari_sound_comm_device::sound_response_w)
{
synchronize(TID_6502_WRITE, data);
}
//-------------------------------------------------
// sound_command_r: Handles reading data
// communicated from the main CPU to the sound
// CPU.
//-------------------------------------------------
READ8_MEMBER(atari_sound_comm_device::sound_command_r)
{
m_main_to_sound_ready = false;
m_sound_cpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE);
return m_main_to_sound_data;
}
//-------------------------------------------------
// update_sound_irq: Called whenever the IRQ state
// changes. An interrupt is generated if either
// sound_irq_gen() was called, or if the YM2151
// generated an interrupt via the
// ym2151_irq_gen() callback.
//-------------------------------------------------
void atari_sound_comm_device::update_sound_irq()
{
if (m_timed_int || m_ym2151_int)
m_sound_cpu->set_input_line(M6502_IRQ_LINE, ASSERT_LINE);
else
m_sound_cpu->set_input_line(M6502_IRQ_LINE, CLEAR_LINE);
}
//-------------------------------------------------
// delayed_sound_reset: Synchronizes the sound
// reset command between the two CPUs.
//-------------------------------------------------
void atari_sound_comm_device::delayed_sound_reset(int param)
{
// unhalt and reset the sound CPU
if (param == 0)
{
m_sound_cpu->set_input_line(INPUT_LINE_HALT, CLEAR_LINE);
m_sound_cpu->set_input_line(INPUT_LINE_RESET, PULSE_LINE);
}
// reset the sound write state
m_sound_to_main_ready = false;
m_main_int_cb(CLEAR_LINE);
// allocate a high frequency timer until a response is generated
// the main CPU is *very* sensistive to the timing of the response
machine().scheduler().boost_interleave(SOUND_TIMER_RATE, SOUND_TIMER_BOOST);
}
//-------------------------------------------------
// delayed_sound_write: Synchronizes a data write
// from the main CPU to the sound CPU.
//-------------------------------------------------
void atari_sound_comm_device::delayed_sound_write(int data)
{
// warn if we missed something
if (m_main_to_sound_ready)
logerror("Missed command from 68010\n");
// set up the states and signal an NMI to the sound CPU
m_main_to_sound_data = data;
m_main_to_sound_ready = true;
m_sound_cpu->set_input_line(INPUT_LINE_NMI, ASSERT_LINE);
// allocate a high frequency timer until a response is generated
// the main CPU is *very* sensistive to the timing of the response
machine().scheduler().boost_interleave(SOUND_TIMER_RATE, SOUND_TIMER_BOOST);
}
//-------------------------------------------------
// delayed_6502_write: Synchronizes a data write
// from the sound CPU to the main CPU.
//-------------------------------------------------
void atari_sound_comm_device::delayed_6502_write(int data)
{
// warn if we missed something
if (m_sound_to_main_ready)
logerror("Missed result from 6502\n");
// set up the states and signal the sound interrupt to the main CPU
m_sound_to_main_data = data;
m_sound_to_main_ready = true;
m_main_int_cb(ASSERT_LINE);
}
//**************************************************************************
// VAD VIDEO CONTROLLER DEVICE
//**************************************************************************
// device type definition
const device_type ATARI_VAD = &device_creator<atari_vad_device>;
//-------------------------------------------------
// atari_vad_device - constructor
//-------------------------------------------------
atari_vad_device::atari_vad_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, ATARI_VAD, "Atari VAD", tag, owner, clock, "atarivad", __FILE__),
device_video_interface(mconfig, *this),
m_scanline_int_cb(*this),
m_alpha_tilemap(*this, "alpha"),
m_playfield_tilemap(*this, "playfield"),
m_playfield2_tilemap(*this, "playfield2"),
m_mob(*this, "mob"),
m_eof_data(*this, "eof"),
m_scanline_int_timer(nullptr),
m_tilerow_update_timer(nullptr),
m_eof_timer(nullptr),
m_palette_bank(0),
m_pf0_xscroll_raw(0),
m_pf0_yscroll(0),
m_pf1_xscroll_raw(0),
m_pf1_yscroll(0),
m_mo_xscroll(0),
m_mo_yscroll(0)
{
}
//-------------------------------------------------
// control_write: Does the bulk of the word for an I/O
// write.
//-------------------------------------------------
WRITE16_MEMBER(atari_vad_device::control_write)
{
uint16_t newword = m_control[offset];
COMBINE_DATA(&newword);
internal_control_write(offset, newword);
}
//-------------------------------------------------
// control_read: Handles an I/O read from the video controller.
//-------------------------------------------------
READ16_MEMBER(atari_vad_device::control_read)
{
logerror("vc_r(%02X)\n", offset);
// a read from offset 0 returns the current scanline
// also sets bit 0x4000 if we're in VBLANK
if (offset == 0)
{
int result = m_screen->vpos();
if (result > 255)
result = 255;
if (result > m_screen->visible_area().max_y)
result |= 0x4000;
return result;
}
else
return m_control[offset];
}
//-------------------------------------------------
// alpha_w: Generic write handler for alpha RAM.
//-------------------------------------------------
WRITE16_MEMBER(atari_vad_device::alpha_w)
{
m_alpha_tilemap->write(space, offset, data, mem_mask);
}
//-------------------------------------------------
// playfield_upper_w: Generic write handler for
// upper word of split playfield RAM.
//-------------------------------------------------
WRITE16_MEMBER(atari_vad_device::playfield_upper_w)
{
m_playfield_tilemap->write_ext(space, offset, data, mem_mask);
if (m_playfield2_tilemap != nullptr)
m_playfield2_tilemap->write_ext(space, offset, data, mem_mask);
}
//-------------------------------------------------
// playfield_latched_lsb_w: Generic write handler for
// lower word of playfield RAM with a latch in the LSB of the
// upper word.
//-------------------------------------------------
WRITE16_MEMBER(atari_vad_device::playfield_latched_lsb_w)
{
m_playfield_tilemap->write(space, offset, data, mem_mask);
if ((m_control[0x0a] & 0x80) != 0)
m_playfield_tilemap->write_ext(space, offset, m_control[0x1d], uint16_t(0x00ff));
}
//-------------------------------------------------
// playfield_latched_msb_w: Generic write handler for
// lower word of playfield RAM with a latch in the MSB of the
// upper word.
//-------------------------------------------------
WRITE16_MEMBER(atari_vad_device::playfield_latched_msb_w)
{
m_playfield_tilemap->write(space, offset, data, mem_mask);
if ((m_control[0x0a] & 0x80) != 0)
m_playfield_tilemap->write_ext(space, offset, m_control[0x1c], uint16_t(0xff00));
}
//-------------------------------------------------
// playfield2_latched_msb_w: Generic write handler for
// lower word of second playfield RAM with a latch in the MSB
// of the upper word.
//-------------------------------------------------
WRITE16_MEMBER(atari_vad_device::playfield2_latched_msb_w)
{
m_playfield2_tilemap->write(space, offset, data, mem_mask);
if ((m_control[0x0a] & 0x80) != 0)
m_playfield2_tilemap->write_ext(space, offset, m_control[0x1c], uint16_t(0xff00));
}
//-------------------------------------------------
// device_start: Start up the device
//-------------------------------------------------
void atari_vad_device::device_start()
{
// verify configuration
if (m_playfield_tilemap == nullptr)
throw emu_fatalerror("Playfield tilemap not found!");
if (m_eof_data == nullptr)
throw emu_fatalerror("EOF data not found!");
// resolve callbacks
m_scanline_int_cb.resolve_safe();
// allocate timers
m_scanline_int_timer = timer_alloc(TID_SCANLINE_INT);
m_tilerow_update_timer = timer_alloc(TID_TILEROW_UPDATE);
m_eof_timer = timer_alloc(TID_EOF);
// register for save states
save_item(NAME(m_palette_bank)); // which palette bank is enabled
save_item(NAME(m_pf0_xscroll_raw)); // playfield 1 xscroll raw value
save_item(NAME(m_pf0_yscroll)); // playfield 1 yscroll
save_item(NAME(m_pf1_xscroll_raw)); // playfield 2 xscroll raw value
save_item(NAME(m_pf1_yscroll)); // playfield 2 yscroll
save_item(NAME(m_mo_xscroll)); // sprite xscroll
save_item(NAME(m_mo_yscroll)); // sprite xscroll
}
//-------------------------------------------------
// device_reset: Handle a device reset by
// clearing the interrupt lines and states
//-------------------------------------------------
void atari_vad_device::device_reset()
{
// share extended memory between the two tilemaps
if (m_playfield2_tilemap != nullptr)
m_playfield2_tilemap->extmem().set(m_playfield_tilemap->extmem());
// reset the state
m_palette_bank = 0;
m_pf0_xscroll_raw = m_pf1_xscroll_raw = 0;
m_pf0_yscroll = m_pf1_yscroll = 0;
m_mo_xscroll = m_mo_yscroll = 0;
memset(m_control, 0, sizeof(m_control));
// start the timers
m_tilerow_update_timer->adjust(m_screen->time_until_pos(0));
m_eof_timer->adjust(m_screen->time_until_pos(0));
}
//-------------------------------------------------
// device_timer: Handle device-specific timer
// calbacks
//-------------------------------------------------
void atari_vad_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch (id)
{
case TID_SCANLINE_INT:
m_scanline_int_cb(ASSERT_LINE);
break;
case TID_TILEROW_UPDATE:
update_tilerow(timer, param);
break;
case TID_EOF:
eof_update(timer);
break;
}
}
//-------------------------------------------------
// internal_control_write: Handle writes to the
// control registers and EOF updates
//-------------------------------------------------
void atari_vad_device::internal_control_write(offs_t offset, uint16_t newword)
{
// switch off the offset
uint16_t oldword = m_control[offset];
m_control[offset] = newword;
switch (offset)
{
//
// VAD register map:
//
// 00 = HDW_ENABLE
// A000 = enable other VAD params
// E000 = enable other VAD params but disable update from ALPHA RAM
// 01 = HDW_VSY_LD (standard = 0x795)
// 0FF8 = V_SY_S
// 0007 = V_SY_E
// 02 = HDW_VBL_LD (standard = 0xAEF)
// FE00 = VLAST
// 01FF = V_BL_S
// 03 = HDW_VINT
// 04 = HDW_HSY_LD (standard = 0x5EEF)
// FC00 = H_SY_E/2
// 03FF = H_SY_S
// 05 = HDW_HBL_LD (relief = 0x72AC, batman/thunderj = 0xBEB1, shuuz/offtwall = 0xCEB1)
// F000 = LB_CLR
// 0800 = H_BL_E/16
// 0400 = H_BL_E/1
// 03FF = H_BL_S
// 06 = HDW_SLIP_LD (relief/batman/shuuz = 0x6F05, shuuz/offtwall = 0x0F05)
// 7F00 = SLIP_S
// 0080 = SLIP_E_SE1
// 007F = SLIP_E
// 07 = HDW_APDMA_LD (relief/batman/shuuz = 0xBE84, shuuz = 0xB90E, offtwall = 0xB90D)
// C000 = OPFPIC
// 3800 = PF_HRESET/16
// 0780 = PFAT
// 0070 = AL_HRESET (depending on ALHSIZ)
// 000F = ALPHA_DMA
// 08 = HDW_PMBASE_LD (relief/batman/shuuz = 0x0519, shuuz/offtwall = 0x49B4)
// E000 = P1BASE/0x2000
// 1C00 = P2BASE/0x2000
// 0380 = PABASE/0x2000
// 0040 = PMASK
// 003E = MOBASE/0x800
// 0001 = MOMASK
// 09 = HDW_ALBASE_LD (relief/batman/shuuz = 0x061F, shuuz/offtwall = 0x017F)
// 0400 = OPMINUS1
// 03C0 = ALBASE/0x1000
// 003F = SLIPBASE/0x80
// 0A = HDW_OPT_LD (relief/batman/shuuz = 0x4410, shuuz = 0x0250, offtwall = 0x0A10)
// 8000 = OPFHSIZ - subtract 1 from PF2 attribute horizontal stamp address
// 4000 = OPIUM - playfield 2 enable
// 2000 = OLS_EN - linescroll enable
// 1000 = OSLPINK - split links enable
// 0800 = OSLORAM - slow MO DMA cycles
// 0400 = O_DCDMA - enable double time color RAM DMA cycles
// 0200 = O_DVDMA - enable double time video RAM DMA cycles
// 0100 = OADROEN - tristate address bus
// 0080 = OAS_EN - enable autostore of playfield attributes
// 0040 = OCRDWE - enable 8-bit color RAM
// 0020 = OVRDTACK - wait for VRAM DMA on writes
// 0010 = OCRDTACK - wait for CRAM DMA on writes
// 0008 = OALHSIZ - alpha horiz stamp size (0=8)
// 0004 = OPFHSIZ - PF horiz stamp size (0=8)
// 0002 = OPFVSIZ - PF vertical stamp size (0=8)
// 0001 = OMOVSIZ - MO vertical stamp size (0=8)
// 10 = HDW_MOCON (MOB chip MOB code)
// 11 = HDW_PCON (MOB chip PF code)
// 12 = HDW_GRCON (MOB chip GR code)
// 13 = MO_hscroll (9)
// 14 = PF1_hscroll (A)
// 15 = PF2_hscroll (B)
// 16 = MO_vscroll (D)
// 17 = PF1_vscroll (E)
// 18 = PF2_vscroll (F)
//
// 3efffe = reset to wrong configuration?
//
case 0:
//if (oldword != newword) printf("Word 0 = %04X\n", newword);
break;
// set the scanline interrupt here
case 0x03:
if (oldword != newword || !m_scanline_int_timer->enabled())
m_scanline_int_timer->adjust(m_screen->time_until_pos(newword & 0x1ff));
break;
// latch enable
case 0x0a:
// check for palette banking
if (m_palette_bank != (((newword & 0x0400) >> 10) ^ 1))
{
m_screen->update_partial(m_screen->vpos());
m_palette_bank = ((newword & 0x0400) >> 10) ^ 1;
}
//if ((oldword & ~0x0080) != (newword & ~0x0080)) printf("Latch control = %04X\n", newword);
break;
// indexed parameters
case 0x10: case 0x11: case 0x12: case 0x13:
case 0x14: case 0x15: case 0x16: case 0x17:
case 0x18: case 0x19: case 0x1a: case 0x1b:
update_parameter(newword);
break;
// scanline IRQ ack here
case 0x1e:
m_scanline_int_cb(CLEAR_LINE);
break;
// log anything else
default:
if (oldword != newword)
logerror("vc_w(%02X, %04X) ** [prev=%04X]\n", offset, newword, oldword);
break;
}
}
//-------------------------------------------------
// update_pf_xscrolls: Update the playfield
// scroll values.
//-------------------------------------------------
inline void atari_vad_device::update_pf_xscrolls()
{
m_playfield_tilemap->set_scrollx(0, m_pf0_xscroll_raw + ((m_pf1_xscroll_raw) & 7));
if (m_playfield2_tilemap != nullptr)
m_playfield2_tilemap->set_scrollx(0, m_pf1_xscroll_raw + 4);
}
//-------------------------------------------------
// update_parameter: Update parameters, shared
// between end-of-frame, tilerow updates, and
// direct control writes.
//-------------------------------------------------
void atari_vad_device::update_parameter(uint16_t newword)
{
switch (newword & 15)
{
case 9:
m_mo_xscroll = (newword >> 7) & 0x1ff;
if (m_mob != nullptr)
m_mob->set_xscroll(m_mo_xscroll);
break;
case 10:
m_pf1_xscroll_raw = (newword >> 7) & 0x1ff;
update_pf_xscrolls();
break;
case 11:
m_pf0_xscroll_raw = (newword >> 7) & 0x1ff;
update_pf_xscrolls();
break;
case 13:
m_mo_yscroll = (newword >> 7) & 0x1ff;
if (m_mob != nullptr)
m_mob->set_yscroll(m_mo_yscroll);
break;
case 14:
m_pf1_yscroll = (newword >> 7) & 0x1ff;
if (m_playfield2_tilemap != nullptr)
m_playfield2_tilemap->set_scrolly(0, m_pf1_yscroll);
break;
case 15:
m_pf0_yscroll = (newword >> 7) & 0x1ff;
m_playfield_tilemap->set_scrolly(0, m_pf0_yscroll);
break;
}
}
//-------------------------------------------------
// update_tilerow: Fetch parameters stored at
// the end of the current tilerow, which affect
// rowscrolling.
//-------------------------------------------------
void atari_vad_device::update_tilerow(emu_timer &timer, int scanline)
{
// skip if out of bounds, or not enabled
if (scanline <= m_screen->visible_area().max_y && (m_control[0x0a] & 0x2000) != 0 && m_alpha_tilemap != nullptr)
{
// iterate over non-visible alpha tiles in this row
int offset = scanline / 8 * 64 + 48 + 2 * (scanline % 8);
int data0 = m_alpha_tilemap->basemem_read(offset++);
int data1 = m_alpha_tilemap->basemem_read(offset++);
// force an update if we have data
if (scanline > 0 && ((data0 | data1) & 15) != 0)
m_screen->update_partial(scanline - 1);
// write the data
if ((data0 & 15) != 0)
update_parameter(data0);
if ((data1 & 15) != 0)
update_parameter(data1);
}
// update the timer to go off at the start of the next row
scanline += ((m_control[0x0a] & 0x2000) != 0) ? 1 : 8;
if (scanline >= m_screen->height())
scanline = 0;
timer.adjust(m_screen->time_until_pos(scanline), scanline);
}
//-------------------------------------------------
// eof_update: Callback that slurps up data and
// feeds it into the video controller registers
// every refresh.
//-------------------------------------------------
void atari_vad_device::eof_update(emu_timer &timer)
{
// echo all the commands to the video controller
for (int i = 0; i < 0x1c; i++)
if (m_eof_data[i] != 0)
internal_control_write(i, m_eof_data[i]);
// update the scroll positions
/* atarimo_set_xscroll(0, m_mo_xscroll);
atarimo_set_yscroll(0, m_mo_yscroll);
update_pf_xscrolls();
m_playfield_tilemap->set_scrolly(0, m_pf0_yscroll);
if (m_playfield2_tilemap != nullptr)
m_playfield2_tilemap->set_scrolly(0, m_pf1_yscroll);*/
timer.adjust(m_screen->time_until_pos(0));
// use this for debugging the video controller values
#if 0
if (machine().input().code_pressed(KEYCODE_8))
{
static FILE *out;
if (!out) out = fopen("scroll.log", "w");
if (out)
{
for (i = 0; i < 64; i++)
fprintf(out, "%04X ", data[i]);
fprintf(out, "\n");
}
}
#endif
}
//**************************************************************************
// EEPROM INTERFACE DEVICE
//**************************************************************************
// device type definition
const device_type ATARI_EEPROM_2804 = &device_creator<atari_eeprom_2804_device>;
const device_type ATARI_EEPROM_2816 = &device_creator<atari_eeprom_2816_device>;
//-------------------------------------------------
// atari_eeprom_device - constructor
//-------------------------------------------------
atari_eeprom_device::atari_eeprom_device(const machine_config &mconfig, device_type devtype, const char *name, const char *tag, device_t *owner, const char *shortname, const char *file)
: device_t(mconfig, devtype, name, tag, owner, 0, shortname, file),
m_eeprom(*this, "eeprom"),
m_unlocked(false)
{
}
//-------------------------------------------------
// unlock_read/unlock_write - unlock read/write
// handlers
//-------------------------------------------------
READ8_MEMBER(atari_eeprom_device::unlock_read) { m_unlocked = true; return space.unmap(); }
WRITE8_MEMBER(atari_eeprom_device::unlock_write) { m_unlocked = true; }
READ16_MEMBER(atari_eeprom_device::unlock_read) { m_unlocked = true; return space.unmap(); }
WRITE16_MEMBER(atari_eeprom_device::unlock_write) { m_unlocked = true; }
READ32_MEMBER(atari_eeprom_device::unlock_read) { m_unlocked = true; return space.unmap(); }
WRITE32_MEMBER(atari_eeprom_device::unlock_write) { m_unlocked = true; }
//-------------------------------------------------
// read/write - data read/write handlers
//-------------------------------------------------
READ8_MEMBER(atari_eeprom_device::read)
{
return m_eeprom->read(space, offset);
}
WRITE8_MEMBER(atari_eeprom_device::write)
{
if (m_unlocked)
m_eeprom->write(space, offset, data, mem_mask);
else
logerror("%s: Attemptedt to write to EEPROM while not unlocked\n", machine().describe_context());
m_unlocked = false;
}
//-------------------------------------------------
// device_start: Start up the device
//-------------------------------------------------
void atari_eeprom_device::device_start()
{
// register for save states
save_item(NAME(m_unlocked));
}
//-------------------------------------------------
// device_reset: Handle a device reset by
// clearing the interrupt lines and states
//-------------------------------------------------
void atari_eeprom_device::device_reset()
{
// reset unlocked state
m_unlocked = false;
}
//-------------------------------------------------
// atari_eeprom_2804_device - constructor
//-------------------------------------------------
atari_eeprom_2804_device::atari_eeprom_2804_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: atari_eeprom_device(mconfig, ATARI_EEPROM_2804, "Atari EEPROM Interface (2804)", tag, owner, "atari2804", __FILE__)
{
}
//-------------------------------------------------
// device_mconfig_additions - return machine
// config fragment
//-------------------------------------------------
MACHINE_CONFIG_FRAGMENT(atari_eeprom_2804_config)
MCFG_EEPROM_2804_ADD("eeprom")
MACHINE_CONFIG_END
machine_config_constructor atari_eeprom_2804_device::device_mconfig_additions() const
{
return MACHINE_CONFIG_NAME(atari_eeprom_2804_config);
}
//-------------------------------------------------
// atari_eeprom_2816_device - constructor
//-------------------------------------------------
atari_eeprom_2816_device::atari_eeprom_2816_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: atari_eeprom_device(mconfig, ATARI_EEPROM_2816, "Atari EEPROM Interface (2816)", tag, owner, "atari2816", __FILE__)
{
}
//-------------------------------------------------
// device_mconfig_additions - return machine
// config fragment
//-------------------------------------------------
MACHINE_CONFIG_FRAGMENT(atari_eeprom_2816_config)
MCFG_EEPROM_2816_ADD("eeprom")
MACHINE_CONFIG_END
machine_config_constructor atari_eeprom_2816_device::device_mconfig_additions() const
{
return MACHINE_CONFIG_NAME(atari_eeprom_2816_config);
}
/***************************************************************************
OVERALL INIT
***************************************************************************/
atarigen_state::atarigen_state(const machine_config &mconfig, device_type type, const char *tag)
: driver_device(mconfig, type, tag),
m_scanline_int_state(0),
m_sound_int_state(0),
m_video_int_state(0),
m_xscroll(*this, "xscroll"),
m_yscroll(*this, "yscroll"),
m_slapstic_num(0),
m_slapstic(nullptr),
m_slapstic_bank(0),
m_slapstic_last_pc(0),
m_slapstic_last_address(0),
m_slapstic_base(0),
m_slapstic_mirror(0),
m_scanlines_per_callback(0),
m_maincpu(*this, "maincpu"),
m_gfxdecode(*this, "gfxdecode"),
m_screen(*this, "screen"),
m_palette(*this, "palette"),
m_generic_paletteram_16(*this, "paletteram"),
m_slapstic_device(*this, ":slapstic")
{
}
void atarigen_state::machine_start()
{
// allocate timers for all screens
int i = 0;
for (screen_device &screen : screen_device_iterator(*this))
{
assert(i <= ARRAY_LENGTH(m_screen_timer));
m_screen_timer[i].screen = &screen;
m_screen_timer[i].scanline_interrupt_timer = timer_alloc(TID_SCANLINE_INTERRUPT, (void *)&screen);
m_screen_timer[i].scanline_timer = timer_alloc(TID_SCANLINE_TIMER, (void *)&screen);
i++;
}
save_item(NAME(m_scanline_int_state));
save_item(NAME(m_sound_int_state));
save_item(NAME(m_video_int_state));
save_item(NAME(m_slapstic_num));
save_item(NAME(m_slapstic_bank));
save_item(NAME(m_slapstic_last_pc));
save_item(NAME(m_slapstic_last_address));
save_item(NAME(m_scanlines_per_callback));
}
void atarigen_state::machine_reset()
{
// reset the interrupt states
m_video_int_state = m_sound_int_state = m_scanline_int_state = 0;
// reset the slapstic
if (m_slapstic_num != 0)
{
if (!m_slapstic_device.found())
fatalerror("Slapstic device is missing?\n");
m_slapstic_device->slapstic_reset();
slapstic_update_bank(m_slapstic_device->slapstic_bank());
}
}
void atarigen_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch (id)
{
case TID_SCANLINE_INTERRUPT:
{
scanline_int_gen(*m_maincpu);
screen_device *screen = reinterpret_cast<screen_device *>(ptr);
timer.adjust(screen->frame_period());
break;
}
case TID_SCANLINE_TIMER:
scanline_timer(timer, *reinterpret_cast<screen_device *>(ptr), param);
break;
// unhalt the CPU that was passed as a pointer
case TID_UNHALT_CPU:
reinterpret_cast<device_t *>(ptr)->execute().set_input_line(INPUT_LINE_HALT, CLEAR_LINE);
break;
}
}
void atarigen_state::scanline_update(screen_device &screen, int scanline)
{
}
/***************************************************************************
INTERRUPT HANDLING
***************************************************************************/
//-------------------------------------------------
// scanline_int_set: Sets the scanline when the next
// scanline interrupt should be generated.
//-------------------------------------------------
void atarigen_state::scanline_int_set(screen_device &screen, int scanline)
{
get_screen_timer(screen)->scanline_interrupt_timer->adjust(screen.time_until_pos(scanline));
}
//-------------------------------------------------
// sound_int_write_line: Standard write line
// callback for the scanline interrupt
//-------------------------------------------------
WRITE_LINE_MEMBER(atarigen_state::scanline_int_write_line)
{
m_scanline_int_state = state;
update_interrupts();
}
//-------------------------------------------------
// scanline_int_gen: Standard interrupt routine
// which sets the scanline interrupt state.
//-------------------------------------------------
INTERRUPT_GEN_MEMBER(atarigen_state::scanline_int_gen)
{
m_scanline_int_state = 1;
update_interrupts();
}
//-------------------------------------------------
// scanline_int_ack_w: Resets the state of the
// scanline interrupt.
//-------------------------------------------------
WRITE16_MEMBER(atarigen_state::scanline_int_ack_w)
{
m_scanline_int_state = 0;
update_interrupts();
}
//-------------------------------------------------
// sound_int_write_line: Standard write line
// callback for the sound interrupt
//-------------------------------------------------
WRITE_LINE_MEMBER(atarigen_state::sound_int_write_line)
{
m_sound_int_state = state;
update_interrupts();
}
//-------------------------------------------------
// sound_int_gen: Standard interrupt routine which
// sets the sound interrupt state.
//-------------------------------------------------
INTERRUPT_GEN_MEMBER(atarigen_state::sound_int_gen)
{
m_sound_int_state = 1;
update_interrupts();
}
//-------------------------------------------------
// sound_int_ack_w: Resets the state of the sound
// interrupt.
//-------------------------------------------------
WRITE16_MEMBER(atarigen_state::sound_int_ack_w)
{
m_sound_int_state = 0;
update_interrupts();
}
//-------------------------------------------------
// video_int_gen: Standard interrupt routine which
// sets the video interrupt state.
//-------------------------------------------------
INTERRUPT_GEN_MEMBER(atarigen_state::video_int_gen)
{
m_video_int_state = 1;
update_interrupts();
}
//-------------------------------------------------
// video_int_ack_w: Resets the state of the video
// interrupt.
//-------------------------------------------------
WRITE16_MEMBER(atarigen_state::video_int_ack_w)
{
m_video_int_state = 0;
update_interrupts();
}
/***************************************************************************
SLAPSTIC HANDLING
***************************************************************************/
inline void atarigen_state::slapstic_update_bank(int bank)
{
// if the bank has changed, copy the memory; Pit Fighter needs this
if (bank != m_slapstic_bank)
{
// bank 0 comes from the copy we made earlier
if (bank == 0)
memcpy(m_slapstic, &m_slapstic_bank0[0], 0x2000);
else
memcpy(m_slapstic, &m_slapstic[bank * 0x1000], 0x2000);
// remember the current bank
m_slapstic_bank = bank;
}
}
void atarigen_state::device_post_load()
{
if (m_slapstic_num != 0)
{
if (!m_slapstic_device.found())
fatalerror("Slapstic device is missing?\n");
slapstic_update_bank(m_slapstic_device->slapstic_bank());
}
}
//-------------------------------------------------
// slapstic_configure: Installs memory handlers for the
// slapstic and sets the chip number.
//-------------------------------------------------
void atarigen_state::slapstic_configure(cpu_device &device, offs_t base, offs_t mirror, uint8_t *mem)
{
if (!m_slapstic_device.found())
fatalerror("Slapstic device is missing\n");
// initialize the slapstic
m_slapstic_num = m_slapstic_device->m_chipnum;
m_slapstic_device->slapstic_init();
// install the memory handlers
address_space &program = device.space(AS_PROGRAM);
program.install_readwrite_handler(base, base + 0x7fff, 0, mirror, 0, read16_delegate(FUNC(atarigen_state::slapstic_r), this), write16_delegate(FUNC(atarigen_state::slapstic_w), this));
m_slapstic = (uint16_t *)mem;
// allocate memory for a copy of bank 0
m_slapstic_bank0.resize(0x2000);
memcpy(&m_slapstic_bank0[0], m_slapstic, 0x2000);
// ensure we recopy memory for the bank
m_slapstic_bank = 0xff;
// install an opcode base handler if we are a 68000 or variant
m_slapstic_base = base;
m_slapstic_mirror = mirror;
}
//-------------------------------------------------
// slapstic_w: Assuming that the slapstic sits in
// ROM memory space, we just simply tweak the slapstic at this
// address and do nothing more.
//-------------------------------------------------
WRITE16_MEMBER(atarigen_state::slapstic_w)
{
if (!m_slapstic_device.found())
fatalerror("Slapstic device is missing?\n");
slapstic_update_bank(m_slapstic_device->slapstic_tweak(space, offset));
}
//-------------------------------------------------
// slapstic_r: Tweaks the slapstic at the appropriate
// address and then reads a word from the underlying memory.
//-------------------------------------------------
READ16_MEMBER(atarigen_state::slapstic_r)
{
if (!m_slapstic_device.found())
fatalerror("Slapstic device is missing?\n");
// fetch the result from the current bank first
int result = m_slapstic[offset & 0xfff];
// then determine the new one
slapstic_update_bank(m_slapstic_device->slapstic_tweak(space, offset));
return result;
}
/***************************************************************************
SCANLINE TIMING
***************************************************************************/
//-------------------------------------------------
// scanline_timer_reset: Sets up the scanline timer.
//-------------------------------------------------
void atarigen_state::scanline_timer_reset(screen_device &screen, int frequency)
{
// set the scanline callback
m_scanlines_per_callback = frequency;
// set a timer to go off at scanline 0
if (frequency != 0)
get_screen_timer(screen)->scanline_timer->adjust(screen.time_until_pos(0));
}
//-------------------------------------------------
// scanline_timer: Called once every n scanlines
// to generate the periodic callback to the main
// system.
//-------------------------------------------------
void atarigen_state::scanline_timer(emu_timer &timer, screen_device &screen, int scanline)
{
// callback
scanline_update(screen, scanline);
// generate another
scanline += m_scanlines_per_callback;
if (scanline >= screen.height())
scanline = 0;
timer.adjust(screen.time_until_pos(scanline), scanline);
}
/***************************************************************************
VIDEO HELPERS
***************************************************************************/
//-------------------------------------------------
// halt_until_hblank_0: Halts CPU 0 until the
// next HBLANK.
//-------------------------------------------------
void atarigen_state::halt_until_hblank_0(device_t &device, screen_device &screen)
{
// halt the CPU until the next HBLANK
int hpos = screen.hpos();
int width = screen.width();
int hblank = width * 9 / 10;
// if we're in hblank, set up for the next one
if (hpos >= hblank)
hblank += width;
// halt and set a timer to wake up
device.execute().set_input_line(INPUT_LINE_HALT, ASSERT_LINE);
timer_set(screen.scan_period() * (hblank - hpos) / width, TID_UNHALT_CPU, 0, (void *)&device);
}
/***************************************************************************
MISC HELPERS
***************************************************************************/
//-------------------------------------------------
// blend_gfx: Takes two GFXElements and blends their
// data together to form one. Then frees the second.
//-------------------------------------------------
void atarigen_state::blend_gfx(int gfx0, int gfx1, int mask0, int mask1)
{
gfx_element *gx0 = m_gfxdecode->gfx(gfx0);
gfx_element *gx1 = m_gfxdecode->gfx(gfx1);
uint8_t *srcdata, *dest;
int c, x, y;
// allocate memory for the assembled data
srcdata = auto_alloc_array(machine(), uint8_t, gx0->elements() * gx0->width() * gx0->height());
// loop over elements
dest = srcdata;
for (c = 0; c < gx0->elements(); c++)
{
const uint8_t *c0base = gx0->get_data(c);
const uint8_t *c1base = gx1->get_data(c);
// loop over height
for (y = 0; y < gx0->height(); y++)
{
const uint8_t *c0 = c0base;
const uint8_t *c1 = c1base;
for (x = 0; x < gx0->width(); x++)
*dest++ = (*c0++ & mask0) | (*c1++ & mask1);
c0base += gx0->rowbytes();
c1base += gx1->rowbytes();
}
}
// int newdepth = gx0->depth() * gx1->depth();
int granularity = gx0->granularity();
gx0->set_raw_layout(srcdata, gx0->width(), gx0->height(), gx0->elements(), 8 * gx0->width(), 8 * gx0->width() * gx0->height());
gx0->set_granularity(granularity);
// free the second graphics element
m_gfxdecode->set_gfx(gfx1, nullptr);
}