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