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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
46 | MT24LC256::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
95 | MT24LC256::~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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
107 | void MT24LC256::writeSDA(bool state) |
108 | { |
109 | mySDA = state; |
110 | myCyclesWhenSDASet = mySystem.cycles(); |
111 | |
112 | update(); |
113 | } |
114 | |
115 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
116 | void MT24LC256::writeSCL(bool state) |
117 | { |
118 | mySCL = state; |
119 | myCyclesWhenSCLSet = mySystem.cycles(); |
120 | |
121 | update(); |
122 | } |
123 | |
124 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
125 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
151 | void MT24LC256::systemReset() |
152 | { |
153 | myPageHit.fill(false); |
154 | } |
155 | |
156 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
157 | void MT24LC256::eraseAll() |
158 | { |
159 | myData.fill(INIT_VALUE); |
160 | myDataChanged = true; |
161 | } |
162 | |
163 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
164 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
177 | bool MT24LC256::isPageUsed(uInt32 page) const |
178 | { |
179 | if(page < PAGE_NUM) |
180 | return myPageHit[page]; |
181 | else |
182 | return false; |
183 | } |
184 | |
185 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
186 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
200 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
228 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
272 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
387 | bool 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 | |