1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include <cstdio>
19
20#include "System.hxx"
21#include "MT24LC256.hxx"
22
23//#define DEBUG_EEPROM
24
25#ifdef DEBUG_EEPROM
26 static char jpee_msg[256];
27 #define JPEE_LOG0(msg) jpee_logproc(msg);
28 #define JPEE_LOG1(msg,arg1) sprintf(jpee_msg,(msg),(arg1)), jpee_logproc(jpee_msg);
29 #define JPEE_LOG2(msg,arg1,arg2) sprintf(jpee_msg,(msg),(arg1),(arg2)), jpee_logproc(jpee_msg);
30#else
31 #define JPEE_LOG0(msg)
32 #define JPEE_LOG1(msg,arg1)
33 #define JPEE_LOG2(msg,arg1,arg2)
34#endif
35
36/*
37 State values for I2C:
38 0 - Idle
39 1 - Byte going to chip (shift left until bit 8 is set)
40 2 - Chip outputting acknowledgement
41 3 - Byte coming in from chip (shift left until lower 8 bits are clear)
42 4 - Chip waiting for acknowledgement
43*/
44
45// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
46MT24LC256::MT24LC256(const string& filename, const System& system,
47 Controller::onMessageCallback callback)
48 : mySystem(system),
49 myCallback(callback),
50 mySDA(false),
51 mySCL(false),
52 myTimerActive(false),
53 myCyclesWhenTimerSet(0),
54 myCyclesWhenSDASet(0),
55 myCyclesWhenSCLSet(0),
56 myDataFile(filename),
57 myDataFileExists(false),
58 myDataChanged(false),
59 jpee_mdat(0),
60 jpee_sdat(0),
61 jpee_mclk(0),
62 jpee_sizemask(0),
63 jpee_pagemask(0),
64 jpee_smallmode(0),
65 jpee_logmode(0),
66 jpee_pptr(0),
67 jpee_state(0),
68 jpee_nb(0),
69 jpee_address(0),
70 jpee_ad_known(0)
71{
72 // Load the data from an external file (if it exists)
73 ifstream in(myDataFile, std::ios_base::binary);
74 if(in.is_open())
75 {
76 // Get length of file; it must be 32768
77 in.seekg(0, std::ios::end);
78 if(uInt32(in.tellg()) == FLASH_SIZE)
79 {
80 in.seekg(0, std::ios::beg);
81 in.read(reinterpret_cast<char*>(myData.data()), myData.size());
82 myDataFileExists = true;
83 }
84 }
85 else
86 myDataFileExists = false;
87
88 // Then initialize the I2C state
89 jpee_init();
90
91 systemReset();
92}
93
94// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
95MT24LC256::~MT24LC256()
96{
97 // Save EEPROM data to external file only when necessary
98 if(!myDataFileExists || myDataChanged)
99 {
100 ofstream out(myDataFile, std::ios_base::binary);
101 if(out.is_open())
102 out.write(reinterpret_cast<char*>(myData.data()), myData.size());
103 }
104}
105
106// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
107void MT24LC256::writeSDA(bool state)
108{
109 mySDA = state;
110 myCyclesWhenSDASet = mySystem.cycles();
111
112 update();
113}
114
115// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
116void MT24LC256::writeSCL(bool state)
117{
118 mySCL = state;
119 myCyclesWhenSCLSet = mySystem.cycles();
120
121 update();
122}
123
124// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
125void MT24LC256::update()
126{
127#define jpee_clock(x) ( (x) ? \
128 (jpee_mclk = 1) : \
129 (jpee_mclk && (jpee_clock_fall(),1), jpee_mclk = 0))
130
131#define jpee_data(x) ( (x) ? \
132 (!jpee_mdat && jpee_sdat && jpee_mclk && (jpee_data_stop(),1), jpee_mdat = 1) : \
133 (jpee_mdat && jpee_sdat && jpee_mclk && (jpee_data_start(),1), jpee_mdat = 0))
134
135 // These pins have to be updated at the same time
136 // However, there's no guarantee that the writeSDA() and writeSCL()
137 // methods will be called at the same time or in the correct order, so
138 // we only do the write when they have the same 'timestamp'
139 if(myCyclesWhenSDASet == myCyclesWhenSCLSet)
140 {
141#ifdef DEBUG_EEPROM
142 cerr << endl << " I2C_PIN_WRITE(SCL = " << mySCL
143 << ", SDA = " << mySDA << ")" << " @ " << mySystem.cycles() << endl;
144#endif
145 jpee_clock(mySCL);
146 jpee_data(mySDA);
147 }
148}
149
150// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
151void MT24LC256::systemReset()
152{
153 myPageHit.fill(false);
154}
155
156// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
157void MT24LC256::eraseAll()
158{
159 myData.fill(INIT_VALUE);
160 myDataChanged = true;
161}
162
163// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
164void MT24LC256::eraseCurrent()
165{
166 for(uInt32 page = 0; page < PAGE_NUM; ++page)
167 {
168 if(myPageHit[page])
169 {
170 std::fill_n(myData.begin() + page * PAGE_SIZE, PAGE_SIZE, INIT_VALUE);
171 myDataChanged = true;
172 }
173 }
174}
175
176// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
177bool MT24LC256::isPageUsed(uInt32 page) const
178{
179 if(page < PAGE_NUM)
180 return myPageHit[page];
181 else
182 return false;
183}
184
185// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
186void MT24LC256::jpee_init()
187{
188 jpee_sdat = 1;
189 jpee_address = 0;
190 jpee_state=0;
191 jpee_sizemask = FLASH_SIZE - 1;
192 jpee_pagemask = PAGE_SIZE - 1;
193 jpee_smallmode = 0;
194 jpee_logmode = -1;
195 if(!myDataFileExists)
196 myData.fill(INIT_VALUE);
197}
198
199// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
200void MT24LC256::jpee_data_start()
201{
202 /* We have a start condition */
203 if (jpee_state == 1 && (jpee_nb != 1 || jpee_pptr != 3))
204 {
205 JPEE_LOG0("I2C_WARNING ABANDON WRITE")
206 jpee_ad_known = 0;
207 }
208 if (jpee_state == 3)
209 {
210 JPEE_LOG0("I2C_WARNING ABANDON READ")
211 }
212 if (!jpee_timercheck(0))
213 {
214 JPEE_LOG0("I2C_START")
215 jpee_state = 2;
216 }
217 else
218 {
219 JPEE_LOG0("I2C_BUSY")
220 jpee_state = 0;
221 }
222 jpee_pptr = 0;
223 jpee_nb = 0;
224 jpee_packet[0] = 0;
225}
226
227// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
228void MT24LC256::jpee_data_stop()
229{
230 if (jpee_state == 1 && jpee_nb != 1)
231 {
232 JPEE_LOG0("I2C_WARNING ABANDON_WRITE")
233 jpee_ad_known = 0;
234 }
235 if (jpee_state == 3)
236 {
237 JPEE_LOG0("I2C_WARNING ABANDON_READ")
238 jpee_ad_known = 0;
239 }
240 /* We have a stop condition. */
241 if (jpee_state == 1 && jpee_nb == 1 && jpee_pptr > 3)
242 {
243 jpee_timercheck(1);
244 JPEE_LOG2("I2C_STOP(Write %d bytes at %04X)",jpee_pptr-3,jpee_address)
245 if (((jpee_address + jpee_pptr-4) ^ jpee_address) & ~jpee_pagemask)
246 {
247 jpee_pptr = 4+jpee_pagemask-(jpee_address & jpee_pagemask);
248 JPEE_LOG1("I2C_WARNING PAGECROSSING!(Truncate to %d bytes)",jpee_pptr-3)
249 }
250 for (int i=3; i<jpee_pptr; i++)
251 {
252 myDataChanged = true;
253 myPageHit[jpee_address / PAGE_SIZE] = true;
254
255 myCallback("AtariVox/SaveKey EEPROM write");
256
257 myData[(jpee_address++) & jpee_sizemask] = jpee_packet[i];
258 if (!(jpee_address & jpee_pagemask))
259 break; /* Writes can't cross page boundary! */
260 }
261 jpee_ad_known = 0;
262 }
263#ifdef DEBUG_EEPROM
264 else
265 jpee_logproc("I2C_STOP");
266#endif
267
268 jpee_state = 0;
269}
270
271// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
272void MT24LC256::jpee_clock_fall()
273{
274 switch(jpee_state)
275 {
276 case 1:
277 jpee_nb <<= 1;
278 jpee_nb |= jpee_mdat;
279 if (jpee_nb & 256)
280 {
281 if (!jpee_pptr)
282 {
283 jpee_packet[0] = uInt8(jpee_nb);
284 if (jpee_smallmode && ((jpee_nb & 0xF0) == 0xA0))
285 {
286 jpee_packet[1] = (jpee_nb >> 1) & 7;
287 #ifdef DEBUG_EEPROM
288 if (jpee_packet[1] != (jpee_address >> 8) && (jpee_packet[0] & 1))
289 jpee_logproc("I2C_WARNING ADDRESS MSB CHANGED");
290 #endif
291 jpee_nb &= 0x1A1;
292 }
293 if (jpee_nb == 0x1A0)
294 {
295 JPEE_LOG1("I2C_SENT(%02X--start write)",jpee_packet[0])
296 jpee_state = 2;
297 jpee_sdat = 0;
298 }
299 else if (jpee_nb == 0x1A1)
300 {
301 jpee_state = 4;
302 JPEE_LOG2("I2C_SENT(%02X--start read @%04X)", jpee_packet[0],jpee_address)
303 #ifdef DEBUG_EEPROM
304 if (!jpee_ad_known)
305 jpee_logproc("I2C_WARNING ADDRESS IS UNKNOWN");
306 #endif
307 jpee_sdat = 0;
308 }
309 else
310 {
311 JPEE_LOG1("I2C_WARNING ODDBALL FIRST BYTE!(%02X)",jpee_nb & 0xFF)
312 jpee_state = 0;
313 }
314 }
315 else
316 {
317 jpee_state = 2;
318 jpee_sdat = 0;
319 }
320 }
321 break;
322
323 case 2:
324 if (jpee_nb)
325 {
326 if (!jpee_pptr)
327 {
328 jpee_packet[0] = uInt8(jpee_nb);
329 if (jpee_smallmode)
330 jpee_pptr=2;
331 else
332 jpee_pptr=1;
333 }
334 else if (jpee_pptr < 70)
335 {
336 JPEE_LOG1("I2C_SENT(%02X)",jpee_nb & 0xFF)
337 jpee_packet[jpee_pptr++] = uInt8(jpee_nb);
338 jpee_address = (jpee_packet[1] << 8) | jpee_packet[2];
339 if (jpee_pptr > 2)
340 jpee_ad_known = 1;
341 }
342 #ifdef DEBUG_EEPROM
343 else
344 jpee_logproc("I2C_WARNING OUTPUT_OVERFLOW!");
345 #endif
346 }
347 jpee_sdat = 1;
348 jpee_nb = 1;
349 jpee_state=1;
350 break;
351
352 case 4:
353 if (jpee_mdat && jpee_sdat)
354 {
355 JPEE_LOG0("I2C_READ_NAK")
356 jpee_state=0;
357 break;
358 }
359 jpee_state=3;
360 myPageHit[jpee_address / PAGE_SIZE] = true;
361
362 myCallback("AtariVox/SaveKey EEPROM read");
363
364 jpee_nb = (myData[jpee_address & jpee_sizemask] << 1) | 1; /* Fall through */
365 JPEE_LOG2("I2C_READ(%04X=%02X)",jpee_address,jpee_nb/2)
366 [[fallthrough]];
367
368 case 3:
369 jpee_sdat = !!(jpee_nb & 256);
370 jpee_nb <<= 1;
371 if (!(jpee_nb & 510))
372 {
373 jpee_state = 4;
374 jpee_sdat = 1;
375 ++jpee_address;
376 }
377 break;
378
379 default:
380 /* Do nothing */
381 break;
382 }
383 JPEE_LOG2("I2C_CLOCK (dat=%d/%d)",jpee_mdat,jpee_sdat)
384}
385
386// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
387bool MT24LC256::jpee_timercheck(int mode)
388{
389 /*
390 Evaluate how long the EEPROM is busy. When invoked with an argument of 1,
391 start a timer (probably about 5 milliseconds); when invoked with an
392 argument of 0, return zero if the timer has expired or non-zero if it is
393 still running.
394 */
395 if(mode) // set timer
396 {
397 myCyclesWhenTimerSet = mySystem.cycles();
398 return myTimerActive = true;
399 }
400 else // read timer
401 {
402 if(myTimerActive)
403 {
404 uInt64 elapsed = mySystem.cycles() - myCyclesWhenTimerSet;
405 myTimerActive = elapsed < uInt64(5000000.0 / 838.0);
406 }
407 return myTimerActive;
408 }
409}
410