1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
|
// license:BSD-3-Clause
// copyright-holders:Christian Brunschen
/***************************************************************************
Generic 28-series Parallel EEPROM read and write logic,
including write protection command sequences.
Caters for different speeds such as X28C256, X28HC256, etc.
Caters for different storage sizes such as X28C64, X28C256, X28C010,
XM28C020, etc.
***************************************************************************/
#ifndef MAME_MACHINE_EEPROM28_H
#define MAME_MACHINE_EEPROM28_H
#pragma once
#include <algorithm>
#include <array>
#include <cstdint>
#define EE28_LOG_GENERAL (1 << 0)
#define EE28_LOG_DETAIL (1 << 1)
// #define EE28_VERBOSE (EE28_LOG_GENERAL | EE28_LOG_DETAIL)
#if defined(EE28_VERBOSE) && (EE28_VERBOSE)
#define EE28LOGMASKED(mask, ...) do { if (EE28_VERBOSE & (mask)) (logerror)(__VA_ARGS__); } while (0)
#define EE28LOG(...) EE28LOGMASKED(EE28_LOG_GENERAL, __VA_ARGS__)
#else
#define EE28LOGMASKED(...)
#define EE28LOG(...)
#endif // EE28_VERBOSE
//**************************************************************************
// TYPE DEFINITIONS
//**************************************************************************
// eeprom28_device: 28-series EEPROM with paged write and software write protection
/**
* Template parameters
*
* AddressBits:
* The number of bits in the address bus.
* _28_64 EEPROMs store 64 kbits = 8 kbytes = 13 address bits,
* _28_256 ones store 256 kbits = 32 kbytes = 15 address bits,
* _28_512 EEPROMs store 512 kbits = 64 kbytes = 16 address bits,
* _28_010 ones store 1024 kbits = 128 kbytes = 17 address bits.
*
* PageSizeBytes:
* These EEPROMs support writing an entire page at a time. These page sizes vary
* by EEPROM size and by manufacturer. 64 and 128 byte page sizes are both common.
*
* TBLCUsec:
* The EEPROM's T_BLC, the "Byte Load Cycle Time", in microseconds.
* After a byte write, if another byte on the same page is written within T_BLC,
* those writes will be combined in a single programming cycle. Or in other words,
* the programming cycle starts T_BLC after the most recent write.
* Xicor X28C256 has T_BLC = 100 microseconds.
* If TBLCUsec == 0, then there is no timed end to the Byte Load Cycle, so we
* _must_ trigger programming in some other way. The only way to do that is to triggger
* programming on read(), so that is what we do. See also ProgramOnRead below, for
* explicitly enabling this behaviour.
*
* TWCUsec:
* The EEPROM's T_WC, the "Write Cycle Time", in microseconds. This is the amount
* of time it takes for the EEPROM to complete is programming cycle.
* Xicor X28C256 has T_WC typ = 5ms = 5000 microseconds, max = 10ms = 10000 microseconds.
* Xicor X28HC256 has T_WC typ = 3ms = 3000 microseconds, max = 5ms = 5000 microseconds.
*
* ProgramOnRead:
* Instead of waiting for the TBLCUsec interval to elapse, whenever a Read hapens,
* immediately start any pending programming cycle.
* This is not present on real devices but here it allows us to create a 'fast' eeprom.
* In particular, if we know that the client always follows writes with reads to verify
* that the programming cycle completes, then we can set TBLCUsec to 0 and thus implicitly
* ProgramOnRead to true. We now have a device that obeys the EEPROM write
* protection commands but does not have to wait for T_BLC to expire before writing the
* buffered data to storage.
* If we then also set TWCUsec to 0, we have a device that also immediately writes the
* buffered data to storage, giving us a very fast EEPROM device.
* This can be used for example for external EEPROM cartridges, where emulating the precise
* timing of a particular chip is unlikely to be very important, and instead, allowing
* that cartridge to be as quick as possible may give a more pleasant user experience.
*
* HasIdPage:
* In addition to the data, has an extra page if "identification" data, which is accessed by
* calling `set_access_id_page(1)`, which allows the ID page to be accessed at the top of
* the chip's address range.
* On Atmel AT28 EEPROMs, this is achieved by driving pin A9 to +12V.
*
* HasHardwareChipErase:
* While `chip_erase` is asserted (by `set_chip_erase(1)`), a write to any address will trigger
* erasing all data on the chip. This will set all data, including any ID data, to 0xff.
* This will take TCEUsec microseconds (20ms == 20000us by default).
* On Atmel AT28 series chipe, this is achieved by driving pin /OE (Output enable)
* to +12V.
*
* HasSoftwareChipErase:
* In addition to the usual software write protection commands, adds a further command.
* If, at the end of the "protection disable" command sequence, instead of writing a final
* 0x20 (to disable write protection) the host writes 0x10, then a software chip erase
* is initiated. This will set all data (including any ID data) to 0xff. This will take
* TCEUsec microseconds (20ms == 20000us by default).
*
* TCEUsec:
* The time it takes for a Chip Erase, either hardware or software, to complete.
* 20ms == 20000us by default. If set to 0, chip erase will take effect immediately.
*/
#define EEPROM28_PARAMS int AddressBits, uint32_t PageSizeBytes, uint32_t TBLCUsec, uint32_t TWCUsec, bool ProgramOnRead, bool HasIdPage, bool HasHardwareChipErase, bool HasSoftwareChipErase, uint32_t TCEUsec
#define EEPROM28_PARAMS_WITH_DEFAULTS int AddressBits, uint32_t PageSizeBytes, uint32_t TBLCUsec, uint32_t TWCUsec, bool ProgramOnRead = false, bool HasIdPage = false, bool HasHardwareChipErase = false, bool HasSoftwareChipErase = false, uint32_t TCEUsec = 20'000
#define EEPROM28_ARGS AddressBits, PageSizeBytes, TBLCUsec, TWCUsec, ProgramOnRead, HasIdPage, HasHardwareChipErase, HasSoftwareChipErase, TCEUsec
template<EEPROM28_PARAMS_WITH_DEFAULTS>
class eeprom28_device : public device_t
{
using self = eeprom28_device<EEPROM28_ARGS>;
public:
static constexpr bool HAS_ID_PAGE = HasIdPage;
static constexpr bool HAS_HARDWARE_CHIP_ERASE = HasHardwareChipErase;
static constexpr bool HAS_SOFTWARE_CHIP_ERASE = HasSoftwareChipErase;
static constexpr bool HAS_CHIP_ERASE = HAS_HARDWARE_CHIP_ERASE || HAS_SOFTWARE_CHIP_ERASE;
static constexpr uint32_t ADDRESS_BITS = AddressBits;
static constexpr uint32_t DATA_SIZE_BYTES = 1 << AddressBits;
static constexpr uint32_t ADDRESS_MASK = DATA_SIZE_BYTES - 1;
static constexpr uint32_t PAGE_SIZE_BYTES = PageSizeBytes;
static constexpr uint32_t PAGE_OFFSET_MASK = PageSizeBytes - 1;
static constexpr uint32_t PAGE_MASK = ~(PAGE_OFFSET_MASK);
static constexpr uint32_t ID_PAGE_SIZE_BYTES = HasIdPage ? PageSizeBytes : 0;
static constexpr uint32_t ID_PAGE_OFFSET = DATA_SIZE_BYTES - ID_PAGE_SIZE_BYTES;
static constexpr uint32_t ID_PAGE = PAGE_MASK + 1;
static constexpr uint32_t TOTAL_SIZE_BYTES = DATA_SIZE_BYTES + ID_PAGE_SIZE_BYTES;
static constexpr uint32_t T_BLC_USEC = TBLCUsec;
static constexpr uint32_t T_WC_USEC = TWCUsec;
static constexpr bool PROGRAM_ON_READ = (TBLCUsec == 0) || ProgramOnRead;
static constexpr uint32_t T_CE_USEC = TCEUsec;
static constexpr uint8_t INVERSE_DATA_BIT = 1 << 7;
static constexpr uint8_t TOGGLE_BIT = 1 << 6;
// construction/destruction
eeprom28_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock = 0)
: device_t(mconfig, type, tag, owner, clock)
{}
virtual ~eeprom28_device() {}
void write(uint32_t offset, uint8_t data);
uint8_t read(uint32_t offset);
// Allow clients direct access to stored data.
std::array<uint8_t, TOTAL_SIZE_BYTES> &data()
{
return m_storage;
}
// Allow access to the current software data protection status
bool is_software_data_protection_enabled()
{
return m_software_data_protection_enabled;
}
void set_software_data_protection_enabled(bool software_data_protection_enabled)
{
m_software_data_protection_enabled = software_data_protection_enabled;
}
// Allow users to override device timing.
void override_t_blc_usec(uint32_t t_blc_usec = T_BLC_USEC)
{
m_t_blc_usec = t_blc_usec;
}
void override_t_wc_usec(uint32_t t_wc_usec = T_WC_USEC)
{
m_t_wc_usec = t_wc_usec;
}
void override_program_on_read(bool program_on_read = PROGRAM_ON_READ)
{
m_program_on_read = program_on_read;
}
void override_t_ce_usec(uint32_t t_ce_usec) requires (HasHardwareChipErase || HasSoftwareChipErase)
{
m_t_ce_usec = t_ce_usec;
}
void set_chip_erase(int state) requires HasHardwareChipErase
{
m_chip_erase = (state != 0);
}
void set_access_id_page(int state) requires HasIdPage
{
m_access_id_page = (state != 0);
}
#ifndef X28_VISIBLE_FOR_TESTING
protected:
#endif // X28_VISIBLE_FOR_TESTING
// device-level overrides
virtual void device_start() override ATTR_COLD
{
if (m_t_blc_usec > 0 && m_start_programming_timer == nullptr) {
m_start_programming_timer = timer_alloc(FUNC(self::start_programming_cycle), this);
}
if (m_t_wc_usec > 0 && m_programming_completed_timer == nullptr) {
m_programming_completed_timer = timer_alloc(FUNC(self::programming_cycle_complete), this);
}
save_item(NAME(m_storage));
save_item(NAME(m_program_buffer_to_eeprom));
save_item(NAME(m_last_written_offset));
save_item(NAME(m_toggle_bit));
save_item(NAME(m_state));
save_item(NAME(m_command_state));
save_item(NAME(m_software_data_protection_enabled));
save_item(NAME(m_buffering_page));
save_item(NAME(m_page_buffer));
save_item(NAME(m_t_blc_usec));
save_item(NAME(m_t_wc_usec));
save_item(NAME(m_program_on_read));
save_item(NAME(m_t_ce_usec));
save_item(NAME(m_chip_erase));
save_item(NAME(m_access_id_page));
}
virtual void device_reset() override ATTR_COLD
{
if (m_start_programming_timer != nullptr)
m_start_programming_timer->enable(false);
if (m_programming_completed_timer != nullptr)
m_programming_completed_timer->enable(false);
change_to_state(COMMAND_STATE_NONE);
change_to_state(STATE_IDLE);
m_last_written_offset = -1;
m_software_data_protection_enabled = false;
m_buffering_page = -1;
}
// Change State to a new internal state
void change_to_state(int ns)
{
EE28LOGMASKED(EE28_LOG_DETAIL, "Changing state to %d\r\n", ns);
m_state = ns;
}
// Error in the internal state machine, return to the correct idle internal state
void state_machine_error()
{
change_to_state(m_software_data_protection_enabled ? STATE_IDLE : STATE_BUFFERING);
}
// Change State to a new command processing state
void change_to_command_state(int ns)
{
EE28LOGMASKED(EE28_LOG_DETAIL, "Changing state to %d\r\n", ns);
m_command_state = ns;
}
// Error in the command state machine, return to the correct idle internal state
void command_state_machine_error()
{
change_to_command_state(COMMAND_STATE_NONE);
}
// internal state
enum {
// idle state: reads work as normal, writes will succeed or fail depending on
// m_software_data_protection_enabled - except for those writes that are part of one
// of the protection enable or disable sequences.
STATE_IDLE,
// After detecting the third write that initiates a protection enable sequence,
// writing A0 to address 5555 (1555 on X28C64).
// At this point the device will accept one page write, before then going into
// or remaining in write-protected mode.
STATE_PROTECTED_WRITE,
// When a write operation starts, this selects a page that is being written,
// and writes the byte to an internal buffer.
// As long as the next write is to the same page (higher address bits remain
// the same) and the next write is initiated within T_BLC, more bytes can be
// written, and those too will be written to the internal buffer.
// If no more writes happen within T_BLC, the programming cycle starts,
// during which time the buffer will be saved to the corresponding page in
// the persistent EEPROM storage.
STATE_BUFFERING,
// Buffered data is being programmed into the EEPROM.
STATE_PROGRAMMING,
};
// Command processing state
enum {
// Not in any command sequence.
COMMAND_STATE_NONE = 0,
// after detecting the first write that initiates a command sequence,
// writing AA to address 5555 (1555 on X28C64)
COMMAND_STATE_1,
// after detecting the second write that initiates a command sequence,
// writing 55 to address 2AAA (0AAA on X28C64)
COMMAND_STATE_2,
// If in this state the client writes A0 to 5555 (1555 on X28C64),
// that concludes the Protection Enable command sequence and enters
// the Protected Write state: allowing writes to one page, at the end of which
// the device will enter the Write Protected state, setting
// m_write_protecion_enabled = true.
// If instead the client writes 80 to 5555 (1555 on X28C64), this continues
// the Protection Disable command sequence.
// after detecting the third write that initiates a protection disable command sequence,
// writing 80 to address 5555 (1555 on X28C64)
COMMAND_STATE_PROTECION_DISABLE_3,
// after detecting the fourth write that initiates a protection disable command sequence,
// writing AA to address 5555 (1555 on X28C64)
COMMAND_STATE_PROTECION_DISABLE_4,
// after detecting the fifth write that initiates a protection disable command sequence.
// writing 55 to address 2AAA (0AAA on X28C64)
COMMAND_STATE_PROTECION_DISABLE_5,
// after detecting the sixth write in the protection disable command sequence:
// - writing 20 to address 5555 (1555 on X28C64),
// the device will return to COMMAND_STATE_NONE and STATE_IDLE with
// m_software_data_protection_enabled = false.
// If HasSoftwareChipErase == true, then:
// - writing 10 to address 5555 (1555 on AT28C64b),
// the device will perform a chip erase, filling its storage with FF. This will take TCEUsec.
// While this is ongoing, Toggle Bit Polling will work, but as there was no specific most
// recent address writte, /DATA Polling will not.
};
std::array<uint8_t, TOTAL_SIZE_BYTES> m_storage {};
bool m_program_buffer_to_eeprom = false;
emu_timer *m_start_programming_timer = nullptr;
emu_timer *m_programming_completed_timer = nullptr;
uint32_t m_last_written_offset = 0;
uint8_t m_toggle_bit = 0;
int m_state = STATE_IDLE;
int m_command_state = COMMAND_STATE_NONE;
bool m_software_data_protection_enabled = false;
int32_t m_buffering_page = -1;
std::array<uint8_t, PAGE_SIZE_BYTES> m_page_buffer;
// Configurable overrides: initialize per the device's definition.
uint32_t m_t_blc_usec = T_BLC_USEC;
uint32_t m_t_wc_usec = T_WC_USEC;
bool m_program_on_read = PROGRAM_ON_READ;
uint32_t m_t_ce_usec = T_CE_USEC;
bool m_chip_erase = false;
bool m_access_id_page = false;
// Timer callbacks
void start_programming_cycle(s32 param = 0)
{
EE28LOG("%s: start_programming_cycle for page %04x\n", machine().describe_context(), m_buffering_page);
change_to_state(STATE_PROGRAMMING);
if (m_program_buffer_to_eeprom) {
EE28LOG("%s: writing buffer to page %04x\n", machine().describe_context(), m_buffering_page);
std::memcpy(&m_storage[storage_page(m_buffering_page)], &m_page_buffer[0], PAGE_SIZE_BYTES);
}
m_buffering_page = -1;
if (m_t_wc_usec == 0) {
programming_cycle_complete();
} else if (m_t_wc_usec > 0) {
m_programming_completed_timer->adjust(attotime::from_usec(m_t_wc_usec));
}
}
void programming_cycle_complete(s32 param = 0)
{
EE28LOG("%s: programming_cycle_complete\n", machine().describe_context());
change_to_state(STATE_IDLE);
m_program_buffer_to_eeprom = false;
EE28LOGMASKED(EE28_LOG_DETAIL, "m_program_buffer_to_eeprom -> %d\r\n", m_program_buffer_to_eeprom);
}
// Software Data Protection Helper
void disable_software_data_protection() {
// We have now received a complete "disable write protection" command. So we:
// - Disable write protection, i.e., enable writes.
m_software_data_protection_enabled = false;
// - Note that we're no longer in a command sequence.
change_to_command_state(COMMAND_STATE_NONE);
// - Write protection was disabled, and the preceding writes were just part of that command sequence.
m_program_buffer_to_eeprom = false;
// printf("m_program_buffer_to_eeprom -> %d\r\n", m_program_buffer_to_eeprom);
if (m_t_blc_usec > 0) {
// Since we're explicitly starting the programming cycle, disable the timer
m_start_programming_timer->enable(false);
}
// - Start the programming cycle
start_programming_cycle();
}
// Chip Erase
void start_erase_cycle() requires (HasHardwareChipErase || HasSoftwareChipErase) {
if (HAS_CHIP_ERASE) {
change_to_state(STATE_PROGRAMMING);
// Erase all the data by setting it to 0xff
std::fill_n(&m_storage[0], TOTAL_SIZE_BYTES, 0xff);
if (m_t_ce_usec > 0) {
m_programming_completed_timer->adjust(attotime::from_usec(TCEUsec));
} else {
programming_cycle_complete();
}
}
}
// are we accessing the ID page?
bool is_accessing_id_page(uint32_t offset) requires HasIdPage {
if (HAS_ID_PAGE) {
return m_access_id_page && (offset & PAGE_MASK) == ID_PAGE_OFFSET;
} else {
return false;
}
}
uint32_t storage_offset(uint32_t offset) requires HasIdPage {
return is_accessing_id_page(offset) ? (offset - ID_PAGE_OFFSET + DATA_SIZE_BYTES) : offset;
}
uint32_t storage_offset(uint32_t offset) requires (!HasIdPage) {
return offset;
}
uint32_t storage_page(uint32_t offset) {
return storage_offset(offset) & PAGE_MASK;
}
};
// include the implementations for read() and write()
#include "eeprom28.ipp"
/*
* A variant of the eeprom28_device class that implements `device_nvram_interface`
* for when the EEPROM represents non-volatile memory used in a bigger system.
* Same template parameters as `eeprom28_device`.
*/
template<EEPROM28_PARAMS_WITH_DEFAULTS>
class eeprom28_nvram_device
: public eeprom28_device<EEPROM28_ARGS>
, public device_nvram_interface
{
using self = eeprom28_nvram_device<EEPROM28_ARGS>;
using super = eeprom28_device<EEPROM28_ARGS>;
public:
eeprom28_nvram_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock = 0)
: super(mconfig, type, tag, owner, clock)
, device_nvram_interface(mconfig, *this)
, m_default_data(*this, DEVICE_SELF)
{
}
protected:
optional_region_ptr<uint8_t> m_default_data;
// derived class overrides
virtual void nvram_default() override
{
std::fill_n(&this->m_storage[0], self::TOTAL_SIZE_BYTES, 0xff);
/* populate from a memory region if present */
if (m_default_data.found())
{
std::memcpy(&this->m_storage[0], &m_default_data[0], std::min(static_cast<size_t>(self::TOTAL_SIZE_BYTES), m_default_data.length()));
}
}
virtual bool nvram_read(util::read_stream &file) override
{
std::vector<uint8_t> buffer(self::TOTAL_SIZE_BYTES + 2);
// try to read one more byte than we really need, to detect wrong-size input
auto const [err, actual] = util::read(file, &buffer[0], self::TOTAL_SIZE_BYTES + 2);
if (err || (actual != self::TOTAL_SIZE_BYTES + 1))
return false;
std::memcpy(&this->m_storage[0], &buffer[0], self::TOTAL_SIZE_BYTES);
this->m_software_data_protection_enabled = buffer[self::TOTAL_SIZE_BYTES] != 0;
return true;
}
virtual bool nvram_write(util::write_stream &file) override
{
std::vector<uint8_t> buffer(self::TOTAL_SIZE_BYTES + 1);
std::memcpy(&buffer[0], &this->m_storage[0], self::TOTAL_SIZE_BYTES);
buffer[self::TOTAL_SIZE_BYTES] = this->m_software_data_protection_enabled;
auto const [err, actual] = util::write(file, &buffer[0], self::TOTAL_SIZE_BYTES + 1);
return !err;
}
};
#undef EEPROM28_PARAMS
#undef EEPROM28_PARAMS_WITH_DEFAULTS
#undef EEPROM28_ARGS
#undef EE28_VERBOSE
#undef EE28LOG
#undef EE28LOGMASKED
#endif // MAME_MACHINE_EEPROM28_H
|