/* ay.c: AY-3-8912 routines Copyright (c) 1999-2016 Philip Kendall Copyright (c) 2015 Stuart Brady This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Author contact information: E-mail: philip-fuse@shadowmagic.org.uk */ #include #include #include "compat.h" #include "debugger/debugger.h" #include "infrastructure/startup_manager.h" #include "machine.h" #include "module.h" #include "periph.h" #include "printer.h" #include "psg.h" #include "sound.h" /* Unused bits in the AY registers are silently zeroed out; these masks accomplish this */ static const libspectrum_byte mask[ AY_REGISTERS ] = { 0xff, 0x0f, 0xff, 0x0f, 0xff, 0x0f, 0x1f, 0xff, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0x0f, 0xff, 0xff, }; static void ay_reset( int hard_reset ); static void ay_from_snapshot( libspectrum_snap *snap ); static void ay_to_snapshot( libspectrum_snap *snap ); static libspectrum_dword get_current_register( void ); static void set_current_register( libspectrum_dword value ); static module_info_t ay_module_info = { /* .reset = */ ay_reset, /* .romcs = */ NULL, /* .snapshot_enabled = */ NULL, /* .snapshot_from = */ ay_from_snapshot, /* .snapshot_to = */ ay_to_snapshot, }; static periph_port_t ay_ports[] = { { 0xc002, 0xc000, ay_registerport_read, ay_registerport_write }, { 0xc002, 0x8000, NULL, ay_dataport_write }, { 0, 0, NULL, NULL } }; static periph_t ay_periph = { /* .option = */ NULL, /* .ports = */ ay_ports, /* .hard_reset = */ 0, /* .actinate = */ NULL, }; static periph_port_t ay_ports_plus3[] = { { 0xc002, 0xc000, ay_registerport_read, ay_registerport_write }, { 0xc002, 0x8000, ay_registerport_read, ay_dataport_write }, { 0, 0, NULL, NULL } }; static periph_t ay_periph_plus3 = { /* .option = */ NULL, /* .ports = */ ay_ports_plus3, /* .hard_reset = */ 0, /* .activate = */ NULL, }; static periph_port_t ay_ports_full_decode[] = { { 0xffff, 0xfffd, ay_registerport_read, ay_registerport_write }, { 0xffff, 0xbffd, NULL, ay_dataport_write }, { 0, 0, NULL, NULL } }; static periph_t ay_periph_full_decode = { /* .option = */ NULL, /* .ports = */ ay_ports_full_decode, /* .hard_reset = */ 0, /* .activate = */ NULL, }; static periph_port_t ay_ports_timex[] = { { 0x00ff, 0x00f5, ay_registerport_read, ay_registerport_write }, { 0x00ff, 0x00f6, NULL, ay_dataport_write }, { 0, 0, NULL, NULL } }; static periph_t ay_periph_timex = { /* .option = */ NULL, /* .ports = */ ay_ports_timex, /* .hard_reset = */ 0, /* .activate = */ NULL, }; /* Debugger system variables */ static const char * const debugger_type_string = "ay"; static const char * const current_register_detail_string = "current"; static int ay_init( void *context ) { module_register( &ay_module_info ); periph_register( PERIPH_TYPE_AY, &ay_periph ); periph_register( PERIPH_TYPE_AY_PLUS3, &ay_periph_plus3 ); periph_register( PERIPH_TYPE_AY_FULL_DECODE, &ay_periph_full_decode ); periph_register( PERIPH_TYPE_AY_TIMEX, &ay_periph_timex ); debugger_system_variable_register( debugger_type_string, current_register_detail_string, get_current_register, set_current_register ); return 0; } void ay_register_startup( void ) { startup_manager_module dependencies[] = { STARTUP_MANAGER_MODULE_DEBUGGER, STARTUP_MANAGER_MODULE_SETUID, }; startup_manager_register( STARTUP_MANAGER_MODULE_AY, dependencies, ARRAY_SIZE( dependencies ), ay_init, NULL, NULL ); } static void ay_reset( int hard_reset GCC_UNUSED ) { ayinfo *ay = &machine_current->ay; ay->current_register = 0; memset( ay->registers, 0, sizeof( ay->registers ) ); } /* What happens when the AY register port (traditionally 0xfffd on the 128K machines) is read from */ libspectrum_byte ay_registerport_read( libspectrum_word port GCC_UNUSED, libspectrum_byte *attached ) { int current; const libspectrum_byte port_input = 0xbf; /* always allow serial output */ *attached = 0xff; current = machine_current->ay.current_register; /* The AY I/O ports return input directly from the port when in input mode; but in output mode, they return an AND between the register value and the port input. So, allow for this when reading R14... */ if( current == 14 ) { if(machine_current->ay.registers[7] & 0x40) return (port_input & machine_current->ay.registers[14]); else return port_input; } /* R15 is simpler to do, as the 8912 lacks the second I/O port, and the input-mode input is always 0xff */ if( current == 15 && !( machine_current->ay.registers[7] & 0x80 ) ) return 0xff; /* Otherwise return register value, appropriately masked */ return machine_current->ay.registers[ current ] & mask[ current ]; } /* And when it's written to */ void ay_registerport_write( libspectrum_word port GCC_UNUSED, libspectrum_byte b ) { set_current_register( b ); } /* What happens when the AY data port (traditionally 0xbffd on the 128K machines) is written to; no corresponding read function as this always returns 0xff */ void ay_dataport_write( libspectrum_word port GCC_UNUSED, libspectrum_byte b ) { int current; current = machine_current->ay.current_register; machine_current->ay.registers[ current ] = b & mask[ current ]; sound_ay_write( current, b, tstates ); if( psg_recording ) psg_write_register( current, b ); if( current == 14 ) printer_serial_write( b ); } void ay_state_from_snapshot( libspectrum_snap *snap ) { size_t i; ay_registerport_write( 0xfffd, libspectrum_snap_out_ay_registerport( snap ) ); for( i = 0; i < AY_REGISTERS; i++ ) { machine_current->ay.registers[i] = libspectrum_snap_ay_registers( snap, i ); sound_ay_write( i, machine_current->ay.registers[i], 0 ); } } static void ay_from_snapshot( libspectrum_snap *snap ) { if( machine_current->capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_AY ) { ay_state_from_snapshot( snap ); } } static void ay_to_snapshot( libspectrum_snap *snap ) { size_t i; libspectrum_snap_set_out_ay_registerport( snap, machine_current->ay.current_register ); for( i = 0; i < AY_REGISTERS; i++ ) libspectrum_snap_set_ay_registers( snap, i, machine_current->ay.registers[i] ); } static libspectrum_dword get_current_register( void ) { return machine_current->ay.current_register; } static void set_current_register( libspectrum_dword value ) { machine_current->ay.current_register = (value & 0x0f); }