mame/src/mess/machine/lynx.c

2138 lines
62 KiB
C

/******************************************************************************
PeT mess@utanet.at 2000,2001
******************************************************************************/
#include "emu.h"
#include "includes/lynx.h"
#include "cpu/m6502/m6502.h"
#include "imagedev/cartslot.h"
#define PAD_UP 0x80
#define PAD_DOWN 0x40
#define PAD_LEFT 0x20
#define PAD_RIGHT 0x10
/****************************************
Graphics Drawing
****************************************/
/*
2008-10 FP:
Current implementation: lynx_blitter reads what will be drawn and sets which line_functions to use.
It then calls lynx_blit_lines which sets the various flip bits (horizontal and vertical) and calls
the chosen line_function. These functions (available in various versions, depending on how many
color bits are to be used) finally call lynx_plot_pixel which draws the sprite.
Notice however that, based on the problems in Electrocop, Jimmy Connors Tennis and Switchblade II
(among the others), it clearly seems that something is being lost in some of the passages. From
my partial understanding while comparing the code with the manual, I would suspect the loops in
the line_functions to be not completely correct.
This whole part will be eventually moved to video/ once I'm satisfied (or I give up).
*/
/* modes from blitter command */
enum {
BACKGROUND = 0,
BACKGROUND_NO_COLL,
BOUNDARY_SHADOW,
BOUNDARY,
NORMAL_SPRITE,
NO_COLL,
XOR_SPRITE,
SHADOW
};
static UINT8 lynx_read_ram(lynx_state *state, UINT16 address)
{
UINT8 result = 0x00;
if (address <= 0xfbff)
result = state->m_mem_0000[address - 0x0000];
else if (address <= 0xfcff)
result = state->m_mem_fc00[address - 0xfc00];
else if (address <= 0xfdff)
result = state->m_mem_fd00[address - 0xfd00];
else if (address <= 0xfff7)
result = state->m_mem_fe00[address - 0xfe00];
else if (address >= 0xfffa)
result = state->m_mem_fffa[address - 0xfffa];
return result;
}
static void lynx_write_ram(lynx_state *state, UINT16 address, UINT8 data)
{
if (address <= 0xfbff)
state->m_mem_0000[address - 0x0000] = data;
else if (address <= 0xfcff)
state->m_mem_fc00[address - 0xfc00] = data;
else if (address <= 0xfdff)
state->m_mem_fd00[address - 0xfd00] = data;
else if (address <= 0xfff7)
state->m_mem_fe00[address - 0xfe00] = data;
else if (address >= 0xfffa)
state->m_mem_fffa[address - 0xfffa] = data;
}
/* The pen numbers range from '0' to 'F. Pen numbers '1' through 'D' are always collidable and opaque. The other
ones have different behavior depending on the sprite type: there are 8 types of sprites, each has different
characteristics relating to some or all of their pen numbers.
* Shadow Error: The hardware is missing an inverter in the 'shadow' generator. This causes sprite types that
did not invoke shadow to now invoke it and vice versa. The only actual functionality loss is that 'exclusive or'
sprites and 'background' sprites will have shadow enabled.
The sprite types relate to specific hardware functions according to the following table:
-------------------------- SHADOW
| ----------------------- BOUNDARY_SHADOW
| | -------------------- NORMAL_SPRITE
| | | ----------------- BOUNDARY
| | | | -------------- BACKGROUND (+ shadow, due to bug in 'E' pen)
| | | | | ----------- BACKGROUND_NO_COLL
| | | | | | -------- NO_COLL
| | | | | | | ----- XOR_SPRITE (+ shadow, due to bug in 'E' pen)
| | | | | | | |
1 0 1 0 1 1 1 1 F is opaque
0 0 1 1 0 0 0 0 E is collideable
0 0 1 1 0 0 0 0 0 is opaque and collideable
1 1 1 1 0 0 0 1 allow collision detect
1 1 1 1 1 0 0 1 allow coll. buffer access
0 0 0 0 0 0 0 1 exclusive-or the data
*/
INLINE void lynx_plot_pixel(lynx_state *state, const int mode, const INT16 x, const int y, const int color)
{
UINT8 back;
UINT16 screen;
UINT16 colbuf;
state->m_blitter.everon = TRUE;
screen = state->m_blitter.screen + y * 80 + x / 2;
colbuf = state->m_blitter.colbuf + y * 80 + x / 2;
/* a note on timing: The hardware packs the pixel data and updates the screen and collision buffer a byte at a time.
Thus the buffer update for two pixels takes 3 memory accesses for a normal sprite (write to screen buffer, read/write to collision buffer).
+1 memory access for palette fetch?
*/
switch (mode&0x7)
{
case NORMAL_SPRITE:
/* A sprite may be set to 'normal'. This means that pen number '0' will be transparent and
non-collideable. All other pens will be opaque and collideable */
if (color == 0)
break;
if (!(x & 0x01)) /* Upper nibble */
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, (back & 0x0f) | (color << 4));
state->m_blitter.memory_accesses++;
if(state->m_blitter.sprite_collide && !(state->m_blitter.no_collide))
{
back = lynx_read_ram(state, colbuf);
lynx_write_ram(state, colbuf, (back & ~0xf0) | (state->m_blitter.spritenr << 4));
state->m_blitter.memory_accesses += 2;
if ((back >> 4) > state->m_blitter.fred)
state->m_blitter.fred = back >> 4;
}
state->m_blitter.memory_accesses++;
}
else /* Lower nibble */
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, (back & 0xf0) | color);
if(state->m_blitter.sprite_collide && !(state->m_blitter.no_collide))
{
back = lynx_read_ram(state, colbuf);
lynx_write_ram(state, colbuf, (back & ~0x0f) | (state->m_blitter.spritenr));
if ((back & 0x0f) > state->m_blitter.fred)
state->m_blitter.fred = back >> 4;
}
}
break;
case BOUNDARY:
/* A sprite may be set to 'boundary'. This is a 'normal' sprite with the exception that pen
number 'F' is transparent (and still collideable). */
if (color == 0)
break;
if (!(x & 0x01)) /* Upper nibble */
{
if (color != 0x0f)
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, (back & 0x0f) | (color << 4));
state->m_blitter.memory_accesses++;
}
if(state->m_blitter.sprite_collide && !(state->m_blitter.no_collide))
{
back = lynx_read_ram(state, colbuf);
lynx_write_ram(state, colbuf, (back & ~0xf0) | (state->m_blitter.spritenr << 4));
if ((back >> 4) > state->m_blitter.fred)
state->m_blitter.fred = back >> 4;
state->m_blitter.memory_accesses += 2;
}
state->m_blitter.memory_accesses++;
}
else /* Lower nibble */
{
if (color != 0x0f)
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, (back & 0xf0) | color);
}
if(state->m_blitter.sprite_collide && !(state->m_blitter.no_collide))
{
back = lynx_read_ram(state, colbuf);
lynx_write_ram(state, colbuf, (back & ~0x0f) | (state->m_blitter.spritenr));
if ((back & 0x0f) > state->m_blitter.fred)
state->m_blitter.fred = back >> 4;
}
}
break;
case SHADOW:
/* A sprite may be set to 'shadow'. This is a 'normal' sprite with the exception that pen
number 'E' is non-collideable (but still opaque) */
if (color == 0)
break;
if (!(x & 0x01)) /* Upper nibble */
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, (back & 0x0f) | (color << 4));
state->m_blitter.memory_accesses++;
if (state->m_blitter.sprite_collide && (color != 0x0e) && !(state->m_blitter.no_collide))
{
back = lynx_read_ram(state, colbuf);
lynx_write_ram(state, colbuf, (back & ~0xf0) | (state->m_blitter.spritenr << 4));
if ((back >> 4) > state->m_blitter.fred)
state->m_blitter.fred = back >> 4;
state->m_blitter.memory_accesses += 2;
}
state->m_blitter.memory_accesses++;
}
else /* Lower nibble */
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, (back & 0xf0) | color);
if (state->m_blitter.sprite_collide && (color != 0x0e) && !(state->m_blitter.no_collide))
{
back = lynx_read_ram(state, colbuf);
lynx_write_ram(state, colbuf, (back & ~0x0f) | (state->m_blitter.spritenr));
if ((back & 0x0f) > state->m_blitter.fred)
state->m_blitter.fred = back >> 4;
}
}
break;
case BOUNDARY_SHADOW:
/* This sprite is a 'normal' sprite with the characteristics of both 'boundary'
and 'shadow'. That is, pen number 'F' is transparent (and still collideable) and
pen number 'E' is non-collideable (but still opaque). */
if (color == 0)
break;
if (!(x & 0x01)) /* Upper nibble */
{
if (color != 0x0f)
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, (back & 0x0f) | (color << 4));
state->m_blitter.memory_accesses++;
}
if (state->m_blitter.sprite_collide && (color != 0x0e) && !(state->m_blitter.no_collide))
{
back = lynx_read_ram(state, colbuf);
lynx_write_ram(state, colbuf, (back & ~0xf0) | (state->m_blitter.spritenr << 4));
if ((back >> 4) > state->m_blitter.fred)
state->m_blitter.fred = back >> 4;
state->m_blitter.memory_accesses += 2;
}
state->m_blitter.memory_accesses++;
}
else /* Lower nibble */
{
if (color != 0x0f)
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, (back & 0xf0) | color);
}
if (state->m_blitter.sprite_collide && (color != 0x0e) && !(state->m_blitter.no_collide))
{
back = lynx_read_ram(state, colbuf);
lynx_write_ram(state, colbuf, (back & ~0x0f) | (state->m_blitter.spritenr));
if ((back & 0x0f) > state->m_blitter.fred)
state->m_blitter.fred = back >> 4;
}
}
break;
case BACKGROUND:
/* A sprite may be set to 'background'. This sprite will overwrite the contents of the video and
collision buffers. Pens '0' and 'F' are no longer transparent. This sprite is used to initialize
the buffers at the start of a 'painting'. Additionally, no collision detection is done, and no write
to the collision depository occurs. The 'E' error will cause the pen number 'E' to be non-collideable
and therefore not clear the collision buffer */
if (!(x & 0x01)) /* Upper nibble */
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, (back & 0x0f) | (color << 4));
state->m_blitter.memory_accesses++;
if (state->m_blitter.sprite_collide && (color != 0x0e) && !(state->m_blitter.no_collide))
{
back = lynx_read_ram(state, colbuf);
lynx_write_ram(state, colbuf, (back & ~0xf0) | (state->m_blitter.spritenr << 4));
state->m_blitter.memory_accesses++;
}
state->m_blitter.memory_accesses++;
}
else /* Lower nibble */
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, (back & 0xf0) | color);
if (state->m_blitter.sprite_collide && (color != 0x0e) && !(state->m_blitter.no_collide))
{
back = lynx_read_ram(state, colbuf);
lynx_write_ram(state, colbuf, (back & ~0x0f) | (state->m_blitter.spritenr));
}
}
break;
case BACKGROUND_NO_COLL:
/* This is a 'background' sprite with the exception that no activity occurs in the collision buffer */
if (!(x & 0x01)) /* Upper nibble */
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, (back & 0x0f) | (color << 4));
state->m_blitter.memory_accesses++;
state->m_blitter.memory_accesses++;
}
else /* Lower nibble */
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, (back & 0xf0) | color);
}
break;
case NO_COLL:
/* A sprite may be set to 'non-collideable'. This means that it will have no affect on the contents of
the collision buffer and all other collision activities are overridden (pen 'F' is not collideable). */
if (color == 0)
break;
if (!(x & 0x01)) /* Upper nibble */
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, (back & 0x0f) | (color << 4));
state->m_blitter.memory_accesses++;
state->m_blitter.memory_accesses++;
}
else /* Lower nibble */
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, (back & 0xf0) | color);
}
break;
case XOR_SPRITE:
/* This is a 'normal' sprite with the exception that the data from the video buffer is exclusive-ored
with the sprite data and written back out to the video buffer. Collision activity is 'normal'. The 'E'
error will cause the pen number 'E' to be non-collideable and therefore not react with the collision
buffer */
if (color == 0)
break;
if (!(x & 0x01)) /* Upper nibble */
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, back^(color << 4));
state->m_blitter.memory_accesses += 2;
if (state->m_blitter.sprite_collide && (color != 0x0e) && !(state->m_blitter.no_collide))
{
back = lynx_read_ram(state, colbuf);
lynx_write_ram(state, colbuf, (back & ~0xf0) | (state->m_blitter.spritenr << 4));
if ((back >> 4) > state->m_blitter.fred)
state->m_blitter.fred = back >> 4;
state->m_blitter.memory_accesses += 2;
}
state->m_blitter.memory_accesses++;
}
else /* Lower nibble */
{
back = lynx_read_ram(state, screen);
lynx_write_ram(state, screen, back^color);
if (state->m_blitter.sprite_collide && (color != 0x0e) && !(state->m_blitter.no_collide))
{
back = lynx_read_ram(state, colbuf);
lynx_write_ram(state, colbuf, (back & ~0x0f) | (state->m_blitter.spritenr));
if ((back & 0x0f) > state->m_blitter.fred)
state->m_blitter.fred = back >> 4;
}
}
break;
}
}
static void lynx_blit_do_work( lynx_state *state, const int y, const int xdir, const int bits_per_pixel, const int mask )
{
int next_line_addr,i,j;
int xi, bits, color;
UINT16 width_accum, buffer;
next_line_addr = lynx_read_ram(state, state->m_blitter.bitmap); // offset to second sprite line
width_accum = (xdir == 1) ? state->m_blitter.width_offset : 0;
state->m_blitter.memory_accesses++;
for (xi = state->m_blitter.x_pos - state->m_blitter.xoff, bits = 0, buffer = 0, j = 1; j < next_line_addr;j++)
{
buffer = (buffer << 8) | lynx_read_ram(state, state->m_blitter.bitmap + j);
bits += 8; // current bits in buffer
state->m_blitter.memory_accesses++;
for ( ; bits > bits_per_pixel; bits -= bits_per_pixel) // last data packet at end of scanline is not rendered (qix, blulght)
{
color = state->m_blitter.color[(buffer >> (bits - bits_per_pixel)) & mask];
width_accum += state->m_blitter.width;
for (i = 0; i < (width_accum>>8); i++, xi += xdir)
{
if ((xi >= 0) && (xi < 160))
{
lynx_plot_pixel(state, state->m_blitter.mode, xi, y, color);
}
}
width_accum &= 0xff;
}
}
}
static void lynx_blit_2color_line(lynx_state *state, const int y, const int xdir) {lynx_blit_do_work(state, y, xdir, 1, 0x01);}
static void lynx_blit_4color_line(lynx_state *state, const int y, const int xdir) {lynx_blit_do_work(state, y, xdir, 2, 0x03);}
static void lynx_blit_8color_line(lynx_state *state, const int y, const int xdir) {lynx_blit_do_work(state, y, xdir, 3, 0x07);}
static void lynx_blit_16color_line(lynx_state *state, const int y, const int xdir) {lynx_blit_do_work(state, y, xdir, 4, 0x0f);}
static void lynx_blit_rle_do_work( lynx_state *state, const INT16 y, const int xdir, const int bits_per_pixel, const int mask )
{
int i;
int xi;
int buffer, bits, j;
int literal_data, count, color;
UINT16 width_accum;
width_accum = (xdir == 1) ? state->m_blitter.width_offset : 0;
for( bits = 0, j = 0, buffer = 0, xi = state->m_blitter.x_pos - state->m_blitter.xoff; ; ) /* through the rle entries */
{
if (bits < 5 + bits_per_pixel) /* under 7 bits no complete entry */
{
j++;
if (j >= lynx_read_ram(state, state->m_blitter.bitmap))
return;
bits += 8;
buffer = (buffer << 8) | lynx_read_ram(state,state->m_blitter.bitmap + j);
state->m_blitter.memory_accesses++;
}
literal_data = (buffer >> (bits - 1)) & 0x01;
bits--;
count = (buffer >> (bits - 4)) & 0x0f; // repeat count (packed) or pixel count (literal)
bits -= 4;
if (literal_data) /* count of different pixels */
{
for ( ; count >= 0; count--)
{
if (bits < bits_per_pixel)
{
j++;
if (j >= lynx_read_ram(state, state->m_blitter.bitmap))
return;
bits += 8;
buffer = (buffer << 8) | lynx_read_ram(state, state->m_blitter.bitmap + j);
state->m_blitter.memory_accesses++;
}
color = state->m_blitter.color[(buffer >> (bits - bits_per_pixel)) & mask];
bits -= bits_per_pixel;
width_accum += state->m_blitter.width;
for (i = 0; i < (width_accum>>8); i++, xi += xdir)
{
if ((xi >= 0) && (xi < 160))
lynx_plot_pixel(state, state->m_blitter.mode, xi, y, color);
}
width_accum &= 0xff;
}
}
else /* count of same pixels */
{
if (count == 0) // 4 bit count value of zero indicates end-of-line in a packed sprite
return;
if (bits < bits_per_pixel)
{
j++;
if (j >= lynx_read_ram(state, state->m_blitter.bitmap))
return;
bits += 8;
buffer = (buffer << 8) | lynx_read_ram(state, state->m_blitter.bitmap + j);
state->m_blitter.memory_accesses++;
}
color = state->m_blitter.color[(buffer >> (bits - bits_per_pixel)) & mask];
bits -= bits_per_pixel;
for ( ; count>=0; count--)
{
width_accum += state->m_blitter.width;
for (i = 0; i < (width_accum>>8); i++, xi += xdir)
{
if ((xi >= 0) && (xi < 160))
lynx_plot_pixel(state, state->m_blitter.mode, xi, y, color);
}
width_accum &= 0xff;
}
}
}
}
static void lynx_blit_2color_rle_line(lynx_state *state, const int y, const int xdir) {lynx_blit_rle_do_work(state, y, xdir, 1, 0x01);}
static void lynx_blit_4color_rle_line(lynx_state *state, const int y, const int xdir) {lynx_blit_rle_do_work(state, y, xdir, 2, 0x03);}
static void lynx_blit_8color_rle_line(lynx_state *state, const int y, const int xdir) {lynx_blit_rle_do_work(state, y, xdir, 3, 0x07);}
static void lynx_blit_16color_rle_line(lynx_state *state, const int y, const int xdir) {lynx_blit_rle_do_work(state, y, xdir, 4, 0x0f);}
static void lynx_blit_lines(lynx_state *state)
{
int i;
INT16 y;
int ydir = 0, xdir = 0;
int flip = 0;
state->m_blitter.everon = FALSE;
switch (state->m_blitter.spr_ctl1 & 0x03) /* Initial drawing direction */
{
case 0: // Down/Right (quadrant 0)
xdir = 1;
ydir = 1;
flip = 0;
break;
case 1: // Down/Left (blockout) (quadrant 3)
xdir = -1;
ydir = 1;
flip = 3;
break;
case 2: // Up/Right (fat bobby) (quadrant 1)
xdir = 1;
ydir = -1;
flip = 1;
break;
case 3: // Up/Left (quadrant 2)
xdir = -1;
ydir = -1;
flip = 2;
break;
}
if (state->m_blitter.spr_ctl0 & 0x20) /* Horizontal Flip */
{
xdir *= -1;
}
if (state->m_blitter.spr_ctl0 & 0x10) /* Vertical Flip */
{
ydir *= -1;
}
// Set height accumulator based on drawing direction
state->m_blitter.height_accumulator = (ydir == 1) ? state->m_blitter.height_offset : 0x00;
// loop through lines, next line offset of zero indicates end of sprite
for (y = state->m_blitter.y_pos - state->m_blitter.yoff; (i = lynx_read_ram(state, state->m_blitter.bitmap)); state->m_blitter.bitmap += i)
{
state->m_blitter.memory_accesses++;
if (i == 1) // draw next quadrant
{
// centered sprites sprdemo3, fat bobby, blockout
switch (flip & 0x03)
{
case 0:
case 2:
ydir *= -1;
state->m_blitter.y_pos += ydir;
break;
case 1:
case 3:
xdir *= -1;
state->m_blitter.x_pos += xdir;
break;
}
flip++;
y = state->m_blitter.y_pos - state->m_blitter.yoff;
state->m_blitter.height_accumulator = (ydir == 1) ? state->m_blitter.height_offset : 0x00;
continue;
}
state->m_blitter.height_accumulator += state->m_blitter.height;
for (int i=0; i < (state->m_blitter.height_accumulator>>8); i++, y += ydir)
{
if (y >= 0 && y < 102)
state->m_blitter.line_function(state, y, xdir);
state->m_blitter.width += (INT16)state->m_blitter.stretch;
if (state->m_blitter.vstretch) // doesn't seem to be used
{
state->m_blitter.height += (INT16)state->m_blitter.stretch;
logerror("vertical stretch enabled");
}
state->m_blitter.tilt_accumulator += state->m_blitter.tilt;
state->m_blitter.x_pos += (state->m_blitter.tilt_accumulator>>8);
state->m_blitter.tilt_accumulator &= 0xff;
}
state->m_blitter.height_accumulator &= 0xff;
}
}
static TIMER_CALLBACK(lynx_blitter_timer)
{
lynx_state *state = machine.driver_data<lynx_state>();
state->m_blitter.busy=0; // blitter finished
cputag_set_input_line(machine, "maincpu", INPUT_LINE_HALT, CLEAR_LINE);
}
/*
control 0
bit 7,6: 00 2 color
01 4 color
11 8 colors?
11 16 color
bit 5,4: 00 right down
01 right up
10 left down
11 left up
#define SHADOW (0x07)
#define XORSHADOW (0x06)
#define NONCOLLIDABLE (0x05)
#define NORMAL (0x04)
#define BOUNDARY (0x03)
#define BOUNDARYSHADOW (0x02)
#define BKGRNDNOCOL (0x01)
#define BKGRND (0x00)
control 1
bit 7: 0 bitmap rle encoded
1 not encoded
bit 3: 0 color info with command
1 no color info with command
#define RELHVST (0x30)
#define RELHVS (0x20)
#define RELHV (0x10)
#define SKIPSPRITE (0x04)
#define DUP (0x02)
#define DDOWN (0x00)
#define DLEFT (0x01)
#define DRIGHT (0x00)
coll
#define DONTCOLLIDE (0x20)
word next
word data
word x
word y
word width
word height
pixel c0 90 20 0000 datapointer x y 0100 0100 color (8 colorbytes)
4 bit direct?
datapointer 2 10 0
98 (0 colorbytes)
box c0 90 20 0000 datapointer x y width height color
datapointer 2 10 0
c1 98 00 4 bit direct without color bytes (raycast)
40 10 20 4 bit rle (sprdemo2)
line c1 b0 20 0000 datapointer x y 0100 0100 stretch tilt:x/y color (8 color bytes)
or
line c0 b0 20 0000 datapointer x y 0100 0100 stretch tilt:x/y color
datapointer 2 11 0
text ?(04) 90 20 0000 datapointer x y width height color
datapointer 2 10 0
stretch: hsize adder
tilt: hpos adder
*/
static void lynx_blitter(running_machine &machine)
{
lynx_state *state = machine.driver_data<lynx_state>();
static const int lynx_colors[4]={2,4,8,16};
UINT8 palette_offset;
UINT8 coldep;
static void (* const blit_line[4])(lynx_state *state, const int y, const int xdir)= {
lynx_blit_2color_line,
lynx_blit_4color_line,
lynx_blit_8color_line,
lynx_blit_16color_line
};
static void (* const blit_rle_line[4])(lynx_state *state, const int y, const int xdir)= {
lynx_blit_2color_rle_line,
lynx_blit_4color_rle_line,
lynx_blit_8color_rle_line,
lynx_blit_16color_rle_line
};
int i; int colors;
state->m_blitter.mem = (UINT8*)machine.device("maincpu")->memory().space(AS_PROGRAM)->get_read_ptr(0x0000);
state->m_blitter.busy = 1; // blitter working
state->m_blitter.memory_accesses = 0;
// Last SCB is indicated by zero in the high byte of SCBNEXT
while (state->m_blitter.scb_next & 0xff00)
{
state->m_blitter.stretch = 0;
state->m_blitter.tilt = 0;
state->m_blitter.tilt_accumulator = 0;
state->m_blitter.scb = state->m_blitter.scb_next; // current scb
state->m_blitter.scb_next = lynx_read_ram(state, state->m_blitter.scb + SCB_SCBNEXT) | (lynx_read_ram(state, state->m_blitter.scb + SCB_SCBNEXT + 1) << 8); // next scb
state->m_blitter.spr_ctl0 = lynx_read_ram(state, state->m_blitter.scb + SCB_SPRCTL0);
state->m_blitter.spr_ctl1 = lynx_read_ram(state, state->m_blitter.scb + SCB_SPRCTL1);
state->m_blitter.spr_coll = lynx_read_ram(state, state->m_blitter.scb + SCB_SPRCOLL);
state->m_blitter.memory_accesses += 5;
if(!(state->m_blitter.spr_ctl1 & 0x04)) // sprite will be processed (if sprite is skipped first 5 bytes are still copied to suzy)
{
state->m_blitter.bitmap = lynx_read_ram(state, state->m_blitter.scb + SCB_SPRDLINE) | (lynx_read_ram(state, state->m_blitter.scb + SCB_SPRDLINE + 1) << 8);
state->m_blitter.x_pos = lynx_read_ram(state, state->m_blitter.scb + SCB_HPOSSTRT) | (lynx_read_ram(state, state->m_blitter.scb + SCB_HPOSSTRT + 1) << 8);
state->m_blitter.y_pos = lynx_read_ram(state, state->m_blitter.scb + SCB_VPOSSTRT) | (lynx_read_ram(state, state->m_blitter.scb + SCB_VPOSSTRT + 1) << 8);
state->m_blitter.memory_accesses += 6;
switch(state->m_blitter.spr_ctl1 & 0x30) // reload sprite scaling
{
case 0x30: // width, height, tilt, stretch
state->m_blitter.tilt = lynx_read_ram(state, state->m_blitter.scb + SCB_TILT) | (lynx_read_ram(state, state->m_blitter.scb + SCB_TILT + 1) << 8);
state->m_blitter.memory_accesses+=2;
case 0x20: // width, height, stretch
state->m_blitter.stretch = lynx_read_ram(state, state->m_blitter.scb + SCB_STRETCH) | (lynx_read_ram(state, state->m_blitter.scb + SCB_STRETCH + 1) << 8);
state->m_blitter.memory_accesses+=2;
case 0x10: // width, height
state->m_blitter.width = lynx_read_ram(state, state->m_blitter.scb + SCB_SPRHSIZ) | (lynx_read_ram(state, state->m_blitter.scb + SCB_SPRHSIZ + 1) << 8);
state->m_blitter.height = lynx_read_ram(state, state->m_blitter.scb + SCB_SPRVSIZ) | (lynx_read_ram(state, state->m_blitter.scb + SCB_SPRVSIZ + 1) << 8);
state->m_blitter.memory_accesses+=4;
}
if(!(state->m_blitter.spr_ctl1 & 0x08)) // reload palette
{
if (state->m_blitter.spr_ctl1 & 0x30)
palette_offset = 0x0b + 2*(((state->m_blitter.spr_ctl1 & 0x30)>>4) + 1); // palette data offset depends on width, height, etc. reloading
else
palette_offset = 0x0b;
colors = lynx_colors[state->m_blitter.spr_ctl0 >> 6];
for (i = 0; i < colors / 2; i++)
{
state->m_blitter.color[i * 2] = lynx_read_ram(state, state->m_blitter.scb + palette_offset + i) >> 4;
state->m_blitter.color[i * 2 + 1 ] = lynx_read_ram(state, state->m_blitter.scb + palette_offset + i) & 0x0f;
state->m_blitter.memory_accesses++;
}
}
}
if (!(state->m_blitter.spr_ctl1 & 0x04)) // if 0, we skip this sprite
{
state->m_blitter.colpos = state->m_blitter.scb + (state->m_suzy.data[COLLOFFL] | (state->m_suzy.data[COLLOFFH]<<8));
state->m_blitter.mode = state->m_blitter.spr_ctl0 & 0x07;
if (state->m_blitter.spr_ctl1 & 0x80) // totally literal sprite
state->m_blitter.line_function = blit_line[state->m_blitter.spr_ctl0 >> 6];
else
state->m_blitter.line_function = blit_rle_line[state->m_blitter.spr_ctl0 >> 6];
state->m_blitter.sprite_collide = !(state->m_blitter.spr_coll & 0x20);
state->m_blitter.spritenr = state->m_blitter.spr_coll & 0x0f;
state->m_blitter.fred = 0;
/* Draw Sprite */
lynx_blit_lines(state);
if (state->m_blitter.sprite_collide && !(state->m_blitter.no_collide))
{
switch (state->m_blitter.mode)
{
case BOUNDARY_SHADOW:
case BOUNDARY:
case NORMAL_SPRITE:
case XOR_SPRITE:
case SHADOW:
lynx_write_ram(state, state->m_blitter.colpos, state->m_blitter.fred);
break;
}
}
if (state->m_suzy.data[SPRGO] & 0x04) // Everon enabled
{
coldep = lynx_read_ram(state, state->m_blitter.colpos);
if (!state->m_blitter.everon)
coldep |= 0x80;
else
coldep &= 0x7f;
lynx_write_ram(state, state->m_blitter.colpos, coldep);
}
}
}
machine.scheduler().timer_set(machine.device<cpu_device>("maincpu")->cycles_to_attotime(state->m_blitter.memory_accesses), FUNC(lynx_blitter_timer));
}
/****************************************
Suzy Emulation
****************************************/
/* Math bugs of the original hardware:
- in signed multiply, the hardware thinks that 8000 is a positive number
- in signed multiply, the hardware thinks that 0 is a negative number. This is not an immediate
problem for a multiply by zero, since the answer will be re-negated to the correct polarity of
zero. However, since it will set the sign flag, you can not depend on the sign flag to be correct
if you just load the lower byte after a multiply by zero.
- in divide, the remainder will have 2 possible errors, depending on its actual value (no further
notes on these errors available) */
void lynx_state::lynx_divide()
{
UINT32 left;
UINT16 right;
UINT32 res, mod;
/*
Hardware divide:
EFGH
* NP
----------------
ABCD
Remainder (JK)LM
*/
left = m_suzy.data[MATH_H] | (m_suzy.data[MATH_G] << 8) | (m_suzy.data[MATH_F] << 16) | (m_suzy.data[MATH_E] << 24);
right = m_suzy.data[MATH_P] | (m_suzy.data[MATH_N] << 8);
m_suzy.accumulate_overflow = FALSE;
if (right == 0)
{
m_suzy.accumulate_overflow = TRUE; /* during divisions, this bit is used to detect denominator = 0 */
res = 0xffffffff;
mod = 0; //?
}
else
{
res = left / right;
mod = left % right;
}
// logerror("coprocessor %8x / %8x = %4x\n", left, right, res);
m_suzy.data[MATH_D] = res & 0xff;
m_suzy.data[MATH_C] = res >> 8;
m_suzy.data[MATH_B] = res >> 16;
m_suzy.data[MATH_A] = res >> 24;
m_suzy.data[MATH_M] = mod & 0xff;
m_suzy.data[MATH_L] = mod >> 8;
m_suzy.data[MATH_K] = 0; // documentation states the hardware sets these to zero on divides
m_suzy.data[MATH_J] = 0;
}
void lynx_state::lynx_multiply()
{
UINT16 left, right;
UINT32 res, accu;
/*
Hardware multiply:
AB
* CD
----------------
EFGH
Accumulate JKLM
*/
m_suzy.accumulate_overflow = FALSE;
left = m_suzy.data[MATH_B] | (m_suzy.data[MATH_A] << 8);
right = m_suzy.data[MATH_D] | (m_suzy.data[MATH_C] << 8);
res = left * right;
if (m_suzy.signed_math)
{
if (!(m_sign_AB + m_sign_CD)) /* different signs */
res = (res ^ 0xffffffff) + 1;
}
m_suzy.data[MATH_H] = res & 0xff;
m_suzy.data[MATH_G] = res >> 8;
m_suzy.data[MATH_F] = res >> 16;
m_suzy.data[MATH_E] = res >> 24;
if (m_suzy.accumulate)
{
accu = m_suzy.data[MATH_M] | m_suzy.data[MATH_L] << 8 | m_suzy.data[MATH_K] << 16 | m_suzy.data[MATH_J] << 24;
accu += res;
if (accu < res)
m_suzy.accumulate_overflow = TRUE;
m_suzy.data[MATH_M] = accu;
m_suzy.data[MATH_L] = accu >> 8;
m_suzy.data[MATH_K] = accu >> 16;
m_suzy.data[MATH_J] = accu >> 24;
}
}
READ8_MEMBER(lynx_state::suzy_read)
{
UINT8 value = 0, input;
switch (offset)
{
case TILTACUML:
return m_blitter.tilt_accumulator & 0xff;
case TILTACUMH:
return m_blitter.tilt_accumulator>>8;
case HOFFL:
return m_blitter.xoff & 0xff;
case HOFFH:
return m_blitter.xoff>>8;
case VOFFL:
return m_blitter.yoff & 0xff;
case VOFFH:
return m_blitter.yoff>>8;
case VIDBASL:
return m_blitter.screen & 0xff;
case VIDBASH:
return m_blitter.screen>>8;
case COLLBASL:
return m_blitter.colbuf & 0xff;
case COLLBASH:
return m_blitter.colbuf>>8;
case SCBNEXTL:
return m_blitter.scb_next & 0xff;
case SCBNEXTH:
return m_blitter.scb_next>>8;
case SPRDLINEL:
return m_blitter.bitmap & 0xff;
case SPRDLINEH:
return m_blitter.bitmap>>8;
case HPOSSTRTL:
return m_blitter.x_pos & 0xff;
case HPOSSTRTH:
return m_blitter.x_pos>>8;
case VPOSSTRTL:
return m_blitter.y_pos & 0xff;
case VPOSSTRTH:
return m_blitter.y_pos>>8;
case SPRHSIZL:
return m_blitter.width & 0xff;
case SPRHSIZH:
return m_blitter.width>>8;
case SPRVSIZL:
return m_blitter.height & 0xff;
case SPRVSIZH:
return m_blitter.height>>8;
case STRETCHL:
return m_blitter.stretch & 0xff;
case STRETCHH:
return m_blitter.stretch>>8;
case TILTL:
return m_blitter.tilt & 0xff;
case TILTH:
return m_blitter.tilt>>8;
// case SPRDOFFL:
// case SPRVPOSL:
// case COLLOFFL:
case VSIZACUML:
return m_blitter.height_accumulator & 0xff;
case VSIZACUMH:
return m_blitter.height_accumulator>>8;
case HSIZOFFL:
return m_blitter.width_offset & 0xff;
case HSIZOFFH:
return m_blitter.width_offset>>8;
case VSIZOFFL:
return m_blitter.height_offset & 0xff;
case VSIZOFFH:
return m_blitter.height_offset>>8;
case SCBADRL:
return m_blitter.scb & 0xff;
case SCBADRH:
return m_blitter.scb>>8;
//case PROCADRL:
case SUZYHREV:
return 0x01; // must not be 0 for correct power up
case SPRSYS:
// math busy, last carry, unsafe access, and stop on current sprite bits not implemented.
if (m_suzy.accumulate_overflow)
value |= 0x40;
if (m_blitter.vstretch)
value |= 0x10;
if (m_blitter.lefthanded)
value |= 0x08;
if (m_blitter.busy)
value |= 0x01;
break;
case JOYSTICK:
input = ioport("JOY")->read();
switch (m_rotate)
{
case 1:
value = input;
input &= 0x0f;
if (value & PAD_UP) input |= PAD_LEFT;
if (value & PAD_LEFT) input |= PAD_DOWN;
if (value & PAD_DOWN) input |= PAD_RIGHT;
if (value & PAD_RIGHT) input |= PAD_UP;
break;
case 2:
value = input;
input &= 0x0f;
if (value & PAD_UP) input |= PAD_RIGHT;
if (value & PAD_RIGHT) input |= PAD_DOWN;
if (value & PAD_DOWN) input |= PAD_LEFT;
if (value & PAD_LEFT) input |= PAD_UP;
break;
}
if (m_blitter.lefthanded)
{
value = input & 0x0f;
if (input & PAD_UP) value |= PAD_DOWN;
if (input & PAD_DOWN) value |= PAD_UP;
if (input & PAD_LEFT) value |= PAD_RIGHT;
if (input & PAD_RIGHT) value |= PAD_LEFT;
}
else
value = input;
break;
case SWITCHES:
value = ioport("PAUSE")->read();
break;
case RCART:
value = *(machine().root_device().memregion("user1")->base() + (m_suzy.high * m_granularity) + m_suzy.low);
m_suzy.low = (m_suzy.low + 1) & (m_granularity - 1);
break;
//case RCART_BANK1: /* we need bank 1 emulation!!! */
case SPRCTL0:
case SPRCTL1:
case SPRCOLL:
case SPRINIT:
case SUZYBUSEN:
case SPRGO:
logerror("read from write only register %x\n", offset);
value = 0;
break;
default:
value = m_suzy.data[offset];
}
//logerror("suzy read %.2x %.2x\n",offset,value);
return value;
}
WRITE8_MEMBER(lynx_state::suzy_write)
{
m_suzy.data[offset] = data;
//logerror("suzy write %.2x %.2x\n",offset,data);
/* Additional effects of a write */
/* Even addresses are the LSB. Any CPU write to an LSB in 0x00-0x7f will set the MSB to 0. */
/* This in particular holds for math quantities: Writing to B (0x54), D (0x52),
F (0x62), H (0x60), K (0x6e) or M (0x6c) will force a '0' to be written to A (0x55),
C (0x53), E (0x63), G (0x61), J (0x6f) or L (0x6d) respectively */
if ((offset < 0x80) && !(offset & 0x01))
m_suzy.data[offset + 1] = 0;
switch(offset)
{
//case TMPADRL:
//case TMPADRH:
case TILTACUML:
m_blitter.tilt_accumulator = data; // upper byte forced to zero see above.
break;
case TILTACUMH:
m_blitter.tilt_accumulator &= 0xff;
m_blitter.tilt_accumulator |= data<<8;
break;
case HOFFL:
m_blitter.xoff = data;
break;
case HOFFH:
m_blitter.xoff &= 0xff;
m_blitter.xoff |= data<<8;
break;
case VOFFL:
m_blitter.yoff = data;
break;
case VOFFH:
m_blitter.yoff &= 0xff;
m_blitter.yoff |= data<<8;
break;
case VIDBASL:
m_blitter.screen = data;
break;
case VIDBASH:
m_blitter.screen &= 0xff;
m_blitter.screen |= data<<8;
break;
case COLLBASL:
m_blitter.colbuf = data;
break;
case COLLBASH:
m_blitter.colbuf &= 0xff;
m_blitter.colbuf |= data<<8;
break;
case SCBNEXTL:
m_blitter.scb_next = data;
break;
case SCBNEXTH:
m_blitter.scb_next &= 0xff;
m_blitter.scb_next |= data<<8;
break;
case SPRDLINEL:
m_blitter.bitmap = data;
break;
case SPRDLINEH:
m_blitter.bitmap &= 0xff;
m_blitter.bitmap |= data<<8;
break;
case HPOSSTRTL:
m_blitter.x_pos = data;
case HPOSSTRTH:
m_blitter.x_pos &= 0xff;
m_blitter.x_pos |= data<<8;
case VPOSSTRTL:
m_blitter.y_pos = data;
case VPOSSTRTH:
m_blitter.y_pos &= 0xff;
m_blitter.y_pos |= data<<8;
case SPRHSIZL:
m_blitter.width = data;
break;
case SPRHSIZH:
m_blitter.width &= 0xff;
m_blitter.width |= data<<8;
break;
case SPRVSIZL:
m_blitter.height = data;
break;
case SPRVSIZH:
m_blitter.height &= 0xff;
m_blitter.height |= data<<8;
break;
case STRETCHL:
m_blitter.stretch = data;
break;
case STRETCHH:
m_blitter.stretch &= 0xff;
m_blitter.stretch |= data<<8;
break;
case TILTL:
m_blitter.tilt = data;
break;
case TILTH:
m_blitter.tilt &= 0xff;
m_blitter.tilt |= data<<8;
break;
// case SPRDOFFL:
// case SPRVPOSL:
// case COLLOFFL:
case VSIZACUML:
m_blitter.height_accumulator = data;
break;
case VSIZACUMH:
m_blitter.height_accumulator &= 0xff;
m_blitter.height_accumulator |= data<<8;
break;
case HSIZOFFL:
m_blitter.width_offset = data;
break;
case HSIZOFFH:
m_blitter.width_offset &= 0xff;
m_blitter.width_offset |= data<<8;
break;
case VSIZOFFL:
m_blitter.height_offset = data;
break;
case VSIZOFFH:
m_blitter.height_offset &= 0xff;
m_blitter.height_offset |= data<<8;
break;
case SCBADRL:
m_blitter.scb = data;
break;
case SCBADRH:
m_blitter.scb &= 0xff;
m_blitter.scb |= data<<8;
break;
//case PROCADRL:
/* Writing to M (0x6c) will also clear the accumulator overflow bit */
case MATH_M:
m_suzy.accumulate_overflow = FALSE;
break;
case MATH_C:
/* If we are going to perform a signed multiplication, we store the sign and convert the number
to an unsigned one */
if (m_suzy.signed_math)
{
UINT16 factor, temp;
factor = m_suzy.data[MATH_D] | (m_suzy.data[MATH_C] << 8);
if ((factor - 1) & 0x8000) /* here we use -1 to cover the math bugs on the sign of 0 and 0x8000 */
{
temp = (factor ^ 0xffff) + 1;
m_sign_CD = - 1;
m_suzy.data[MATH_D] = temp & 0xff;
m_suzy.data[MATH_C] = temp >> 8;
}
else
m_sign_CD = 1;
}
break;
case MATH_D:
/* Documentation states that writing to the MATH_D will set MATH_C to zero but not update the sign flag.
Implementing the sign detection as described in the documentation causes Stun Runners to not work.
Either the sign error in the docs is not as described or writing to the lower byte does update the sign flag.
Here I assume the sign flag gets updated. */
if (data)
m_sign_CD = 1;
break;
/* Writing to A will start a 16 bit multiply */
/* If we are going to perform a signed multiplication, we also store the sign and convert the
number to an unsigned one */
case MATH_A:
if (m_suzy.signed_math)
{
UINT16 factor, temp;
factor = m_suzy.data[MATH_B] | (m_suzy.data[MATH_A] << 8);
if ((factor - 1) & 0x8000) /* here we use -1 to cover the math bugs on the sign of 0 and 0x8000 */
{
temp = (factor ^ 0xffff) + 1;
m_sign_AB = - 1;
m_suzy.data[MATH_B] = temp & 0xff;
m_suzy.data[MATH_A] = temp >> 8;
}
else
m_sign_AB = 1;
}
lynx_multiply();
break;
/* Writing to E will start a 16 bit divide */
case MATH_E:
lynx_divide();
break;
case SPRCTL0:
m_blitter.spr_ctl0 = data;
break;
case SPRCTL1:
m_blitter.spr_ctl1 = data;
break;
case SPRCOLL:
m_blitter.spr_coll = data;
break;
case SUZYBUSEN:
logerror("write to SUSYBUSEN %x \n", data);
break;
case SPRSYS:
m_suzy.signed_math = (data & 0x80) ? 1:0;
m_suzy.accumulate = (data & 0x40) ? 1:0;
m_blitter.no_collide = (data & 0x20) ? 1:0;
m_blitter.vstretch = (data & 0x10) ? 1:0;
m_blitter.lefthanded = (data & 0x08) ? 1:0;
// unsafe access clear and sprite engine stop request are not enabled
if (data & 0x02) logerror("sprite engine stop request\n");
break;
case SPRGO:
if ((data & 0x01) && m_suzy.data[SUZYBUSEN])
{
//m_blitter.time = machine().time();
lynx_blitter(machine());
}
break;
case JOYSTICK:
case SWITCHES:
logerror("warning: write to read-only button registers\n");
break;
}
}
/****************************************
Mikey emulation
****************************************/
/*
0xfd0a r sync signal?
0xfd81 r interrupt source bit 2 vertical refresh
0xfd80 w interrupt quit
0xfd87 w bit 1 !clr bit 0 blocknumber clk
0xfd8b w bit 1 blocknumber hi B
0xfd94 w 0
0xfd95 w 4
0xfda0-f rw farben 0..15
0xfdb0-f rw bit0..3 farben 0..15
*/
/*
DISPCTL EQU $FD92 ; set to $D by INITMIKEY
; B7..B4 0
; B3 1 EQU color
; B2 1 EQU 4 bit mode
; B1 1 EQU flip screen
; B0 1 EQU video DMA enabled
*/
static void lynx_draw_line(running_machine &machine)
{
lynx_state *state = machine.driver_data<lynx_state>();
int x, y;
UINT16 j; // clipping needed!
UINT8 byte;
UINT16 *line;
// calculate y: first three lines are vblank,
y = 101-state->m_timer[2].counter;
// Documentation states lower two bits of buffer address are ignored (thus 0xfffc mask)
j = (state->m_mikey.disp_addr & 0xfffc) + y * 160 / 2;
if (state->m_mikey.data[0x92] & 0x02)
{
j -= 160 * 102 / 2 - 1;
line = &state->m_bitmap_temp.pix16(102 - 1 - y);
for (x = 160 - 2; x >= 0; j++, x -= 2)
{
byte = lynx_read_ram(state, j);
line[x + 1] = state->m_palette[(byte >> 4) & 0x0f];
line[x + 0] = state->m_palette[(byte >> 0) & 0x0f];
}
}
else
{
line = &state->m_bitmap_temp.pix16(y);
for (x = 0; x < 160; j++, x += 2)
{
byte = lynx_read_ram(state, j);
line[x + 0] = state->m_palette[(byte >> 4) & 0x0f];
line[x + 1] = state->m_palette[(byte >> 0) & 0x0f];
}
}
}
/****************************************
Timers
****************************************/
/*
HCOUNTER EQU TIMER0
VCOUNTER EQU TIMER2
SERIALRATE EQU TIMER4
TIM_BAKUP EQU 0 ; backup-value (count+1)
TIM_CNTRL1 EQU 1 ; timer-control register
TIM_CNT EQU 2 ; current counter
TIM_CNTRL2 EQU 3 ; dynamic control
; TIM_CNTRL1
TIM_IRQ EQU %10000000 ; enable interrupt (not TIMER4 !)
TIM_RESETDONE EQU %01000000 ; reset timer done
TIM_MAGMODE EQU %00100000 ; nonsense in Lynx !!
TIM_RELOAD EQU %00010000 ; enable reload
TIM_COUNT EQU %00001000 ; enable counter
TIM_LINK EQU %00000111
; link timers (0->2->4 / 1->3->5->7->Aud0->Aud1->Aud2->Aud3->1
TIM_64us EQU %00000110
TIM_32us EQU %00000101
TIM_16us EQU %00000100
TIM_8us EQU %00000011
TIM_4us EQU %00000010
TIM_2us EQU %00000001
TIM_1us EQU %00000000
;TIM_CNTRL2 (read-only)
; B7..B4 unused
TIM_DONE EQU %00001000 ; set if timer's done; reset with TIM_RESETDONE
TIM_LAST EQU %00000100 ; last clock (??)
TIM_BORROWIN EQU %00000010
TIM_BORROWOUT EQU %00000001
*/
#define NR_LYNX_TIMERS 8
static TIMER_CALLBACK(lynx_timer_shot);
static void lynx_timer_init(running_machine &machine, int which)
{
lynx_state *state = machine.driver_data<lynx_state>();
memset( &state->m_timer[which], 0, sizeof(LYNX_TIMER) );
state->m_timer[which].timer = machine.scheduler().timer_alloc(FUNC(lynx_timer_shot));
}
static void lynx_timer_signal_irq(running_machine &machine, int which)
{
lynx_state *state = machine.driver_data<lynx_state>();
if ( ( state->m_timer[which].cntrl1 & 0x80 ) && ( which != 4 ) ) // if interrupts are enabled and timer != 4
{
state->m_mikey.data[0x81] |= ( 1 << which ); // set interupt poll register
cputag_set_input_line(machine, "maincpu", INPUT_LINE_HALT, CLEAR_LINE);
cputag_set_input_line(machine, "maincpu", M65SC02_IRQ_LINE, ASSERT_LINE);
}
switch ( which ) // count down linked timers
{
case 0:
switch (state->m_timer[2].counter)
{
case 104:
break;
case 103:
state->m_mikey.vb_rest = 1;
break;
case 102:
state->m_mikey.disp_addr = state->m_mikey.data[0x94] | (state->m_mikey.data[0x95] << 8);
break;
case 101:
state->m_mikey.vb_rest = 0;
lynx_draw_line( machine );
break;
default:
lynx_draw_line( machine );
}
lynx_timer_count_down( machine, 2 );
break;
case 2:
copybitmap(state->m_bitmap, state->m_bitmap_temp, 0, 0, 0, 0, machine.primary_screen->cliprect());
lynx_timer_count_down( machine, 4 );
break;
case 1:
lynx_timer_count_down( machine, 3 );
break;
case 3:
lynx_timer_count_down( machine, 5 );
break;
case 5:
lynx_timer_count_down( machine, 7 );
break;
case 7:
lynx_audio_count_down( state->m_audio, 0 );
break;
}
}
void lynx_timer_count_down(running_machine &machine, int which)
{
lynx_state *state = machine.driver_data<lynx_state>();
if ( ( state->m_timer[which].cntrl1 & 0x0f ) == 0x0f ) // count and linking enabled
{
if ( state->m_timer[which].counter > 0 )
{
state->m_timer[which].counter--;
//state->m_timer[which].borrow_in = 1;
return;
}
if ( state->m_timer[which].counter == 0 )
{
if (state->m_timer[which].cntrl2 & 0x01) // borrow out
{
lynx_timer_signal_irq(machine, which);
if ( state->m_timer[which].cntrl1 & 0x10 ) // if reload enabled
{
state->m_timer[which].counter = state->m_timer[which].bakup;
}
else
{
state->m_timer[which].cntrl2 |= 8; // set timer done
}
state->m_timer[which].cntrl2 &= ~0x01; // clear borrow out
}
else
state->m_timer[which].cntrl2 |= 0x01; // set borrow out
return;
}
}
else
{
//state->m_timer[which].borrow_in = 0;
state->m_timer[which].cntrl2 &= ~0x01;
}
}
static UINT32 lynx_time_factor(int val)
{
switch(val)
{
case 0: return 1000000;
case 1: return 500000;
case 2: return 250000;
case 3: return 125000;
case 4: return 62500;
case 5: return 31250;
case 6: return 15625;
default: fatalerror("invalid value\n");
}
}
static TIMER_CALLBACK(lynx_timer_shot)
{
lynx_state *state = machine.driver_data<lynx_state>();
lynx_timer_signal_irq( machine, param );
if ( ! ( state->m_timer[param].cntrl1 & 0x10 ) ) // if reload not enabled
{
state->m_timer[param].timer_active = 0;
state->m_timer[param].cntrl2 |= 8; // set timer done
}
else
{
attotime t = (attotime::from_hz(lynx_time_factor(state->m_timer[param].cntrl1 & 0x07)) * (state->m_timer[param].bakup + 1));
state->m_timer[param].timer->adjust(t, param);
}
}
UINT8 lynx_state::lynx_timer_read(int which, int offset)
{
UINT8 value = 0;
switch (offset)
{
case 0:
value = m_timer[which].bakup;
break;
case 1:
value = m_timer[which].cntrl1;
break;
case 2:
if ((m_timer[which].cntrl1 & 0x07) == 0x07) // linked timer
{
value = m_timer[which].counter;
}
else
{
if ( m_timer[which].timer_active )
{
value = (UINT8) (m_timer[which].timer->remaining().as_ticks(1000000>>(m_timer[which].cntrl1 & 0x07)));
value -= value ? 1 : 0;
}
}
break;
case 3:
value = m_timer[which].cntrl2;
break;
}
// logerror("timer %d read %x %.2x\n", which, offset, value);
return value;
}
void lynx_state::lynx_timer_write(int which, int offset, UINT8 data)
{
//logerror("timer %d write %x %.2x\n", which, offset, data);
attotime t;
if ( m_timer[which].timer_active && ((m_timer[which].cntrl1 & 0x07) != 0x07))
{
m_timer[which].counter = (UINT8) (m_timer[which].timer->remaining().as_ticks(1000000>>(m_timer[which].cntrl1 & 0x07)));
m_timer[which].counter -= (m_timer[which].counter) ? 1 : 0;
}
switch (offset)
{
case 0:
m_timer[which].bakup = data;
break;
case 1:
m_timer[which].cntrl1 = data;
if (data & 0x40) // reset timer done
m_timer[which].cntrl2 &= ~0x08;
break;
case 2:
m_timer[which].counter = data;
break;
case 3:
m_timer[which].cntrl2 = (m_timer[which].cntrl2 & ~0x08) | (data & 0x08);
break;
}
/* Update timers */
//if ( offset < 3 )
//{
m_timer[which].timer->reset();
m_timer[which].timer_active = 0;
if ((m_timer[which].cntrl1 & 0x08) && !(m_timer[which].cntrl2 & 0x08)) // if enable count
{
if ((m_timer[which].cntrl1 & 0x07) != 0x07) // if not set to link mode
{
t = (attotime::from_hz(lynx_time_factor(m_timer[which].cntrl1 & 0x07)) * (m_timer[which].counter + 1));
m_timer[which].timer->adjust(t, which);
m_timer[which].timer_active = 1;
}
}
//}
}
/****************************************
UART Emulation
****************************************/
static void lynx_uart_reset(lynx_state *state)
{
memset(&state->m_uart, 0, sizeof(state->m_uart));
}
static TIMER_CALLBACK(lynx_uart_loopback_timer)
{
lynx_state *state = machine.driver_data<lynx_state>();
state->m_uart.received = FALSE;
}
static TIMER_CALLBACK(lynx_uart_timer)
{
lynx_state *state = machine.driver_data<lynx_state>();
if (state->m_uart.buffer_loaded)
{
state->m_uart.data_to_send = state->m_uart.buffer;
state->m_uart.buffer_loaded = FALSE;
machine.scheduler().timer_set(attotime::from_usec(11*16), FUNC(lynx_uart_timer));
}
else
{
state->m_uart.sending = FALSE;
state->m_uart.received = TRUE;
state->m_uart.data_received = state->m_uart.data_to_send;
machine.scheduler().timer_set(attotime::from_usec(11*16), FUNC(lynx_uart_loopback_timer));
if (state->m_uart.serctl & 0x40)
{
state->m_mikey.data[0x81] |= 0x10;
cputag_set_input_line(machine, "maincpu", INPUT_LINE_HALT, CLEAR_LINE);
cputag_set_input_line(machine, "maincpu", M65SC02_IRQ_LINE, ASSERT_LINE);
}
}
if (state->m_uart.serctl & 0x80)
{
state->m_mikey.data[0x81] |= 0x10;
cputag_set_input_line(machine, "maincpu", INPUT_LINE_HALT, CLEAR_LINE);
cputag_set_input_line(machine, "maincpu", M65SC02_IRQ_LINE, ASSERT_LINE);
}
}
static READ8_HANDLER(lynx_uart_r)
{
lynx_state *state = space->machine().driver_data<lynx_state>();
UINT8 value = 0x00;
switch (offset)
{
case 0x8c:
if (!state->m_uart.buffer_loaded)
value |= 0x80;
if (state->m_uart.received)
value |= 0x40;
if (!state->m_uart.sending)
value |= 0x20;
break;
case 0x8d:
value = state->m_uart.data_received;
break;
}
logerror("uart read %.2x %.2x\n", offset, value);
return value;
}
WRITE8_MEMBER(lynx_state::lynx_uart_w)
{
logerror("uart write %.2x %.2x\n", offset, data);
switch (offset)
{
case 0x8c:
m_uart.serctl = data;
break;
case 0x8d:
if (m_uart.sending)
{
m_uart.buffer = data;
m_uart.buffer_loaded = TRUE;
}
else
{
m_uart.sending = TRUE;
m_uart.data_to_send = data;
// timing not accurate, baude rate should be calculated from timer 4 backup value and clock rate
machine().scheduler().timer_set(attotime::from_usec(11*16), FUNC(lynx_uart_timer));
}
break;
}
}
/****************************************
Mikey memory handlers
****************************************/
READ8_MEMBER(lynx_state::mikey_read)
{
UINT8 direction, value = 0x00;
switch (offset)
{
case 0x00: case 0x01: case 0x02: case 0x03:
case 0x04: case 0x05: case 0x06: case 0x07:
case 0x08: case 0x09: case 0x0a: case 0x0b:
case 0x0c: case 0x0d: case 0x0e: case 0x0f:
case 0x10: case 0x11: case 0x12: case 0x13:
case 0x14: case 0x15: case 0x16: case 0x17:
case 0x18: case 0x19: case 0x1a: case 0x1b:
case 0x1c: case 0x1d: case 0x1e: case 0x1f:
value = lynx_timer_read(offset >> 2, offset & 0x03);
break;
case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f:
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f:
case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x50:
value = lynx_audio_read(m_audio, offset);
break;
case 0x80:
case 0x81:
value = m_mikey.data[0x81]; // both registers access the same interupt status byte
// logerror( "mikey read %.2x %.2x\n", offset, value );
break;
case 0x84:
case 0x85:
value = 0x00;
break;
case 0x86:
value = 0x80;
break;
case 0x88:
value = 0x01;
break;
case 0x8b:
direction = m_mikey.data[0x8a];
value |= (direction & 0x01) ? (m_mikey.data[offset] & 0x01) : 0x01; // External Power input
value |= (direction & 0x02) ? (m_mikey.data[offset] & 0x02) : 0x00; // Cart Address Data output (0 turns cart power on)
value |= (direction & 0x04) ? (m_mikey.data[offset] & 0x04) : 0x04; // noexp input
// REST read returns actual rest state anded with rest output bit
value |= (direction & 0x08) ? (((m_mikey.data[offset] & 0x08) && (m_mikey.vb_rest)) ? 0x00 : 0x08) : 0x00; // rest output
value |= (direction & 0x10) ? (m_mikey.data[offset] & 0x10) : 0x10; // audin input
/* Hack: we disable COMLynx */
value |= 0x04;
/* B5, B6 & B7 are not used */
break;
case 0x8c:
case 0x8d:
value = lynx_uart_r(&space, offset);
break;
default:
value = m_mikey.data[offset];
//logerror( "mikey read %.2x %.2x\n", offset, value );
}
return value;
}
WRITE8_MEMBER(lynx_state::mikey_write)
{
switch (offset)
{
case 0x00: case 0x01: case 0x02: case 0x03:
case 0x04: case 0x05: case 0x06: case 0x07:
case 0x08: case 0x09: case 0x0a: case 0x0b:
case 0x0c: case 0x0d: case 0x0e: case 0x0f:
case 0x10: case 0x11: case 0x12: case 0x13:
case 0x14: case 0x15: case 0x16: case 0x17:
case 0x18: case 0x19: case 0x1a: case 0x1b:
case 0x1c: case 0x1d: case 0x1e: case 0x1f:
lynx_timer_write(offset >> 2, offset & 3, data);
return;
case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f:
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f:
case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x50:
lynx_audio_write(m_audio, offset, data);
return;
case 0x80:
m_mikey.data[0x81] &= ~data; // clear interrupt source
// logerror("mikey write %.2x %.2x\n", offset, data);
if (!m_mikey.data[0x81])
cputag_set_input_line(machine(), "maincpu", M65SC02_IRQ_LINE, CLEAR_LINE);
break;
/* Is this correct? */ // Notes say writing to register will result in interupt being triggered.
case 0x81:
m_mikey.data[0x81] |= data;
if (data)
{
cputag_set_input_line(machine(), "maincpu", INPUT_LINE_HALT, CLEAR_LINE);
cputag_set_input_line(machine(), "maincpu", M65SC02_IRQ_LINE, ASSERT_LINE);
logerror("direct write to interupt register\n");
}
break;
case 0x87:
m_mikey.data[offset] = data;
if (data & 0x02) // Power (1 = on)
{
if (data & 0x01) // Cart Address Strobe
{
m_suzy.high <<= 1;
if (m_mikey.data[0x8b] & 0x02)
m_suzy.high |= 1;
m_suzy.high &= 0xff;
m_suzy.low = 0;
}
}
else
{
m_suzy.high = 0;
m_suzy.low = 0;
}
break;
case 0x8c: case 0x8d:
lynx_uart_w(space, offset, data);
break;
case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7:
case 0xa8: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf:
case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7:
case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf:
m_mikey.data[offset] = data;
/* RED = 0xb- & 0x0f, GREEN = 0xa- & 0x0f, BLUE = (0xb- & 0xf0) >> 4 */
m_palette[offset & 0x0f] = machine().pens[
((m_mikey.data[0xb0 + (offset & 0x0f)] & 0x0f)) |
((m_mikey.data[0xa0 + (offset & 0x0f)] & 0x0f) << 4) |
((m_mikey.data[0xb0 + (offset & 0x0f)] & 0xf0) << 4)];
break;
/* TODO: properly implement these writes */
case 0x8b:
m_mikey.data[offset] = data;
if (m_mikey.data[0x8a] & 0x10)
logerror("Trying to enable bank 1 write. %d\n", m_mikey.data[offset] & 0x10);
break;
//case 0x90: // SDONEACK - Suzy Done Acknowledge
case 0x91: // CPUSLEEP - CPU Bus Request Disable
m_mikey.data[offset] = data;
if (!data && m_blitter.busy)
{
cputag_set_input_line(machine(), "maincpu", INPUT_LINE_HALT, ASSERT_LINE);
/* A write of '0' to this address will reset the CPU bus request flip flop */
}
break;
case 0x94: case 0x95:
m_mikey.data[offset]=data;
break;
case 0x9c: case 0x9d: case 0x9e:
m_mikey.data[offset]=data;
logerror("Mtest%d write: %x\n", offset&0x3, data);
break;
default:
m_mikey.data[offset]=data;
//logerror("mikey write %.2x %.2x\n",offset,data);
break;
}
}
/****************************************
Init / Config
****************************************/
READ8_MEMBER(lynx_state::lynx_memory_config_r)
{
return m_memory_config;
}
WRITE8_MEMBER(lynx_state::lynx_memory_config_w)
{
/* bit 7: hispeed, uses page mode accesses (4 instead of 5 cycles )
* when these are safe in the cpu */
m_memory_config = data;
if (data & 1) {
space.install_readwrite_bank(0xfc00, 0xfcff, "bank1");
} else {
space.install_readwrite_handler(0xfc00, 0xfcff, read8_delegate(FUNC(lynx_state::suzy_read),this), write8_delegate(FUNC(lynx_state::suzy_write),this));
}
if (data & 2) {
space.install_readwrite_bank(0xfd00, 0xfdff, "bank2");
} else {
space.install_readwrite_handler(0xfd00, 0xfdff, read8_delegate(FUNC(lynx_state::mikey_read),this), write8_delegate(FUNC(lynx_state::mikey_write),this));
}
if (data & 1)
membank("bank1")->set_base(m_mem_fc00);
if (data & 2)
membank("bank2")->set_base(m_mem_fd00);
membank("bank3")->set_entry((data & 4) ? 1 : 0);
membank("bank4")->set_entry((data & 8) ? 1 : 0);
}
static void lynx_reset(running_machine &machine)
{
lynx_state *state = machine.driver_data<lynx_state>();
state->lynx_memory_config_w(*machine.device("maincpu")->memory().space(AS_PROGRAM), 0, 0);
cputag_set_input_line(machine, "maincpu", INPUT_LINE_HALT, CLEAR_LINE);
cputag_set_input_line(machine, "maincpu", M65SC02_IRQ_LINE, CLEAR_LINE);
memset(&state->m_suzy, 0, sizeof(state->m_suzy));
memset(&state->m_mikey, 0, sizeof(state->m_mikey));
state->m_suzy.data[0x88] = 0x01;
state->m_suzy.data[0x90] = 0x00;
state->m_suzy.data[0x91] = 0x00;
state->m_mikey.data[0x80] = 0x00;
state->m_mikey.data[0x81] = 0x00;
state->m_mikey.data[0x88] = 0x01;
state->m_mikey.data[0x8a] = 0x00;
state->m_mikey.data[0x8c] = 0x00;
state->m_mikey.data[0x90] = 0x00;
state->m_mikey.data[0x92] = 0x00;
lynx_uart_reset(state);
// hack to allow current object loading to work
#if 0
lynx_timer_write( state, 0, 0, 160 ); // set backup value (hpos) = 160
lynx_timer_write( state, 0, 1, 0x10 | 0x8 | 0 ); // enable count, enable reload, 1us period
lynx_timer_write( state, 2, 0, 105 ); // set backup value (vpos) = 102
lynx_timer_write( state, 2, 1, 0x10 | 0x8 | 7 ); // enable count, enable reload, link
#endif
}
static void lynx_postload(lynx_state *state)
{
state->lynx_memory_config_w( *state->machine().device("maincpu")->memory().space(AS_PROGRAM), 0, state->m_memory_config);
}
MACHINE_START( lynx )
{
lynx_state *state = machine.driver_data<lynx_state>();
state->m_bitmap_temp.allocate(160,102,0,0);
int i;
state->save_item(NAME(state->m_memory_config));
state->save_pointer(NAME(state->m_mem_fe00.target()), state->m_mem_fe00.bytes());
machine.save().register_postload(save_prepost_delegate(FUNC(lynx_postload), state));
state->membank("bank3")->configure_entry(0, machine.root_device().memregion("maincpu")->base() + 0x0000);
state->membank("bank3")->configure_entry(1, state->m_mem_fe00);
state->membank("bank4")->configure_entry(0, state->memregion("maincpu")->base() + 0x01fa);
state->membank("bank4")->configure_entry(1, state->m_mem_fffa);
state->m_audio = machine.device("custom");
memset(&state->m_suzy, 0, sizeof(state->m_suzy));
machine.add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(FUNC(lynx_reset),&machine));
for (i = 0; i < NR_LYNX_TIMERS; i++)
lynx_timer_init(machine, i);
}
MACHINE_RESET( lynx )
{
lynx_state *state = machine.driver_data<lynx_state>();
render_target *target = machine.render().first_target();
target->set_view(state->m_rotate);
}
/****************************************
Image handling
****************************************/
static void lynx_partialhash(hash_collection &dest, const unsigned char *data,
unsigned long length, const char *functions)
{
if (length <= 64)
return;
dest.compute(&data[64], length - 64, functions);
}
int lynx_verify_cart (char *header, int kind)
{
if (kind)
{
if (strncmp("BS93", &header[6], 4))
{
logerror("This is not a valid Lynx image\n");
return IMAGE_VERIFY_FAIL;
}
}
else
{
if (strncmp("LYNX",&header[0],4))
{
if (!strncmp("BS93", &header[6], 4))
{
logerror("This image is probably a Quickload image with .lnx extension\n");
logerror("Try to load it with -quickload\n");
}
else
logerror("This is not a valid Lynx image\n");
return IMAGE_VERIFY_FAIL;
}
}
return IMAGE_VERIFY_PASS;
}
static DEVICE_IMAGE_LOAD( lynx_cart )
{
/* Lynx carts have 19 address lines, the upper 8 used for bank select. The lower
11 bits are used to address data within the selected bank. Valid bank sizes are 256,
512, 1024 or 2048 bytes. Commercial roms use all 256 banks.*/
lynx_state *state = image.device().machine().driver_data<lynx_state>();
UINT8 *rom = state->memregion("user1")->base();
UINT32 size;
UINT8 header[0x40];
if (image.software_entry() == NULL)
{
const char *filetype;
size = image.length();
/* 64 byte header
LYNX
intelword lower counter size
0 0 1 0
32 chars name
22 chars manufacturer
*/
filetype = image.filetype();
if (!mame_stricmp (filetype, "lnx"))
{
if (image.fread( header, 0x40) != 0x40)
return IMAGE_INIT_FAIL;
/* Check the image */
if (lynx_verify_cart((char*)header, LYNX_CART) == IMAGE_VERIFY_FAIL)
return IMAGE_INIT_FAIL;
/* 2008-10 FP: According to Handy source these should be page_size_bank0. Are we using
it correctly in MESS? Moreover, the next two values should be page_size_bank1. We should
implement this as well */
state->m_granularity = header[4] | (header[5] << 8);
logerror ("%s %dkb cartridge with %dbyte granularity from %s\n",
header + 10, size / 1024, state->m_granularity, header + 42);
size -= 0x40;
}
else if (!mame_stricmp (filetype, "lyx"))
{
/* 2008-10 FP: FIXME: .lyx file don't have an header, hence they miss "lynx_granularity"
(see above). What if bank 0 has to be loaded elsewhere? And what about bank 1?
These should work with most .lyx files, but we need additional info on raw cart images */
if (size == 0x20000)
state->m_granularity = 0x0200;
else if (size == 0x80000)
state->m_granularity = 0x0800;
else
state->m_granularity = 0x0400;
}
if (image.fread( rom, size) != size)
return IMAGE_INIT_FAIL;
}
else
{
size = image.get_software_region_length("rom");
if (size > 0xffff) // 64,128,256,512k cartridges
state->m_granularity = size >> 8;
else
state->m_granularity = 0x400; // Homebrew roms not using all 256 banks (T-Tris) (none currently in softlist)
memcpy(rom, image.get_software_region("rom"), size);
const char *rotate = image.get_feature("rotation");
state->m_rotate = 0;
if (rotate)
{
if(strcmp(rotate, "RIGHT") == 0) {
state->m_rotate = 1;
}
else if (strcmp(rotate, "LEFT") == 0) {
state->m_rotate = 2;
}
}
}
return IMAGE_INIT_PASS;
}
MACHINE_CONFIG_FRAGMENT(lynx_cartslot)
MCFG_CARTSLOT_ADD("cart")
MCFG_CARTSLOT_EXTENSION_LIST("lnx,lyx")
MCFG_CARTSLOT_MANDATORY
MCFG_CARTSLOT_INTERFACE("lynx_cart")
MCFG_CARTSLOT_LOAD(lynx_cart)
MCFG_CARTSLOT_PARTIALHASH(lynx_partialhash)
/* Software lists */
MCFG_SOFTWARE_LIST_ADD("cart_list","lynx")
MACHINE_CONFIG_END