1 /* specplus3.c: Spectrum +2A/+3 specific routines
2 Copyright (c) 1999-2011 Philip Kendall, Darren Salt
3
4 $Id: specplus3.c 4638 2012-01-21 12:52:14Z fredm $
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 Author contact information:
21
22 E-mail: philip-fuse@shadowmagic.org.uk
23
24 */
25
26 #include <config.h>
27
28 #include <stdio.h>
29
30 #include <errno.h>
31 #include <limits.h>
32 #include <stdarg.h>
33 #include <string.h>
34 #include <unistd.h>
35
36 #include <libspectrum.h>
37
38 #include "compat.h"
39 #include "fuse.h"
40 #include "machine.h"
41 #include "machines_periph.h"
42 #include "memory.h"
43 #include "periph.h"
44 #include "peripherals/disk/fdd.h"
45 #include "peripherals/disk/upd_fdc.h"
46 #include "peripherals/printer.h"
47 #include "settings.h"
48 #include "snapshot.h"
49 #include "spec128.h"
50 #include "spec48.h"
51 #include "specplus3.h"
52 #include "spectrum.h"
53 #include "ui/ui.h"
54 #include "utils.h"
55 #include "options.h" /* needed for get combo options */
56
57 #define DISK_TRY_MERGE(heads) ( option_enumerate_diskoptions_disk_try_merge() == 2 || \
58 ( option_enumerate_diskoptions_disk_try_merge() == 1 && heads == 1 ) )
59
60 static int normal_memory_map( int rom, int page );
61 static void special_memory_map( int which );
62 static void select_special_map( int page1, int page2, int page3, int page4 );
63
64 static int specplus3_reset( void );
65
66 #define SPECPLUS3_NUM_DRIVES 2
67 upd_fdc *specplus3_fdc;
68 static upd_fdc_drive specplus3_drives[ SPECPLUS3_NUM_DRIVES ];
69
70 int
specplus3_port_from_ula(libspectrum_word port GCC_UNUSED)71 specplus3_port_from_ula( libspectrum_word port GCC_UNUSED )
72 {
73 /* No contended ports */
74 return 0;
75 }
76
specplus3_init(fuse_machine_info * machine)77 int specplus3_init( fuse_machine_info *machine )
78 {
79 machine->machine = LIBSPECTRUM_MACHINE_PLUS3;
80 machine->id = "plus3";
81
82 machine->reset = specplus3_reset;
83
84 machine->timex = 0;
85 machine->ram.port_from_ula = specplus3_port_from_ula;
86 machine->ram.contend_delay = spectrum_contend_delay_76543210;
87 machine->ram.contend_delay_no_mreq = spectrum_contend_delay_none;
88 machine->ram.valid_pages = 8;
89
90 machine->unattached_port = spectrum_unattached_port_none;
91
92 specplus3_765_init();
93 specplus3_menu_items();
94
95 machine->shutdown = specplus3_shutdown;
96
97 machine->memory_map = specplus3_memory_map;
98
99 return 0;
100 }
101
102 void
specplus3_765_update_fdd(void)103 specplus3_765_update_fdd( void )
104 {
105 specplus3_fdc->speedlock = settings_current.plus3_detect_speedlock ? 0 : -1;
106 }
107
108 void
specplus3_765_init(void)109 specplus3_765_init( void )
110 {
111 int i;
112 upd_fdc_drive *d;
113
114 specplus3_fdc = upd_fdc_alloc_fdc( UPD765A, UPD_CLOCK_4MHZ );
115 /*!!!! the plus3 only use the US0 pin to select drives,
116 so drive 2 := drive 0 and drive 3 := drive 1 !!!!*/
117 specplus3_fdc->drive[0] = &specplus3_drives[ 0 ];
118 specplus3_fdc->drive[1] = &specplus3_drives[ 1 ];
119 specplus3_fdc->drive[2] = &specplus3_drives[ 0 ];
120 specplus3_fdc->drive[3] = &specplus3_drives[ 1 ];
121
122 for( i = 0; i < SPECPLUS3_NUM_DRIVES; i++ ) {
123 d = &specplus3_drives[ i ];
124 d->disk.flag = DISK_FLAG_PLUS3_CPC;
125 }
126 /* builtin drive 1 head 42 track */
127 fdd_init( &specplus3_drives[ 0 ].fdd, FDD_SHUGART, &fdd_params[ 1 ], 0 );
128 fdd_init( &specplus3_drives[ 1 ].fdd, FDD_SHUGART, NULL, 0 ); /* drive geometry 'autodetect' */
129 specplus3_fdc->set_intrq = NULL;
130 specplus3_fdc->reset_intrq = NULL;
131 specplus3_fdc->set_datarq = NULL;
132 specplus3_fdc->reset_datarq = NULL;
133
134 specplus3_765_update_fdd();
135 }
136
137 void
specplus3_765_reset(void)138 specplus3_765_reset( void )
139 {
140 const fdd_params_t *dt;
141
142 upd_fdc_master_reset( specplus3_fdc );
143 dt = &fdd_params[ option_enumerate_diskoptions_drive_plus3a_type() + 1 ]; /* +1 => there is no `Disabled' */
144 fdd_init( &specplus3_drives[ 0 ].fdd, FDD_SHUGART, dt, 1 );
145
146 dt = &fdd_params[ option_enumerate_diskoptions_drive_plus3b_type() ];
147 fdd_init( &specplus3_drives[ 1 ].fdd, dt->enabled ? FDD_SHUGART : FDD_TYPE_NONE, dt, 1 );
148 }
149
150 static int
specplus3_reset(void)151 specplus3_reset( void )
152 {
153 int error;
154
155 error = machine_load_rom( 0, settings_current.rom_plus3_0,
156 settings_default.rom_plus3_0, 0x4000 );
157 if( error ) return error;
158 error = machine_load_rom( 1, settings_current.rom_plus3_1,
159 settings_default.rom_plus3_1, 0x4000 );
160 if( error ) return error;
161 error = machine_load_rom( 2, settings_current.rom_plus3_2,
162 settings_default.rom_plus3_2, 0x4000 );
163 if( error ) return error;
164 error = machine_load_rom( 3, settings_current.rom_plus3_3,
165 settings_default.rom_plus3_3, 0x4000 );
166 if( error ) return error;
167
168 error = specplus3_plus2a_common_reset();
169 if( error ) return error;
170
171 periph_clear();
172 machines_periph_plus3();
173
174 periph_set_present( PERIPH_TYPE_UPD765, PERIPH_PRESENT_ALWAYS );
175
176 periph_update();
177
178 specplus3_765_reset();
179 specplus3_menu_items();
180
181 spec48_common_display_setup();
182
183 return 0;
184 }
185
186 int
specplus3_plus2a_common_reset(void)187 specplus3_plus2a_common_reset( void )
188 {
189 int error;
190 size_t i;
191
192 machine_current->ram.current_page=0; machine_current->ram.current_rom=0;
193 machine_current->ram.locked=0;
194 machine_current->ram.special=0;
195 machine_current->ram.last_byte=0;
196 machine_current->ram.last_byte2=0;
197
198 memory_current_screen = 5;
199 memory_screen_mask = 0xffff;
200
201 /* All memory comes from the home bank */
202 for( i = 0; i < MEMORY_PAGES_IN_64K; i++ )
203 memory_map_read[i].source = memory_map_write[i].source = memory_source_ram;
204
205 /* RAM pages 4, 5, 6 and 7 contended */
206 for( i = 0; i < 8; i++ )
207 memory_ram_set_16k_contention( i, i >= 4 );
208
209 error = normal_memory_map( 0, 0 ); if( error ) return error;
210
211 return 0;
212 }
213
214 static int
normal_memory_map(int rom,int page)215 normal_memory_map( int rom, int page )
216 {
217 /* ROM as specified */
218 memory_map_16k( 0x0000, memory_map_rom, rom );
219 /* RAM 5 */
220 memory_map_16k( 0x4000, memory_map_ram, 5 );
221 /* RAM 2 */
222 memory_map_16k( 0x8000, memory_map_ram, 2 );
223 /* RAM 0 */
224 memory_map_16k( 0xc000, memory_map_ram, page );
225
226 return 0;
227 }
228
229 static void
special_memory_map(int which)230 special_memory_map( int which )
231 {
232 switch( which ) {
233 case 0: select_special_map( 0, 1, 2, 3 ); break;
234 case 1: select_special_map( 4, 5, 6, 7 ); break;
235 case 2: select_special_map( 4, 5, 6, 3 ); break;
236 case 3: select_special_map( 4, 7, 6, 3 ); break;
237
238 default:
239 ui_error( UI_ERROR_ERROR, "unknown +3 special configuration %d", which );
240 fuse_abort();
241 }
242 }
243
244 static void
select_special_map(int page1,int page2,int page3,int page4)245 select_special_map( int page1, int page2, int page3, int page4 )
246 {
247 memory_map_16k( 0x0000, memory_map_ram, page1 );
248 memory_map_16k( 0x4000, memory_map_ram, page2 );
249 memory_map_16k( 0x8000, memory_map_ram, page3 );
250 memory_map_16k( 0xc000, memory_map_ram, page4 );
251 }
252
253 void
specplus3_memoryport2_write(libspectrum_word port GCC_UNUSED,libspectrum_byte b)254 specplus3_memoryport2_write( libspectrum_word port GCC_UNUSED,
255 libspectrum_byte b )
256 {
257 /* Let the parallel printer code know about the strobe bit */
258 printer_parallel_strobe_write( b & 0x10 );
259
260 /* If this was called by a machine which has a +3-style disk, set
261 the state of both floppy drive motors */
262 if( machine_current->capabilities &&
263 LIBSPECTRUM_MACHINE_CAPABILITY_PLUS3_DISK ) {
264
265 fdd_motoron( &specplus3_drives[0].fdd, b & 0x08 );
266 fdd_motoron( &specplus3_drives[1].fdd, b & 0x08 );
267
268 ui_statusbar_update( UI_STATUSBAR_ITEM_DISK,
269 b & 0x08 ? UI_STATUSBAR_STATE_ACTIVE :
270 UI_STATUSBAR_STATE_INACTIVE );
271 }
272
273 /* Do nothing else if we've locked the RAM configuration */
274 if( machine_current->ram.locked ) return;
275
276 /* Store the last byte written in case we need it */
277 machine_current->ram.last_byte2 = b;
278
279 machine_current->memory_map();
280 }
281
282 int
specplus3_memory_map(void)283 specplus3_memory_map( void )
284 {
285 int page, rom, screen;
286
287 page = machine_current->ram.last_byte & 0x07;
288 screen = ( machine_current->ram.last_byte & 0x08 ) ? 7 : 5;
289 rom =
290 ( ( machine_current->ram.last_byte & 0x10 ) >> 4 ) |
291 ( ( machine_current->ram.last_byte2 & 0x04 ) >> 1 );
292
293 /* If we changed the active screen, mark the entire display file as
294 dirty so we redraw it on the next pass */
295 if( memory_current_screen != screen ) {
296 display_update_critical( 0, 0 );
297 display_refresh_main_screen();
298 memory_current_screen = screen;
299 }
300
301 /* Check whether we want a special RAM configuration */
302 if( machine_current->ram.last_byte2 & 0x01 ) {
303
304 /* If so, select it */
305 machine_current->ram.special = 1;
306 special_memory_map( ( machine_current->ram.last_byte2 & 0x06 ) >> 1 );
307
308 } else {
309
310 /* If not, we're selecting the high bit of the current ROM */
311 machine_current->ram.special = 0;
312 normal_memory_map( rom, page );
313
314 }
315
316 machine_current->ram.current_page = page;
317 machine_current->ram.current_rom = rom;
318
319 memory_romcs_map();
320
321 return 0;
322 }
323
324 void
specplus3_menu_items(void)325 specplus3_menu_items( void )
326 {
327 const fdd_params_t *dt;
328
329 /* We can eject disks only if they are currently present */
330 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_A_EJECT,
331 specplus3_drives[ SPECPLUS3_DRIVE_A ].fdd.loaded );
332 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_A_FLIP_SET,
333 !specplus3_drives[ SPECPLUS3_DRIVE_A ].fdd.upsidedown );
334 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_A_WP_SET,
335 !specplus3_drives[ SPECPLUS3_DRIVE_A ].fdd.wrprot );
336
337 dt = &fdd_params[ option_enumerate_diskoptions_drive_plus3b_type() ];
338 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_B, dt->enabled );
339 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_B_EJECT,
340 specplus3_drives[ SPECPLUS3_DRIVE_B ].fdd.loaded );
341 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_B_FLIP_SET,
342 !specplus3_drives[ SPECPLUS3_DRIVE_B ].fdd.upsidedown );
343 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_B_WP_SET,
344 !specplus3_drives[ SPECPLUS3_DRIVE_B ].fdd.wrprot );
345 }
346
347 libspectrum_byte
specplus3_fdc_status(libspectrum_word port GCC_UNUSED,int * attached)348 specplus3_fdc_status( libspectrum_word port GCC_UNUSED, int *attached )
349 {
350 *attached = 1;
351 return upd_fdc_read_status( specplus3_fdc );
352 }
353
354 libspectrum_byte
specplus3_fdc_read(libspectrum_word port GCC_UNUSED,int * attached)355 specplus3_fdc_read( libspectrum_word port GCC_UNUSED, int *attached )
356 {
357 *attached = 1;
358 return upd_fdc_read_data( specplus3_fdc );
359 }
360
361 void
specplus3_fdc_write(libspectrum_word port GCC_UNUSED,libspectrum_byte data)362 specplus3_fdc_write( libspectrum_word port GCC_UNUSED, libspectrum_byte data )
363 {
364 upd_fdc_write_data( specplus3_fdc, data );
365 }
366
367 /* FDC UI related functions */
368
369 int
specplus3_disk_insert(specplus3_drive_number which,const char * filename,int autoload)370 specplus3_disk_insert( specplus3_drive_number which, const char *filename,
371 int autoload )
372 {
373 int error;
374 upd_fdc_drive *d;
375 const fdd_params_t *dt;
376
377 if( which >= SPECPLUS3_NUM_DRIVES ) {
378 ui_error( UI_ERROR_ERROR, "specplus3_disk_insert: unknown drive %d",
379 which );
380 fuse_abort();
381 }
382
383 d = &specplus3_drives[ which ];
384
385 /* Eject any disk already in the drive */
386 if( d->fdd.loaded ) {
387 /* Abort the insert if we want to keep the current disk */
388 if( specplus3_disk_eject( which ) ) return 0;
389 }
390
391 if( filename ) {
392 error = disk_open( &d->disk, filename, 0, DISK_TRY_MERGE( d->fdd.fdd_heads ) );
393 if( error != DISK_OK ) {
394 ui_error( UI_ERROR_ERROR, "Failed to open disk image: %s",
395 disk_strerror( error ) );
396 return 1;
397 }
398 } else {
399 switch( which ) {
400 case 0:
401 dt = &fdd_params[ option_enumerate_diskoptions_drive_plus3a_type() + 1 ]; /* +1 => there is no `Disabled' */
402 break;
403 case 1:
404 default:
405 dt = &fdd_params[ option_enumerate_diskoptions_drive_plus3b_type() ];
406 break;
407 }
408 error = disk_new( &d->disk, dt->heads, dt->cylinders, DISK_DENS_AUTO, DISK_UDI );
409 disk_preformat( &d->disk ); /* pre-format disk for +3 */
410 if( error != DISK_OK ) {
411 ui_error( UI_ERROR_ERROR, "Failed to create disk image: %s",
412 disk_strerror( error ) );
413 return 1;
414 }
415 }
416
417 fdd_load( &d->fdd, &d->disk, 0 );
418
419 /* Set the 'eject' item active */
420 switch( which ) {
421 case SPECPLUS3_DRIVE_A:
422 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_A_EJECT, 1 );
423 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_A_FLIP_SET,
424 !specplus3_drives[ SPECPLUS3_DRIVE_A ].fdd.upsidedown );
425 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_A_WP_SET,
426 !specplus3_drives[ SPECPLUS3_DRIVE_A ].fdd.wrprot );
427 break;
428 case SPECPLUS3_DRIVE_B:
429 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_B_EJECT, 1 );
430 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_B_FLIP_SET,
431 !specplus3_drives[ SPECPLUS3_DRIVE_B ].fdd.upsidedown );
432 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_B_WP_SET,
433 !specplus3_drives[ SPECPLUS3_DRIVE_B ].fdd.wrprot );
434 break;
435 }
436
437 if( filename && autoload ) {
438 /* XXX */
439 }
440
441 return 0;
442 }
443
444 int
specplus3_disk_eject(specplus3_drive_number which)445 specplus3_disk_eject( specplus3_drive_number which )
446 {
447 upd_fdc_drive *d;
448
449 if( which >= SPECPLUS3_NUM_DRIVES )
450 return 1;
451
452 d = &specplus3_drives[ which ];
453
454 if( d->disk.type == DISK_TYPE_NONE )
455 return 0;
456
457 if( d->disk.dirty ) {
458
459 ui_confirm_save_t confirm = ui_confirm_save(
460 "Disk in drive %c has been modified.\n"
461 "Do you want to save it?",
462 which == SPECPLUS3_DRIVE_A ? 'A' : 'B'
463 );
464
465 switch( confirm ) {
466
467 case UI_CONFIRM_SAVE_SAVE:
468 if( specplus3_disk_save( which, 0 ) ) return 1; /* first save it...*/
469 break;
470
471 case UI_CONFIRM_SAVE_DONTSAVE: break;
472 case UI_CONFIRM_SAVE_CANCEL: return 1;
473
474 }
475 }
476
477 fdd_unload( &d->fdd );
478 disk_close( &d->disk );
479
480 /* Set the 'eject' item inactive */
481 switch( which ) {
482 case SPECPLUS3_DRIVE_A:
483 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_A_EJECT, 0 );
484 break;
485 case SPECPLUS3_DRIVE_B:
486 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_B_EJECT, 0 );
487 break;
488 }
489 return 0;
490 }
491
492 int
specplus3_disk_save(specplus3_drive_number which,int saveas)493 specplus3_disk_save( specplus3_drive_number which, int saveas )
494 {
495 upd_fdc_drive *d;
496
497 if( which >= SPECPLUS3_NUM_DRIVES )
498 return 1;
499
500 d = &specplus3_drives[ which ];
501
502 if( d->disk.type == DISK_TYPE_NONE )
503 return 0;
504
505 if( d->disk.filename == NULL ) saveas = 1;
506 if( ui_plus3_disk_write( which, saveas ) ) return 1;
507 d->disk.dirty = 0;
508 return 0;
509 }
510
511 int
specplus3_disk_flip(specplus3_drive_number which,int flip)512 specplus3_disk_flip( specplus3_drive_number which, int flip )
513 {
514 upd_fdc_drive *d;
515
516 if( which >= SPECPLUS3_NUM_DRIVES )
517 return 1;
518
519 d = &specplus3_drives[ which ];
520
521 if( !d->fdd.loaded )
522 return 1;
523
524 fdd_flip( &d->fdd, flip );
525
526 /* Update the 'flip' menu items */
527 switch( which ) {
528 case SPECPLUS3_DRIVE_A:
529 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_A_FLIP_SET,
530 !specplus3_drives[ SPECPLUS3_DRIVE_A ].fdd.upsidedown );
531 break;
532 case SPECPLUS3_DRIVE_B:
533 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_B_FLIP_SET,
534 !specplus3_drives[ SPECPLUS3_DRIVE_B ].fdd.upsidedown );
535 break;
536 }
537 return 0;
538 }
539
540 int
specplus3_disk_writeprotect(specplus3_drive_number which,int wrprot)541 specplus3_disk_writeprotect( specplus3_drive_number which, int wrprot )
542 {
543 upd_fdc_drive *d;
544
545 if( which >= SPECPLUS3_NUM_DRIVES )
546 return 1;
547
548 d = &specplus3_drives[ which ];
549
550 if( !d->fdd.loaded )
551 return 1;
552
553 fdd_wrprot( &d->fdd, wrprot );
554
555 /* Update the 'write protect' menu item */
556 switch( which ) {
557 case SPECPLUS3_DRIVE_A:
558 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_A_WP_SET,
559 !specplus3_drives[ SPECPLUS3_DRIVE_A ].fdd.wrprot );
560 break;
561 case SPECPLUS3_DRIVE_B:
562 ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_PLUS3_B_WP_SET,
563 !specplus3_drives[ SPECPLUS3_DRIVE_B ].fdd.wrprot );
564 break;
565 }
566 return 0;
567 }
568
569 int
specplus3_disk_write(specplus3_drive_number which,const char * filename)570 specplus3_disk_write( specplus3_drive_number which, const char *filename )
571 {
572 upd_fdc_drive *d = &specplus3_drives[ which ];
573 int error;
574
575 d->disk.type = DISK_TYPE_NONE;
576 if( filename == NULL ) filename = d->disk.filename; /* write over original file */
577 error = disk_write( &d->disk, filename );
578
579 if( error != DISK_OK ) {
580 ui_error( UI_ERROR_ERROR, "couldn't write '%s' file: %s", filename,
581 disk_strerror( error ) );
582 return 1;
583 }
584
585 if( d->disk.filename && strcmp( filename, d->disk.filename ) ) {
586 free( d->disk.filename );
587 d->disk.filename = utils_safe_strdup( filename );
588 }
589 return 0;
590 }
591
592 fdd_t *
specplus3_get_fdd(specplus3_drive_number which)593 specplus3_get_fdd( specplus3_drive_number which )
594 {
595 return &( specplus3_drives[ which ].fdd );
596 }
597
598 int
specplus3_shutdown(void)599 specplus3_shutdown( void )
600 {
601 return 0;
602 }
603