summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/machine/lynx.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mame/machine/lynx.cpp')
-rw-r--r--src/mame/machine/lynx.cpp2144
1 files changed, 2144 insertions, 0 deletions
diff --git a/src/mame/machine/lynx.cpp b/src/mame/machine/lynx.cpp
new file mode 100644
index 00000000000..14ebecf2bbf
--- /dev/null
+++ b/src/mame/machine/lynx.cpp
@@ -0,0 +1,2144 @@
+// license:GPL-2.0+
+// copyright-holders:Peter Trauner
+/******************************************************************************
+ PeT mess@utanet.at 2000,2001
+******************************************************************************/
+
+#include "emu.h"
+#include "includes/lynx.h"
+#include "cpu/m6502/m65sc02.h"
+#include "render.h"
+
+#define PAD_UP 0x80
+#define PAD_DOWN 0x40
+#define PAD_LEFT 0x20
+#define PAD_RIGHT 0x10
+
+
+/****************************************
+
+ Graphics Drawing
+
+****************************************/
+
+/* modes from blitter command */
+enum {
+ BACKGROUND = 0,
+ BACKGROUND_NO_COLL,
+ BOUNDARY_SHADOW,
+ BOUNDARY,
+ NORMAL_SPRITE,
+ NO_COLL,
+ XOR_SPRITE,
+ SHADOW
+};
+
+UINT8 lynx_state::lynx_read_ram(UINT16 address)
+{
+ UINT8 result = 0x00;
+ if (address <= 0xfbff)
+ result = m_mem_0000[address - 0x0000];
+ else if (address <= 0xfcff)
+ result = m_mem_fc00[address - 0xfc00];
+ else if (address <= 0xfdff)
+ result = m_mem_fd00[address - 0xfd00];
+ else if (address <= 0xfff7)
+ result = m_mem_fe00[address - 0xfe00];
+ else if (address >= 0xfffa)
+ result = m_mem_fffa[address - 0xfffa];
+ return result;
+}
+
+void lynx_state::lynx_write_ram(UINT16 address, UINT8 data)
+{
+ if (address <= 0xfbff)
+ m_mem_0000[address - 0x0000] = data;
+ else if (address <= 0xfcff)
+ m_mem_fc00[address - 0xfc00] = data;
+ else if (address <= 0xfdff)
+ m_mem_fd00[address - 0xfd00] = data;
+ else if (address <= 0xfff7)
+ m_mem_fe00[address - 0xfe00] = data;
+ else if (address >= 0xfffa)
+ 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_state::lynx_plot_pixel(const int mode, const INT16 x, const int y, const int color)
+{
+ UINT8 back;
+ UINT16 screen;
+ UINT16 colbuf;
+
+ m_blitter.everon = TRUE;
+ screen = m_blitter.screen + y * 80 + x / 2;
+ colbuf = 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(screen);
+ lynx_write_ram(screen, (back & 0x0f) | (color << 4));
+ m_blitter.memory_accesses++;
+
+ if(m_blitter.sprite_collide && !(m_blitter.no_collide))
+ {
+ back = lynx_read_ram(colbuf);
+ lynx_write_ram(colbuf, (back & ~0xf0) | (m_blitter.spritenr << 4));
+ m_blitter.memory_accesses += 2;
+ if ((back >> 4) > m_blitter.fred)
+ m_blitter.fred = back >> 4;
+ }
+ m_blitter.memory_accesses++;
+ }
+ else /* Lower nibble */
+ {
+ back = lynx_read_ram(screen);
+ lynx_write_ram(screen, (back & 0xf0) | color);
+
+ if(m_blitter.sprite_collide && !(m_blitter.no_collide))
+ {
+ back = lynx_read_ram(colbuf);
+ lynx_write_ram(colbuf, (back & ~0x0f) | (m_blitter.spritenr));
+ if ((back & 0x0f) > m_blitter.fred)
+ 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(screen);
+ lynx_write_ram(screen, (back & 0x0f) | (color << 4));
+ m_blitter.memory_accesses++;
+ }
+ if(m_blitter.sprite_collide && !(m_blitter.no_collide))
+ {
+ back = lynx_read_ram(colbuf);
+ lynx_write_ram(colbuf, (back & ~0xf0) | (m_blitter.spritenr << 4));
+ if ((back >> 4) > m_blitter.fred)
+ m_blitter.fred = back >> 4;
+ m_blitter.memory_accesses += 2;
+ }
+ m_blitter.memory_accesses++;
+ }
+ else /* Lower nibble */
+ {
+ if (color != 0x0f)
+ {
+ back = lynx_read_ram(screen);
+ lynx_write_ram(screen, (back & 0xf0) | color);
+ }
+ if(m_blitter.sprite_collide && !(m_blitter.no_collide))
+ {
+ back = lynx_read_ram(colbuf);
+ lynx_write_ram(colbuf, (back & ~0x0f) | (m_blitter.spritenr));
+ if ((back & 0x0f) > m_blitter.fred)
+ 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(screen);
+ lynx_write_ram(screen, (back & 0x0f) | (color << 4));
+ m_blitter.memory_accesses++;
+
+ if (m_blitter.sprite_collide && (color != 0x0e) && !(m_blitter.no_collide))
+ {
+ back = lynx_read_ram(colbuf);
+ lynx_write_ram(colbuf, (back & ~0xf0) | (m_blitter.spritenr << 4));
+ if ((back >> 4) > m_blitter.fred)
+ m_blitter.fred = back >> 4;
+ m_blitter.memory_accesses += 2;
+ }
+ m_blitter.memory_accesses++;
+ }
+ else /* Lower nibble */
+ {
+ back = lynx_read_ram(screen);
+ lynx_write_ram(screen, (back & 0xf0) | color);
+
+ if (m_blitter.sprite_collide && (color != 0x0e) && !(m_blitter.no_collide))
+ {
+ back = lynx_read_ram(colbuf);
+ lynx_write_ram(colbuf, (back & ~0x0f) | (m_blitter.spritenr));
+ if ((back & 0x0f) > m_blitter.fred)
+ 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(screen);
+ lynx_write_ram(screen, (back & 0x0f) | (color << 4));
+ m_blitter.memory_accesses++;
+ }
+ if (m_blitter.sprite_collide && (color != 0x0e) && !(m_blitter.no_collide))
+ {
+ back = lynx_read_ram(colbuf);
+ lynx_write_ram(colbuf, (back & ~0xf0) | (m_blitter.spritenr << 4));
+ if ((back >> 4) > m_blitter.fred)
+ m_blitter.fred = back >> 4;
+ m_blitter.memory_accesses += 2;
+ }
+ m_blitter.memory_accesses++;
+ }
+ else /* Lower nibble */
+ {
+ if (color != 0x0f)
+ {
+ back = lynx_read_ram(screen);
+ lynx_write_ram(screen, (back & 0xf0) | color);
+ }
+ if (m_blitter.sprite_collide && (color != 0x0e) && !(m_blitter.no_collide))
+ {
+ back = lynx_read_ram(colbuf);
+ lynx_write_ram(colbuf, (back & ~0x0f) | (m_blitter.spritenr));
+ if ((back & 0x0f) > m_blitter.fred)
+ 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(screen);
+ lynx_write_ram(screen, (back & 0x0f) | (color << 4));
+ m_blitter.memory_accesses++;
+
+ if (m_blitter.sprite_collide && (color != 0x0e) && !(m_blitter.no_collide))
+ {
+ back = lynx_read_ram(colbuf);
+ lynx_write_ram(colbuf, (back & ~0xf0) | (m_blitter.spritenr << 4));
+ m_blitter.memory_accesses++;
+ }
+ m_blitter.memory_accesses++;
+ }
+ else /* Lower nibble */
+ {
+ back = lynx_read_ram(screen);
+ lynx_write_ram(screen, (back & 0xf0) | color);
+
+ if (m_blitter.sprite_collide && (color != 0x0e) && !(m_blitter.no_collide))
+ {
+ back = lynx_read_ram(colbuf);
+ lynx_write_ram(colbuf, (back & ~0x0f) | (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(screen);
+ lynx_write_ram(screen, (back & 0x0f) | (color << 4));
+ m_blitter.memory_accesses++;
+ m_blitter.memory_accesses++;
+ }
+ else /* Lower nibble */
+ {
+ back = lynx_read_ram(screen);
+ lynx_write_ram(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(screen);
+ lynx_write_ram(screen, (back & 0x0f) | (color << 4));
+ m_blitter.memory_accesses++;
+ m_blitter.memory_accesses++;
+ }
+ else /* Lower nibble */
+ {
+ back = lynx_read_ram(screen);
+ lynx_write_ram(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(screen);
+ lynx_write_ram(screen, back^(color << 4));
+ m_blitter.memory_accesses += 2;
+ if (m_blitter.sprite_collide && (color != 0x0e) && !(m_blitter.no_collide))
+ {
+ back = lynx_read_ram(colbuf);
+ lynx_write_ram(colbuf, (back & ~0xf0) | (m_blitter.spritenr << 4));
+ if ((back >> 4) > m_blitter.fred)
+ m_blitter.fred = back >> 4;
+ m_blitter.memory_accesses += 2;
+ }
+ m_blitter.memory_accesses++;
+ }
+ else /* Lower nibble */
+ {
+ back = lynx_read_ram(screen);
+ lynx_write_ram(screen, back^color);
+ if (m_blitter.sprite_collide && (color != 0x0e) && !(m_blitter.no_collide))
+ {
+ back = lynx_read_ram(colbuf);
+ lynx_write_ram(colbuf, (back & ~0x0f) | (m_blitter.spritenr));
+ if ((back & 0x0f) > m_blitter.fred)
+ m_blitter.fred = back >> 4;
+ }
+ }
+ break;
+ }
+}
+
+void lynx_state::lynx_blit_do_work( 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(m_blitter.bitmap); // offset to second sprite line
+ width_accum = (xdir == 1) ? m_blitter.width_offset : 0;
+ m_blitter.memory_accesses++;
+
+ for (xi = m_blitter.x_pos - m_blitter.xoff, bits = 0, buffer = 0, j = 1; j < next_line_addr;j++)
+ {
+ buffer = (buffer << 8) | lynx_read_ram(m_blitter.bitmap + j);
+ bits += 8; // current bits in buffer
+ 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 = m_blitter.color[(buffer >> (bits - bits_per_pixel)) & mask];
+ width_accum += m_blitter.width;
+ for (i = 0; i < (width_accum>>8); i++, xi += xdir)
+ {
+ if ((xi >= 0) && (xi < 160))
+ {
+ lynx_plot_pixel(m_blitter.mode, xi, y, color);
+ }
+ }
+ width_accum &= 0xff;
+ }
+ }
+}
+
+void lynx_state::lynx_blit_rle_do_work( 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) ? m_blitter.width_offset : 0;
+ for( bits = 0, j = 0, buffer = 0, xi = m_blitter.x_pos - 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(m_blitter.bitmap))
+ return;
+
+ bits += 8;
+ buffer = (buffer << 8) | lynx_read_ram(m_blitter.bitmap + j);
+ 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(m_blitter.bitmap))
+ return;
+ bits += 8;
+ buffer = (buffer << 8) | lynx_read_ram(m_blitter.bitmap + j);
+ m_blitter.memory_accesses++;
+ }
+
+ color = m_blitter.color[(buffer >> (bits - bits_per_pixel)) & mask];
+ bits -= bits_per_pixel;
+ width_accum += m_blitter.width;
+ for (i = 0; i < (width_accum>>8); i++, xi += xdir)
+ {
+ if ((xi >= 0) && (xi < 160))
+ lynx_plot_pixel(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(m_blitter.bitmap))
+ return;
+ bits += 8;
+ buffer = (buffer << 8) | lynx_read_ram(m_blitter.bitmap + j);
+ m_blitter.memory_accesses++;
+ }
+
+ color = m_blitter.color[(buffer >> (bits - bits_per_pixel)) & mask];
+ bits -= bits_per_pixel;
+
+ for ( ; count>=0; count--)
+ {
+ width_accum += m_blitter.width;
+ for (i = 0; i < (width_accum >> 8); i++, xi += xdir)
+ {
+ if ((xi >= 0) && (xi < 160))
+ lynx_plot_pixel(m_blitter.mode, xi, y, color);
+ }
+ width_accum &= 0xff;
+ }
+ }
+ }
+}
+
+void lynx_state::lynx_blit_lines()
+{
+ static const int lynx_color_masks[4] = { 0x01, 0x03, 0x07, 0x0f };
+ INT16 y;
+ int i;
+ int ydir = 0, xdir = 0;
+ int flip = 0;
+
+ m_blitter.everon = FALSE;
+
+ switch (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 (m_blitter.spr_ctl0 & 0x20) /* Horizontal Flip */
+ {
+ xdir *= -1;
+ }
+
+ if (m_blitter.spr_ctl0 & 0x10) /* Vertical Flip */
+ {
+ ydir *= -1;
+ }
+
+ // Set height accumulator based on drawing direction
+ m_blitter.height_accumulator = (ydir == 1) ? m_blitter.height_offset : 0x00;
+
+ // loop through lines, next line offset of zero indicates end of sprite
+ for (y = m_blitter.y_pos - m_blitter.yoff; (i = lynx_read_ram(m_blitter.bitmap)); m_blitter.bitmap += i)
+ {
+ 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;
+ m_blitter.y_pos += ydir;
+ break;
+ case 1:
+ case 3:
+ xdir *= -1;
+ m_blitter.x_pos += xdir;
+ break;
+ }
+ flip++;
+ y = m_blitter.y_pos - m_blitter.yoff;
+ m_blitter.height_accumulator = (ydir == 1) ? m_blitter.height_offset : 0x00;
+ continue;
+ }
+
+ m_blitter.height_accumulator += m_blitter.height;
+ for (int j = 0; j < (m_blitter.height_accumulator >> 8); j++, y += ydir)
+ {
+ if (y >= 0 && y < 102)
+ {
+ if (m_blitter.use_rle)
+ lynx_blit_rle_do_work(y, xdir, m_blitter.line_color + 1, lynx_color_masks[m_blitter.line_color]);
+ else
+ lynx_blit_do_work(y, xdir, m_blitter.line_color + 1, lynx_color_masks[m_blitter.line_color]);
+ }
+ m_blitter.width += (INT16)m_blitter.stretch;
+ if (m_blitter.vstretch) // doesn't seem to be used
+ {
+ m_blitter.height += (INT16)m_blitter.stretch;
+ logerror("vertical stretch enabled");
+ }
+ m_blitter.tilt_accumulator += m_blitter.tilt;
+ m_blitter.x_pos += (m_blitter.tilt_accumulator>>8);
+ m_blitter.tilt_accumulator &= 0xff;
+ }
+ m_blitter.height_accumulator &= 0xff;
+ }
+}
+
+void lynx_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
+{
+ switch (id)
+ {
+ case TIMER_BLITTER:
+ lynx_blitter_timer(ptr, param);
+ break;
+ case TIMER_SHOT:
+ lynx_timer_shot(ptr, param);
+ break;
+ case TIMER_UART_LOOPBACK:
+ lynx_uart_loopback_timer(ptr, param);
+ break;
+ case TIMER_UART:
+ lynx_uart_timer(ptr, param);
+ break;
+ default:
+ assert_always(FALSE, "Unknown id in lynx_state::device_timer");
+ }
+}
+
+TIMER_CALLBACK_MEMBER(lynx_state::lynx_blitter_timer)
+{
+ m_blitter.busy=0; // blitter finished
+ m_maincpu->set_input_line(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
+
+*/
+
+void lynx_state::lynx_blitter()
+{
+ static const int lynx_colors[4] = { 2, 4, 8, 16 };
+ UINT8 palette_offset;
+ UINT8 coldep;
+ int colors;
+
+ m_blitter.busy = 1; // blitter working
+ m_blitter.memory_accesses = 0;
+
+ // Last SCB is indicated by zero in the high byte of SCBNEXT
+ while (m_blitter.scb_next & 0xff00)
+ {
+ m_blitter.stretch = 0;
+ m_blitter.tilt = 0;
+ m_blitter.tilt_accumulator = 0;
+
+ m_blitter.scb = m_blitter.scb_next; // current scb
+ m_blitter.scb_next = lynx_read_ram(m_blitter.scb + SCB_SCBNEXT) | (lynx_read_ram(m_blitter.scb + SCB_SCBNEXT + 1) << 8); // next scb
+ m_blitter.spr_ctl0 = lynx_read_ram(m_blitter.scb + SCB_SPRCTL0);
+ m_blitter.spr_ctl1 = lynx_read_ram(m_blitter.scb + SCB_SPRCTL1);
+ m_blitter.spr_coll = lynx_read_ram(m_blitter.scb + SCB_SPRCOLL);
+ m_blitter.memory_accesses += 5;
+
+ if(!(m_blitter.spr_ctl1 & 0x04)) // sprite will be processed (if sprite is skipped first 5 bytes are still copied to suzy)
+ {
+ m_blitter.bitmap = lynx_read_ram(m_blitter.scb + SCB_SPRDLINE) | (lynx_read_ram(m_blitter.scb + SCB_SPRDLINE + 1) << 8);
+ m_blitter.x_pos = lynx_read_ram(m_blitter.scb + SCB_HPOSSTRT) | (lynx_read_ram(m_blitter.scb + SCB_HPOSSTRT + 1) << 8);
+ m_blitter.y_pos = lynx_read_ram(m_blitter.scb + SCB_VPOSSTRT) | (lynx_read_ram(m_blitter.scb + SCB_VPOSSTRT + 1) << 8);
+ m_blitter.memory_accesses += 6;
+
+ switch(m_blitter.spr_ctl1 & 0x30) // reload sprite scaling
+ {
+ case 0x30: // width, height, tilt, stretch
+ m_blitter.tilt = lynx_read_ram(m_blitter.scb + SCB_TILT) | (lynx_read_ram(m_blitter.scb + SCB_TILT + 1) << 8);
+ m_blitter.memory_accesses+=2;
+ case 0x20: // width, height, stretch
+ m_blitter.stretch = lynx_read_ram(m_blitter.scb + SCB_STRETCH) | (lynx_read_ram(m_blitter.scb + SCB_STRETCH + 1) << 8);
+ m_blitter.memory_accesses+=2;
+ case 0x10: // width, height
+ m_blitter.width = lynx_read_ram(m_blitter.scb + SCB_SPRHSIZ) | (lynx_read_ram(m_blitter.scb + SCB_SPRHSIZ + 1) << 8);
+ m_blitter.height = lynx_read_ram(m_blitter.scb + SCB_SPRVSIZ) | (lynx_read_ram(m_blitter.scb + SCB_SPRVSIZ + 1) << 8);
+ m_blitter.memory_accesses+=4;
+ }
+
+ if(!(m_blitter.spr_ctl1 & 0x08)) // reload palette
+ {
+ if (m_blitter.spr_ctl1 & 0x30)
+ palette_offset = 0x0b + 2*(((m_blitter.spr_ctl1 & 0x30)>>4) + 1); // palette data offset depends on width, height, etc. reloading
+ else
+ palette_offset = 0x0b;
+
+ colors = lynx_colors[m_blitter.spr_ctl0 >> 6];
+
+ for (int i = 0; i < colors / 2; i++)
+ {
+ m_blitter.color[i * 2] = lynx_read_ram(m_blitter.scb + palette_offset + i) >> 4;
+ m_blitter.color[i * 2 + 1 ] = lynx_read_ram(m_blitter.scb + palette_offset + i) & 0x0f;
+ m_blitter.memory_accesses++;
+ }
+ }
+ }
+
+
+ if (!(m_blitter.spr_ctl1 & 0x04)) // if 0, we skip this sprite
+ {
+ m_blitter.colpos = m_blitter.scb + (m_suzy.data[COLLOFFL] | (m_suzy.data[COLLOFFH]<<8));
+ m_blitter.mode = m_blitter.spr_ctl0 & 0x07;
+ m_blitter.use_rle = m_blitter.spr_ctl1 & 0x80 ? 0 : 1;
+ m_blitter.line_color = (m_blitter.spr_ctl0 >> 6) & 0x03;
+
+ m_blitter.sprite_collide = !(m_blitter.spr_coll & 0x20);
+ m_blitter.spritenr = m_blitter.spr_coll & 0x0f;
+ m_blitter.fred = 0;
+
+ /* Draw Sprite */
+ lynx_blit_lines();
+
+ if (m_blitter.sprite_collide && !(m_blitter.no_collide))
+ {
+ switch (m_blitter.mode)
+ {
+ case BOUNDARY_SHADOW:
+ case BOUNDARY:
+ case NORMAL_SPRITE:
+ case XOR_SPRITE:
+ case SHADOW:
+ lynx_write_ram(m_blitter.colpos, m_blitter.fred);
+ break;
+ }
+ }
+
+ if (m_suzy.data[SPRGO] & 0x04) // Everon enabled
+ {
+ coldep = lynx_read_ram(m_blitter.colpos);
+ if (!m_blitter.everon)
+ coldep |= 0x80;
+ else
+ coldep &= 0x7f;
+ lynx_write_ram(m_blitter.colpos, coldep);
+ }
+ }
+ }
+
+ timer_set(m_maincpu->cycles_to_attotime(m_blitter.memory_accesses), TIMER_BLITTER);
+}
+
+
+/****************************************
+
+ 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:
+ if (m_cart->exists())
+ value = m_cart->read_rom(space, (m_suzy.high * m_granularity) + m_suzy.low);
+ else
+ value = 0;
+ 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();
+ }
+ 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
+*/
+
+void lynx_state::lynx_draw_line()
+{
+ int x, y;
+ UINT16 j; // clipping needed!
+ UINT8 byte;
+ UINT16 *line;
+
+
+ // calculate y: first three lines are vblank,
+ y = 101-m_timer[2].counter;
+ // Documentation states lower two bits of buffer address are ignored (thus 0xfffc mask)
+ j = (m_mikey.disp_addr & 0xfffc) + y * 160 / 2;
+
+ if (m_mikey.data[0x92] & 0x02)
+ {
+ j -= 160 * 102 / 2 - 1;
+ line = &m_bitmap_temp.pix16(102 - 1 - y);
+ for (x = 160 - 2; x >= 0; j++, x -= 2)
+ {
+ byte = lynx_read_ram(j);
+ line[x + 1] = m_lynx_palette[(byte >> 4) & 0x0f];
+ line[x + 0] = m_lynx_palette[(byte >> 0) & 0x0f];
+ }
+ }
+ else
+ {
+ line = &m_bitmap_temp.pix16(y);
+ for (x = 0; x < 160; j++, x += 2)
+ {
+ byte = lynx_read_ram(j);
+ line[x + 0] = m_lynx_palette[(byte >> 4) & 0x0f];
+ line[x + 1] = m_lynx_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
+
+
+
+
+void lynx_state::lynx_timer_init(int which)
+{
+ memset(&m_timer[which], 0, sizeof(LYNX_TIMER));
+ m_timer[which].timer = timer_alloc(TIMER_SHOT);
+
+ save_item(NAME(m_timer[which].bakup), which);
+ save_item(NAME(m_timer[which].cntrl1), which);
+ save_item(NAME(m_timer[which].cntrl2), which);
+ save_item(NAME(m_timer[which].counter), which);
+ save_item(NAME(m_timer[which].timer_active), which);
+}
+
+void lynx_state::lynx_timer_signal_irq(int which)
+{
+ if ((m_timer[which].cntrl1 & 0x80) && (which != 4)) // if interrupts are enabled and timer != 4
+ {
+ m_mikey.data[0x81] |= (1 << which); // set interupt poll register
+ m_maincpu->set_input_line(INPUT_LINE_HALT, CLEAR_LINE);
+ m_maincpu->set_input_line(M65SC02_IRQ_LINE, ASSERT_LINE);
+ }
+ switch (which) // count down linked timers
+ {
+ case 0:
+ switch (m_timer[2].counter)
+ {
+ case 104:
+ break;
+ case 103:
+ m_mikey.vb_rest = 1;
+ break;
+ case 102:
+ m_mikey.disp_addr = m_mikey.data[0x94] | (m_mikey.data[0x95] << 8);
+ break;
+ case 101:
+ m_mikey.vb_rest = 0;
+ lynx_draw_line();
+ break;
+ default:
+ lynx_draw_line();
+ }
+ lynx_timer_count_down(2);
+ break;
+ case 2:
+ copybitmap(m_bitmap, m_bitmap_temp, 0, 0, 0, 0, machine().first_screen()->cliprect());
+ lynx_timer_count_down(4);
+ break;
+ case 1:
+ lynx_timer_count_down(3);
+ break;
+ case 3:
+ lynx_timer_count_down(5);
+ break;
+ case 5:
+ lynx_timer_count_down(7);
+ break;
+ case 7:
+ m_sound->count_down(0);
+ break;
+ }
+}
+
+void lynx_state::lynx_timer_count_down(int which)
+{
+ if ((m_timer[which].cntrl1 & 0x0f) == 0x0f) // count and linking enabled
+ {
+ if (m_timer[which].counter > 0)
+ {
+ m_timer[which].counter--;
+ //m_timer[which].borrow_in = 1;
+ return;
+ }
+ if (m_timer[which].counter == 0)
+ {
+ if (m_timer[which].cntrl2 & 0x01) // borrow out
+ {
+ lynx_timer_signal_irq(which);
+ if (m_timer[which].cntrl1 & 0x10) // if reload enabled
+ {
+ m_timer[which].counter = m_timer[which].bakup;
+ }
+ else
+ {
+ m_timer[which].cntrl2 |= 8; // set timer done
+ }
+ m_timer[which].cntrl2 &= ~0x01; // clear borrow out
+ }
+ else
+ m_timer[which].cntrl2 |= 0x01; // set borrow out
+ return;
+ }
+ }
+ else
+ {
+ //m_timer[which].borrow_in = 0;
+ m_timer[which].cntrl2 &= ~0x01;
+ }
+}
+
+UINT32 lynx_state::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");
+ }
+}
+
+TIMER_CALLBACK_MEMBER(lynx_state::lynx_timer_shot)
+{
+ lynx_timer_signal_irq(param);
+ if (!(m_timer[param].cntrl1 & 0x10)) // if reload not enabled
+ {
+ m_timer[param].timer_active = 0;
+ m_timer[param].cntrl2 |= 8; // set timer done
+ }
+ else
+ {
+ attotime t = (attotime::from_hz(lynx_time_factor(m_timer[param].cntrl1 & 0x07)) * (m_timer[param].bakup + 1));
+ 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
+
+****************************************/
+
+
+void lynx_state::lynx_uart_reset()
+{
+ memset(&m_uart, 0, sizeof(m_uart));
+}
+
+TIMER_CALLBACK_MEMBER(lynx_state::lynx_uart_loopback_timer)
+{
+ m_uart.received = FALSE;
+}
+
+TIMER_CALLBACK_MEMBER(lynx_state::lynx_uart_timer)
+{
+ if (m_uart.buffer_loaded)
+ {
+ m_uart.data_to_send = m_uart.buffer;
+ m_uart.buffer_loaded = FALSE;
+ timer_set(attotime::from_usec(11*16), TIMER_UART);
+ }
+ else
+ {
+ m_uart.sending = FALSE;
+ m_uart.received = TRUE;
+ m_uart.data_received = m_uart.data_to_send;
+ timer_set(attotime::from_usec(11*16), TIMER_UART_LOOPBACK);
+ if (m_uart.serctl & 0x40)
+ {
+ m_mikey.data[0x81] |= 0x10;
+ m_maincpu->set_input_line(INPUT_LINE_HALT, CLEAR_LINE);
+ m_maincpu->set_input_line(M65SC02_IRQ_LINE, ASSERT_LINE);
+ }
+ }
+
+ if (m_uart.serctl & 0x80)
+ {
+ m_mikey.data[0x81] |= 0x10;
+ m_maincpu->set_input_line(INPUT_LINE_HALT, CLEAR_LINE);
+ m_maincpu->set_input_line(M65SC02_IRQ_LINE, ASSERT_LINE);
+ }
+}
+
+READ8_MEMBER(lynx_state::lynx_uart_r)
+{
+ UINT8 value = 0x00;
+ switch (offset)
+ {
+ case 0x8c:
+ if (!m_uart.buffer_loaded)
+ value |= 0x80;
+ if (m_uart.received)
+ value |= 0x40;
+ if (!m_uart.sending)
+ value |= 0x20;
+ break;
+
+ case 0x8d:
+ value = 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
+ timer_set(attotime::from_usec(11*16), TIMER_UART);
+ }
+ 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 = m_sound->read(space, 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, mem_mask);
+ 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:
+ m_sound->write(space, 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])
+ m_maincpu->set_input_line(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)
+ {
+ m_maincpu->set_input_line(INPUT_LINE_HALT, CLEAR_LINE);
+ m_maincpu->set_input_line(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_lynx_palette[offset & 0x0f] = m_palette->pen(
+ ((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)
+ {
+ m_maincpu->set_input_line(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");
+ membank("bank1")->set_base(m_mem_fc00);
+ }
+ 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");
+ membank("bank2")->set_base(m_mem_fd00);
+ }
+ else
+ space.install_readwrite_handler(0xfd00, 0xfdff, read8_delegate(FUNC(lynx_state::mikey_read),this), write8_delegate(FUNC(lynx_state::mikey_write),this));
+
+ membank("bank3")->set_entry((data & 4) ? 1 : 0);
+ membank("bank4")->set_entry((data & 8) ? 1 : 0);
+}
+
+void lynx_state::machine_reset()
+{
+ lynx_memory_config_w(m_maincpu->space(AS_PROGRAM), 0, 0);
+
+ m_maincpu->set_input_line(INPUT_LINE_HALT, CLEAR_LINE);
+ m_maincpu->set_input_line(M65SC02_IRQ_LINE, CLEAR_LINE);
+
+ memset(&m_suzy, 0, sizeof(m_suzy));
+ memset(&m_mikey, 0, sizeof(m_mikey));
+
+ m_suzy.data[0x88] = 0x01;
+ m_suzy.data[0x90] = 0x00;
+ m_suzy.data[0x91] = 0x00;
+ m_mikey.data[0x80] = 0x00;
+ m_mikey.data[0x81] = 0x00;
+ m_mikey.data[0x88] = 0x01;
+ m_mikey.data[0x8a] = 0x00;
+ m_mikey.data[0x8c] = 0x00;
+ m_mikey.data[0x90] = 0x00;
+ m_mikey.data[0x92] = 0x00;
+
+ lynx_uart_reset();
+
+ // hack to allow current object loading to work
+#if 0
+ lynx_timer_write( this, 0, 0, 160 ); // set backup value (hpos) = 160
+ lynx_timer_write( this, 0, 1, 0x10 | 0x8 | 0 ); // enable count, enable reload, 1us period
+ lynx_timer_write( this, 2, 0, 105 ); // set backup value (vpos) = 102
+ lynx_timer_write( this, 2, 1, 0x10 | 0x8 | 7 ); // enable count, enable reload, link
+#endif
+
+ render_target *target = machine().render().first_target();
+ target->set_view(m_rotate);
+}
+
+void lynx_state::lynx_postload()
+{
+ lynx_memory_config_w(m_maincpu->space(AS_PROGRAM), 0, m_memory_config);
+}
+
+void lynx_state::machine_start()
+{
+ m_bitmap_temp.allocate(160,102,0,0);
+
+ // save driver variables
+ save_item(NAME(m_memory_config));
+ save_item(NAME(m_sign_AB));
+ save_item(NAME(m_sign_CD));
+ save_item(NAME(m_lynx_palette));
+ save_item(NAME(m_rotate));
+ // save blitter variables
+ save_item(NAME(m_blitter.screen));
+ save_item(NAME(m_blitter.colbuf));
+ save_item(NAME(m_blitter.colpos));
+ save_item(NAME(m_blitter.xoff));
+ save_item(NAME(m_blitter.yoff));
+ save_item(NAME(m_blitter.mode));
+ save_item(NAME(m_blitter.spr_coll));
+ save_item(NAME(m_blitter.spritenr));
+ save_item(NAME(m_blitter.x_pos));
+ save_item(NAME(m_blitter.y_pos));
+ save_item(NAME(m_blitter.width));
+ save_item(NAME(m_blitter.height));
+ save_item(NAME(m_blitter.tilt_accumulator));
+ save_item(NAME(m_blitter.width_accumulator));
+ save_item(NAME(m_blitter.height_accumulator));
+ save_item(NAME(m_blitter.width_offset));
+ save_item(NAME(m_blitter.height_offset));
+ save_item(NAME(m_blitter.stretch));
+ save_item(NAME(m_blitter.tilt));
+ save_item(NAME(m_blitter.color));
+ save_item(NAME(m_blitter.bitmap));
+ save_item(NAME(m_blitter.use_rle));
+ save_item(NAME(m_blitter.line_color));
+ save_item(NAME(m_blitter.spr_ctl0));
+ save_item(NAME(m_blitter.spr_ctl1));
+ save_item(NAME(m_blitter.scb));
+ save_item(NAME(m_blitter.scb_next));
+ save_item(NAME(m_blitter.sprite_collide));
+ save_item(NAME(m_blitter.everon));
+ save_item(NAME(m_blitter.fred));
+ save_item(NAME(m_blitter.memory_accesses));
+ save_item(NAME(m_blitter.no_collide));
+ save_item(NAME(m_blitter.vstretch));
+ save_item(NAME(m_blitter.lefthanded));
+ save_item(NAME(m_blitter.busy));
+ // save suzy variables
+ save_item(NAME(m_suzy.data));
+ save_item(NAME(m_suzy.high));
+ save_item(NAME(m_suzy.low));
+ save_item(NAME(m_suzy.signed_math));
+ save_item(NAME(m_suzy.accumulate));
+ save_item(NAME(m_suzy.accumulate_overflow));
+ // save mikey variables
+ save_item(NAME(m_mikey.data));
+ save_item(NAME(m_mikey.disp_addr));
+ save_item(NAME(m_mikey.vb_rest));
+ // save uart variables
+ save_item(NAME(m_uart.serctl));
+ save_item(NAME(m_uart.data_received));
+ save_item(NAME(m_uart.data_to_send));
+ save_item(NAME(m_uart.buffer));
+ save_item(NAME(m_uart.received));
+ save_item(NAME(m_uart.sending));
+ save_item(NAME(m_uart.buffer_loaded));
+
+ machine().save().register_postload(save_prepost_delegate(FUNC(lynx_state::lynx_postload), this));
+
+ membank("bank3")->configure_entry(0, memregion("maincpu")->base() + 0x0000);
+ membank("bank3")->configure_entry(1, m_mem_fe00);
+ membank("bank4")->configure_entry(0, memregion("maincpu")->base() + 0x01fa);
+ membank("bank4")->configure_entry(1, m_mem_fffa);
+
+ for (int i = 0; i < NR_LYNX_TIMERS; i++)
+ lynx_timer_init(i);
+}
+
+
+/****************************************
+
+ Image handling
+
+****************************************/
+
+int lynx_state::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;
+}
+
+DEVICE_IMAGE_LOAD_MEMBER( lynx_state, 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.*/
+ UINT32 size = m_cart->common_get_size("rom");
+ UINT16 gran = 0;
+
+ if (image.software_entry() == NULL)
+ {
+ // check for lnx header
+ const char *filetype = image.filetype();
+ if (!core_stricmp(filetype, "lnx"))
+ {
+ // 64 byte header
+ // LYNX
+ // intelword lower counter size
+ // 0 0 1 0
+ // 32 chars name
+ // 22 chars manufacturer
+ UINT8 header[0x40];
+ image.fread(header, 0x40);
+
+ // 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 */
+ gran = header[4] | (header[5] << 8);
+
+ logerror ("%s %dkb cartridge with %dbyte granularity from %s\n", header + 10, size / 1024, gran, header + 42);
+ size -= 0x40;
+ }
+ }
+
+ m_cart->rom_alloc(size, GENERIC_ROM8_WIDTH, ENDIANNESS_LITTLE);
+ m_cart->common_load_rom(m_cart->get_rom_base(), size, "rom");
+
+ // set-up granularity
+ if (image.software_entry() == NULL)
+ {
+ const char *filetype = image.filetype();
+ if (!core_stricmp(filetype, "lnx")) // from header
+ m_granularity = gran;
+ else if (!core_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)
+ m_granularity = 0x0200;
+ else if (size == 0x80000)
+ m_granularity = 0x0800;
+ else
+ m_granularity = 0x0400;
+ }
+ }
+ else
+ {
+ if (size > 0xffff) // 64,128,256,512k cartridges
+ m_granularity = size >> 8;
+ else
+ m_granularity = 0x400; // Homebrew roms not using all 256 banks (T-Tris) (none currently in softlist)
+ }
+
+ // set-up rotation from softlist
+ if (image.software_entry() != NULL)
+ {
+ const char *rotate = image.get_feature("rotation");
+ m_rotate = 0;
+ if (rotate)
+ {
+ if (!core_stricmp(rotate, "RIGHT"))
+ m_rotate = 1;
+ else if (!core_stricmp(rotate, "LEFT"))
+ m_rotate = 2;
+ }
+
+ }
+
+ return IMAGE_INIT_PASS;
+}