text: /* Mednafen - Multi-system Emulator
   *
   *  Copyright (C) 2007, 2008, 2009 EkeEke
   *
   * 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
   */
  /*
   Genesis Plus GX EEPROM emulation modified 2011 for usage in Mednafen
  */
  
  #include "../shared.h"
  #include "cart.h"
  #include "map_eeprom.h"
  
  typedef enum
  {
    STAND_BY = 0,
    WAIT_STOP,
    GET_SLAVE_ADR,
    GET_WORD_ADR_7BITS,
    GET_WORD_ADR_HIGH,
    GET_WORD_ADR_LOW,
    WRITE_DATA,
    READ_DATA,
  
  } T_EEPROM_STATE;
  
  /* this defines the type of EEPROM inside the game cartridge as Backup RAM
   *
   * Here are some notes from 8BitWizard (http://www.spritesmind.net/_GenDev/forum):
   *
   * Mode 1 (7-bit) - the chip takes a single byte with a 7-bit memory address and a R/W bit (24C01)
   * Mode 2 (8-bit) - the chip takes a 7-bit device address and R/W bit followed by an 8-bit memory address;
   * the device address may contain up to three more memory address bits (24C01 - 24C16).
   * You can also string eight 24C01, four 24C02, two 24C08, or various combinations, set their address config lines correctly,
   * and the result appears exactly the same as a 24C16
   * Mode 3 (16-bit) - the chip takes a 7-bit device address and R/W bit followed by a 16-bit memory address (24C32 and larger)
   *
   * Also, while most 24Cxx are addressed at 200000-2FFFFF, I have found two different ways of mapping the control lines. 
   * EA uses SDA on D7 (read/write) and SCL on D6 (write only), and I have found boards using different mapping (I think Accolade)
   * which uses D1-read=SDA, D0-write=SDA, D1-write=SCL. Accolade also has a custom-chip mapper which may even use a third method. 
   */
  
  typedef struct
  {
    uint8 address_bits;     /* number of bits needed to address memory: 7, 8 or 16 */
    uint16 size_mask;       /* depends on the max size of the memory (in bytes) */
    uint16 pagewrite_mask;  /* depends on the maximal number of bytes that can be written in a single write cycle */
    uint32 sda_in_adr;      /* 68000 memory address mapped to SDA_IN */
    uint32 sda_out_adr;     /* 68000 memory address mapped to SDA_OUT */
    uint32 scl_adr;         /* 68000 memory address mapped to SCL */
    uint8 sda_in_bit;       /* bit offset for SDA_IN */
    uint8 sda_out_bit;      /* bit offset for SDA_OUT */
    uint8 scl_bit;          /* bit offset for SCL */
  
  } T_EEPROM_TYPE;
  
  typedef struct
  {
   int board_type;
   T_EEPROM_TYPE type;
  } EEPROM_Config;
  
  static const EEPROM_Config database[EEP_TOTAL] = 
  {
          /* ACCLAIM mappers */
          /* 24C02 (old mapper) */
          { EEP_ACCLAIM_24C02_OLD,{8,  0xFF,   0xFF,   0x200001, 0x200001, 0x200001, 0, 1, 1}},
  
          /* 24C02 */
          { EEP_ACCLAIM_24C02,    {8,  0xFF,   0xFF,   0x200001, 0x200001, 0x200000, 0, 0, 0}},
  
          /* 24C04 */
          { EEP_ACCLAIM_24C04,    {8,  0x1FF,  0x1FF,  0x200001, 0x200001, 0x200000, 0, 0, 0}},
  
          /* 24C16 */
          { EEP_ACCLAIM_24C16,    {8,  0x7FF,  0x7FF,  0x200001, 0x200001, 0x200000, 0, 0, 0}},
  
          /* 24C65 */
          {EEP_ACCLAIM_24C65,     {16, 0x1FFF, 0x1FFF, 0x200001, 0x200001, 0x200000, 0, 0, 0}},
          
          /* EA mapper (24C01 only) */
          { EEP_EA_24C01,         {7,  0x7F,   0x7F,   0x200001, 0x200001, 0x200001, 7, 7, 6}},
          
          /* SEGA mapper (24C01 only) */
          { EEP_SEGA_24C01,       {7,  0x7F,   0x7F,   0x200001, 0x200001, 0x200001, 0, 0, 1}},
          
          /* CODEMASTERS mapper */
  
          /* 24C08 */
          { EEP_CM_24C08,         {8,  0x3FF,  0x3FF,  0x300000, 0x380001, 0x300000, 0, 7, 1}},
  
          /* 24C16 */
          { EEP_CM_24C16,         {8,  0x7FF,  0x7FF,  0x300000, 0x380001, 0x300000, 0, 7, 1}},
  
          /* 24C65 */
          { EEP_CM_24C65,         {16, 0x1FFF, 0x1FFF, 0x300000, 0x380001, 0x300000, 0, 7, 1}}
  };
  
  
  class MD_Cart_Type_EEPROM : public MD_Cart_Type
  {
  	public:
  
          MD_Cart_Type_EEPROM(const md_game_info *ginfo, const uint8 *ROM, const uint32 ROM_size, const int BoardType);
          virtual ~MD_Cart_Type_EEPROM() override;
  	virtual void Reset(void) override;
  
          virtual void Write8(uint32 A, uint8 V) override;
          virtual void Write16(uint32 A, uint16 V) override;
          virtual uint8 Read8(uint32 A) override;
          virtual uint16 Read16(uint32 A) override;
          virtual int StateAction(StateMem *sm, int load, int data_only, const char *section_name) override;
  
          // In bytes
          virtual uint32 GetNVMemorySize(void) override;
          virtual void ReadNVMemory(uint8 *buffer) override;
          virtual void WriteNVMemory(const uint8 *buffer) override;
  
  	private:
  
  	void WriteEEPROM(unsigned int address, unsigned int value, bool word_access);
  	unsigned int ReadEEPROM(unsigned int address, bool word_access);
  
  
  	const uint8 *rom;
  	uint32 rom_size;
  	
          void Detect_START();
          void Detect_STOP();
  
          uint8 sda;                              /* current /SDA line state */
          uint8 scl;                              /* current /SCL line state */
          uint8 old_sda;                  /* previous /SDA line state */
          uint8 old_scl;                  /* previous /SCL line state */
          uint8 cycles;                   /* current operation cycle number (0-9) */
          uint8 rw;                               /* operation type (1:READ, 0:WRITE) */
          uint16 slave_mask;              /* device address (shifted by the memory address width)*/
          uint16 word_address;    /* memory address */
  
          uint8 *sram;
  
          T_EEPROM_STATE state;   /* current operation state */
          T_EEPROM_TYPE type;             /* EEPROM characteristics for this game */
  };
  
  
  MD_Cart_Type_EEPROM::MD_Cart_Type_EEPROM(const md_game_info *ginfo, const uint8 *ROM, const uint32 ROM_size, const int BoardType)
  {
   bool found_board_type = false;
  
   this->rom = ROM;
   this->rom_size = ROM_size;
  
   /* initialize eeprom */
   sda = old_sda = 1;
   scl = old_scl = 1;
   state = STAND_BY;
  
   for(int i = 0; i < EEP_TOTAL; i++)
   {
    if(database[i].board_type == BoardType)
    {
     memcpy(&type, &database[i].type, sizeof(T_EEPROM_TYPE));
     found_board_type = true;
     break;
    }
   }
  
   // Debugging sanity check
   if(!found_board_type)
    throw(0);
  
   /* set SRAM start & end address */
   sram = new uint8[type.size_mask + 1];
   memset(sram, 0xFF, type.size_mask + 1);
  }
  
  MD_Cart_Type_EEPROM::~MD_Cart_Type_EEPROM()
  {
   if(sram)
   {
    delete[] sram;
   }
  }
  
  INLINE void MD_Cart_Type_EEPROM::Detect_START()
  {
    if (old_scl && scl)
    {
      if (old_sda && !sda)
      {
        cycles = 0;
        slave_mask = 0;
        if (type.address_bits == 7)
        {
          word_address = 0;
          state = GET_WORD_ADR_7BITS;
        }
        else state = GET_SLAVE_ADR;
      }
    }
  }
  
  INLINE void MD_Cart_Type_EEPROM::Detect_STOP()
  {
    if (old_scl && scl)
    {
      if (!old_sda && sda)
      {
        state = STAND_BY;
      }
    }
  }
  
  void MD_Cart_Type_EEPROM::Reset(void)
  {
    sda = old_sda = 1;
    scl = old_scl = 1;
    state = STAND_BY;
    cycles = 0;
    rw = 0;
    slave_mask = 0;
    word_address = 0;
  }
  
  void MD_Cart_Type_EEPROM::WriteEEPROM(unsigned int address, unsigned int data, bool word_access)
  {
    /* decode SCL and SDA value */
    if (word_access)
    {
      /* 16-bits access */
      if (type.sda_in_adr == address) sda = (data >> (8 + type.sda_in_bit)) & 1;      /* MSB */
      else if (type.sda_in_adr == (address | 1)) sda = (data >> type.sda_in_bit) & 1; /* LSB */
      else sda = old_sda;
  
      if (type.scl_adr == address) scl = (data >> (8 + type.scl_bit)) & 1;      /* MSB */
      else if (type.scl_adr == (address | 1)) scl = (data >> type.scl_bit) & 1; /* LSB */
      else scl = old_scl;
    }
    else
    {
      if (type.sda_in_adr == address) sda = (data >> type.sda_in_bit) & 1;
      else sda = old_sda;
  
      if (type.scl_adr == address) scl = (data >> type.scl_bit) & 1;
      else scl = old_scl;
    }
  
    /* EEPROM current state */
    switch (state)
    {
      /* Standby Mode */
      case STAND_BY:
        Detect_START();
        Detect_STOP();
        break;
  
      /* Suspended Mode */
      case WAIT_STOP:
        Detect_STOP();
        break;
  
      /* Get Word Address 7 bits: MODE-1 only (24C01)
       * and R/W bit
       */
      case GET_WORD_ADR_7BITS:
        Detect_START();
        Detect_STOP();
  
        /* look for SCL LOW to HIGH transition */
        if (!old_scl && scl)
        {
          if (cycles == 0) cycles ++;
        }
  
  
        /* look for SCL HIGH to LOW transition */
        if (old_scl && !scl && (cycles > 0))
        {
          if (cycles < 8)
          {
            word_address |= (old_sda << (7 - cycles));
          }
          else if (cycles == 8)
          {
            rw = old_sda;
          }
          else
          {  /* ACK CYCLE */
            cycles = 0;
            word_address &= type.size_mask;
            state = rw ? READ_DATA : WRITE_DATA;
          }
  
          cycles ++;
        }
        break;
  
  
      /* Get Slave Address (3bits) : MODE-2 & MODE-3 only (24C01 - 24C512) (0-3bits, depending on the array size)
       * or/and Word Address MSB: MODE-2 only (24C04 - 24C16) (0-3bits, depending on the array size)
       * and R/W bit
       */
      case GET_SLAVE_ADR:
  
        Detect_START();
        Detect_STOP();
  
        /* look for SCL LOW to HIGH transition */
        if (!old_scl && scl)
        {
          if (cycles == 0) cycles ++;
        }
  
        /* look for SCL HIGH to LOW transition */
        if (old_scl && !scl && (cycles > 0))
        {
          if ((cycles > 4) && (cycles <8))
          {
            if ((type.address_bits == 16) ||
              (type.size_mask < (1 << (15 - cycles))))
            {
              /* this is a SLAVE ADDRESS bit */
              slave_mask |= (old_sda << (7 - cycles));
            }
            else
            {
              /* this is a WORD ADDRESS high bit */
              if (old_sda) word_address |= (1 << (15 - cycles));
              else word_address &= ~(1 << (15 - cycles));
            }
          }
          else if (cycles == 8) rw = old_sda;
          else if (cycles > 8)
          {
            /* ACK CYCLE */
            cycles = 0;
            if (type.address_bits == 16)
            {
              /* two ADDRESS bytes */
              state = rw ? READ_DATA : GET_WORD_ADR_HIGH;
              slave_mask = 0;
            }
            else
            {
              /* one ADDRESS byte */
              state = rw ? READ_DATA : GET_WORD_ADR_LOW;
              slave_mask <<= 8;
            }
          }
  
          cycles ++;
        }
        break;
  
      /* Get Word Address MSB (4-8bits depending on the array size)
       * MODE-3 only (24C32 - 24C512)
       */
      case GET_WORD_ADR_HIGH:
  
        Detect_START();
        Detect_STOP();
  
        /* look for SCL HIGH to LOW transition */
        if (old_scl && !scl)
        {        
          if (cycles < 9)
          {
            if ((type.size_mask + 1) < (1 << (17 - cycles)))
            {
              /* ignored bit: slave mask should be right-shifted by one  */
              slave_mask >>= 1;
            }
            else
            {
              /* this is a WORD ADDRESS high bit */
              if (old_sda) word_address |= (1 << (16 - cycles));
              else word_address &= ~(1 << (16 - cycles));
            }
  
            cycles ++;
          }
          else
          {
            /* ACK CYCLE */
            cycles = 1;
            state = GET_WORD_ADR_LOW;
          }
        }
        break;
  
  
      /* Get Word Address LSB: 7bits (24C01) or 8bits (24C02-24C512)
       * MODE-2 and MODE-3 only (24C01 - 24C512)
       */
      case GET_WORD_ADR_LOW: 
  
        Detect_START();
        Detect_STOP();
  
        /* look for SCL HIGH to LOW transition */
        if (old_scl && !scl)
        {
          if (cycles < 9)
          {
            if ((type.size_mask + 1) < (1 << (9 - cycles)))
            {
              /* ignored bit (X24C01): slave mask should be right-shifted by one  */
              slave_mask >>= 1;
            }
            else
            {
              /* this is a WORD ADDRESS high bit */
              if (old_sda) word_address |= (1 << (8 - cycles));
              else word_address &= ~(1 << (8 - cycles));
            }
  
            cycles ++;
          }
          else
          {
            /* ACK CYCLE */
            cycles = 1;
            word_address &= type.size_mask;
            state = WRITE_DATA;
          }
        }
        break;
  
  
      /*
       * Read Cycle
       */
      case READ_DATA:
  
        Detect_START();
        Detect_STOP();
  
        /* look for SCL HIGH to LOW transition */
        if (old_scl && !scl)
        {
          if (cycles < 9) cycles ++;
          else
          {
            cycles = 1;
  
            /* ACK not received */
            if (old_sda) state = WAIT_STOP;
           }
        }
        break;
  
  
      /*
       * Write Cycle
       */
      case WRITE_DATA:
  
        Detect_START();
        Detect_STOP();
  
        /* look for SCL HIGH to LOW transition */
        if (old_scl && !scl)
        {        
          if (cycles < 9)
          {
            /* Write DATA bits (max 64kBytes) */
            uint16 sram_address = (slave_mask | word_address) & 0xFFFF;
            if (old_sda) sram[sram_address] |= (1 << (8 - cycles));
            else sram[sram_address] &= ~(1 << (8 - cycles));
  
            if (cycles == 8) 
            {
              /* WORD ADDRESS is incremented (roll up at maximum pagesize) */
              word_address = (word_address & (0xFFFF - type.pagewrite_mask)) | 
                          ((word_address + 1) & type.pagewrite_mask);
            }
  
            cycles ++;
          }
          else cycles = 1;  /* ACK cycle */
        }
        break;
    }
  
    old_scl = scl;
    old_sda = sda;
  }
  
  unsigned int MD_Cart_Type_EEPROM::ReadEEPROM(unsigned int address, bool word_access)
  {
    uint8 sda_out = sda;
  
    /* EEPROM state */
    switch (state)
    {
      case READ_DATA:
        if (cycles < 9)
        {
          /* Return DATA bits (max 64kBytes) */
          uint16 sram_address = (slave_mask | word_address) & 0xffff;
          sda_out = (sram[sram_address] >> (8 - cycles)) & 1;
  
          if (cycles == 8)
          {
            /* WORD ADDRESS is incremented (roll up at maximum array size) */
            word_address ++;
            word_address &= type.size_mask;
          }
        }
        break;
  
      case GET_WORD_ADR_7BITS:
      case GET_SLAVE_ADR:
      case GET_WORD_ADR_HIGH:
      case GET_WORD_ADR_LOW:
      case WRITE_DATA:
        if (cycles == 9) sda_out = 0;
        break;
  
      default:
        break;
    }
  
    /* memory access */
    if (word_access)
    {
      /* 16-bits access */
      if (type.sda_out_adr & 1) return (sda_out << type.sda_out_bit); /* LSB */
      else return (sda_out << (type.sda_out_bit + 8));  /* MSB */
    }
    else return (sda_out << type.sda_out_bit);
  }
  
  
  void MD_Cart_Type_EEPROM::Write8(uint32 A, uint8 V)
  {
   if((A == type.sda_in_adr) || (A == type.scl_adr))
    WriteEEPROM(A, V, false);
  }
  
  void MD_Cart_Type_EEPROM::Write16(uint32 A, uint16 V)
  {
   if((A == (type.sda_in_adr & 0xFFFFFE)) || (A == (type.scl_adr & 0xFFFFFE)))
    WriteEEPROM(A, V, true);
  }
  
  uint8 MD_Cart_Type_EEPROM::Read8(uint32 A)
  {
   if(A == type.sda_out_adr)
    return(ReadEEPROM(A, false));
  
   //
  
   if(A < 0x400000)
   {
    if(A >= rom_size)
    {
     MD_DBG(MD_DBG_WARNING, "[MAP_EEPROM] Unknown read8 from 0x%08x\n", A);
     return(0);
    }
    return(READ_BYTE_MSB(rom, A));
   }
   return(m68k_read_bus_8(A));
  }
  
  uint16 MD_Cart_Type_EEPROM::Read16(uint32 A)
  {
   if(A == (type.sda_out_adr & 0xFFFFFE))
    return(ReadEEPROM(A, true));
  
   //
  
   if(A < 0x400000)
   {
    if(A >= rom_size)
    {
     MD_DBG(MD_DBG_WARNING, "[MAP_EEPROM] Unknown read16 from 0x%08x\n", A);
     return(0);
    }
    return(READ_WORD_MSB(rom, A));
   }
  
   return(m68k_read_bus_16(A));
  }
  
  int MD_Cart_Type_EEPROM::StateAction(StateMem *sm, int load, int data_only, const char *section_name)
  {
   SFORMAT StateRegs[] =
   {
    SFVAR(sda),
    SFVAR(scl),
    SFVAR(old_sda),
    SFVAR(old_scl),
    SFVAR(cycles),
    SFVAR(rw),
    SFVAR(slave_mask),
    SFVAR(word_address),
    SFVAR(state),
    SFPTR8(sram, type.size_mask + 1),
    SFEND
   };
  
   int ret = MDFNSS_StateAction(sm, load, data_only, StateRegs, section_name);
   if(load)
   {
  
   }
   return(ret);
  }
  
  uint32 MD_Cart_Type_EEPROM::GetNVMemorySize(void)
  {
   return(type.size_mask + 1);
  }
  
  void MD_Cart_Type_EEPROM::ReadNVMemory(uint8 *buffer)
  {
   memcpy(buffer, sram, type.size_mask + 1);
  }
  
  void MD_Cart_Type_EEPROM::WriteNVMemory(const uint8 *buffer)
  {
   memcpy(sram, buffer, type.size_mask + 1);
  }
  
  MD_Cart_Type *MD_Make_Cart_Type_EEPROM(const md_game_info *ginfo, const uint8 *ROM, const uint32 ROM_size, const uint32 iparam, const char *sparam)
  {
   return(new MD_Cart_Type_EEPROM(ginfo, ROM, ROM_size, iparam));
  }
  