OXYGENHUB
◆ main openmsx / src / memory / AmdFlash.hh
20.7 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef AMDFLASH_HH
2
#define AMDFLASH_HH
3
4
#include "serialize_meta.hh"
5
6
#include "BitField.hh"
7
#include "EmuDuration.hh"
8
#include "EmuTime.hh"
9
#include "Schedulable.hh"
10
#include "cstd.hh"
11
#include "narrow.hh"
12
#include "outer.hh"
13
#include "power_of_two.hh"
14
#include "static_vector.hh"
15
16
#include <algorithm>
17
#include <cstddef>
18
#include <cstdint>
19
#include <memory>
20
#include <span>
21
#include <string>
22
#include <utility>
23
#include <vector>
24
25
namespace openmsx {
26
27
class MSXMotherBoard;
28
class Rom;
29
class SRAM;
30
class DeviceConfig;
31
32
class AmdFlash
33
{
34
public:
35
	// See JEDEC JEP106 https://www.jedec.org/standards-documents/docs/jep-106ab
36
	enum class ManufacturerID : uint8_t {
37
		AMD = 0x01,
38
		STM = 0x20,
39
		SST = 0xBF,
40
	};
41
42
	struct AutoSelect {
43
		ManufacturerID manufacturer;
44
		static_vector<uint8_t, 2> device; // single-byte or double-byte (0x7E prefix)
45
		uint16_t extraCode = 0x0000;      // code at 3rd index
46
		uint16_t undefined = 0xFFFF;      // undefined values
47
		bool oddZero : 1 = false;         // odd bytes are zero, not mirrored
48
		size_t readMask = 0x03;           // read address mask
49
50
		constexpr void validate() const {
51
			// check parity
52
			assert(std::popcount(std::to_underlying(manufacturer)) & 1);
53
			// extended marker is not part of ID
54
			assert(!device.empty() && device[0] != 0x7E);
55
		}
56
	};
57
58
	enum class DeviceInterface : uint16_t {
59
		x8 = 0x0000,
60
		x8x16 = 0x0002,
61
	};
62
63
	struct Region {
64
		size_t count = 0;
65
		power_of_two<size_t> size = 1;
66
	};
67
68
	struct Geometry {
69
		constexpr Geometry(DeviceInterface deviceInterface_, std::initializer_list<Region> regions_,
70
			int writeProtectPinRange_ = 0)
71
			: deviceInterface(deviceInterface_)
72
			, regions(regions_)
73
			, writeProtectPinRange(writeProtectPinRange_)
74
			, size(sum(regions, [](Region r) { return r.count * r.size; }))
75
			// Originally sum(regions, &Region::count), but seems to mis-compile to 0 on MSVC.
76
			// It looks like sum with projection doesn’t work at compile-time in MSVC 2022?
77
			//   https://developercommunity.visualstudio.com/t/wrong-code-bug-in-constexpr-evaluation/10673004
78
			, sectorCount(sum(regions, [](Region r) { return r.count; })) {}
79
		DeviceInterface deviceInterface;
80
		static_vector<Region, 4> regions;
81
		int writeProtectPinRange; // sectors protected by WP#, negative: from top
82
		power_of_two<size_t> size;
83
		size_t sectorCount;
84
85
		constexpr void validate() const {
86
			assert(std::ranges::all_of(regions, [](const auto& region) { return region.count > 0; }));
87
			assert(narrow_cast<unsigned>(cstd::abs(writeProtectPinRange)) <= sectorCount);
88
		}
89
	};
90
91
	struct Erase {
92
		bool suspend : 1 = false;                             // erase suspend support
93
		EmuDuration duration = EmuDuration::zero();           // per 64K sector
94
		EmuDuration timerDuration = EmuDuration::usec(50);    // to accept additional sector erase commands
95
		EmuDuration minimumDuration = EmuDuration::usec(100); // when all sectors protected
96
		EmuDuration suspendDuration = EmuDuration::zero();    // time to suspend (max)
97
98
		constexpr void validate() const {
99
			assert(duration > EmuDuration::zero());
100
			assert(!suspend || suspendDuration > EmuDuration::zero());
101
		}
102
	};
103
104
	struct Program {
105
		bool shortAbortReset : 1 = false; // permit short program abort reset command
106
		bool bitRaiseError   : 1 = true;  // raising bit from 0 to 1 causes DQ5 error
107
		bool suspend         : 1 = false; // program suspend support
108
		bool enhancedSuspend : 1 = false; // enhanced program suspend / resume commands
109
		power_of_two<size_t> fastPageSize = 1;   // >1 enables fast program commands
110
		power_of_two<size_t> bufferPageSize = 1; // >1 enables buffer program command
111
		EmuDuration duration = EmuDuration::zero();           // per command (incl. fast program)
112
		EmuDuration additionalDuration = EmuDuration::zero(); // additional time per additional byte
113
		EmuDuration suspendDuration = EmuDuration::zero();    // time to suspend
114
115
		constexpr void validate() const {
116
			assert(fastPageSize <= bufferPageSize);
117
			assert(duration > EmuDuration::zero());
118
			assert(bufferPageSize == 1 || additionalDuration > EmuDuration::zero());
119
			assert(!enhancedSuspend || suspend);
120
			assert(!suspend || suspendDuration > EmuDuration::zero());
121
		}
122
	};
123
124
	struct CFI {
125
		bool command : 1 = false;
126
		bool withManufacturerDevice : 1 = false; // < 0x10 contains mfr / device
127
		bool withAutoSelect : 1 = false;         // < 0x10 contains autoselect
128
		bool exitCommand : 1 = false;            // also exit by writing 0xFF
129
		size_t commandMask = 0xFF;               // command address mask
130
		size_t readMask = 0x7F;                  // read address mask
131
		struct SystemInterface {
132
			struct Supply {
133
				uint8_t minVcc = 0;
134
				uint8_t maxVcc = 0;
135
				uint8_t minVpp = 0;
136
				uint8_t maxVpp = 0;
137
			} supply = {};
138
			struct TypicalTimeout {
139
				power_of_two<unsigned> singleProgram = 1;
140
				power_of_two<unsigned> multiProgram = 1;
141
				power_of_two<unsigned> sectorErase = 1;
142
				power_of_two<unsigned> chipErase = 1;
143
			} typTimeout = {};
144
			struct MaxTimeoutMultiplier {
145
				power_of_two<unsigned> singleProgram = 1;
146
				power_of_two<unsigned> multiProgram = 1;
147
				power_of_two<unsigned> sectorErase = 1;
148
				power_of_two<unsigned> chipErase = 1;
149
			} maxTimeoutMult = {};
150
		} systemInterface = {};
151
		struct PrimaryAlgorithm {
152
			struct Version {
153
				uint8_t major = 0;
154
				uint8_t minor = 0;
155
			} version = {};
156
			uint8_t addressSensitiveUnlock : 2 = 0;
157
			uint8_t siliconRevision : 6 = 0;
158
			uint8_t eraseSuspend = 0;
159
			uint8_t sectorProtect = 0;
160
			uint8_t sectorTemporaryUnprotect = 0;
161
			uint8_t sectorProtectScheme = 0;
162
			uint8_t simultaneousOperation = 0;
163
			uint8_t burstMode = 0;
164
			uint8_t pageMode = 0;
165
			struct Supply {
166
				uint8_t minAcc = 0;
167
				uint8_t maxAcc = 0;
168
			} supply = {};
169
			uint8_t bootBlockFlag = 0;
170
			uint8_t programSuspend = 0;
171
		} primaryAlgorithm = {};
172
173
		constexpr void validate(const Erase& erase_, const Program& program_) const {
174
			(void)erase_, (void)program_;
175
			assert(!command || primaryAlgorithm.version.major == 1);
176
			assert(!command || erase_.suspend == (primaryAlgorithm.eraseSuspend > 0));
177
			assert(!command || program_.suspend == (primaryAlgorithm.programSuspend > 0));
178
		}
179
	};
180
181
	struct Misc {
182
		bool statusCommand       : 1 = false; // enables status command
183
		bool continuityCommand   : 1 = false; // enables continuity check command
184
		bool readyPin            : 1 = false; // device has ready / not busy pin (RY/BY#)
185
186
		constexpr void validate() const {
187
			assert(!continuityCommand || statusCommand);
188
		}
189
	};
190
191
	struct Chip {
192
		AutoSelect autoSelect;
193
		Geometry geometry;
194
		Erase erase = {};
195
		Program program = {};
196
		CFI cfi = {};
197
		Misc misc = {};
198
199
		constexpr void validate() const {
200
			autoSelect.validate();
201
			geometry.validate();
202
			erase.validate();
203
			program.validate();
204
			cfi.validate(erase, program);
205
			misc.validate();
206
		}
207
	};
208
209
	struct ValidatedChip {
210
		constexpr ValidatedChip(const Chip& chip_) : chip(chip_) {
211
			chip.validate();
212
		}
213
		const Chip chip;
214
	};
215
216
	/** Create AmdFlash with given configuration.
217
	 * @param rom The initial content for this flash
218
	 * @param chip Contains chip configuration for this flash.
219
	 * @param sectorInfo
220
	 *   A span containing the size and write protected status of each
221
	 *   sector in the flash. This implicitly also communicates the number
222
	 *   of sectors (a sector is a region in the flash that can be erased
223
	 *   individually). There exist flash roms were the different sectors
224
	 *   are not all equally large, that's why it's required to enumerate
225
	 *   the size of each sector (instead of simply specifying the size and
226
	 *   the number of sectors).
227
	 * @param addressing Specify addressing mode (11-bit or 12-bit)
228
	 * @param config The motherboard this flash belongs to
229
	 * @param load Load initial content (hack for 'Matra INK')
230
	 */
231
	AmdFlash(const Rom& rom, const ValidatedChip& chip,
232
	         std::span<const bool> writeProtectSectors,
233
	         DeviceConfig& config);
234
	AmdFlash(const std::string& name, const ValidatedChip& chip,
235
	         std::span<const bool> writeProtectSectors,
236
	         DeviceConfig& config, std::string_view id = {});
237
	~AmdFlash();
238
239
	void reset();
240
	/**
241
	 * Setting the Vpp/WP# pin LOW enables a certain kind of write
242
	 * protection of some sectors. Currently it is implemented that it will
243
	 * enable protection of the first two sectors. (As for example in
244
	 * Numonix/Micron M29W640FB/M29W640GB.)
245
	 */
246
	void setVppWpPinLow(bool value) { vppWpPinLow = value; }
247
	bool getReadyPin() const;
248
249
	[[nodiscard]] power_of_two<size_t> size() const { return chip.geometry.size; }
250
	[[nodiscard]] uint8_t read(size_t address, EmuTime time);
251
	[[nodiscard]] uint8_t peek(size_t address, EmuTime time) const;
252
	void write(size_t address, uint8_t value, EmuTime time);
253
	[[nodiscard]] const uint8_t* getReadCacheLine(size_t address) const;
254
255
	template<typename Archive>
256
	void serialize(Archive& ar, unsigned version);
257
258
//private:
259
	struct AddressValue {
260
		size_t addr;
261
		uint8_t value;
262
263
		auto operator<=>(const AddressValue&) const = default;
264
265
		template<typename Archive>
266
		void serialize(Archive& ar, unsigned version);
267
	};
268
269
	enum class State : uint8_t { READ, AUTOSELECT, CFI, STATUS, ERROR, ERASE_SECTOR, ERASE_CHIP, PROGRAM };
270
271
	struct Sector {
272
		size_t index = 0;
273
		size_t address = 0;
274
		power_of_two<size_t> size = 1;
275
		bool writeProtect = false;
276
		ptrdiff_t writeAddress = -1;
277
		const uint8_t* readAddress = nullptr;
278
279
		std::weak_ordering operator<=>(const Sector& sector) const { return address <=> sector.address; }
280
		bool operator==(const Sector& sector) const { return address == sector.address; }
281
	};
282
283
private:
284
	AmdFlash(const std::string& name, const ValidatedChip& chip,
285
	         const Rom* rom, std::span<const bool> writeProtectSectors,
286
	         DeviceConfig& config, std::string_view id);
287
288
	[[nodiscard]] size_t getSectorIndex(size_t address) const;
289
	[[nodiscard]] Sector& getSector(size_t address) { return sectors[getSectorIndex(address)]; };
290
	[[nodiscard]] const Sector& getSector(size_t address) const { return sectors[getSectorIndex(address)]; };
291
	[[nodiscard]] bool isWritable(const Sector& sector) const;
292
293
	void softReset();
294
	void clearStatus();
295
	void setState(State newState);
296
297
	[[nodiscard]] uint16_t peekAutoSelect(size_t address, uint16_t undefined = 0xFFFF) const;
298
	[[nodiscard]] uint16_t peekCFI(size_t address) const;
299
300
	[[nodiscard]] bool checkCommandReset();
301
	[[nodiscard]] bool checkCommandLongReset();
302
	[[nodiscard]] bool checkCommandAutoSelect();
303
	[[nodiscard]] bool checkCommandCFIQuery();
304
	[[nodiscard]] bool checkCommandCFIExit();
305
	[[nodiscard]] bool checkCommandStatusRead();
306
	[[nodiscard]] bool checkCommandStatusClear();
307
	[[nodiscard]] bool checkCommandContinuityCheck();
308
	[[nodiscard]] bool checkCommandEraseSector(EmuTime time);
309
	[[nodiscard]] bool checkCommandEraseAdditionalSector(EmuTime time);
310
	[[nodiscard]] bool checkCommandEraseChip(EmuTime time);
311
	[[nodiscard]] bool checkCommandSuspend(EmuTime time);
312
	[[nodiscard]] bool checkCommandResume(EmuTime time);
313
	[[nodiscard]] bool checkCommandProgram(EmuTime time);
314
	[[nodiscard]] bool checkCommandDoubleByteProgram(EmuTime time);
315
	[[nodiscard]] bool checkCommandQuadrupleByteProgram(EmuTime time);
316
	[[nodiscard]] bool checkCommandProgramHelper(size_t numBytes, std::span<const uint8_t> cmdSeq, EmuTime time);
317
	[[nodiscard]] bool checkCommandBufferProgram(EmuTime time);
318
	[[nodiscard]] bool partialMatch(std::span<const uint8_t> dataSeq) const;
319
320
	void scheduleEraseOperation(EmuTime time);
321
	void scheduleProgramOperation(EmuTime time);
322
	void execOperation(EmuTime time);
323
	void execEraseOperation(EmuTime time);
324
	void execProgramOperation(EmuTime time);
325
	void execSuspend(EmuTime time);
326
327
	MSXMotherBoard& motherBoard;
328
	std::unique_ptr<SRAM> ram;
329
	const Chip& chip;
330
	std::vector<Sector> sectors;
331
	std::vector<AddressValue> cmd;
332
	State state = State::READ;
333
	bool vppWpPinLow = false; // true = protection on
334
335
	template<size_t POS, size_t WIDTH = 1, typename T2 = bool>
336
	using BF8 = BitField<uint8_t, POS, WIDTH, T2>;
337
338
	union OperationStatus {
339
		uint8_t raw = 0x00;
340
		BF8<7> dataPolling;
341
		BF8<6> busyToggle;
342
		BF8<5> error;
343
		BF8<4> dq4;
344
		BF8<3> eraseTimer;
345
		BF8<2> eraseToggle;
346
		BF8<1> abort;
347
		BF8<0> dq0;
348
	} status;
349
350
	union StatusRegister {
351
		uint8_t raw = 0x80;
352
		BF8<7> ready;
353
		BF8<6> eraseSuspend;
354
		BF8<5> eraseStatus;
355
		BF8<4> programStatus;
356
		BF8<3> bufferAbort;
357
		BF8<2> programSuspend;
358
		BF8<1> sectorLock;
359
		BF8<0> continuity;
360
	} statusRegister;
361
362
	struct EraseOperation {
363
		size_t index = 0;
364
		std::vector</*bool*/uint8_t> buffer;
365
366
		bool isPending(const Sector& sector) const;
367
		void markPending(const Sector& sector);
368
		void reset();
369
		template<typename Archive>
370
		void serialize(Archive& ar, unsigned version);
371
	} erase;
372
373
	struct ProgramOperation {
374
		size_t address = 0;
375
		size_t index = 0;
376
		std::vector<uint16_t> buffer;
377
378
		void reset();
379
		template<typename Archive>
380
		void serialize(Archive& ar, unsigned version);
381
	} program;
382
383
	struct SyncOperation : Schedulable {
384
		friend class AmdFlash;
385
		explicit SyncOperation(Scheduler& s) : Schedulable(s) {}
386
		void executeUntil(EmuTime time) override {
387
			auto& outer = OUTER(AmdFlash, syncOperation);
388
			outer.execOperation(time);
389
		}
390
	} syncOperation;
391
392
	struct SyncSuspend : Schedulable {
393
		friend class AmdFlash;
394
		explicit SyncSuspend(Scheduler& s) : Schedulable(s) {}
395
		void executeUntil(EmuTime time) override {
396
			auto& outer = OUTER(AmdFlash, syncSuspend);
397
			outer.execSuspend(time);
398
		}
399
	} syncSuspend;
400
};
401
SERIALIZE_CLASS_VERSION(AmdFlash, 4);
402
403
namespace AmdFlashChip
404
{
405
	using ValidatedChip = AmdFlash::ValidatedChip;
406
	using enum AmdFlash::ManufacturerID;
407
	using DeviceInterface = AmdFlash::DeviceInterface;
408
409
	// AMD AM29F040B
410
	static constexpr ValidatedChip AM29F040B = {{
411
		.autoSelect{.manufacturer = AMD, .device{0xA4}, .extraCode = 0x01},
412
		.geometry{
413
			DeviceInterface::x8, {
414
				{.count = 8, .size = 0x10000},
415
			},
416
		},
417
		.erase{.suspend = true, .duration = EmuDuration::msec(1000), .suspendDuration = EmuDuration::usec(20)},
418
		.program{.duration = EmuDuration::usec(7)},
419
	}};
420
421
	// AMD AM29F016D
422
	static constexpr ValidatedChip AM29F016D = {{
423
		.autoSelect{.manufacturer = AMD, .device{0xAD}},
424
		.geometry{
425
			DeviceInterface::x8, {
426
				{.count = 32, .size = 0x10000},
427
			},
428
		},
429
		.erase{.suspend = true, .duration = EmuDuration::msec(1000), .suspendDuration = EmuDuration::usec(20)},
430
		.program{.duration = EmuDuration::usec(7)},
431
		.cfi{
432
			.command = true,
433
			.systemInterface{
434
				.supply = {.minVcc = 0x45, .maxVcc = 0x55, .minVpp = 0x00, .maxVpp = 0x00},
435
				.typTimeout     = {.singleProgram =  8, .multiProgram = 1, .sectorErase = 1024, .chipErase = 1},
436
				.maxTimeoutMult = {.singleProgram = 32, .multiProgram = 1, .sectorErase =   16, .chipErase = 1},
437
			},
438
			.primaryAlgorithm{
439
				.version = {.major = 1, .minor = 1},
440
				.addressSensitiveUnlock = 0,
441
				.siliconRevision = 0,
442
				.eraseSuspend = 2,
443
				.sectorProtect = 4,
444
				.sectorTemporaryUnprotect = 1,
445
				.sectorProtectScheme = 4,
446
				.simultaneousOperation = 0,
447
				.burstMode = 0,
448
				.pageMode = 0,
449
				.supply = {.minAcc = 0, .maxAcc = 0},
450
				.bootBlockFlag = 0
451
			},
452
		},
453
		.misc{.readyPin = true},
454
	}};
455
456
	// Microchip SST39SF010  (128kB, 32 x 4kB sectors)
457
	static constexpr ValidatedChip SST39SF010 = {{
458
		.autoSelect{.manufacturer = SST, .device{0xB5}},
459
		.geometry{
460
			DeviceInterface::x8, {
461
				{.count = 32, .size = 0x1000},
462
			},
463
		},
464
		.erase{.duration = EmuDuration::msec(25)},
465
		.program{.duration = EmuDuration::usec(20)},
466
	}};
467
468
	// Numonyx M29W800DB
469
	static constexpr ValidatedChip M29W800DB = {{
470
		.autoSelect{.manufacturer = STM, .device{0x5B}},
471
		.geometry{
472
			DeviceInterface::x8x16, {
473
				{.count = 1, .size = 0x4000},
474
				{.count = 2, .size = 0x2000},
475
				{.count = 1, .size = 0x8000},
476
				{.count = 15, .size = 0x10000},
477
			},
478
		},
479
		.erase{.suspend = true, .duration = EmuDuration::msec(800), .suspendDuration = EmuDuration::usec(25)},
480
		.program{.duration = EmuDuration::usec(10)},
481
		.cfi{
482
			.command = true,
483
			.systemInterface{
484
				.supply = {.minVcc = 0x27, .maxVcc = 0x36, .minVpp = 0x00, .maxVpp = 0x00},
485
				.typTimeout     = {.singleProgram = 16, .multiProgram = 1, .sectorErase = 1024, .chipErase = 1},
486
				.maxTimeoutMult = {.singleProgram = 16, .multiProgram = 1, .sectorErase =    8, .chipErase = 1},
487
			},
488
			.primaryAlgorithm{
489
				.version = {.major = 1, .minor = 0},
490
				.addressSensitiveUnlock = 0,
491
				.siliconRevision = 0,
492
				.eraseSuspend = 2,
493
				.sectorProtect = 1,
494
				.sectorTemporaryUnprotect = 1,
495
				.sectorProtectScheme = 4,
496
				.simultaneousOperation = 0,
497
				.burstMode = 0,
498
				.pageMode = 0,
499
			},
500
		},
501
		.misc{.readyPin = true},
502
	}};
503
504
	// Micron M29W640GB
505
	static constexpr ValidatedChip M29W640GB = {{
506
		.autoSelect{.manufacturer = STM, .device{0x10, 0x00}, .extraCode = 0x0008, .undefined = 0, .oddZero = true, .readMask = 0x7F},
507
		.geometry{
508
			DeviceInterface::x8x16, {
509
				{.count = 8, .size = 0x2000},
510
				{.count = 127, .size = 0x10000},
511
			}, 2
512
		},
513
		.erase{.suspend = true, .duration = EmuDuration::msec(500), .suspendDuration = EmuDuration::usec(4)},
514
		.program{
515
			.shortAbortReset = true, .bitRaiseError = true, .suspend = true, .fastPageSize = 8, .bufferPageSize = 32,
516
			.duration = EmuDuration::usec(10), .additionalDuration = EmuDuration::usec(5.5), .suspendDuration = EmuDuration::usec(4)
517
		},
518
		.cfi{
519
			.command = true, .withManufacturerDevice = true, .commandMask = 0x7FF, .readMask = 0xFF,
520
			.systemInterface{
521
				.supply = {.minVcc = 0x27, .maxVcc = 0x36, .minVpp = 0xB5, .maxVpp = 0xC5},
522
				.typTimeout     = {.singleProgram = 16, .multiProgram = 1, .sectorErase = 1024, .chipErase = 1},
523
				.maxTimeoutMult = {.singleProgram = 16, .multiProgram = 1, .sectorErase =    8, .chipErase = 1},
524
			},
525
			.primaryAlgorithm{
526
				.version = {.major = 1, .minor = 3},
527
				.addressSensitiveUnlock = 0,
528
				.siliconRevision = 0,
529
				.eraseSuspend = 2,
530
				.sectorProtect = 4,
531
				.sectorTemporaryUnprotect = 1,
532
				.sectorProtectScheme = 4,
533
				.simultaneousOperation = 0,
534
				.burstMode = 0,
535
				.pageMode = 1,
536
				.supply = {.minAcc = 0xB5, .maxAcc = 0xC5},
537
				.bootBlockFlag = 0x02,
538
				.programSuspend = 1,
539
			},
540
		},
541
		.misc{.readyPin = true},
542
	}};
543
544
	// Infineon / Cypress / Spansion S29GL064N90TFI04
545
	static constexpr ValidatedChip S29GL064N90TFI04 = {{
546
		.autoSelect{.manufacturer = AMD, .device{0x10, 0x00}, .extraCode = 0xFF0A, .readMask = 0x0F},
547
		.geometry{
548
			DeviceInterface::x8x16, {
549
				{.count = 8, .size = 0x2000},
550
				{.count = 127, .size = 0x10000},
551
			}, 1
552
		},
553
		.erase{.suspend = true, .duration = EmuDuration::msec(500), .suspendDuration = EmuDuration::usec(20)},
554
		.program{
555
			.suspend = true, .bufferPageSize = 32,
556
			.duration = EmuDuration::usec(60), .additionalDuration = EmuDuration::usec(5.8), .suspendDuration = EmuDuration::usec(20)
557
		},
558
		.cfi{
559
			.command = true,
560
			.systemInterface{
561
				.supply = {.minVcc = 0x27, .maxVcc = 0x36, .minVpp = 0x00, .maxVpp = 0x00},
562
				.typTimeout     = {.singleProgram = 128, .multiProgram = 128, .sectorErase = 1024, .chipErase = 1},
563
				.maxTimeoutMult = {.singleProgram =   8, .multiProgram =  32, .sectorErase =   16, .chipErase = 1},
564
			},
565
			.primaryAlgorithm{
566
				.version = {.major = 1, .minor = 3},
567
				.addressSensitiveUnlock = 0,
568
				.siliconRevision = 8,
569
				.eraseSuspend = 2,
570
				.sectorProtect = 1,
571
				.sectorTemporaryUnprotect = 0,
572
				.sectorProtectScheme = 8,
573
				.simultaneousOperation = 0,
574
				.burstMode = 0,
575
				.pageMode = 2,
576
				.supply = {.minAcc = 0xB5, .maxAcc = 0xC5},
577
				.bootBlockFlag = 0x02,
578
				.programSuspend = 1,
579
			},
580
		},
581
		.misc{.readyPin = true},
582
	}};
583
584
	// Infineon / Cypress / Spansion S29GL064S70TFI040
585
	static constexpr ValidatedChip S29GL064S70TFI040 = {{
586
		.autoSelect{.manufacturer = AMD, .device{0x10, 0x00}, .extraCode = 0xFF0A, .readMask = 0x0F},
587
		.geometry{
588
			DeviceInterface::x8x16, {
589
				{.count = 8, .size = 0x2000},
590
				{.count = 127, .size = 0x10000},
591
			}, 2
592
		},
593
		.erase{.suspend = true, .duration = EmuDuration::msec(300), .suspendDuration = EmuDuration::usec(30)},
594
		.program{
595
			.shortAbortReset = false, .bitRaiseError = false, .suspend = true, .enhancedSuspend = true, .bufferPageSize = 256,
596
			.duration = EmuDuration::usec(150), .additionalDuration = EmuDuration::usec(1.0), .suspendDuration = EmuDuration::usec(23.5)
597
		},
598
		.cfi{
599
			.command = true, .withAutoSelect = true, .exitCommand = true, .commandMask = 0xFF, .readMask = 0x7F,
600
			.systemInterface{
601
				.supply = {.minVcc = 0x27, .maxVcc = 0x36, .minVpp = 0x00, .maxVpp = 0x00},
602
				.typTimeout     = {.singleProgram = 256, .multiProgram = 256, .sectorErase = 512, .chipErase = 65536},
603
				.maxTimeoutMult = {.singleProgram =   8, .multiProgram =   8, .sectorErase =   2, .chipErase =     1}},
604
			.primaryAlgorithm{
605
				.version = {.major = 1, .minor = 3},
606
				.addressSensitiveUnlock = 0,
607
				.siliconRevision = 8,
608
				.eraseSuspend = 2,
609
				.sectorProtect = 1,
610
				.sectorTemporaryUnprotect = 0,
611
				.sectorProtectScheme = 8,
612
				.simultaneousOperation = 0,
613
				.burstMode = 0,
614
				.pageMode = 2,
615
				.supply = {.minAcc = 0xB5, .maxAcc = 0xC5},
616
				.bootBlockFlag = 0x02,
617
				.programSuspend = 1,
618
			},
619
		},
620
		.misc{.statusCommand = true, .continuityCommand = true, .readyPin = true},
621
	}};
622
}
623
624
} // namespace openmsx
625
626
#endif