// license:BSD-3-Clause
// copyright-holders:Alex Pasadyn, Zsolt Vasvari, Ernesto Corvi, Aaron Giles
// thanks-to:Kurt Mahan
/*************************************************************************
Williams/Midway Y/Z-unit system
**************************************************************************/
#include "emu.h"
#include "includes/midyunit.h"
#include "screen.h"
/* compile-time options */
#define LOG_DMA 0 /* DMAs are logged if the 'L' key is pressed */
/* constants for the DMA chip */
enum
{
DMA_COMMAND = 0,
DMA_ROWBYTES,
DMA_OFFSETLO,
DMA_OFFSETHI,
DMA_XSTART,
DMA_YSTART,
DMA_WIDTH,
DMA_HEIGHT,
DMA_PALETTE,
DMA_COLOR
};
/*************************************
*
* Video startup
*
*************************************/
VIDEO_START_MEMBER(midyunit_state,common)
{
/* allocate memory */
m_cmos_ram = std::make_unique<uint16_t[]>((0x2000 * 4)/2);
m_local_videoram = make_unique_clear<uint16_t[]>(0x80000/2);
m_pen_map = std::make_unique<pen_t[]>(65536);
m_nvram->set_base(m_cmos_ram.get(), 0x2000 * 4);
m_dma_timer = timer_alloc(TIMER_DMA);
m_autoerase_line_timer = timer_alloc(TIMER_AUTOERASE_LINE);
/* reset all the globals */
m_cmos_page = 0;
m_autoerase_enable = 0;
m_yawdim_dma = 0;
/* reset DMA state */
memset(m_dma_register, 0, sizeof(m_dma_register));
memset(&m_dma_state, 0, sizeof(m_dma_state));
/* register for state saving */
save_item(NAME(m_autoerase_enable));
save_pointer(NAME(m_local_videoram), 0x80000/2);
save_pointer(NAME(m_cmos_ram), (0x2000 * 4)/2);
save_item(NAME(m_videobank_select));
save_item(NAME(m_dma_register));
}
VIDEO_START_MEMBER(midyunit_state,midyunit_4bit)
{
int i;
VIDEO_START_CALL_MEMBER(common);
/* init for 4-bit */
for (i = 0; i < 65536; i++)
m_pen_map[i] = ((i & 0xf000) >> 8) | (i & 0x000f);
m_palette_mask = 0x00ff;
}
VIDEO_START_MEMBER(midyunit_state,midyunit_6bit)
{
int i;
VIDEO_START_CALL_MEMBER(common);
/* init for 6-bit */
for (i = 0; i < 65536; i++)
m_pen_map[i] = ((i & 0xc000) >> 8) | (i & 0x0f3f);
m_palette_mask = 0x0fff;
}
VIDEO_START_MEMBER(midyunit_state,mkyawdim)
{
VIDEO_START_CALL_MEMBER(midyunit_6bit);
m_yawdim_dma = 1;
}
VIDEO_START_MEMBER(midyunit_state,midzunit)
{
int i;
VIDEO_START_CALL_MEMBER(common);
/* init for 8-bit */
for (i = 0; i < 65536; i++)
m_pen_map[i] = i & 0x1fff;
m_palette_mask = 0x1fff;
}
/*************************************
*
* Banked graphics ROM access
*
*************************************/
READ16_MEMBER(midyunit_state::midyunit_gfxrom_r)
{
offset *= 2;
if (m_palette_mask == 0x00ff)
return m_gfx_rom[offset] | (m_gfx_rom[offset] << 4) |
(m_gfx_rom[offset + 1] << 8) | (m_gfx_rom[offset + 1] << 12);
else
return m_gfx_rom[offset] | (m_gfx_rom[offset + 1] << 8);
}
/*************************************
*
* Video/color RAM read/write
*
*************************************/
WRITE16_MEMBER(midyunit_state::midyunit_vram_w)
{
offset *= 2;
if (m_videobank_select)
{
if (ACCESSING_BITS_0_7)
m_local_videoram[offset] = (data & 0x00ff) | (m_dma_register[DMA_PALETTE] << 8);
if (ACCESSING_BITS_8_15)
m_local_videoram[offset + 1] = (data >> 8) | (m_dma_register[DMA_PALETTE] & 0xff00);
}
else
{
if (ACCESSING_BITS_0_7)
m_local_videoram[offset] = (m_local_videoram[offset] & 0x00ff) | (data << 8);
if (ACCESSING_BITS_8_15)
m_local_videoram[offset + 1] = (m_local_videoram[offset + 1] & 0x00ff) | (data & 0xff00);
}
}
READ16_MEMBER(midyunit_state::midyunit_vram_r)
{
offset *= 2;
if (m_videobank_select)
return (m_local_videoram[offset] & 0x00ff) | (m_local_videoram[offset + 1] << 8);
else
return (m_local_videoram[offset] >> 8) | (m_local_videoram[offset + 1] & 0xff00);
}
/*************************************
*
* Shift register read/write
*
*************************************/
TMS340X0_TO_SHIFTREG_CB_MEMBER(midyunit_state::to_shiftreg)
{
memcpy(shiftreg, &m_local_videoram[address >> 3], 2 * 512 * sizeof(uint16_t));
}
TMS340X0_FROM_SHIFTREG_CB_MEMBER(midyunit_state::from_shiftreg)
{
memcpy(&m_local_videoram[address >> 3], shiftreg, 2 * 512 * sizeof(uint16_t));
}
/*************************************
*
* Y/Z-unit control register
*
*************************************/
WRITE16_MEMBER(midyunit_state::midyunit_control_w)
{
/*
* Narc system register
* ------------------
*
* | Bit | Use
* --+-FEDCBA9876543210-+------------
* | xxxxxxxx-------- | 7 segment led on CPU board
* | --------xx------ | CMOS page
* | ----------x----- | - OBJ PAL RAM select
* | -----------x---- | - autoerase enable
* | ---------------- | - watchdog
*
*/
if (ACCESSING_BITS_0_7)
{
/* CMOS page is bits 6-7 */
m_cmos_page = ((data >> 6) & 3) * 0x1000;
/* video bank select is bit 5 */
m_videobank_select = (data >> 5) & 1;
/* handle autoerase disable (bit 4) */
m_autoerase_enable = ((data & 0x10) == 0);
}
}
/*************************************
*
* Palette handlers
*
*************************************/
WRITE16_MEMBER(midyunit_state::midyunit_paletteram_w)
{
int newword;
COMBINE_DATA(&m_generic_paletteram_16[offset]);
newword = m_generic_paletteram_16[offset];
m_palette->set_pen_color(offset & m_palette_mask, pal5bit(newword >> 10), pal5bit(newword >> 5), pal5bit(newword >> 0));
}
/*************************************
*
* DMA drawing routines
*
*************************************/
void midyunit_state::dma_draw(uint16_t command)
{
struct dma_state_t &dma_state = m_dma_state;
int dx = (command & 0x10) ? -1 : 1;
int height = dma_state.height;
int width = dma_state.width;
uint8_t *base = m_gfx_rom;
uint32_t offset = dma_state.offset >> 3;
uint16_t pal = dma_state.palette;
uint16_t color = pal | dma_state.color;
int x, y;
/* we only need the low 4 bits of the command */
command &= 0x0f;
/* loop over the height */
for (y = 0; y < height; y++)
{
int tx = dma_state.xpos;
int ty = dma_state.ypos;
uint32_t o = offset;
uint16_t *dest;
/* determine Y position */
ty = (ty + y) & 0x1ff;
offset += dma_state.rowbytes;
/* determine destination pointer */
dest = &m_local_videoram[ty * 512];
/* check for overruns if they are relevant */
if (o >= 0x06000000 && command < 0x0c)
continue;
/* switch off the zero/non-zero options */
switch (command)
{
case 0x00: /* draw nothing */
break;
case 0x01: /* draw only 0 pixels */
for (x = 0; x < width; x++, tx += dx)
if (base[o++] == 0)
dest[tx] = pal;
break;
case 0x02: /* draw only non-0 pixels */
for (x = 0; x < width; x++, tx += dx)
{
int pixel = base[o++];
if (pixel != 0)
dest[tx] = pal | pixel;
}
break;
case 0x03: /* draw all pixels */
for (x = 0; x < width; x++, tx += dx)
dest[tx] = pal | base[o++];
break;
case 0x04: /* color only 0 pixels */
case 0x05: /* color only 0 pixels */
for (x = 0; x < width; x++, tx += dx)
if (base[o++] == 0)
dest[tx] = color;
break;
case 0x06: /* color only 0 pixels, copy the rest */
case 0x07: /* color only 0 pixels, copy the rest */
for (x = 0; x < width; x++, tx += dx)
{
int pixel = base[o++];
dest[tx] = (pixel == 0) ? color : (pal | pixel);
}
break;
case 0x08: /* color only non-0 pixels */
case 0x0a: /* color only non-0 pixels */
for (x = 0; x < width; x++, tx += dx)
if (base[o++] != 0)
dest[tx] = color;
break;
case 0x09: /* color only non-0 pixels, copy the rest */
case 0x0b: /* color only non-0 pixels, copy the rest */
for (x = 0; x < width; x++, tx += dx)
{
int pixel = base[o++];
dest[tx] = (pixel != 0) ? color : (pal | pixel);
}
break;
case 0x0c: /* color all pixels */
case 0x0d: /* color all pixels */
case 0x0e: /* color all pixels */
case 0x0f: /* color all pixels */
for (x = 0; x < width; x++, tx += dx)
dest[tx] = color;
break;
}
}
}
/*************************************
*
* Timer callbacks
*
*************************************/
void midyunit_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch (id)
{
case TIMER_DMA:
dma_callback(ptr, param);
break;
case TIMER_AUTOERASE_LINE:
autoerase_line(ptr, param);
break;
default:
throw emu_fatalerror("Unknown id in midyunit_state::device_timer");
}
}
TIMER_CALLBACK_MEMBER(midyunit_state::dma_callback)
{
m_dma_register[DMA_COMMAND] &= ~0x8000; /* tell the cpu we're done */
m_maincpu->set_input_line(0, ASSERT_LINE);
}
/*************************************
*
* DMA reader
*
*************************************/
READ16_MEMBER(midyunit_state::midyunit_dma_r)
{
return m_dma_register[offset];
}
/*************************************
*
* DMA write handler
*
*************************************/
/*
* DMA registers
* ------------------
*
* Register | Bit | Use
* ----------+-FEDCBA9876543210-+------------
* 0 | x--------------- | trigger write (or clear if zero)
* | ---184-1-------- | unknown
* | ----------x----- | flip y
* | -----------x---- | flip x
* | ------------x--- | blit nonzero pixels as color
* | -------------x-- | blit zero pixels as color
* | --------------x- | blit nonzero pixels
* | ---------------x | blit zero pixels
* 1 | xxxxxxxxxxxxxxxx | width offset
* 2 | xxxxxxxxxxxxxxxx | source address low word
* 3 | xxxxxxxxxxxxxxxx | source address high word
* 4 | xxxxxxxxxxxxxxxx | detination x
* 5 | xxxxxxxxxxxxxxxx | destination y
* 6 | xxxxxxxxxxxxxxxx | image columns
* 7 | xxxxxxxxxxxxxxxx | image rows
* 8 | xxxxxxxxxxxxxxxx | palette
* 9 | xxxxxxxxxxxxxxxx | color
*/
WRITE16_MEMBER(midyunit_state::midyunit_dma_w)
{
struct dma_state_t &dma_state = m_dma_state;
uint32_t gfxoffset;
int command;
/* blend with the current register contents */
COMBINE_DATA(&m_dma_register[offset]);
/* only writes to DMA_COMMAND actually cause actions */
if (offset != DMA_COMMAND)
return;
/* high bit triggers action */
command = m_dma_register[DMA_COMMAND];
m_maincpu->set_input_line(0, CLEAR_LINE);
if (!(command & 0x8000))
return;
if (LOG_DMA)
{
if (machine().input().code_pressed(KEYCODE_L))
{
logerror("----\n");
logerror("DMA command %04X: (xflip=%d yflip=%d)\n",
command, (command >> 4) & 1, (command >> 5) & 1);
logerror(" offset=%08X pos=(%d,%d) w=%d h=%d rb=%d\n",
m_dma_register[DMA_OFFSETLO] | (m_dma_register[DMA_OFFSETHI] << 16),
(int16_t)m_dma_register[DMA_XSTART], (int16_t)m_dma_register[DMA_YSTART],
m_dma_register[DMA_WIDTH], m_dma_register[DMA_HEIGHT], (int16_t)m_dma_register[DMA_ROWBYTES]);
logerror(" palette=%04X color=%04X\n",
m_dma_register[DMA_PALETTE], m_dma_register[DMA_COLOR]);
}
}
g_profiler.start(PROFILER_USER1);
/* fill in the basic data */
dma_state.rowbytes = (int16_t)m_dma_register[DMA_ROWBYTES];
dma_state.xpos = (int16_t)m_dma_register[DMA_XSTART];
dma_state.ypos = (int16_t)m_dma_register[DMA_YSTART];
dma_state.width = m_dma_register[DMA_WIDTH];
dma_state.height = m_dma_register[DMA_HEIGHT];
dma_state.palette = m_dma_register[DMA_PALETTE] << 8;
dma_state.color = m_dma_register[DMA_COLOR] & 0xff;
/* determine the offset and adjust the rowbytes */
gfxoffset = m_dma_register[DMA_OFFSETLO] | (m_dma_register[DMA_OFFSETHI] << 16);
if (command & 0x10)
{
if (!m_yawdim_dma)
{
gfxoffset -= (dma_state.width - 1) * 8;
dma_state.rowbytes = (dma_state.rowbytes - dma_state.width + 3) & ~3;
}
else
dma_state.rowbytes = (dma_state.rowbytes + dma_state.width + 3) & ~3;
dma_state.xpos += dma_state.width - 1;
}
else
dma_state.rowbytes = (dma_state.rowbytes + dma_state.width + 3) & ~3;
/* apply Y clipping */
if (dma_state.ypos < 0)
{
dma_state.height -= -dma_state.ypos;
dma_state.offset += (-dma_state.ypos * dma_state.rowbytes) << 3;
dma_state.ypos = 0;
}
if (dma_state.ypos + dma_state.height > 512)
dma_state.height = 512 - dma_state.ypos;
/* apply X clipping */
if (!(command & 0x10))
{
if (dma_state.xpos < 0)
{
dma_state.width -= -dma_state.xpos;
dma_state.offset += -dma_state.xpos << 3;
dma_state.xpos = 0;
}
if (dma_state.xpos + dma_state.width > 512)
dma_state.width = 512 - dma_state.xpos;
}
else
{
if (dma_state.xpos >= 512)
{
dma_state.width -= dma_state.xpos - 511;
dma_state.offset += (dma_state.xpos - 511) << 3;
dma_state.xpos = 511;
}
if (dma_state.xpos - dma_state.width < 0)
dma_state.width = dma_state.xpos;
}
/* determine the location and draw */
if (gfxoffset < 0x02000000)
gfxoffset += 0x02000000;
{
dma_state.offset = gfxoffset - 0x02000000;
dma_draw(command);
}
/* signal we're done */
m_dma_timer->adjust(attotime::from_nsec(41 * dma_state.width * dma_state.height));
g_profiler.stop();
}
/*************************************
*
* Core refresh routine
*
*************************************/
TIMER_CALLBACK_MEMBER(midyunit_state::autoerase_line)
{
int scanline = param;
if (m_autoerase_enable && scanline >= 0 && scanline < 510)
memcpy(&m_local_videoram[512 * scanline], &m_local_videoram[512 * (510 + (scanline & 1))], 512 * sizeof(uint16_t));
}
TMS340X0_SCANLINE_IND16_CB_MEMBER(midyunit_state::scanline_update)
{
uint16_t *src = &m_local_videoram[(params->rowaddr << 9) & 0x3fe00];
uint16_t *dest = &bitmap.pix16(scanline);
int coladdr = params->coladdr << 1;
int x;
/* adjust the display address to account for ignored bits */
for (x = params->heblnk; x < params->hsblnk; x++)
dest[x] = m_pen_map[src[coladdr++ & 0x1ff]];
/* handle autoerase on the previous line */
autoerase_line(nullptr, params->rowaddr - 1);
/* if this is the last update of the screen, set a timer to clear out the final line */
/* (since we update one behind) */
if (scanline == screen.visible_area().max_y)
m_autoerase_line_timer->adjust(screen.time_until_pos(scanline + 1), params->rowaddr);
}