/*******************************************************************************
 * NewOswan
 * io.c: I/O ports implementaton
 * Based on the original Oswan-unix
 * Copyright (c) 2014-2021 986-Studio. All rights reserved.
 *
 ******************************************************************************/

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>  /* UNIX standard function definitions */
#include <errno.h>   /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */
#include <sys/mman.h>

#include "log.h"
#include "rom.h"
#include "./nec/nec.h"
#include "./nec/necintrf.h"
#include "gpu.h"
#include "audio.h"
#include "memory.h"
#include "ws.h"
#include <stdbool.h>

//#define IO_DUMP
//define EEPROM_DEBUG

extern uint8_t *externalEeprom;
extern uint32_t romAddressMask;
extern uint16_t *internalEeprom;
extern nec_Regs I;
extern uint64_t nec_monotonicCycles;
extern uint32_t sramSize;

enum
{
    EEPROM_SUBCOMMAND = 0,  /* 00 00 */
    EEPROM_WRITE,           /* 01 xx */
    EEPROM_READ,            /* 10 xx */
    EEPROM_ERASE,           /* 11 xx */
    EEPROM_WRITEDISABLE,    /* 00 00 */
    EEPROM_WRITEALL,        /* 00 01 */
    EEPROM_ERASEALL,        /* 00 10 */
    EEPROM_WRITEENABLE      /* 00 11 */
};

char *eii_CommandName[] = {
    "SUB", "WRI", "RED", "ERA", "WRD", "WRA", "ERL", "WRE",
};

uint8_t iee_WriteEnable = false;
uint16_t iee_SelAddress = 0;
uint16_t iee_Databuffer = 0;
uint8_t iee_Mode = EEPROM_READ;

uint8_t cee_WriteEnable = true;
uint16_t cee_SelAddress = 0;
uint16_t cee_Databuffer = 0;
uint8_t cee_Mode = EEPROM_READ;


uint8_t *ws_ioRam = NULL;

uint8_t ws_key_start;
uint8_t ws_key_x4;
uint8_t ws_key_x2;
uint8_t ws_key_x1;
uint8_t ws_key_x3;
uint8_t ws_key_y4;
uint8_t ws_key_y2;
uint8_t ws_key_y1;
uint8_t ws_key_y3;
uint8_t ws_key_button_a;
uint8_t ws_key_button_b;
uint8_t ws_key_flipped;

int rtcDataRegisterReadCount = 0;

FILE *ioLogFp = NULL;

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
void io_reset(void)
{
    ws_key_start = 0;
    ws_key_x4 = 0;
    ws_key_x2 = 0;
    ws_key_x1 = 0;
    ws_key_x3 = 0;
    ws_key_y4 = 0;
    ws_key_y2 = 0;
    ws_key_y1 = 0;
    ws_key_y3 = 0;
    ws_key_button_a = 0;
    ws_key_button_b = 0;
    int i;

    for (i = 0 ; i < 0x100 ; i++)
    {
        /*
         * 0x90 should probably be a better value as for some reason
         * the Swan seems to like to returh 0x90 for not connected memory/IO
         * Keep 0x00 for now until the whole IO "default" value is solved.
         */
        ws_ioRam[i] = 0x00;
    }

    ws_ioRam[0xC0] = 0xFF;
    ws_ioRam[0xC1] = 0xFF;
    ws_ioRam[0xC2] = 0xFF;
    ws_ioRam[0xC3] = 0xFF;

    rtcDataRegisterReadCount = 0;
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
void io_init(void)
{
    if (ws_ioRam == NULL)
    {
        ws_ioRam = (uint8_t *)malloc(0x100);
    }

    io_reset();
    ws_key_flipped = 0;

#ifdef IO_DUMP
    ioLogFp = fopen("iodump.csv", "wt");
#endif
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
void io_flipControls(void)
{
    ws_key_flipped = !ws_key_flipped;
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
void io_done(void)
{
    if (ws_ioRam == NULL)
    {
        free(ws_ioRam);
    }

#ifdef IO_DUMP
    fclose(ioLogFp);
#endif
}

/* Serial port */
#define BDR_9600 (0)
#define BDR_38400 (1)
#define SERIAL_PORT "/dev/tty.USA19H141P1.1"
int serialfd = -1;
int serial_have_data = 0;
unsigned char serial_data = 0;
int serial_speed = BDR_9600;

void open_serial()
{
    if (serialfd < 0)
    {
        serialfd = open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_NDELAY);

        //set_baudrate(serial_speed);
        serial_have_data = 0;
    }
}

void set_baudrate(int speed)
{
    struct termios options;

    if (serialfd < 0)
    {
        return;
    }

    tcgetattr(serialfd, &options);

    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;

    if (speed == BDR_9600)
    {
        cfsetispeed(&options, B9600);
    }
    else
    {
        cfsetospeed(&options, B38400);
    }

#if 0
    options.c_cflag &= ~CNEW_RTSCTS;
#else
    options.c_cflag &= ~CRTSCTS;
#endif
    options.c_cflag |= (CLOCAL | CREAD);

    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

    options.c_oflag &= ~OPOST;

    tcsetattr(serialfd, TCSANOW, &options);

    /* Make sure read is not blocking */
        fcntl(serialfd, F_SETFL, FNDELAY);
}

void close_serial()
{
    close(serialfd);
    serialfd = -1;
}

void check_serial_data()
{
    unsigned char buf[10];
    int f;

    if (serialfd < 0)
    {
        return;
    }

    if (serial_have_data == 0)
    {
        f = read(serialfd, buf, 1);

        if (f > 0)
        {
            Log(TLOG_DEBUG, "serial", "Have data from serial [%d]!", f);
            fflush(stdout);
            serial_have_data = 0x01;
            serial_data = buf[0];
        }
    }

    if (serial_have_data > 0)
    {
        /* Gen an int if enabled */
        if (ws_ioRam[0xB2] & 0x04)
        {
            ws_ioRam[0xb6] &= ~0x04;
            Log(TLOG_DEBUG, "serial", "SERIAL INNNNNTTTT!!!!!!!");
            nec_int((ws_ioRam[0xb0] + 3) * 4);
        }
    }
}

unsigned char read_serial()
{
    unsigned char buf[10];
    int f;

    if (serialfd < 0)
    {
        return 0xFF;
    }

    if (serial_have_data > 0)
    {
        serial_have_data = 0;
        return serial_data;
    }

    f = read(serialfd, buf, 1);

    if (f == 1)
    {
        return buf[0];
    }

    return 0x42;
}

void write_serial(unsigned char value)
{
    if (serialfd < 0)
    {
        return;
    }

    write(serialfd, &value, 1);
}


////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
uint8_t io_readport(uint8_t port)
{
    int w1, w2;
    uint8_t retVal = 0;

    /*
       if (port > 0x100)
       {
          port &= 0xFF;
          if (port > 0x100)
          {
             return 0x00;
          }
       }
    */

    switch (port)
    {
    case 0x4e:
    case 0x4f:
    case 0x50:
    case 0x51:
    case 0x80:
    case 0x81:
    case 0x82:
    case 0x83:
    case 0x84:
    case 0x85:
    case 0x86:
    case 0x87:
    case 0x88:
    case 0x89:
    case 0x8a:
    case 0x8b:
    case 0x8c:
    case 0x8d:
    case 0x8e:
    case 0x8f:
    case 0x90:
    case 0x91:
    case 0x92:
    case 0x93:
    case 0x94:
        retVal = ws_audio_port_read(port);
        break;

    case 0xb5:
        w1 = ws_ioRam[0xb5];

        if (w1 & 0x40)
        {
            w2 = 0x00;
            w2 = (ws_key_start << 1) | (ws_key_button_a << 2) | (ws_key_button_b << 3);
            retVal = (uint8_t)((w1 & 0xf0) | w2);
            break;
        }

        if (w1 & 0x20)
        {
            w2 = 0x00;
            w2 = (ws_key_x1 << 0) | (ws_key_x2 << 1) | (ws_key_x3 << 2) | (ws_key_x4 << 3);
            retVal = (uint8_t)((w1 & 0xf0) | w2);
            break;
        }

        if (w1 & 0x10)
        {
            w2 = 0x00;
            w2 = (ws_key_y1 << 0) | (ws_key_y2 << 1) | (ws_key_y3 << 2) | (ws_key_y4 << 3);
            retVal = (uint8_t)((w1 & 0xf0) | w2);
        }

        break;

    case 0xbe:  // internal eeprom status/command register

        // ack eeprom write
        if (ws_ioRam[0xbe] & 0x20)
        {
            retVal = ws_ioRam[0xbe] | 2;
            break;
        }

        // ack eeprom read
        if (ws_ioRam[0xbe] & 0x10)
        {
            retVal = ws_ioRam[0xbe] | 1;
            break;
        }

        // else ack both
        retVal = ws_ioRam[0xbe] | 3;
        break;

    case 0x62:
        switch (ws_get_system())
        {
        case WS_SYSTEM_AUTODETECT:
        case WS_SYSTEM_MONO:
        case WS_SYSTEM_COLOR:
            retVal = 0x00;
            break;

        case WS_SYSTEM_CRYSTAL:
            retVal = 0x80;
            break;
        }
        break;

    case 0xba:  // eeprom even byte read
        retVal = iee_Databuffer & 0x00FF;
        break;

    case 0xbb:  // eeprom odd byte read
        retVal = (iee_Databuffer & 0xFF00) >> 8;
        break;

    case 0xc0 : // ???
        retVal = ((ws_ioRam[0xc0] & 0xf) | 0x20);
        goto exit;


    case 0xC8:
        // ack eeprom write
        if (ws_ioRam[0xbe] & 0x20)
        {
            retVal = ws_ioRam[0xbe] | 2;
            break;
        }

        // ack eeprom read
        if (ws_ioRam[0xbe] & 0x10)
        {
            retVal = ws_ioRam[0xbe] | 1;
            break;
        }

        // else ack both
        retVal = ws_ioRam[0xbe] | 3;
        break;

    case 0xC4:  // eeprom even byte read
        return cee_Databuffer & 0x00FF;

    case 0xC5:  // eeprom odd byte read
        return (cee_Databuffer & 0xFF00) >> 8;

    case 0xca : // RTC Command and status register
        // set ack to always 1
        retVal = (ws_ioRam[0xca] | 0x80);
        goto exit;

    case 0xcb : // RTC data register

        if (ws_ioRam[0xca] == 0x15)   // get time command
        {
            struct tm *newtime;
            time_t long_time;
            time(&long_time);
            newtime = localtime(&long_time);

#define  BCD(value) ((value/10)<<4)|(value%10)

            switch (rtcDataRegisterReadCount)
            {
            case 0:
                rtcDataRegisterReadCount++;
                retVal = BCD(newtime->tm_year - 100);
                goto exit;

            case 1:
                rtcDataRegisterReadCount++;
                retVal = BCD(newtime->tm_mon);
                goto exit;

            case 2:
                rtcDataRegisterReadCount++;
                retVal = BCD(newtime->tm_mday);
                goto exit;

            case 3:
                rtcDataRegisterReadCount++;
                retVal = BCD(newtime->tm_wday);
                goto exit;

            case 4:
                rtcDataRegisterReadCount++;
                retVal = BCD(newtime->tm_hour);
                goto exit;

            case 5:
                rtcDataRegisterReadCount++;
                retVal = BCD(newtime->tm_min);
                goto exit;

            case 6:
                rtcDataRegisterReadCount = 0;
                retVal = BCD(newtime->tm_sec);
                goto exit;
            }

            return 0;
        }
        else
        {
            // set ack
            retVal = (ws_ioRam[0xcb] | 0x80);
            goto exit;
        }

    case 0xD0:
        retVal = 0;
        goto exit;

        /* Serial port link.. */
    case 0xB1:
        retVal = read_serial();
        Log(TLOG_DEBUG, "serial", "Read %02X", retVal);
        goto exit;

    case 0xB3:
        check_serial_data();

        if (ws_ioRam[0xB3] & 0x80)
        {
            retVal = (ws_ioRam[0xB3] & ~1) | serial_have_data | 0x04;
        }
        else
        {
            retVal = 0x00;
        }

        Log(TLOG_DEBUG, "serial", "<<<<RS232STA: %02X [%c%c%cxx%c%c%c]", retVal, (retVal & 0x80) ? 'E' : 'd',
               (retVal & 0x40) ? '3' : '9', (retVal & 0x20) ? 'R' : 'n', (retVal & 0x04) ? 'E' : 'f',
               (retVal & 0x02) ? 'V' : 'n', (retVal & 0x01) ? 'D' : 'e');
        goto exit;

    case 0xCC:
    case 0xCD:
        retVal = 0;
        break;

    default:
        retVal = ws_ioRam[port];
        if (port > 0xD0)
        {
            Log(TLOG_DEBUG, "io", "ReadIO %02X <= %02X", port, retVal);
        }
        break;

    case 0xA0:
    case 0xAA:
    case 0xAB:
    case 0xAC:
    case 0xAD:
        retVal = ws_gpu_port_read(port);
        break;

    }


    if (port >= 0xC4)
    {
        Log(TLOG_DEBUG, "io", "ReadMBCIO(%02X) <= %02X", port, retVal);
    }


exit:

    if (port < 0xC0)
    {
        switch(port)
        {
        case 0x02:
        case 0x84:
        case 0x85:
        case 0x90:
        case 0x91:
        case 0xB5:
            break;

        default:
            Log(TLOG_DEBUG, "io", "ReadIO %02X <= %02X", port, retVal);
            break;
        }
    }

    if (ioLogFp)
    {
        fprintf(ioLogFp, "%llu, R, %02X, %02X\n", nec_monotonicCycles, port, retVal);
    }
    return retVal;
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
void io_writeport(uint32_t port, uint8_t value)
{
    int unknown_io_port = 0;

    if (ioLogFp)
    {
        fprintf(ioLogFp, "%llu, W, %02X, %02X\n", nec_monotonicCycles, port, value);
    }

    if (port > 0x100)
    {
        port &= 0xFF;
        if (port > 0x100)
        {
            return;
        }
    }

    if ((port == 0xA0) && (ws_ioRam[port] & 0x01) && (~value & 0x01))
    {
        value |= 0x01;
    }

    ws_ioRam[port] = value;

    switch (port)
    {
        /* GPU IOs */
    case 0x00:
        Log(TLOG_DEBUG, "GPU", "Screen enabled: W2:%c W2M:%c, SW:%c, S:%c, S2:%c, S1:%c",
            (value & 0x20)?'Y':'N',
            (value & 0x10)?'I':'O',
            (value & 0x08)?'Y':'N',
            (value & 0x04)?'Y':'N',
            (value & 0x02)?'Y':'N',
            (value & 0x01)?'Y':'N');
        break;

    case 0x04:
        if (ws_gpu_operatingInColor)
        {
            Log(TLOG_DEBUG, "GPU", "Sprite base: %04X", (value & 0x1F) << 9);
        }
        else
        {
            Log(TLOG_DEBUG, "GPU", "Sprite base: %04X", (value & 0x3F) << 9);
        }
        break;
    case 0x07:
        if (ws_gpu_operatingInColor)
        {
            Log(TLOG_DEBUG, "GPU", "Sprite Screen1 base: %04X", (value & 0x7) << 11);
            Log(TLOG_DEBUG, "GPU", "Sprite Screen2 base: %04X", (value & 0x70) << (11-4));
        }
        else
        {
            Log(TLOG_DEBUG, "GPU", "Sprite Screen1 base: %04X", (value & 0xF) << 11);
            Log(TLOG_DEBUG, "GPU", "Sprite Screen2 base: %04X", (value & 0xF0) << (11-4));
        }
        break;
    case 0x10:
        //Log(TLOG_DEBUG, "GPU", "Sprite Screen1 X scroll: %d", value);
        break;
    case 0x11:
        //Log(TLOG_DEBUG, "GPU", "Sprite Screen1 T scroll: %d", value);
        break;
    case 0x12:
        //Log(TLOG_DEBUG, "GPU", "Sprite Screen2 X scroll: %d", value);
        break;
    case 0x13:
        //Log(TLOG_DEBUG, "GPU", "Sprite Screen2 Y scroll: %d", value);
        break;

    case 0x01:
    case 0x02:
    case 0x03:
    case 0x05:
    case 0x06:
    case 0x08:
    case 0x09:
    case 0x0A:
    case 0x0B:
    case 0x0C:
    case 0x0D:
    case 0x0E:
    case 0x0F:
    case 0x14:
        break;

    case 0x15:
        Log(TLOG_DEBUG, "io", "Icons %c %c %c %c %c %c %c %c", (value >> 7) & 1 ? '?' : ' ', (value >> 6) & 1 ? '?' : ' ',
               (value >> 5) & 1 ? '3' : ' ', (value >> 4) & 1 ? '2' : ' ', (value >> 3) & 1 ? '1' : ' ',
               (value >> 2) & 1 ? 'H' : ' ', (value >> 1) & 1 ? 'V' : ' ', (value >> 0) & 1 ? 'S' : ' ');
        break;

        /* Palettes ? */
    case 0x1C:
    case 0x25:
    case 0x2F:
    case 0x38:
    case 0x1D:
    case 0x26:
    case 0x30:
    case 0x39:
    case 0x1E:
    case 0x27:
    case 0x31:
    case 0x3A:
    case 0x1F:
    case 0x28:
    case 0x32:
    case 0x3B:
    case 0x20:
    case 0x29:
    case 0x33:
    case 0x3C:
    case 0x21:
    case 0x2A:
    case 0x34:
    case 0x3E:
    case 0x22:
    case 0x2B:
    case 0x35:
    case 0x3F:
    case 0x23:
    case 0x2C:
    case 0x36:
    case 0x24:
    case 0x2E:
    case 0x37:
        break;

        /* DMAs */
    case 0x40:
    case 0x41:
    case 0x42:
    case 0x43:
    case 0x44:
    case 0x45:
    case 0x46:
    case 0x47:
        break;

    case 0x48:  // DMA

        // bit 7 set to start dma transfer
        if (value & 0x80)
        {
            int dma_start = (((uint32_t)ws_ioRam[0x41]) << 8) | (((uint32_t)ws_ioRam[0x40])) |
                            (((uint32_t)ws_ioRam[0x42]) << 16);
            int dma_end = (((uint32_t)ws_ioRam[0x45]) << 8) | (((uint32_t)ws_ioRam[0x44])) |
                          (((uint32_t)ws_ioRam[0x43]) << 16);
            int dma_size = (((uint32_t)ws_ioRam[0x47]) << 8) | (((uint32_t)ws_ioRam[0x46]));

            for (int ix = 0 ; ix < dma_size ; ix++)
            {
                mem_writemem20(dma_end++, mem_readmem20(dma_start++));
            }

            ws_ioRam[0x47] = 0;
            ws_ioRam[0x46] = 0;
            ws_ioRam[0x41] = (uint8_t)(dma_start >> 8);
            ws_ioRam[0x40] = (uint8_t)(dma_start & 0xff);
            ws_ioRam[0x45] = (uint8_t)(dma_end >> 8);
            ws_ioRam[0x44] = (uint8_t)(dma_end & 0xff);
            ws_ioRam[0x48] = 0;
        }

        break;

        /* Audio */
    case 0x4a:
    case 0x4b:
    case 0x4c:
    case 0x4d:
    case 0x4e:
    case 0x4f:
        ws_audio_port_write(port, value);
        break;

        /* DMA Start! */
    case 0x52:
        break;

        /* GPU (again) */
    case 0x60:
        break;

        /* System */
    case 0x62:
        Log(TLOG_DEBUG, "io", "HeyHo!");
        break;

        /* Audio */
    case 0x80:
    case 0x81:
    case 0x82:
    case 0x83:
    case 0x84:
    case 0x85:
    case 0x86:
    case 0x87:
    case 0x88:
    case 0x89:
    case 0x8a:
    case 0x8b:
    case 0x8c:
    case 0x8d:
    case 0x8e:
    case 0x8f:
    case 0x90:
    case 0x91:
    case 0x92:
    case 0x93:
    case 0x94:
    case 0x95:
    case 0x96:
    case 0x97:
    case 0x98:
    case 0x99:
    case 0x9A:
    case 0x9B:
    case 0x9C:
    case 0x9D:
    case 0x9E:
        ws_audio_port_write(port, value);
        break;

        /* Hardware */
    case 0xA0:

        /* Force cart handshake to be set */
        ws_ioRam[port] |= 0x80;

#ifdef USE_PAGED_MEMORY_ACCESS
        if (value & 0x01)
        {
            Log(TLOG_WARNING, "A0", "Oh yeah %02X BABY", value);
            uint32_t romSize;
            uint8_t *rom = getRom(&romSize);
            set_memory_bank(0xF, ws_get_page_ptr(rom, romSize, (ws_ioRam[0xC0] & 0x0F << 4) + 0x0F));
        }
        break;
#endif
        /* Timers */
    case 0xA2:
    case 0xA4:
    case 0xA5:
    case 0xA6:
    case 0xA7:
    case 0xA8:
    case 0xA9:
    case 0xAA:
    case 0xAB:
        break;


        /* Intc */
    case 0xB0:
    case 0xB2:
    case 0xB4:
    case 0xB6:
        break;

        /* Serial */
    case 0xB1:
        write_serial(value);
        break;

    case 0xB3:
        Log(TLOG_DEBUG, "serial", ">>>>RS232STA: %02X [%c%c%cxx%c%c%c]", value, (value & 0x80) ? 'E' : 'd', (value & 0x40) ? '3' : '9',
               (value & 0x20) ? 'R' : 'n', (value & 0x04) ? 'E' : 'f', (value & 0x02) ? 'V' : 'n',
               (value & 0x01) ? 'D' : 'e');

        /* Serial status: 7 = Enable, 6 = baudrate, 5 = Overrun reset
           2 = Send Buffer empty
           1 = Overrun
           0 = Data Received
         */
        serial_speed = ((value & 040) == 0x00) ? BDR_9600 : BDR_38400;

        if ((value & 0x80) == 0x80)
        {
            open_serial();
            set_baudrate(serial_speed);
            check_serial_data();
        }

        break;

        /* buttons */
    case 0xB5:
        break;

        /* Internal EEPROM */
    case 0xba: /* DATA Low */
        iee_Databuffer = iee_Databuffer & 0xFF00;
        iee_Databuffer = iee_Databuffer | (value);
        break;

    case 0xbb: /* Data High */
        iee_Databuffer = iee_Databuffer & 0x00FF;
        iee_Databuffer = iee_Databuffer | (value << 8);
        break;

    case 0xBC: /* Address Low */
    case 0xBD: /* Address High */
        break;

    case 0xBE: /* Command / Status */
    {
        uint16_t address, command, subcmd;

        iee_SelAddress = (ws_ioRam[0xBD] << 8) | ws_ioRam[0xBC];

        if (ws_gpu_operatingInColor)
        {
            /* 
            13 00
               S CCaa AAAA AAAA
            0001 0011 0000 0000

            */
            /* S CC aaAAAAAAAA */
            command = (iee_SelAddress >> 10) & 0x3;
            address = iee_SelAddress & 0x3FF;
            subcmd = (iee_SelAddress >> 8) & 0x03;
        }
        else
        {
            /* S CC aaAAAA */
            command = (iee_SelAddress >> 6) & 0x3;
            address = iee_SelAddress & 0x3F;
            subcmd = (iee_SelAddress >> 4) & 0x03;
        }


        if (command == EEPROM_SUBCOMMAND)
        {
            command = EEPROM_WRITEDISABLE + subcmd;
        }
#ifdef EEPROM_DEBUG
        printf("IEEP: RA:%04X RD:%04X A:%03X C:%s", iee_SelAddress, iee_Databuffer, address, eii_CommandName[command]);
#endif
        if (value & 0x40)
        {
            /* Sub command */
#ifdef EEPROM_DEBUG
            printf(" - Sub");
#endif
            if (command == EEPROM_WRITEENABLE)
            {
#ifdef EEPROM_DEBUG
                printf(" Write Enable\n");
#endif
                iee_WriteEnable = true;
            }
            else if (command == EEPROM_WRITEDISABLE)
            {
#ifdef EEPROM_DEBUG
                printf(" Write Disable\n");
#endif
                iee_WriteEnable = false;
            }
            else if (command == EEPROM_ERASEALL)
            {
#ifdef EEPROM_DEBUG
                printf(" Erase All\n");
#endif
                if (ws_gpu_operatingInColor)
                {
                    memset(internalEeprom, 0, COLOR_IEEPROM_SIZE);
                }
                else
                {
                    memset(internalEeprom, 0, BW_IEEPROM_SIZE);
                }
            }
#ifdef EEPROM_DEBUG
            else
            {
                printf(" Write All?\n");
            }
#endif
        }
        else if (value & 0x20)
        {
            /* Write */
#ifdef EEPROM_DEBUG
            printf(" - Write?");
#endif
            if (iee_WriteEnable)
            {
#ifdef EEPROM_DEBUG
                printf(" Yes : %04X\n", iee_Databuffer);
#endif
                internalEeprom[address] = iee_Databuffer;
            }
#ifdef EEPROM_DEBUG
            else
            {
                printf(" No\n");
            }
#endif
        }
        else if (value & 0x10)
        {
            /* Read */
#ifdef EEPROM_DEBUG
            printf(" - Read");
#endif
            iee_Databuffer = internalEeprom[address];
#ifdef EEPROM_DEBUG
            printf(" Data : %04X\n", iee_Databuffer);
#endif
        }
#ifdef EEPROM_DEBUG
        else
        {
            printf(" Unknown value: %02X\n", value);
        }
#endif
        fflush(stdout);
    }
        break;


        /* MBC */
#ifndef USE_PAGED_MEMORY_ACCESS
    case 0xC0:
    case 0xC1:
    case 0xC2:
    case 0xC3:
        break;
#else
    case 0xC0:
    {
        /* page 4 to F */
        uint32_t romSize;
        uint8_t *rom = getRom(&romSize);
        for (int i = 0x04 ; i < 0x10 ; i++)
        {
            set_memory_bank(i, ws_get_page_ptr(rom, romSize, (value << 4) + i));
        }

        if (!(ws_ioRam[0xA0] & 0x01))
        {
            set_irom_overlay();
        }
        break;
    }

    case 0xC1:
        /* Sram bank */
        if (sramSize > 0)
        {
            uint32_t sramSize;
            uint8_t *sram = getSram(&sramSize);
            set_memory_bank(0x1, ws_get_page_ptr(sram, sramSize, value));
        }
        break;

    case 0xC2:
    {
        /* page 4 to F */
        uint32_t romSize;
        uint8_t *rom = getRom(&romSize);
        /* Page 2 */
        set_memory_bank(0x2, ws_get_page_ptr(rom, romSize, value));
        break;
    }

    case 0xC3:
    {
        /* page 4 to F */
        uint32_t romSize;
        uint8_t *rom = getRom(&romSize);
        /* Page 3 */
        set_memory_bank(0x3, ws_get_page_ptr(rom, romSize, value));
        break;
    }

#endif

        /* Cart EEPROM */
    case 0xC4: /* Data High */
        cee_Databuffer = cee_Databuffer & 0xFF00;
        cee_Databuffer = cee_Databuffer | (value);
        break;

    case 0xC5: /* Data High */
        cee_Databuffer = cee_Databuffer & 0x00FF;
        cee_Databuffer = cee_Databuffer | (value << 8);
        break;

    case 0xC6: /* Address Low */
    case 0xC7: /* Address High */
        break;

    case 0xC8: /* Command / Status */
    {
        uint16_t address, command, subcmd; /*, start;*/

        cee_SelAddress = (ws_ioRam[0xBD] << 8) | ws_ioRam[0xBC];

        /* S CC aaAAAA */
        command = (cee_SelAddress >> 6) & 0x3;
        address = cee_SelAddress & 0x3F;
        subcmd = (cee_SelAddress >> 4) & 0x03;


        if (command == EEPROM_SUBCOMMAND)
        {
            command = EEPROM_WRITEDISABLE + subcmd;
        }

        printf("CEEP: RA:%04X RD:%04X A:%03X C:%s", cee_SelAddress, cee_Databuffer, address, eii_CommandName[command]);

        if (value & 0x40)
        {
            /* Sub command */
            printf(" - Sub");
            if (command == EEPROM_WRITEENABLE)
            {
                printf(" Write Enable\n");
                cee_WriteEnable = true;
            }
            else if (command == EEPROM_WRITEDISABLE)
            {
                printf(" Write Disable\n");
                cee_WriteEnable = false;
            }
            else if (command == EEPROM_ERASEALL)
            {
                printf(" Erase All\n");
                /* Nothing here at the moment */
            }
            else
            {
                printf(" Write All?\n");
            }
        }
        else if (value & 0x20)
        {
            /* Write */
            printf(" - Write?");
            if (cee_WriteEnable)
            {
                printf(" Yes : %04X\n", cee_Databuffer);
                externalEeprom[address] = cee_Databuffer;
            }
            else
            {
                printf(" No\n");
            }
        }
        else if (value & 0x10)
        {
            /* Read */
            printf(" - Read");
            cee_Databuffer = externalEeprom[address];
            printf(" Data : %04X\n", cee_Databuffer);
        }
        else
        {
            printf(" Unknown value: %02X@", value);
        }
        fflush(stdout);
    }
        break;

    case 0xca:
        if (value == 0x15)
        {
            rtcDataRegisterReadCount = 0;
        }

        break;
        break;

    case 0xCB:
        break;

    case 0xF0:
        break;

    case 0xF1:
        printf("%d\n", (signed short)((value << 8) | ws_ioRam[0xF0]));
        break;

    case 0xF2:
        printf("%c", value);
        fflush(stdout);
        break;

    case 0xB7:
        break; /* Somwthing to write there, but what? */

    default:
        unknown_io_port = 1;
    }

    if ((ws_gpu_port_write(port, value) == 1) && (unknown_io_port == 1))
    {
        Log(TLOG_DEBUG, "io", "WriteIO(%02X, %02X) [%04X:%04Xh];", port, value, I.sregs[CS], I.ip);
    }

    if (port >= 0xC4)
    {
        Log(TLOG_DEBUG, "io", "WriteMBCIO(%02X, %02X);", port, value);
    }
}
