/****************************************************************************** Video Technology Laser 110-310 computers: Video Technology Laser 110 Sanyo Laser 110 Video Technology Laser 200 Salora Fellow Texet TX-8000 Video Technology VZ-200 Video Technology Laser 210 Dick Smith Electronics VZ-200 Sanyo Laser 210 Video Technology Laser 310 Dick Smith Electronics VZ-300 Machine driver: Juergen Buchmueller , Dec 1999 - everything Dirk Best , May 2004 - clean up - fixed mode 1 display (graphic mode) - fixed loading of the DOS ROM - preliminary printer emulation Dirk Best , March 2006 - 64K bank switched memory implemented - better printer emulation - cartridge support Thanks go to: - Guy Thomason - Jason Oakley - Bushy Maunder - and anybody else on the vzemu list :) - Davide Moretti for the detailed description of the colors. Todo: - Lightpen - RS232 serial ****************************************************************************/ #include "driver.h" #include "video/generic.h" #include "video/m6847.h" #include "includes/vtech1.h" #include "devices/cassette.h" #include "devices/printer.h" #include "cpu/z80/z80.h" #include "sound/speaker.h" #include "image.h" #define LOG_VTECH1_LATCH 0 #define LOG_VTECH1_FDC 0 int vtech1_latch = -1; #define TRKSIZE_VZ 0x9a0 /* arbitrary (actually from analyzing format) */ #define TRKSIZE_FM 3172 /* size of a standard FM mode track */ static UINT8 vtech1_track_x2[2] = {80, 80}; static UINT8 vtech1_fdc_wrprot[2] = {0x80, 0x80}; static UINT8 vtech1_fdc_status = 0; static UINT8 vtech1_fdc_data[TRKSIZE_FM]; static int vtech1_data; static int vtech1_fdc_edge = 0; static int vtech1_fdc_bits = 8; static int vtech1_drive = -1; static int vtech1_fdc_start = 0; static int vtech1_fdc_write = 0; static int vtech1_fdc_offs = 0; static int vtech1_fdc_latch = 0; /****************************************************************************** Machine Initialisation ******************************************************************************/ static void common_init_machine(int base) { /* internal ram */ memory_configure_bank(1, 0, 1, mess_ram, 0); memory_set_bank(1, 0); /* expansion memory configuration */ switch (mess_ram_size) { case 18 * 1024: case 22 * 1024: case 32 * 1024: /* install 16KB memory expansion */ memory_install_read8_handler (0, ADDRESS_SPACE_PROGRAM, base, base + 0x3fff, 0, 0, MRA8_BANK2); memory_install_write8_handler(0, ADDRESS_SPACE_PROGRAM, base, base + 0x3fff, 0, 0, MWA8_BANK2); memory_configure_bank(2, 0, 1, mess_ram + base - 0x7800, 0); memory_set_bank(2, 0); memory_install_read8_handler (0, ADDRESS_SPACE_PROGRAM, base + 0x4000, 0xffff, 0, 0, MRA8_NOP); memory_install_write8_handler(0, ADDRESS_SPACE_PROGRAM, base + 0x4000, 0xffff, 0, 0, MWA8_NOP); break; case 66 * 1024: case 4098 * 1024: /* 64KB/4MB memory expansion */ /* install fixed first bank */ memory_install_read8_handler (0, ADDRESS_SPACE_PROGRAM, 0x8000, 0xbfff, 0, 0, MRA8_BANK2); memory_install_write8_handler(0, ADDRESS_SPACE_PROGRAM, 0x8000, 0xbfff, 0, 0, MWA8_BANK2); memory_configure_bank(2, 0, 1, mess_ram + 0x800, 0); memory_set_bank(2, 0); /* install the others, dynamically banked in */ memory_install_read8_handler (0, ADDRESS_SPACE_PROGRAM, 0xc000, 0xffff, 0, 0, MRA8_BANK3); memory_install_write8_handler(0, ADDRESS_SPACE_PROGRAM, 0xc000, 0xffff, 0, 0, MWA8_BANK3); memory_configure_bank(3, 0, (mess_ram_size - 0x4800) / 0x4000, mess_ram + 0x4800, 0x4000); memory_set_bank(3, 0); break; default: /* no memory expansion */ memory_install_read8_handler (0, ADDRESS_SPACE_PROGRAM, base, 0xffff, 0, 0, MRA8_NOP); memory_install_write8_handler(0, ADDRESS_SPACE_PROGRAM, base, 0xffff, 0, 0, MWA8_NOP); break; } } MACHINE_START(laser110) { common_init_machine(0x8000); } MACHINE_START(laser210) { common_init_machine(0x9000); } MACHINE_START(laser310) { common_init_machine(0xb800); } /****************************************************************************** Memory Bank Handling TODO: What happens when an invalid bank is selected? Currently, we just ignore it and keep the current bank. ******************************************************************************/ WRITE8_HANDLER (vtech1_memory_bank_w) { logerror("vtech1_memory_bank_w $%02X\n", data); if (data >= 1) if ((data <= 3 && (mess_ram_size == (66 * 1024))) || (mess_ram_size == (4098 * 1024))) memory_set_bank(3, data - 1); } /****************************************************************************** Cassette Handling ******************************************************************************/ static mess_image *cassette_device_image(void) { return image_from_devtype_and_index(IO_CASSETTE, 0); } /****************************************************************************** Snapshot Handling ******************************************************************************/ SNAPSHOT_LOAD(vtech1) { UINT8 header[24]; UINT16 start, end; /* get the header */ image_fread(image, &header, sizeof(header)); /* get start and end addresses */ start = pick_integer_le(header, 22, 2); end = start + snapshot_size - sizeof(header); /* check if we have enough ram */ if (mess_ram_size < end - start) return INIT_FAIL; /* write it to ram */ image_fread(image, &mess_ram[start - 0x7800], end - start); /* patch variables depending on snapshot type */ switch (header[21]) { case VZ_BASIC: program_write_byte_8(0x78a4, start % 256); /* start of basic program */ program_write_byte_8(0x78a5, start / 256); program_write_byte_8(0x78f9, end % 256); /* end of basic program */ program_write_byte_8(0x78fa, end / 256); program_write_byte_8(0x78fb, end % 256); /* start variable table */ program_write_byte_8(0x78fc, end / 256); program_write_byte_8(0x78fd, end % 256); /* start free mem, end variable table */ program_write_byte_8(0x78fe, end / 256); break; case VZ_MCODE: program_write_byte_8(0x788e, start % 256); /* usr subroutine address */ program_write_byte_8(0x788f, start / 256); break; default: image_seterror(image, IMAGE_ERROR_UNSUPPORTED, "Snapshot format not supported."); return INIT_FAIL; break; } return INIT_PASS; } /****************************************************************************** Floppy Handling ******************************************************************************/ static mess_image *vtech1_file(void) { if (vtech1_drive < 0) return NULL; return image_from_devtype_and_index(IO_FLOPPY, vtech1_drive); } /* int vtech1_floppy_id(int id) { mame_file *file; UINT8 buff[32]; file = image_fopen(IO_FLOPPY, id, FILETYPE_IMAGE, OSD_FOPEN_READ); if (file) { mame_fread(file, buff, sizeof(buff)); mame_fclose(file); if( memcmp(buff, "\x80\x80\x80\x80\x80\x80\x00\xfe\0xe7\0x18\0xc3\x00\x00\x00\x80\x80", 16) == 0 ) return 1; } return 0; } */ DEVICE_LOAD(vtech1_floppy) { int id = image_index_in_device(image); if (image_is_writable(image)) vtech1_fdc_wrprot[id] = 0x00; else vtech1_fdc_wrprot[id] = 0x80; return INIT_PASS; } static void vtech1_get_track(void) { /* drive selected or and image file ok? */ if (vtech1_drive >= 0 && image_exists(vtech1_file())) { int size, offs; size = TRKSIZE_VZ; offs = TRKSIZE_VZ * vtech1_track_x2[vtech1_drive]/2; image_fseek(vtech1_file(), offs, SEEK_SET); size = image_fread(vtech1_file(), vtech1_fdc_data, size); if (LOG_VTECH1_FDC) logerror("get track @$%05x $%04x bytes\n", offs, size); } vtech1_fdc_offs = 0; vtech1_fdc_write = 0; } static void vtech1_put_track(void) { /* drive selected and image file ok? */ if (vtech1_drive >= 0 && vtech1_file() != NULL) { int size, offs; offs = TRKSIZE_VZ * vtech1_track_x2[vtech1_drive]/2; image_fseek(vtech1_file(), offs + vtech1_fdc_start, SEEK_SET); size = image_fwrite(vtech1_file(), &vtech1_fdc_data[vtech1_fdc_start], vtech1_fdc_write); if (LOG_VTECH1_FDC) logerror("put track @$%05X+$%X $%04X/$%04X bytes\n", offs, vtech1_fdc_start, size, vtech1_fdc_write); } } #define PHI0(n) (((n)>>0)&1) #define PHI1(n) (((n)>>1)&1) #define PHI2(n) (((n)>>2)&1) #define PHI3(n) (((n)>>3)&1) READ8_HANDLER(vtech1_fdc_r) { int data = 0xff; switch (offset) { case 1: /* data (read-only) */ if (vtech1_fdc_bits > 0) { if( vtech1_fdc_status & 0x80 ) vtech1_fdc_bits--; data = (vtech1_data >> vtech1_fdc_bits) & 0xff; if (LOG_VTECH1_FDC) { logerror("vtech1_fdc_r bits %d%d%d%d%d%d%d%d\n", (data>>7)&1,(data>>6)&1,(data>>5)&1,(data>>4)&1, (data>>3)&1,(data>>2)&1,(data>>1)&1,(data>>0)&1 ); } } if (vtech1_fdc_bits == 0) { vtech1_data = vtech1_fdc_data[vtech1_fdc_offs]; if (LOG_VTECH1_FDC) logerror("vtech1_fdc_r %d : data ($%04X) $%02X\n", offset, vtech1_fdc_offs, vtech1_data); if(vtech1_fdc_status & 0x80) { vtech1_fdc_bits = 8; vtech1_fdc_offs = (vtech1_fdc_offs + 1) % TRKSIZE_FM; } vtech1_fdc_status &= ~0x80; } break; case 2: /* polling (read-only) */ /* fake */ if (vtech1_drive >= 0) vtech1_fdc_status |= 0x80; data = vtech1_fdc_status; break; case 3: /* write protect status (read-only) */ if (vtech1_drive >= 0) data = vtech1_fdc_wrprot[vtech1_drive]; if (LOG_VTECH1_FDC) logerror("vtech1_fdc_r %d : write_protect $%02X\n", offset, data); break; } return data; } WRITE8_HANDLER(vtech1_fdc_w) { int drive; switch (offset) { case 0: /* latch (write-only) */ drive = (data & 0x10) ? 0 : (data & 0x80) ? 1 : -1; if (drive != vtech1_drive) { vtech1_drive = drive; if (vtech1_drive >= 0) vtech1_get_track(); } if (vtech1_drive >= 0) { if ((PHI0(data) && !(PHI1(data) || PHI2(data) || PHI3(data)) && PHI1(vtech1_fdc_latch)) || (PHI1(data) && !(PHI0(data) || PHI2(data) || PHI3(data)) && PHI2(vtech1_fdc_latch)) || (PHI2(data) && !(PHI0(data) || PHI1(data) || PHI3(data)) && PHI3(vtech1_fdc_latch)) || (PHI3(data) && !(PHI0(data) || PHI1(data) || PHI2(data)) && PHI0(vtech1_fdc_latch))) { if (vtech1_track_x2[vtech1_drive] > 0) vtech1_track_x2[vtech1_drive]--; if (LOG_VTECH1_FDC) logerror("vtech1_fdc_w(%d) $%02X drive %d: stepout track #%2d.%d\n", offset, data, vtech1_drive, vtech1_track_x2[vtech1_drive]/2,5*(vtech1_track_x2[vtech1_drive]&1)); if ((vtech1_track_x2[vtech1_drive] & 1) == 0) vtech1_get_track(); } else if ((PHI0(data) && !(PHI1(data) || PHI2(data) || PHI3(data)) && PHI3(vtech1_fdc_latch)) || (PHI1(data) && !(PHI0(data) || PHI2(data) || PHI3(data)) && PHI0(vtech1_fdc_latch)) || (PHI2(data) && !(PHI0(data) || PHI1(data) || PHI3(data)) && PHI1(vtech1_fdc_latch)) || (PHI3(data) && !(PHI0(data) || PHI1(data) || PHI2(data)) && PHI2(vtech1_fdc_latch))) { if (vtech1_track_x2[vtech1_drive] < 2*40) vtech1_track_x2[vtech1_drive]++; if (LOG_VTECH1_FDC) logerror("vtech1_fdc_w(%d) $%02X drive %d: stepin track #%2d.%d\n", offset, data, vtech1_drive, vtech1_track_x2[vtech1_drive]/2,5*(vtech1_track_x2[vtech1_drive]&1)); if ((vtech1_track_x2[vtech1_drive] & 1) == 0) vtech1_get_track(); } if ((data & 0x40) == 0) { vtech1_data <<= 1; if ((vtech1_fdc_latch ^ data) & 0x20) vtech1_data |= 1; if ((vtech1_fdc_edge ^= 1) == 0) { if (--vtech1_fdc_bits == 0) { UINT8 value = 0; vtech1_data &= 0xffff; if (vtech1_data & 0x4000 ) value |= 0x80; if (vtech1_data & 0x1000 ) value |= 0x40; if (vtech1_data & 0x0400 ) value |= 0x20; if (vtech1_data & 0x0100 ) value |= 0x10; if (vtech1_data & 0x0040 ) value |= 0x08; if (vtech1_data & 0x0010 ) value |= 0x04; if (vtech1_data & 0x0004 ) value |= 0x02; if (vtech1_data & 0x0001 ) value |= 0x01; if (LOG_VTECH1_FDC) logerror("vtech1_fdc_w(%d) data($%04X) $%02X <- $%02X ($%04X)\n", offset, vtech1_fdc_offs, vtech1_fdc_data[vtech1_fdc_offs], value, vtech1_data); vtech1_fdc_data[vtech1_fdc_offs] = value; vtech1_fdc_offs = (vtech1_fdc_offs + 1) % TRKSIZE_FM; vtech1_fdc_write++; vtech1_fdc_bits = 8; } } } /* change of write signal? */ if ((vtech1_fdc_latch ^ data) & 0x40) { /* falling edge? */ if (vtech1_fdc_latch & 0x40) { vtech1_fdc_start = vtech1_fdc_offs; vtech1_fdc_edge = 0; } else { /* data written to track before? */ if (vtech1_fdc_write) vtech1_put_track(); } vtech1_fdc_bits = 8; vtech1_fdc_write = 0; } } vtech1_fdc_latch = data; break; } } /****************************************************************************** Input Handling ******************************************************************************/ READ8_HANDLER(vtech1_lightpen_r) { logerror("vtech1_lightpen_r(%d)\n", offset); return 0xff; } READ8_HANDLER(vtech1_joystick_r) { int data = 0xff; if (!(offset & 1)) data &= readinputportbytag("joystick_0"); if (!(offset & 2)) data &= readinputportbytag("joystick_0_arm"); if (!(offset & 4)) data &= readinputportbytag("joystick_1"); if (!(offset & 8)) data &= readinputportbytag("joystick_1_arm"); return data; } READ8_HANDLER(vtech1_keyboard_r) { static int cassette_bit = 0; int row, data = 0xff; char portname[11]; double level; /* scan keyboard rows */ for (row = 0; row < 8; row++) { sprintf(portname, "keyboard_%d", row); if (!(offset & (1 << row))) data &= readinputportbytag(portname); } if (video_screen_get_vblank(0)) data &= ~0x80; /* cassette input is bit 5 (0x40) */ level = cassette_input(cassette_device_image()); if (level < -0.008) cassette_bit = 0x00; if (level > +0.008) cassette_bit = 0x40; data &= ~cassette_bit; return data; } /************************************************* * bit function * 7 not assigned * 6 not assigned * 5 speaker B * 4 VDC background 0 green, 1 orange * 3 VDC display mode 0 text, 1 graphics * 2 cassette out (MSB) * 1 cassette out (LSB) * 0 speaker A ************************************************/ WRITE8_HANDLER(vtech1_latch_w) { if (LOG_VTECH1_LATCH) logerror("vtech1_latch_w $%02X\n", data); /* cassette data bits toggle? */ if ((vtech1_latch ^ data ) & 0x06) { static double amp[4] = { +1.0, +0.5, -0.5, -1.0 }; cassette_output(cassette_device_image(), amp[(data >> 1) & 3]); } /* speaker data bits toggle? */ if ((vtech1_latch ^ data ) & 0x41) speaker_level_w(0, (data & 1) | ((data >> 5) & 2)); vtech1_latch = data; } /****************************************************************************** Printer Handling ******************************************************************************/ /* The VZ200/300 printer interface uses I/O port address OE Hex for the ASCII character code data and strobe output, and address OOH for the busy/ready-bar status input (bit 0). */ READ8_HANDLER(vtech1_printer_r) { int data = 0xff; if (printer_status(image_from_devtype_and_index(IO_PRINTER, 0), 0)) data &= ~0x01; return data; } WRITE8_HANDLER(vtech1_printer_w) { static int prn_data; switch (offset) { case 0x0d: /* strobe data to printer */ printer_output(image_from_devtype_and_index(IO_PRINTER, 0), prn_data); break; case 0x0e: /* load output latch */ prn_data = data; break; default: logerror("vtech1_printer_w $%02x, unknown offset %02x\n", data, offset); } } /****************************************************************************** Serial RS232 Handling ******************************************************************************/ READ8_HANDLER(vtech1_serial_r) { int data = 0xff; logerror("vtech1_serial_r offset $%02x\n", offset); return data; } WRITE8_HANDLER(vtech1_serial_w) { logerror("vtech1_serial_w $%02x, offset %02x\n", data, offset); }