/* zxatasp.c: ZXATASP interface routines
   Copyright (c) 2003-2005 Garry Lancaster,
		 2004-2005 Philip Kendall

   $Id: zxatasp.c,v 1.15 2007/02/02 16:21:52 pak21 Exp $

   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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

   Author contact information:

   E-mail: Philip Kendall <philip-fuse@shadowmagic.org.uk>

*/

#include <config.h>

#include <string.h>

#include <libspectrum.h>

#include "ide.h"
#include "machine.h"
#include "memory.h"
#include "periph.h"
#include "settings.h"
#include "ui/ui.h"
#include "zxatasp.h"

/*
  TBD: Allow memory size selection (128K/512K)
  TBD: Should support for secondary channel be removed?
       No software ever supported it, and v2.0+ boards don't have it.
*/


/* Private function prototypes */

static libspectrum_byte zxatasp_portA_read( libspectrum_word port,
					    int *attached );
static void zxatasp_portA_write( libspectrum_word port,
				 libspectrum_byte data );
static libspectrum_byte zxatasp_portB_read( libspectrum_word port,
					    int *attached );
static void zxatasp_portB_write( libspectrum_word port,
				 libspectrum_byte data );
static libspectrum_byte zxatasp_portC_read( libspectrum_word port,
					    int *attached );
static void zxatasp_portC_write( libspectrum_word port,
				 libspectrum_byte data );
static libspectrum_byte zxatasp_control_read( libspectrum_word port,
					      int *attached );
static void zxatasp_control_write( libspectrum_word port,
				   libspectrum_byte data );
static void zxatasp_resetports( void );
static void set_zxatasp_bank( int bank );

static void zxatasp_readide( libspectrum_ide_channel *chn,
			     libspectrum_ide_register idereg );
static void zxatasp_writeide( libspectrum_ide_channel *chn,
			      libspectrum_ide_register idereg );

/* Data */

const periph_t zxatasp_peripherals[] = {
  { 0x039f, 0x009f, zxatasp_portA_read, zxatasp_portA_write },
  { 0x039f, 0x019f, zxatasp_portB_read, zxatasp_portB_write },
  { 0x039f, 0x029f, zxatasp_portC_read, zxatasp_portC_write },
  { 0x039f, 0x039f, zxatasp_control_read, zxatasp_control_write },
};

const size_t zxatasp_peripherals_count =
  sizeof( zxatasp_peripherals ) / sizeof( periph_t );

static libspectrum_byte zxatasp_control;
static libspectrum_byte zxatasp_portA;
static libspectrum_byte zxatasp_portB;
static libspectrum_byte zxatasp_portC;
static size_t current_page;

static libspectrum_ide_channel *zxatasp_idechn0=NULL;
static libspectrum_ide_channel *zxatasp_idechn1=NULL;

#define ZXATASP_PAGES 32
#define ZXATASP_PAGE_LENGTH 0x4000
static libspectrum_byte ZXATASPMEM[ ZXATASP_PAGES ][ ZXATASP_PAGE_LENGTH ];

static const size_t ZXATASP_NOT_PAGED = 0xff;

/* We're ignoring all mode bits and only emulating mode 0, basic I/O */
static const libspectrum_byte MC8255_PORT_C_LOW_IO  = 0x01;
static const libspectrum_byte MC8255_PORT_B_IO      = 0x02;
static const libspectrum_byte MC8255_PORT_C_HI_IO   = 0x08;
static const libspectrum_byte MC8255_PORT_A_IO      = 0x10;
static const libspectrum_byte MC8255_SETMODE        = 0x80;

static const libspectrum_byte ZXATASP_IDE_REG       = 0x07;
static const libspectrum_byte ZXATASP_RAM_BANK      = 0x1f;
static const libspectrum_byte ZXATASP_IDE_WR        = 0x08;
static const libspectrum_byte ZXATASP_IDE_RD        = 0x10;
static const libspectrum_byte ZXATASP_IDE_PRIMARY   = 0x20;
static const libspectrum_byte ZXATASP_RAM_LATCH     = 0x40;
static const libspectrum_byte ZXATASP_RAM_DISABLE   = 0x80;
static const libspectrum_byte ZXATASP_IDE_SECONDARY = 0x80;

#define ZXATASP_READ_PRIMARY( x )    \
  ( ( x & ( ZXATASP_IDE_PRIMARY   | ZXATASP_RAM_LATCH |        \
            ZXATASP_IDE_RD        | ZXATASP_IDE_WR      ) ) == \
          ( ZXATASP_IDE_PRIMARY   | ZXATASP_IDE_RD )             )
#define ZXATASP_WRITE_PRIMARY( x )   \
  ( ( x & ( ZXATASP_IDE_PRIMARY   | ZXATASP_RAM_LATCH |        \
            ZXATASP_IDE_RD        | ZXATASP_IDE_WR      ) ) == \
          ( ZXATASP_IDE_PRIMARY   | ZXATASP_IDE_WR )             )
#define ZXATASP_READ_SECONDARY( x )  \
  ( ( x & ( ZXATASP_IDE_SECONDARY | ZXATASP_RAM_LATCH |        \
            ZXATASP_IDE_RD        | ZXATASP_IDE_WR      ) ) == \
          ( ZXATASP_IDE_SECONDARY | ZXATASP_IDE_RD )             )
#define ZXATASP_WRITE_SECONDARY( x ) \
  ( ( x & ( ZXATASP_IDE_SECONDARY | ZXATASP_RAM_LATCH |        \
            ZXATASP_IDE_RD        | ZXATASP_IDE_WR      ) ) == \
          ( ZXATASP_IDE_SECONDARY | ZXATASP_IDE_WR )             )

/* Housekeeping functions */

int
zxatasp_init( void )
{
  int error;

  zxatasp_idechn0 = zxatasp_idechn1 = NULL ;

  error = libspectrum_ide_alloc( &zxatasp_idechn0, LIBSPECTRUM_IDE_DATA16 );
  if( error ) return error;
  error = libspectrum_ide_alloc( &zxatasp_idechn1, LIBSPECTRUM_IDE_DATA16 );
  if( error ) return error;
  
  ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_ZXATASP_MASTER_EJECT, 0 );
  ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_ZXATASP_SLAVE_EJECT, 0 );

  if( settings_current.zxatasp_master_file ) {
    error = libspectrum_ide_insert( zxatasp_idechn0, LIBSPECTRUM_IDE_MASTER,
				    settings_current.zxatasp_master_file );
    if( error ) return error;
    ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_ZXATASP_MASTER_EJECT, 1 );
  }

  if( settings_current.zxatasp_slave_file ) {
    error = libspectrum_ide_insert( zxatasp_idechn0, LIBSPECTRUM_IDE_SLAVE,
				    settings_current.zxatasp_slave_file );
    if( error ) return error;
    ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_ZXATASP_SLAVE_EJECT, 1 );
  }

  return error;
}

int
zxatasp_end( void )
{
  int error;
  
  error = libspectrum_ide_free( zxatasp_idechn0 );
  error = libspectrum_ide_free( zxatasp_idechn1 ) || error;

  zxatasp_idechn0 = zxatasp_idechn1 = NULL ;

  return error;
}

void
zxatasp_reset( void )
{
  if( !settings_current.zxatasp_active ) return;
  
  machine_current->ram.romcs = 1;

  set_zxatasp_bank( 0 ); current_page = 0;
  machine_current->memory_map();

  zxatasp_control = MC8255_SETMODE | MC8255_PORT_A_IO | MC8255_PORT_B_IO |
                    MC8255_PORT_C_HI_IO | MC8255_PORT_C_LOW_IO;
  zxatasp_resetports();

  libspectrum_ide_reset( zxatasp_idechn0 );
  libspectrum_ide_reset( zxatasp_idechn1 );
}

int
zxatasp_insert( const char *filename, libspectrum_ide_unit unit )
{
  char **setting;
  ui_menu_item item;

  switch( unit ) {
  case LIBSPECTRUM_IDE_MASTER:
    setting = &settings_current.zxatasp_master_file;
    item = UI_MENU_ITEM_MEDIA_IDE_ZXATASP_MASTER_EJECT;
    break;
    
  case LIBSPECTRUM_IDE_SLAVE:
    setting = &settings_current.zxatasp_slave_file;
    item = UI_MENU_ITEM_MEDIA_IDE_ZXATASP_SLAVE_EJECT;
    break;
    
  default: return 1;
  }

  return ide_insert( filename, zxatasp_idechn0, unit, zxatasp_commit, setting,
		     item );
}

int
zxatasp_commit( libspectrum_ide_unit unit )
{
  int error;

  error = libspectrum_ide_commit( zxatasp_idechn0, unit );

  return error;
}

int
zxatasp_eject( libspectrum_ide_unit unit )
{
  char **setting;
  ui_menu_item item;

  switch( unit ) {
  case LIBSPECTRUM_IDE_MASTER:
    setting = &settings_current.zxatasp_master_file;
    item = UI_MENU_ITEM_MEDIA_IDE_ZXATASP_MASTER_EJECT;
    break;

  case LIBSPECTRUM_IDE_SLAVE:
    setting = &settings_current.zxatasp_slave_file;
    item = UI_MENU_ITEM_MEDIA_IDE_ZXATASP_SLAVE_EJECT;
    break;
    
  default: return 1;
  }

  return ide_eject( zxatasp_idechn0, unit, zxatasp_commit, setting, item );
}

/* Port read/writes */

libspectrum_byte
zxatasp_portA_read( libspectrum_word port GCC_UNUSED, int *attached )
{
  if( !settings_current.zxatasp_active ) return 0xff;
  
  *attached = 1;
  
  return zxatasp_portA;
}

static void
zxatasp_portA_write( libspectrum_word port GCC_UNUSED, libspectrum_byte data )
{
  if( !settings_current.zxatasp_active ) return;
  
  if( !( zxatasp_control & MC8255_PORT_A_IO ) ) zxatasp_portA = data;
}

libspectrum_byte
zxatasp_portB_read( libspectrum_word port GCC_UNUSED, int *attached )
{
  if( !settings_current.zxatasp_active ) return 0xff;
  
  *attached = 1;
  
  return zxatasp_portB;
}

static void
zxatasp_portB_write( libspectrum_word port GCC_UNUSED, libspectrum_byte data )
{
  if( !settings_current.zxatasp_active ) return;
  
  if( !( zxatasp_control & MC8255_PORT_B_IO ) ) zxatasp_portB = data;
}

libspectrum_byte
zxatasp_portC_read( libspectrum_word port GCC_UNUSED, int *attached )
{
  if( !settings_current.zxatasp_active ) return 0xff;
  
  *attached = 1;
  
  return zxatasp_portC;
}

static void
zxatasp_portC_write( libspectrum_word port GCC_UNUSED, libspectrum_byte data )
{
  libspectrum_byte oldC = zxatasp_portC;
  libspectrum_byte newC;
  
  if( !settings_current.zxatasp_active ) return;

  /* Determine new port C value, dependent on I/O modes */
  newC = ( zxatasp_control & MC8255_PORT_C_LOW_IO )
            ? ( oldC & 0x0f ) : ( data & 0x0f );
            
  newC |= ( zxatasp_control & MC8255_PORT_C_HI_IO )
            ? ( oldC & 0xf0 ) : ( data & 0xf0 );
            
  /* Set the new port value */
  zxatasp_portC = newC;
  
  /* No action can occur if high part of port C is in input mode */
  if( zxatasp_control & MC8255_PORT_C_HI_IO ) return;
  
  /* Check for any I/O action */
  if(  ( ZXATASP_READ_PRIMARY( newC ) ) &
      !( ZXATASP_READ_PRIMARY( oldC ) )   ) {
    zxatasp_readide( zxatasp_idechn0, ( newC & ZXATASP_IDE_REG ) );
    return;
  }
  
  if(  ( ZXATASP_READ_SECONDARY( newC ) ) &
      !( ZXATASP_READ_SECONDARY( oldC ) )   ) {
    zxatasp_readide( zxatasp_idechn1, ( newC & ZXATASP_IDE_REG ) );
    return;
  }
  
  if(  ( ZXATASP_WRITE_PRIMARY( newC ) ) &
      !( ZXATASP_WRITE_PRIMARY( oldC ) )   ) {
    zxatasp_writeide( zxatasp_idechn0, ( newC & ZXATASP_IDE_REG ) );
    return;
  }
  
  if(  ( ZXATASP_WRITE_SECONDARY( newC ) ) &
      !( ZXATASP_WRITE_SECONDARY( oldC ) )   ) {
    zxatasp_writeide( zxatasp_idechn1, ( newC & ZXATASP_IDE_REG ) );
    return;
  }

  if( newC & ZXATASP_RAM_LATCH ) {
    set_zxatasp_bank( newC & ZXATASP_RAM_BANK );

    if( newC & ZXATASP_RAM_DISABLE ) {
      machine_current->ram.romcs = 0;
      current_page = ZXATASP_NOT_PAGED;
    } else {
      machine_current->ram.romcs = 1;
      current_page = newC & ZXATASP_RAM_BANK;
    }

    machine_current->memory_map();
    return;
  }
}

libspectrum_byte
zxatasp_control_read( libspectrum_word port, int *attached )
{
  if( !settings_current.zxatasp_active ) return 0xff;
  
  *attached = 1;
  
  return zxatasp_control;
}

static void
zxatasp_control_write( libspectrum_word port GCC_UNUSED, libspectrum_byte data )
{
  if( !settings_current.zxatasp_active ) return;
  
  if( data & MC8255_SETMODE ) {
    /* Set the control word and reset the ports */
    zxatasp_control = data;
    zxatasp_resetports();

  } else {

    /* Set or reset a bit of port C */
    libspectrum_byte bit = (data >> 1) & 7;
    libspectrum_byte newC = zxatasp_portC;
      
    if( data & 1 ) {
      newC |=  ( 1 << bit );
    } else {
      newC &= ~( 1 << bit );
    }
    
    zxatasp_portC_write( 0, newC );
  }
}

static void
zxatasp_resetports( void )
{
  /* In input mode, ports will return 0xff unless IDE is active,
     which it won't be just after a reset. Output ports are always
     reset to zero.
  */
  zxatasp_portA = (zxatasp_control & MC8255_PORT_A_IO) ? 0xff : 0x00;
  zxatasp_portB = (zxatasp_control & MC8255_PORT_B_IO) ? 0xff : 0x00;
  zxatasp_portC = (zxatasp_control & MC8255_PORT_C_LOW_IO) ? 0x0f : 0x00;
  zxatasp_portC |= (zxatasp_control & MC8255_PORT_C_HI_IO) ? 0xf0 : 0x00;
}

static void
set_zxatasp_bank( int bank )
{
  memory_page *page;
  size_t i, offset;

  for( i = 0; i < 2; i++ ) {

    page = &memory_map_romcs[i];
    offset = i & 1 ? MEMORY_PAGE_SIZE : 0x0000;

    page->page = &ZXATASPMEM[ bank ][ offset ];
    page->writable = !settings_current.zxatasp_wp;
    page->contended = 0;
    
    page->page_num = bank;
    page->offset = offset;
  }
}

/* IDE access */

static void
zxatasp_readide(libspectrum_ide_channel *chn,
                libspectrum_ide_register idereg)
{
  libspectrum_byte dataHi, dataLo;
  
  dataLo = libspectrum_ide_read( chn, idereg );
  
  if( idereg == LIBSPECTRUM_IDE_REGISTER_DATA ) {
    dataHi = libspectrum_ide_read( chn, idereg );
  } else {
    dataHi = 0xff;
  }

  if( zxatasp_control & MC8255_PORT_A_IO ) zxatasp_portA = dataLo;
  if( zxatasp_control & MC8255_PORT_B_IO ) zxatasp_portB = dataHi;
}

static void
zxatasp_writeide(libspectrum_ide_channel *chn,
                 libspectrum_ide_register idereg)
{
  libspectrum_byte dataHi, dataLo;

  dataLo = ( zxatasp_control & MC8255_PORT_A_IO ) ? 0xff : zxatasp_portA;
  dataHi = ( zxatasp_control & MC8255_PORT_B_IO ) ? 0xff : zxatasp_portB;
  
  libspectrum_ide_write( chn, idereg, dataLo );
  
  if( idereg == LIBSPECTRUM_IDE_REGISTER_DATA )
    libspectrum_ide_write( chn, idereg, dataHi );
}

size_t
zxatasp_current_page( void )
{
  return current_page;
}

libspectrum_byte*
zxatasp_ram( size_t page )
{
  return ZXATASPMEM[ page ];
}

void
zxatasp_memory_map( void )
{
  int writable;

  if( settings_current.zxatasp_wp && ( memory_map_romcs[0].page_num & 1 ) ) {
    writable = 0;
  } else {
    writable = 1;
  }

  memory_map_romcs[0].writable = memory_map_romcs[1].writable = writable;

  if( !settings_current.zxatasp_upload ) {
    memory_map_read[0] = memory_map_romcs[0];
    memory_map_read[1] = memory_map_romcs[1];
  }

  memory_map_write[0] = memory_map_romcs[0];
  memory_map_write[1] = memory_map_romcs[1];
}

int
zxatasp_from_snapshot( libspectrum_snap *snap )
{
  size_t i, page;

  if( !libspectrum_snap_zxatasp_active( snap ) ) return 0;

  settings_current.zxatasp_active = 1;
  settings_current.zxatasp_upload = libspectrum_snap_zxatasp_upload( snap );
  settings_current.zxatasp_wp = libspectrum_snap_zxatasp_writeprotect( snap );

  zxatasp_portA   = libspectrum_snap_zxatasp_port_a ( snap );
  zxatasp_portB   = libspectrum_snap_zxatasp_port_b ( snap );
  zxatasp_portC   = libspectrum_snap_zxatasp_port_c ( snap );
  zxatasp_control = libspectrum_snap_zxatasp_control( snap );

  page = libspectrum_snap_zxatasp_current_page( snap );

  if( page != ZXATASP_NOT_PAGED ) {
    machine_current->ram.romcs = 1;
    set_zxatasp_bank( page );
  }

  for( i = 0; i < libspectrum_snap_zxatasp_pages( snap ); i++ )
    if( libspectrum_snap_zxatasp_ram( snap, i ) )
      memcpy( ZXATASPMEM[ i ], libspectrum_snap_zxatasp_ram( snap, i ),
	      ZXATASP_PAGE_LENGTH );

  machine_current->memory_map();

  return 0;
}

int
zxatasp_to_snapshot( libspectrum_snap *snap )
{
  size_t i;
  libspectrum_byte *buffer;

  if( !settings_current.zxatasp_active ) return 0;

  libspectrum_snap_set_zxatasp_active( snap, 1 );
  libspectrum_snap_set_zxatasp_upload( snap, settings_current.zxatasp_upload );
  libspectrum_snap_set_zxatasp_writeprotect( snap,
					     settings_current.zxatasp_wp );

  libspectrum_snap_set_zxatasp_port_a( snap, zxatasp_portA );
  libspectrum_snap_set_zxatasp_port_b( snap, zxatasp_portB );
  libspectrum_snap_set_zxatasp_port_c( snap, zxatasp_portC );
  libspectrum_snap_set_zxatasp_control( snap, zxatasp_control );
  libspectrum_snap_set_zxatasp_current_page( snap, current_page );

  libspectrum_snap_set_zxatasp_pages( snap, ZXATASP_PAGES );

  for( i = 0; i < ZXATASP_PAGES; i++ ) {

    buffer = malloc( ZXATASP_PAGE_LENGTH * sizeof( libspectrum_byte ) );
    if( !buffer ) {
      ui_error( UI_ERROR_ERROR, "Out of memory at %s:%d", __FILE__, __LINE__ );
      return 1;
    }

    memcpy( buffer, ZXATASPMEM[ i ], ZXATASP_PAGE_LENGTH );
    libspectrum_snap_set_zxatasp_ram( snap, i, buffer );
  }
  
  return 0;
}
