mirror of https://github.com/stella-emu/stella.git
414 lines
11 KiB
C++
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;
|
|
}
|