/* ** Spectravideo SVI-318 and SVI-328 ** ** Sean Young, Tomas Karlsson ** ** Todo: ** - serial ports ** - svi_cas format ** - 80 column cartridge */ #include "driver.h" #include "video/generic.h" #include "cpu/z80/z80.h" #include "machine/wd17xx.h" #include "includes/svi318.h" #include "formats/svi_cas.h" #include "machine/8255ppi.h" #include "video/tms9928a.h" #include "devices/basicdsk.h" #include "devices/printer.h" #include "devices/cassette.h" #include "sound/dac.h" #include "sound/ay8910.h" #include "image.h" static SVI_318 svi; static UINT8 *pcart; static void svi318_set_banks (void); /* Cartridge */ static int svi318_verify_cart (UINT8 magic[2]) { /* read the first two bytes */ if ( (magic[0] == 0xf3) && (magic[1] == 0x31) ) return IMAGE_VERIFY_PASS; else return IMAGE_VERIFY_FAIL; } DEVICE_LOAD( svi318_cart ) { UINT8 *p; int size; p = image_malloc(image, 0x8000); if (!p) return INIT_FAIL; memset(p, 0xff, 0x8000); size = image_length(image); if (image_fread(image, p, size) != size) { logerror ("can't read file %s\n", image_filename(image) ); return INIT_FAIL; } if (svi318_verify_cart(p)==IMAGE_VERIFY_FAIL) return INIT_FAIL; pcart = p; svi.banks[0][1] = p; return INIT_PASS; } DEVICE_UNLOAD( svi318_cart ) { pcart = svi.banks[0][1] = NULL; } /* PPI */ /* PPI Port A Input (address 98H) Bit Name Description 1 TA Joystick 1, /SENSE 2 TB Joystick 1, EOC 3 TC Joystick 2, /SENSE 4 TD Joystick 2, EOC 5 TRIGGER1 Joystick 1, Trigger 6 TRIGGER2 Joystick 2, Trigger 7 /READY Cassette, Ready 8 CASR Cassette, Read data */ static READ8_HANDLER ( svi318_ppi_port_a_r ) { int data = 0x0f; if (cassette_input(image_from_devtype_and_index(IO_CASSETTE, 0)) > 0.0038) data |= 0x80; if (!svi318_cassette_present(0) ) data |= 0x40; data |= readinputport(12) & 0x30; return data; } /* PPI Port B Input (address 99H) Bit Name Description 1 IN0 Keyboard, Column status of selected line 2 IN1 Keyboard, Column status of selected line 3 IN2 Keyboard, Column status of selected line 4 IN3 Keyboard, Column status of selected line 5 IN4 Keyboard, Column status of selected line 6 IN5 Keyboard, Column status of selected line 7 IN6 Keyboard, Column status of selected line 8 IN7 Keyboard, Column status of selected line */ static READ8_HANDLER ( svi318_ppi_port_b_r ) { int row; row = ppi8255_peek (0, 2) & 0x0f; if (row <= 10) { if (row == 6) return readinputport(row) & readinputport(14); else return readinputport(row); } return 0xff; } /* PPI Port C Output (address 97H) Bit Name Description 1 KB0 Keyboard, Line select 0 2 KB1 Keyboard, Line select 1 3 KB2 Keyboard, Line select 2 4 KB3 Keyboard, Line select 3 5 CASON Cassette, Motor relay control (0=on, 1=off) 6 CASW Cassette, Write data 7 CASAUD Cassette, Audio out (pulse) 8 SOUND Keyboard, Click sound bit (pulse) */ static WRITE8_HANDLER ( svi318_ppi_port_c_w ) { int val; /* key click */ val = (data & 0x80) ? 0x3e : 0; val += (data & 0x40) ? 0x3e : 0; DAC_signed_data_w (0, val); /* cassette motor on/off */ if (svi318_cassette_present (0) ) { cassette_change_state( image_from_devtype_and_index(IO_CASSETTE, 0), (data & 0x10) ? CASSETTE_MOTOR_DISABLED : CASSETTE_MOTOR_ENABLED, CASSETTE_MOTOR_DISABLED); } /* cassette signal write */ cassette_output(image_from_devtype_and_index(IO_CASSETTE, 0), (data & 0x20) ? -1.0 : +1.0); } static ppi8255_interface svi318_ppi8255_interface = { 1, {svi318_ppi_port_a_r}, {svi318_ppi_port_b_r}, {NULL}, {NULL}, {NULL}, {svi318_ppi_port_c_w} }; READ8_HANDLER( svi318_ppi_r ) { return ppi8255_0_r (offset); } WRITE8_HANDLER( svi318_ppi_w ) { ppi8255_0_w (offset + 2, data); } /* Printer port */ WRITE8_HANDLER( svi318_printer_w ) { if (!offset) svi.prn_data = data; else { if ( (svi.prn_strobe & 1) && !(data & 1) ) printer_output(image_from_devtype_and_index(IO_PRINTER, 0), svi.prn_data); svi.prn_strobe = data; } } READ8_HANDLER( svi318_printer_r ) { if (printer_status(image_from_devtype_and_index(IO_PRINTER, 0), 0) ) return 0xfe; return 0xff; } /* PSG */ /* PSG Port A Input Bit Name Description 1 FWD1 Joystick 1, Forward 2 BACK1 Joystick 1, Back 3 LEFT1 Joystick 1, Left 4 RIGHT1 Joystick 1, Right 5 FWD2 Joystick 2, Forward 6 BACK2 Joystick 2, Back 7 LEFT2 Joystick 2, Left 8 RIGHT2 Joystick 2, Right */ READ8_HANDLER( svi318_psg_port_a_r ) { return readinputport (11); } /* PSG Port B Output Bit Name Description 1 /CART Memory bank 11, ROM 0000-7FFF (Cartridge /CCS1, /CCS2) 2 /BK21 Memory bank 21, RAM 0000-7FFF 3 /BK22 Memory bank 22, RAM 8000-FFFF 4 /BK31 Memory bank 31, RAM 0000-7FFF 5 /BK32 Memory bank 32, RAM 8000-7FFF 6 CAPS Caps-Lock diod 7 /ROMEN0 Memory bank 12, ROM 8000-BFFF* (Cartridge /CCS3) 8 /ROMEN1 Memory bank 12, ROM C000-FFFF* (Cartridge /CCS4) * The /CART signal must be active for any effect and all banks with RAM are disabled. */ WRITE8_HANDLER( svi318_psg_port_b_w ) { if ( (svi.bank_switch ^ data) & 0x20) set_led_status (0, !(data & 0x20) ); svi.bank_switch = data; svi318_set_banks (); } /* Disk drives */ typedef struct { UINT8 driveselect; UINT8 irq_drq; UINT8 heads[2]; } SVI318_FDC_STRUCT; static SVI318_FDC_STRUCT svi318_fdc; static void svi_fdc_callback(wd17xx_state_t state, void *param) { switch( state ) { case WD17XX_IRQ_CLR: svi318_fdc.irq_drq &= ~0x80; break; case WD17XX_IRQ_SET: svi318_fdc.irq_drq |= 0x80; break; case WD17XX_DRQ_CLR: svi318_fdc.irq_drq &= ~0x40; break; case WD17XX_DRQ_SET: svi318_fdc.irq_drq |= 0x40; break; } } WRITE8_HANDLER( svi318_fdc_drive_motor_w ) { switch (data & 3) { case 1: wd17xx_set_drive(0); svi318_fdc.driveselect = 0; break; case 2: wd17xx_set_drive(1); svi318_fdc.driveselect = 1; break; } } WRITE8_HANDLER( svi318_fdc_density_side_w ) { mess_image *image; wd17xx_set_density(data & 0x01 ? DEN_FM_LO:DEN_MFM_LO); wd17xx_set_side(data & 0x02 ? 1:0); image = image_from_devtype_and_index(IO_FLOPPY, svi318_fdc.driveselect); if (image_exists(image)) { UINT8 sectors; UINT16 sectorSize; sectors = data & 0x01 ? 18:17; sectorSize = data & 0x01 ? 128:256; basicdsk_set_geometry(image, 40, svi318_fdc.heads[svi318_fdc.driveselect], sectors, sectorSize, 1, 0, FALSE); } } READ8_HANDLER( svi318_fdc_irqdrq_r ) { return svi318_fdc.irq_drq; } static unsigned long svi318_calcoffset(UINT8 t, UINT8 h, UINT8 s, UINT8 tracks, UINT8 heads, UINT8 sec_per_track, UINT16 sector_length, UINT8 first_sector_id, UINT16 offset_track_zero) { unsigned long o; if ((t==0) && (h==0)) o = (s-first_sector_id)*128; else o = ((t*heads+h)*17+s-first_sector_id)*256-2048; /* (17*256)-(18*128)=2048 */ return o; } DEVICE_LOAD( svi318_floppy ) { int size; int id = image_index_in_device(image); if (!image_has_been_created(image)) { size = image_length (image); switch (size) { case 172032: /* Single sided */ svi318_fdc.heads[id] = 1; break; case 346112: /* Double sided */ svi318_fdc.heads[id] = 2; break; default: return INIT_FAIL; } } else return INIT_FAIL; if (device_load_basicdsk_floppy(image) != INIT_PASS) return INIT_FAIL; basicdsk_set_geometry(image, 40, svi318_fdc.heads[id], 17, 256, 1, 0, FALSE); basicdsk_set_calcoffset(image, svi318_calcoffset); return INIT_PASS; } /* 80 column card */ #include "video/m6845.h" static int svi318_80col_state; static char *svi318_80col_ram = NULL; static int svi318_6845_RA = 0; static int svi318_scr_x = 0; static int svi318_scr_y = 0; static int svi318_HSync = 0; static int svi318_VSync = 0; static int svi318_DE = 0; // called when the 6845 changes the character row static void svi318_Set_RA(int offset, int data) { svi318_6845_RA=data; } // called when the 6845 changes the HSync static void svi318_Set_HSync(int offset, int data) { svi318_HSync=data; if(!svi318_HSync) { svi318_scr_y++; svi318_scr_x = -40; } } // called when the 6845 changes the VSync static void svi318_Set_VSync(int offset, int data) { svi318_VSync=data; if (!svi318_VSync) { svi318_scr_y = 0; } } static void svi318_Set_DE(int offset, int data) { svi318_DE = data; } static struct crtc6845_interface svi318_crtc6845_interface= { 0,// Memory Address register svi318_Set_RA,// Row Address register svi318_Set_HSync,// Horizontal status svi318_Set_VSync,// Vertical status svi318_Set_DE,// Display Enabled status 0,// Cursor status }; /* 80 column card init */ static void svi318_80col_init(void) { /* 2K RAM */ svi318_80col_ram = auto_malloc(0x800); memset(svi318_80col_ram, 0x40, 0x400); /* initialise 6845 */ crtc6845_config(&svi318_crtc6845_interface); svi318_80col_state=(1<<2)|(1<<1); } READ8_HANDLER( svi318_crtc_r ) { return 0xff; } WRITE8_HANDLER( svi318_crtc_w ) { } WRITE8_HANDLER( svi318_crtcbank_w ) { } static void svi318_80col_plot_char_line(int x,int y, mame_bitmap *bitmap) { int w; if (svi318_DE) { unsigned char *data = memory_region(REGION_GFX1); unsigned char data_byte; int char_code; char_code = svi318_80col_ram[crtc6845_memory_address_r(0)&0x07ff]; data_byte = data[(char_code<<3) + svi318_6845_RA]; for (w=0; w<8;w++) { *BITMAP_ADDR16(bitmap, y, x+w) = (data_byte & 0x080) ? 1 : 0; data_byte = data_byte<<1; } } else { for (w=0; w<8;w++) *BITMAP_ADDR16(bitmap, y, x+w) = 0; } } static VIDEO_UPDATE( svi318_80col ) { long c=0; // this is used to time out the screen redraw, in the case that the 6845 is in some way out state. c=0; // loop until the end of the Vertical Sync pulse while((svi318_VSync)&&(c<33274)) { // Clock the 6845 crtc6845_clock(); c++; } // loop until the Vertical Sync pulse goes high // or until a timeout (this catches the 6845 with silly register values that would not give a VSYNC signal) while((!svi318_VSync)&&(c<33274)) { while ((svi318_HSync)&&(c<33274)) { crtc6845_clock(); c++; } // Do all the clever split mode changes in here before the next while loop while ((!svi318_HSync)&&(c<33274)) { // check that we are on the emulated screen area. if ((svi318_scr_x>=0) && (svi318_scr_x<640) && (svi318_scr_y>=0) && (svi318_scr_y<400)) { svi318_80col_plot_char_line(svi318_scr_x, svi318_scr_y, bitmap); } svi318_scr_x+=8; // Clock the 6845 crtc6845_clock(); c++; } } return 0; } VIDEO_UPDATE( svi328b ) { video_update_tms9928a(machine, screen, bitmap, cliprect); video_update_svi318_80col(machine, screen, bitmap, cliprect); return 0; } MACHINE_RESET( svi328b ) { machine_reset_svi318(machine); svi318_80col_init(); } /* Init functions */ void svi318_vdp_interrupt (int i) { cpunum_set_input_line(0, 0, (i ? HOLD_LINE : CLEAR_LINE)); } DRIVER_INIT( svi318 ) { int i, n; /* z80 stuff */ static int z80_cycle_table[] = { Z80_TABLE_op, Z80_TABLE_cb, Z80_TABLE_xy, Z80_TABLE_ed, Z80_TABLE_xycb, Z80_TABLE_ex }; memset(&svi, 0, sizeof (svi) ); svi.svi318 = !strcmp (Machine->gamedrv->name, "svi318"); cpunum_set_input_line_vector (0, 0, 0xff); ppi8255_init (&svi318_ppi8255_interface); /* memory */ svi.empty_bank = auto_malloc (0x8000); memset (svi.empty_bank, 0xff, 0x8000); svi.banks[0][0] = memory_region(REGION_CPU1); svi.banks[1][0] = auto_malloc (0x8000); memset (svi.banks[1][0], 0, 0x8000); /* should also be allocated via dip-switches ... redundant? */ if (!svi.svi318) { svi.banks[1][2] = auto_malloc (0x8000); memset (svi.banks[1][2], 0, 0x8000); } svi.banks[0][1] = pcart; /* adjust z80 cycles for the M1 wait state */ for (i = 0; i < sizeof(z80_cycle_table) / sizeof(z80_cycle_table[0]); i++) { UINT8 *table = auto_malloc (0x100); const UINT8 *old_table; old_table = cpunum_get_info_ptr (0, CPUINFO_PTR_Z80_CYCLE_TABLE + z80_cycle_table[i]); memcpy (table, old_table, 0x100); if (z80_cycle_table[i] == Z80_TABLE_ex) { table[0x66]++; /* NMI overhead (not used) */ table[0xff]++; /* INT overhead */ } else { for (n=0; n<256; n++) { if (z80_cycle_table[i] == Z80_TABLE_op) { table[n]++; } else { table[n] += 2; } } } cpunum_set_info_ptr(0, CPUINFO_PTR_Z80_CYCLE_TABLE + z80_cycle_table[i], (void*)table); } /* floppy */ wd17xx_init(WD_TYPE_179X, svi_fdc_callback, NULL); } static const TMS9928a_interface tms9928a_interface = { TMS9929A, 0x4000, 0, 0, svi318_vdp_interrupt }; MACHINE_START( svi318 ) { TMS9928A_configure(&tms9928a_interface); } MACHINE_RESET( svi318 ) { /* video stuff */ TMS9928A_reset(); /* PPI */ ppi8255_0_w(3, 0x92); svi.bank_switch = 0xff; svi318_set_banks(); wd17xx_reset(); } INTERRUPT_GEN( svi318_interrupt ) { int set, i, p, b, bit; set = readinputport (13); TMS9928A_set_spriteslimit (set & 0x20); TMS9928A_interrupt(); /* memory banks */ for (i=0;i<4;i++) { bit = set & (1 << i); p = (i & 1); b = i / 2 + 2; if (bit && !svi.banks[p][b]) { svi.banks[p][b] = malloc (0x8000); if (!svi.banks[p][b]) logerror ("Cannot malloc bank%d%d!\n", b, p + 1); else { memset (svi.banks[p][b], 0, 0x8000); logerror ("bank%d%d allocated.\n", b, p + 1); } } else if (!bit && svi.banks[p][b]) { free (svi.banks[p][b]); svi.banks[p][b] = NULL; logerror ("bank%d%d freed.\n", b, p + 1); } } } /* Memory */ WRITE8_HANDLER( svi318_writemem0 ) { if (svi.bank1 < 2) return; if (svi.banks[0][svi.bank1]) svi.banks[0][svi.bank1][offset] = data; } WRITE8_HANDLER( svi318_writemem1 ) { switch (svi.bank2) { case 0: if (!svi.svi318 || offset >= 0x4000) svi.banks[1][0][offset] = data; break; case 2: case 3: if (svi.banks[1][svi.bank2]) svi.banks[1][svi.bank2][offset] = data; break; } } static void svi318_set_banks () { const UINT8 v = svi.bank_switch; svi.bank1 = (v&1)?(v&2)?(v&8)?0:3:2:1; svi.bank2 = (v&4)?(v&16)?0:3:2; if (svi.banks[0][svi.bank1]) memory_set_bankptr (1, svi.banks[0][svi.bank1]); else memory_set_bankptr (1, svi.empty_bank); if (svi.banks[1][svi.bank2]) { memory_set_bankptr (2, svi.banks[1][svi.bank2]); memory_set_bankptr (3, svi.banks[1][svi.bank2] + 0x4000); /* SVI-318 has only 16kB RAM -- not 32kb! */ if (!svi.bank2 && svi.svi318) memory_set_bankptr (2, svi.empty_bank); if ((svi.bank1 == 1) && ( (v & 0xc0) != 0xc0)) { memory_set_bankptr (2, (v&80)?svi.empty_bank:svi.banks[1][1] + 0x4000); memory_set_bankptr (3, (v&40)?svi.empty_bank:svi.banks[1][1]); } } else { memory_set_bankptr (2, svi.empty_bank); memory_set_bankptr (3, svi.empty_bank); } } /* Cassette */ int svi318_cassette_present (int id) { return image_exists(image_from_devtype_and_index(IO_CASSETTE, id)); }