stella/src/emucore/MT24LC256.cxx

414 lines
11 KiB
C++

//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include <cstdio>
#include "System.hxx"
#include "Settings.hxx"
#include "MT24LC256.hxx"
#define DEBUG_EEPROM 0
#if DEBUG_EEPROM
char jpee_msg[256];
#define JPEE_LOG0(msg) jpee_logproc(msg)
#define JPEE_LOG1(msg,arg1) sprintf(jpee_msg,(msg),(arg1)), jpee_logproc(jpee_msg)
#define JPEE_LOG2(msg,arg1,arg2) sprintf(jpee_msg,(msg),(arg1),(arg2)), jpee_logproc(jpee_msg)
#else
#define JPEE_LOG0(msg) { }
#define JPEE_LOG1(msg,arg1) { }
#define JPEE_LOG2(msg,arg1,arg2) { }
#endif
/*
State values for I2C:
0 - Idle
1 - Byte going to chip (shift left until bit 8 is set)
2 - Chip outputting acknowledgement
3 - Byte coming in from chip (shift left until lower 8 bits are clear)
4 - Chip waiting for acknowledgement
*/
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MT24LC256::MT24LC256(const string& filename, const System& system)
: mySystem(system),
mySDA(false),
mySCL(false),
myTimerActive(false),
myCyclesWhenTimerSet(0),
myCyclesWhenSDASet(0),
myCyclesWhenSCLSet(0),
myDataFile(filename),
myDataFileExists(false),
myDataChanged(false),
jpee_mdat(0),
jpee_sdat(0),
jpee_mclk(0),
jpee_sizemask(0),
jpee_pagemask(0),
jpee_smallmode(0),
jpee_logmode(0),
jpee_pptr(0),
jpee_state(0),
jpee_nb(0),
jpee_address(0),
jpee_ad_known(0)
{
// Load the data from an external file (if it exists)
ifstream in(myDataFile, std::ios_base::binary);
if(in.is_open())
{
// Get length of file; it must be 32768
in.seekg(0, std::ios::end);
if(uInt32(in.tellg()) == FLASH_SIZE)
{
in.seekg(0, std::ios::beg);
in.read(reinterpret_cast<char*>(myData), FLASH_SIZE);
myDataFileExists = true;
}
}
else
myDataFileExists = false;
// Then initialize the I2C state
jpee_init();
systemReset();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MT24LC256::~MT24LC256()
{
// Save EEPROM data to external file only when necessary
if(!myDataFileExists || myDataChanged)
{
ofstream out(myDataFile, std::ios_base::binary);
if(out.is_open())
out.write(reinterpret_cast<char*>(myData), FLASH_SIZE);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void MT24LC256::writeSDA(bool state)
{
mySDA = state;
myCyclesWhenSDASet = mySystem.cycles();
update();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void MT24LC256::writeSCL(bool state)
{
mySCL = state;
myCyclesWhenSCLSet = mySystem.cycles();
update();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void MT24LC256::update()
{
#define jpee_clock(x) ( (x) ? \
(jpee_mclk = 1) : \
(jpee_mclk && (jpee_clock_fall(),1), jpee_mclk = 0))
#define jpee_data(x) ( (x) ? \
(!jpee_mdat && jpee_sdat && jpee_mclk && (jpee_data_stop(),1), jpee_mdat = 1) : \
(jpee_mdat && jpee_sdat && jpee_mclk && (jpee_data_start(),1), jpee_mdat = 0))
// These pins have to be updated at the same time
// However, there's no guarantee that the writeSDA() and writeSCL()
// methods will be called at the same time or in the correct order, so
// we only do the write when they have the same 'timestamp'
if(myCyclesWhenSDASet == myCyclesWhenSCLSet)
{
#if DEBUG_EEPROM
cerr << endl << " I2C_PIN_WRITE(SCL = " << mySCL
<< ", SDA = " << mySDA << ")" << " @ " << mySystem.cycles() << endl;
#endif
jpee_clock(mySCL);
jpee_data(mySDA);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void MT24LC256::systemReset()
{
std::fill(myPageHit, myPageHit + PAGE_NUM, false);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void MT24LC256::eraseAll()
{
memset(myData, INIT_VALUE, FLASH_SIZE);
myDataChanged = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void MT24LC256::eraseCurrent()
{
for(uInt32 page = 0; page < PAGE_NUM; ++page)
{
if(myPageHit[page])
{
memset(myData + page * PAGE_SIZE, INIT_VALUE, PAGE_SIZE);
myDataChanged = true;
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool MT24LC256::isPageUsed(uInt32 page) const
{
if(page < PAGE_NUM)
return myPageHit[page];
else
return false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void MT24LC256::jpee_init()
{
jpee_sdat = 1;
jpee_address = 0;
jpee_state=0;
jpee_sizemask = FLASH_SIZE - 1;
jpee_pagemask = PAGE_SIZE - 1;
jpee_smallmode = 0;
jpee_logmode = -1;
if(!myDataFileExists)
memset(myData, INIT_VALUE, FLASH_SIZE);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void MT24LC256::jpee_data_start()
{
/* We have a start condition */
if (jpee_state == 1 && (jpee_nb != 1 || jpee_pptr != 3))
{
JPEE_LOG0("I2C_WARNING ABANDON WRITE");
jpee_ad_known = 0;
}
if (jpee_state == 3)
{
JPEE_LOG0("I2C_WARNING ABANDON READ");
}
if (!jpee_timercheck(0))
{
JPEE_LOG0("I2C_START");
jpee_state = 2;
}
else
{
JPEE_LOG0("I2C_BUSY");
jpee_state = 0;
}
jpee_pptr = 0;
jpee_nb = 0;
jpee_packet[0] = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void MT24LC256::jpee_data_stop()
{
if (jpee_state == 1 && jpee_nb != 1)
{
JPEE_LOG0("I2C_WARNING ABANDON_WRITE");
jpee_ad_known = 0;
}
if (jpee_state == 3)
{
JPEE_LOG0("I2C_WARNING ABANDON_READ");
jpee_ad_known = 0;
}
/* We have a stop condition. */
if (jpee_state == 1 && jpee_nb == 1 && jpee_pptr > 3)
{
jpee_timercheck(1);
JPEE_LOG2("I2C_STOP(Write %d bytes at %04X)",jpee_pptr-3,jpee_address);
if (((jpee_address + jpee_pptr-4) ^ jpee_address) & ~jpee_pagemask)
{
jpee_pptr = 4+jpee_pagemask-(jpee_address & jpee_pagemask);
JPEE_LOG1("I2C_WARNING PAGECROSSING!(Truncate to %d bytes)",jpee_pptr-3);
}
for (int i=3; i<jpee_pptr; i++)
{
myDataChanged = true;
myPageHit[jpee_address / PAGE_SIZE] = true;
bool devSettings = mySystem.oSystem().settings().getBool("dev.settings");
if(mySystem.oSystem().settings().getBool(devSettings ? "dev.eepromaccess" : "plr.eepromaccess"))
mySystem.oSystem().frameBuffer().showMessage("AtariVox/SaveKey EEPROM write");
myData[(jpee_address++) & jpee_sizemask] = jpee_packet[i];
if (!(jpee_address & jpee_pagemask))
break; /* Writes can't cross page boundary! */
}
jpee_ad_known = 0;
}
else
JPEE_LOG0("I2C_STOP");
jpee_state = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void MT24LC256::jpee_clock_fall()
{
switch(jpee_state)
{
case 1:
jpee_nb <<= 1;
jpee_nb |= jpee_mdat;
if (jpee_nb & 256)
{
if (!jpee_pptr)
{
jpee_packet[0] = uInt8(jpee_nb);
if (jpee_smallmode && ((jpee_nb & 0xF0) == 0xA0))
{
jpee_packet[1] = (jpee_nb >> 1) & 7;
if (jpee_packet[1] != (jpee_address >> 8) && (jpee_packet[0] & 1))
JPEE_LOG0("I2C_WARNING ADDRESS MSB CHANGED");
jpee_nb &= 0x1A1;
}
if (jpee_nb == 0x1A0)
{
JPEE_LOG1("I2C_SENT(%02X--start write)",jpee_packet[0]);
jpee_state = 2;
jpee_sdat = 0;
}
else if (jpee_nb == 0x1A1)
{
jpee_state = 4;
JPEE_LOG2("I2C_SENT(%02X--start read @%04X)",
jpee_packet[0],jpee_address);
if (!jpee_ad_known)
JPEE_LOG0("I2C_WARNING ADDRESS IS UNKNOWN");
jpee_sdat = 0;
}
else
{
JPEE_LOG1("I2C_WARNING ODDBALL FIRST BYTE!(%02X)",jpee_nb & 0xFF);
jpee_state = 0;
}
}
else
{
jpee_state = 2;
jpee_sdat = 0;
}
}
break;
case 2:
if (jpee_nb)
{
if (!jpee_pptr)
{
jpee_packet[0] = uInt8(jpee_nb);
if (jpee_smallmode)
jpee_pptr=2;
else
jpee_pptr=1;
}
else if (jpee_pptr < 70)
{
JPEE_LOG1("I2C_SENT(%02X)",jpee_nb & 0xFF);
jpee_packet[jpee_pptr++] = uInt8(jpee_nb);
jpee_address = (jpee_packet[1] << 8) | jpee_packet[2];
if (jpee_pptr > 2)
jpee_ad_known = 1;
}
else
JPEE_LOG0("I2C_WARNING OUTPUT_OVERFLOW!");
}
jpee_sdat = 1;
jpee_nb = 1;
jpee_state=1;
break;
case 4:
if (jpee_mdat && jpee_sdat)
{
JPEE_LOG0("I2C_READ_NAK");
jpee_state=0;
break;
}
jpee_state=3;
myPageHit[jpee_address / PAGE_SIZE] = true;
{
bool devSettings = mySystem.oSystem().settings().getBool("dev.settings");
if(mySystem.oSystem().settings().getBool(devSettings ? "dev.eepromaccess" : "plr.eepromaccess"))
mySystem.oSystem().frameBuffer().showMessage("AtariVox/SaveKey EEPROM read");
}
jpee_nb = (myData[jpee_address & jpee_sizemask] << 1) | 1; /* Fall through */
JPEE_LOG2("I2C_READ(%04X=%02X)",jpee_address,jpee_nb/2);
[[fallthrough]];
case 3:
jpee_sdat = !!(jpee_nb & 256);
jpee_nb <<= 1;
if (!(jpee_nb & 510))
{
jpee_state = 4;
jpee_sdat = 1;
++jpee_address;
}
break;
default:
/* Do nothing */
break;
}
JPEE_LOG2("I2C_CLOCK (dat=%d/%d)",jpee_mdat,jpee_sdat);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool MT24LC256::jpee_timercheck(int mode)
{
/*
Evaluate how long the EEPROM is busy. When invoked with an argument of 1,
start a timer (probably about 5 milliseconds); when invoked with an
argument of 0, return zero if the timer has expired or non-zero if it is
still running.
*/
if(mode) // set timer
{
myCyclesWhenTimerSet = mySystem.cycles();
return myTimerActive = true;
}
else // read timer
{
if(myTimerActive)
{
uInt64 elapsed = mySystem.cycles() - myCyclesWhenTimerSet;
myTimerActive = elapsed < uInt64(5000000.0 / 838.0);
}
return myTimerActive;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int MT24LC256::jpee_logproc(char const *st)
{
cerr << " " << st << endl;
return 0;
}