summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Christian Brunschen <6741909+cbrunschen@users.noreply.github.com>2026-04-25 16:08:02 +0100
committer GitHub <noreply@github.com>2026-04-25 11:08:02 -0400
commit49971e37c7814313985f7fee54387dbf7a1cd9c5 (patch)
tree95f0d3ac160e9886e698f11e91eee5bbc03b7adc
parentd3c426df8f0baed9379f6425fdd6c7cdf17b3054 (diff)
Refactor 28-series EEPROM code (#15237)
* Refactor 28-series EEPROM code Refactor 28-series EEPROM code: - Separate out a generic `eeprom28_device` template - Create Xicor x28 instantiations with appropriate template parameters. Add functionality to the `eeprom28_device` template to implement Atmel-style - Identification Page - Hardware Chip Erase - Software Chip Erase Add a templated `eeprom28_nvram_device` subclass that adds an `device_nvram_interface` implementation. Add `at28.{h,cpp}` specializations implementing known Atmel AT28 devices. Both X28 and AT28 devices are made available both with and without nvram support. This also replaces the existing `at28c64b` implementation. This has two current users in the codebase, both of which are Apple II cards. I have very little experience with that, sadly. But with many thanks to @rb6502 I was able to test this in the `booti` device, which helped me identify and fix something I had missed in the ee28 implementation: while the EEPROM is buffering data, reads from the page being buffered will be sourced from the buffer, so will include whatever is currently being buffered. I've gathered more detailed results of testing and am happy to share those on request. I'm using C++20 concepts to elide some member functions from the templates; and macros to simplify declaring device classes and types both with and without NVRAM. All of this works, and adds what I think is quite complete support for a number of similar real-world devices in a way that I hope is reasonable documented and maintainable. I've tried to keep this in line with MAME's guidelines and coding standards and what I've observed as existing practices, but I'm sure there are things I've missed or got wrong. So any feedback or guidance would be greatly appreciated. * ee28.* -> eeprom28.* improve logging macros
-rw-r--r--scripts/src/machine.lua29
-rw-r--r--src/devices/bus/a2bus/a2sd.cpp6
-rw-r--r--src/devices/bus/a2bus/booti.cpp6
-rw-r--r--src/devices/machine/at28.cpp21
-rw-r--r--src/devices/machine/at28.h53
-rw-r--r--src/devices/machine/at28c64b.cpp272
-rw-r--r--src/devices/machine/at28c64b.h80
-rw-r--r--src/devices/machine/eeprom28.h535
-rw-r--r--src/devices/machine/eeprom28.ipp (renamed from src/devices/machine/x28.ipp)114
-rw-r--r--src/devices/machine/x28.cpp55
-rw-r--r--src/devices/machine/x28.h386
-rw-r--r--src/mame/ensoniq/vfxcart.h2
12 files changed, 738 insertions, 821 deletions
diff --git a/scripts/src/machine.lua b/scripts/src/machine.lua
index a63d1b12a82..21c4b9b1ec6 100644
--- a/scripts/src/machine.lua
+++ b/scripts/src/machine.lua
@@ -166,6 +166,20 @@ end
---------------------------------------------------
--
+--@src/devices/machine/at28.h,MACHINES["AT28"] = true
+---------------------------------------------------
+
+if MACHINES["AT28"] then
+ files {
+ MAME_DIR .. "src/devices/machine/at28.cpp",
+ MAME_DIR .. "src/devices/machine/at28.h",
+ MAME_DIR .. "src/devices/machine/ee28.ipp",
+ MAME_DIR .. "src/devices/machine/ee28.h",
+ }
+end
+
+---------------------------------------------------
+--
--@src/devices/machine/autoconfig.h,MACHINES["AUTOCONFIG"] = true
---------------------------------------------------
@@ -897,18 +911,6 @@ end
---------------------------------------------------
--
---@src/devices/machine/at28c64b.h,MACHINES["AT28C64B"] = true
----------------------------------------------------
-
-if MACHINES["AT28C64B"] then
- files {
- MAME_DIR .. "src/devices/machine/at28c64b.cpp",
- MAME_DIR .. "src/devices/machine/at28c64b.h",
- }
-end
-
----------------------------------------------------
---
--@src/devices/machine/at29x.h,MACHINES["AT29X"] = true
---------------------------------------------------
@@ -4204,8 +4206,9 @@ end
if MACHINES["X28"] then
files {
MAME_DIR .. "src/devices/machine/x28.cpp",
- MAME_DIR .. "src/devices/machine/x28.ipp",
MAME_DIR .. "src/devices/machine/x28.h",
+ MAME_DIR .. "src/devices/machine/ee28.ipp",
+ MAME_DIR .. "src/devices/machine/ee28.h",
}
end
diff --git a/src/devices/bus/a2bus/a2sd.cpp b/src/devices/bus/a2bus/a2sd.cpp
index 80cf7fe7ceb..4f8acc1edd2 100644
--- a/src/devices/bus/a2bus/a2sd.cpp
+++ b/src/devices/bus/a2bus/a2sd.cpp
@@ -22,7 +22,7 @@
#include "emu.h"
#include "a2sd.h"
-#include "machine/at28c64b.h"
+#include "machine/at28.h"
#include "machine/spi_sdcard.h"
#define LOG_SPI (1U << 1)
@@ -87,7 +87,7 @@ protected:
TIMER_CALLBACK_MEMBER(shift_tick);
private:
- required_device<at28c64b_device> m_flash;
+ required_device<at28c64b_nvram_device> m_flash;
required_device<spi_sdcard_device> m_sdcard;
u8 m_datain, m_in_latch, m_out_latch;
@@ -107,7 +107,7 @@ private:
//-------------------------------------------------
void a2bus_a2sd_device::device_add_mconfig(machine_config &config)
{
- AT28C64B(config, m_flash, 0);
+ AT28C64B_NVRAM(config, m_flash, 0);
SPI_SDCARD(config, m_sdcard, 0);
m_sdcard->set_prefer_sdhc();
diff --git a/src/devices/bus/a2bus/booti.cpp b/src/devices/bus/a2bus/booti.cpp
index 48a8f041500..9bb07db839e 100644
--- a/src/devices/bus/a2bus/booti.cpp
+++ b/src/devices/bus/a2bus/booti.cpp
@@ -20,7 +20,7 @@
#include "emu.h"
#include "booti.h"
-#include "machine/at28c64b.h"
+#include "machine/at28.h"
#include "machine/ch376.h"
@@ -65,7 +65,7 @@ protected:
virtual bool take_c800() const override { return true; }
private:
- required_device<at28c64b_device> m_flash;
+ required_device<at28c64b_nvram_device> m_flash;
required_device<ch376_device> m_ch376;
int m_rombank;
@@ -81,7 +81,7 @@ private:
void a2bus_booti_device::device_add_mconfig(machine_config &config)
{
- AT28C64B(config, "flash", 0);
+ AT28C64B_NVRAM(config, "flash", 0);
CH376(config, "ch376");
}
diff --git a/src/devices/machine/at28.cpp b/src/devices/machine/at28.cpp
new file mode 100644
index 00000000000..86fe819a2b5
--- /dev/null
+++ b/src/devices/machine/at28.cpp
@@ -0,0 +1,21 @@
+// license:BSD-3-Clause
+// copyright-holders:Christian Brunschen
+/***************************************************************************
+ Atmel AT28 EEPROMs
+***************************************************************************/
+
+#include "emu.h"
+#include "at28.h"
+
+#define DEFINE_AT28_DEVICE_TYPES(Type,Class,ShortName,LongName) \
+DEFINE_DEVICE_TYPE(Type, Class##_device, ShortName, LongName) \
+DEFINE_DEVICE_TYPE(Type##_NVRAM, Class##_nvram_device, ShortName "_nvram", LongName " (nvram)")
+
+// device type definitions
+
+DEFINE_AT28_DEVICE_TYPES(AT28C64B, at28c64b, "at28c64b", "AT28C64B 8Kx8 EEPROM")
+DEFINE_AT28_DEVICE_TYPES(AT28C256, at28c256, "at28c256", "AT28C256 32Kx8 EEPROM")
+DEFINE_AT28_DEVICE_TYPES(AT28C010, at28c010, "at28c010", "AT28C010 128Kx8 EEPROM")
+DEFINE_AT28_DEVICE_TYPES(AT28C040, at28c040, "at28c040", "AT28C040 512Kx8 EEPROM")
+
+#undef DEFINE_AT28_DEVICE_TYPES
diff --git a/src/devices/machine/at28.h b/src/devices/machine/at28.h
new file mode 100644
index 00000000000..ebb11c37f09
--- /dev/null
+++ b/src/devices/machine/at28.h
@@ -0,0 +1,53 @@
+// license:BSD-3-Clause
+// copyright-holders:Christian Brunschen
+/***************************************************************************
+
+ Atmel 28-series Parallel EEPROMs
+
+***************************************************************************/
+
+#ifndef MAME_MACHINE_AT28_H
+#define MAME_MACHINE_AT28_H
+
+#pragma once
+
+#include "eeprom28.h"
+
+// Concrete devices
+
+#define DECLARE_AT28_DEVICES_AND_TYPES(Type, Class, AddrBits, PageBytes, TBLC, TWC, ...) \
+/* Device Type - includes a forward declaration of the Class */ \
+DECLARE_DEVICE_TYPE(Type, Class##_device) \
+/* The Class, which can now refer to the Device Type when calling the superclass's constructor */ \
+class Class##_device \
+: public eeprom28_device<AddrBits, PageBytes, TBLC, TWC, false, true, true, true __VA_OPT__(,) __VA_ARGS__> \
+{ \
+public: \
+ Class##_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0) \
+ : eeprom28_device(mconfig, Type, tag, owner, clock) { } \
+}; \
+/* Device Type - includes a forward declaration of the Class */ \
+DECLARE_DEVICE_TYPE(Type##_NVRAM, Class##_nvram_device) \
+/* The Class, which can now refer to the Device Type when calling the superclass's constructor */ \
+class Class##_nvram_device : public eeprom28_nvram_device<AddrBits, PageBytes, TBLC, TWC, false, true, true, true __VA_OPT__(,) __VA_ARGS__> \
+{ \
+public: \
+ Class##_nvram_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0) \
+ : eeprom28_nvram_device(mconfig, Type##_NVRAM, tag, owner, clock) { } \
+};
+
+// AT28C64B: 64kbit == 8k bytes, 64 bytes per page
+DECLARE_AT28_DEVICES_AND_TYPES(AT28C64B, at28c64b, 13, 64, 100, 5000)
+
+// AT28C256: 256kbit == 32k bytes, 64 bytes per page
+DECLARE_AT28_DEVICES_AND_TYPES(AT28C256, at28c256, 15, 64, 100, 5000)
+
+// AT28C010: 1Mbit = 128k bytes, 128 bytes per page
+DECLARE_AT28_DEVICES_AND_TYPES(AT28C010, at28c010, 17, 128, 100, 10000)
+
+// AT28C040: 4Mbit = 512 kbytes, 256 bytes per page
+DECLARE_AT28_DEVICES_AND_TYPES(AT28C040, at28c040, 19, 256, 150, 10000)
+
+#undef DECLARE_AT28_DEVICES_AND_TYPES
+
+#endif // MAME_MACHINE_AT28_H
diff --git a/src/devices/machine/at28c64b.cpp b/src/devices/machine/at28c64b.cpp
deleted file mode 100644
index 8af8e14a218..00000000000
--- a/src/devices/machine/at28c64b.cpp
+++ /dev/null
@@ -1,272 +0,0 @@
-// license:BSD-3-Clause
-// copyright-holders:smf, R. Belmont
-/***************************************************************************
-
- ATMEL AT28C64B
-
- 64K ( 8K x 8 ) Parallel EEPROM with flash-like in-band signalling
-
-***************************************************************************/
-
-#include "emu.h"
-#include "at28c64b.h"
-
-static constexpr int AT28C64B_DATA_BYTES = 0x10000;
-static constexpr int AT28C64B_ID_BYTES = 0x40;
-static constexpr int AT28C64B_TOTAL_BYTES = AT28C64B_DATA_BYTES + AT28C64B_ID_BYTES;
-
-static constexpr int AT28C64B_ID_OFFSET = 0x1fc0;
-static constexpr int AT28C64B_SECTOR_SIZE = 0x40;
-
-
-//**************************************************************************
-// GLOBAL VARIABLES
-//**************************************************************************
-
-void at28c64b_device::at28c64b_map8(address_map &map)
-{
- map(0x00000, 0x1003f).ram();
-}
-
-
-
-//**************************************************************************
-// LIVE DEVICE
-//**************************************************************************
-
-// device type definition
-DEFINE_DEVICE_TYPE(AT28C64B, at28c64b_device, "at28c64b", "AT28C64B 8Kx8 EEPROM")
-
-//-------------------------------------------------
-// at28c64b_device - constructor
-//-------------------------------------------------
-
-at28c64b_device::at28c64b_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
- : device_t(mconfig, AT28C64B, tag, owner, clock),
- device_memory_interface(mconfig, *this),
- device_nvram_interface(mconfig, *this),
- m_space_config("at28c64b", ENDIANNESS_BIG, 8, 17, 0, address_map_constructor(FUNC(at28c64b_device::at28c64b_map8), this)),
- m_a9_12v(0),
- m_oe_12v(0),
- m_last_write(-1),
- m_state(0),
- m_bytes_in_sector(0),
- m_default_data(*this, DEVICE_SELF)
-{
-}
-
-
-//-------------------------------------------------
-// memory_space_config - return a description of
-// any address spaces owned by this device
-//-------------------------------------------------
-
-device_memory_interface::space_config_vector at28c64b_device::memory_space_config() const
-{
- return space_config_vector {
- std::make_pair(0, &m_space_config)
- };
-}
-
-
-//-------------------------------------------------
-// device_start - device-specific startup
-//-------------------------------------------------
-
-void at28c64b_device::device_start()
-{
- m_write_timer = timer_alloc( FUNC( at28c64b_device::write_complete ), this );
-
- save_item( NAME(m_a9_12v) );
- save_item( NAME(m_oe_12v) );
- save_item( NAME(m_last_write) );
-}
-
-
-//-------------------------------------------------
-// nvram_default - called to initialize NVRAM to
-// its default state
-//-------------------------------------------------
-
-void at28c64b_device::nvram_default()
-{
- uint16_t const default_value = 0xff;
- for (offs_t offs = 0; offs < AT28C64B_TOTAL_BYTES; offs++)
- space(AS_PROGRAM).write_byte(offs, default_value);
-
- /* populate from a memory region if present */
- if (m_default_data.found())
- {
- for (offs_t offs = 0; offs < m_default_data.length(); offs++)
- space(AS_PROGRAM).write_byte(offs, m_default_data[offs]);
- }
-}
-
-
-//-------------------------------------------------
-// nvram_read - called to read NVRAM from the
-// .nv file
-//-------------------------------------------------
-
-bool at28c64b_device::nvram_read(util::read_stream &file)
-{
- std::vector<uint8_t> buffer(AT28C64B_TOTAL_BYTES);
-
- auto const [err, actual] = util::read(file, &buffer[0], AT28C64B_TOTAL_BYTES);
- if (err || (actual != AT28C64B_TOTAL_BYTES))
- return false;
-
- for (offs_t offs = 0; offs < AT28C64B_TOTAL_BYTES; offs++)
- space(AS_PROGRAM).write_byte(offs, buffer[offs]);
-
- return true;
-}
-
-//-------------------------------------------------
-// nvram_write - called to write NVRAM to the
-// .nv file
-//-------------------------------------------------
-
-bool at28c64b_device::nvram_write(util::write_stream &file)
-{
- std::vector<uint8_t> buffer(AT28C64B_TOTAL_BYTES);
-
- for (offs_t offs = 0; offs < AT28C64B_TOTAL_BYTES; offs++)
- buffer[offs] = space(AS_PROGRAM).read_byte(offs);
-
- auto const [err, actual] = util::write(file, &buffer[0], AT28C64B_TOTAL_BYTES);
- return !err;
-}
-
-
-
-//**************************************************************************
-// READ/WRITE HANDLERS
-//**************************************************************************
-
-void at28c64b_device::write(offs_t offset, uint8_t data)
-{
- logerror("%s: AT28C64B: write( %04x, %02x ) state %d last_write %d\n", machine().describe_context(), offset, data, m_state, m_last_write);
-
- if (m_state == STATE_SECTOR_WRITE)
- {
- logerror("SECTOR WRITE: %02x @ %x\n", data, offset);
- this->space(AS_PROGRAM).write_byte(offset, data);
- m_last_write = data;
- m_write_timer->adjust(attotime::from_usec(10));
- m_bytes_in_sector--;
- if (m_bytes_in_sector == 0)
- {
- m_state = STATE_WRITE_PROTECT;
- }
- return;
- }
-
- if( m_last_write >= 0 )
- {
- //logerror( "%s: AT28C64B: write( %04x, %02x ) busy\n", machine().describe_context(), offset, data );
- }
- else if( m_oe_12v )
- {
- //logerror( "%s: AT28C64B: write( %04x, %02x ) erase\n", machine().describe_context(), offset, data );
- if( m_last_write < 0 )
- {
- for( offs_t offs = 0; offs < AT28C64B_TOTAL_BYTES; offs++ )
- {
- this->space(AS_PROGRAM).write_byte( offs, 0xff );
- }
-
- m_last_write = 0xff;
- m_write_timer->adjust( attotime::from_usec( 10 ) );
- }
- }
- else
- {
- if ((offset == 0x1555) && (data == 0xaa))
- {
- m_state = STATE_ID_1;
- return;
- }
-
- if ((m_state == STATE_ID_1) && (offset == 0xaaa) && (data == 0x55))
- {
- m_state = STATE_ID_2;
- return;
- }
-
- if ((m_state == STATE_ID_2) && (offset == 0x1555) && (data == 0xa0))
- {
- m_state = STATE_SECTOR_WRITE;
- m_bytes_in_sector = AT28C64B_SECTOR_SIZE;
- return;
- }
-
- if (m_state == STATE_WRITE_PROTECT)
- {
- logerror("%s: write %02x to %x while write protected\n", machine().describe_context(), data, offset);
- return;
- }
-
- if ((m_a9_12v) && (offset >= AT28C64B_ID_OFFSET) && (offset < (AT28C64B_ID_OFFSET + AT28C64B_ID_BYTES)))
- {
- offset += AT28C64B_ID_BYTES;
- }
-
- if( m_last_write < 0 && this->space(AS_PROGRAM).read_byte( offset ) != data )
- {
- this->space(AS_PROGRAM).write_byte( offset, data );
- m_last_write = data;
- m_write_timer->adjust( attotime::from_usec( 10 ) );
- }
- }
-}
-
-
-uint8_t at28c64b_device::read(offs_t offset)
-{
- if( m_last_write >= 0 )
- {
- uint8_t data = m_last_write ^ 0x80;
- //logerror( "%s: AT28C64B: read( %04x ) write status %02x\n", machine().describe_context(), offset, data );
- return data;
- }
- else
- {
- if( m_a9_12v && offset >= AT28C64B_ID_OFFSET )
- {
- offset += AT28C64B_ID_BYTES;
- }
-
- uint8_t data = this->space(AS_PROGRAM).read_byte( offset );
- //logerror( "%s: AT28C64B: read( %04x ) data %02x\n", machine().describe_context(), offset, data );
- return data;
- }
-}
-
-
-void at28c64b_device::set_a9_12v(int state)
-{
- state &= 1;
- if( m_a9_12v != state )
- {
- //logerror( "%s: AT28C64B: set_a9_12v( %d )\n", machine().describe_context(), state );
- m_a9_12v = state;
- }
-}
-
-
-void at28c64b_device::set_oe_12v(int state)
-{
- state &= 1;
- if( m_oe_12v != state )
- {
- //logerror( "%s: AT28C64B: set_oe_12v( %d )\n", machine().describe_context(), state );
- m_oe_12v = state;
- }
-}
-
-
-TIMER_CALLBACK_MEMBER( at28c64b_device::write_complete )
-{
- m_last_write = -1;
-}
diff --git a/src/devices/machine/at28c64b.h b/src/devices/machine/at28c64b.h
deleted file mode 100644
index 745db9b2ed5..00000000000
--- a/src/devices/machine/at28c64b.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// license:BSD-3-Clause
-// copyright-holders:smf
-/***************************************************************************
-
- ATMEL AT28C64B
-
- 64K ( 8K x 8 ) Parallel EEPROM
-
-***************************************************************************/
-
-#ifndef MAME_MACHINE_AT28C64B_H
-#define MAME_MACHINE_AT28C64B_H
-
-#pragma once
-
-
-//**************************************************************************
-// TYPE DEFINITIONS
-//**************************************************************************
-
-// ======================> at28c64b_device
-
-class at28c64b_device :
- public device_t,
- public device_memory_interface,
- public device_nvram_interface
-{
-public:
- // construction/destruction
- at28c64b_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
-
- void write(offs_t offset, u8 data);
- u8 read(offs_t offset);
-
-protected:
- // device-level overrides
- virtual void device_start() override ATTR_COLD;
-
- // device_memory_interface overrides
- virtual space_config_vector memory_space_config() const override;
-
- // device_nvram_interface overrides
- virtual void nvram_default() override;
- virtual bool nvram_read(util::read_stream &file) override;
- virtual bool nvram_write(util::write_stream &file) override;
-
-private:
- // internal state
- enum
- {
- STATE_IDLE,
- STATE_ID_1,
- STATE_ID_2,
- STATE_SECTOR_WRITE,
- STATE_WRITE_PROTECT
- };
-
- address_space_config m_space_config;
- emu_timer *m_write_timer;
- int m_a9_12v;
- int m_oe_12v;
- int m_last_write;
- int m_state;
- int m_bytes_in_sector;
-
- optional_region_ptr<uint8_t> m_default_data;
-
- // I/O operations
- void set_a9_12v(int state);
- void set_oe_12v(int state);
- TIMER_CALLBACK_MEMBER( write_complete );
-
- void at28c64b_map8(address_map &map) ATTR_COLD;
-};
-
-
-// device type definition
-DECLARE_DEVICE_TYPE(AT28C64B, at28c64b_device)
-
-#endif // MAME_MACHINE_AT28C64B_H
diff --git a/src/devices/machine/eeprom28.h b/src/devices/machine/eeprom28.h
new file mode 100644
index 00000000000..137e27c70a1
--- /dev/null
+++ b/src/devices/machine/eeprom28.h
@@ -0,0 +1,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
diff --git a/src/devices/machine/x28.ipp b/src/devices/machine/eeprom28.ipp
index 8bf59b61013..ae7736e489e 100644
--- a/src/devices/machine/x28.ipp
+++ b/src/devices/machine/eeprom28.ipp
@@ -2,34 +2,30 @@
// copyright-holders:Christian Brunschen
/***************************************************************************
- 28-series Parallel EEPROM sich as Xicor X28 etc.
+ 28-series Parallel EEPROM sich as Xicor X28, Atmel AT28 etc.
Caters for different speeds such as X28C256, X28HC256, etc.
Caters for different storage sizes such as X28C64, X28C256, etc.
***************************************************************************/
-#ifndef MAME_MACHINE_X28_IPP
-#define MAME_MACHINE_X28_IPP
+#ifndef MAME_MACHINE_EE28_IPP
+#define MAME_MACHINE_EE28_IPP
-// Included here to provide context for editors; include guards in x28.h
+// Included here to provide context for editors; include guards in eeprom28.h
// prevent this from actually doing anything.
-#include "x28.h"
+#include "eeprom28.h"
#pragma once
-
-template<
- int AddressBits,
- uint32_t PageSizeBytes,
- uint32_t TBLCUsec,
- uint32_t TWCUsec,
- bool ProgramOnRead
->
-void x28_device<AddressBits, PageSizeBytes, TBLCUsec, TWCUsec, ProgramOnRead>::write(uint32_t offset, uint8_t data)
+template<EEPROM28_PARAMS>
+void eeprom28_device<EEPROM28_ARGS>::write(uint32_t offset, uint8_t data)
{
+ m_toggle_bit = TOGGLE_BIT;
+
+ EE28LOGMASKED(EE28_LOG_DETAIL, "%s: eeprom28.write(%04x, %02x) in state %d\n", machine().describe_context(), offset, data, m_state);
if (m_state == STATE_PROGRAMMING) {
// An attempt to write during a programming cycle does nothing.
- // LOG("IN PROGRAMMING CYCLE: writing %02x @ %04x\n", data, offset);
+ EE28LOG("IN PROGRAMMING CYCLE: writing %02x @ %04x\n", data, offset);
return;
}
@@ -38,7 +34,6 @@ void x28_device<AddressBits, PageSizeBytes, TBLCUsec, TWCUsec, ProgramOnRead>::w
m_start_programming_timer->adjust(attotime::from_usec(m_t_blc_usec));
}
- // LOG("write(%04x, %02x) in state %d\n", offset, data, m_state);
if (offset >= TOTAL_SIZE_BYTES) {
// Attempting to write outside the range of this device does nothing.
return;
@@ -57,7 +52,7 @@ void x28_device<AddressBits, PageSizeBytes, TBLCUsec, TWCUsec, ProgramOnRead>::w
// We're firmly within a protection command sequence.
// Inhibit the actual writing of data during the subsequence programming cycle.
m_program_buffer_to_eeprom = false;
- // LOG("m_program_buffer_to_eeprom -> %d\r\n", m_program_buffer_to_eeprom);
+ EE28LOGMASKED(EE28_LOG_DETAIL, "m_program_buffer_to_eeprom -> %d\r\n", m_program_buffer_to_eeprom);
change_to_command_state(COMMAND_STATE_2);
} else {
// Not, after all in a command sequence.
@@ -94,25 +89,36 @@ void x28_device<AddressBits, PageSizeBytes, TBLCUsec, TWCUsec, ProgramOnRead>::w
command_state_machine_error();
}
} else if (m_command_state == COMMAND_STATE_PROTECION_DISABLE_5) {
- if ((offset == (0x5555 & ADDRESS_MASK)) && (data == 0x20)) {
- // 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;
- // LOG("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);
+ if (offset == (0x5555 & ADDRESS_MASK)) {
+ if constexpr (HAS_SOFTWARE_CHIP_ERASE) {
+ if (data == 0x10) {
+ // We have received a complete "Software Chip Erase command. So we:
+ // - Leave write protection as it is!
+ // - Note that we're no longer in a command sequence.
+ change_to_command_state(COMMAND_STATE_NONE);
+ // - The preceding writes were just part of that command sequence.
+ m_program_buffer_to_eeprom = false;
+
+ // - Start the chip erase cycle
+ start_erase_cycle();
+ // - and now we're done with this write.
+ return;
+ } else if (data == 0x20) {
+ disable_software_data_protection();
+ // - and now we're done with this write.
+ return;
+ } else {
+ command_state_machine_error();
+ }
+ } else {
+ if (data == 0x20) {
+ disable_software_data_protection();
+ // - and now we're done with this write.
+ return;
+ } else {
+ command_state_machine_error();
+ }
}
-
- // - Sart the programming cycle
- start_programming_cycle();
- // - and now we're done with this write.
- return;
} else {
command_state_machine_error();
}
@@ -128,7 +134,7 @@ void x28_device<AddressBits, PageSizeBytes, TBLCUsec, TWCUsec, ProgramOnRead>::w
// so that the command sequence (which will end up in the buffer)
// does not get written to storage.
m_program_buffer_to_eeprom = (!m_software_data_protection_enabled) || (m_state == STATE_PROTECTED_WRITE);
- // LOG("m_program_buffer_to_eeprom -> %d\r\n", m_program_buffer_to_eeprom);
+ EE28LOGMASKED(EE28_LOG_DETAIL, "m_program_buffer_to_eeprom -> %d\r\n", m_program_buffer_to_eeprom);
// We start to buffer a set of writes.
// We do this even if we're write protected - m_program_buffer_to_eeprom protects us.
@@ -138,8 +144,8 @@ void x28_device<AddressBits, PageSizeBytes, TBLCUsec, TWCUsec, ProgramOnRead>::w
// so the buffer can be written into on a byte by byte basis, before being written back
// to storage during the programming cycle.
m_buffering_page = offset & PAGE_MASK;
- const uint8_t *p = &(m_storage[m_buffering_page]);
- std::copy(p, p + PageSizeBytes, std::begin(m_page_buffer));
+ std::memcpy(&m_page_buffer[0], &m_storage[storage_page(m_buffering_page)], PAGE_SIZE_BYTES);
+ EE28LOGMASKED(EE28_LOG_DETAIL, "%s: buffering page %04x\n", machine().describe_context(), m_buffering_page);
}
// Deliberately falling through after detecting the first write and changing state
@@ -159,25 +165,20 @@ void x28_device<AddressBits, PageSizeBytes, TBLCUsec, TWCUsec, ProgramOnRead>::w
// are ignored, only those within the same page are accepted.
if ((offset & PAGE_MASK) == m_buffering_page) {
m_page_buffer[offset & PAGE_OFFSET_MASK] = data;
+ EE28LOGMASKED(EE28_LOG_DETAIL, "%s: buffer[%02x] = %02x\n", machine().describe_context(), offset & PAGE_OFFSET_MASK, data);
}
// Note where the last write occurred.
m_last_written_offset = offset;
}
- // if (m_software_data_protection_enabled) {
- // LOG("X28C: write %02x to %x while write protected\n", data, offset);
- // }
+ if (m_software_data_protection_enabled) {
+ EE28LOGMASKED(EE28_LOG_DETAIL, "X28C: write %02x to %x while write protected\n", data, offset);
+ }
}
-template<
- int AddressBits,
- uint32_t PageSizeBytes,
- uint32_t TBLCUsec,
- uint32_t TWCUsec,
- bool ProgramOnRead
->
-uint8_t x28_device<AddressBits, PageSizeBytes, TBLCUsec, TWCUsec, ProgramOnRead>::read(uint32_t offset)
+template <EEPROM28_PARAMS>
+uint8_t eeprom28_device<EEPROM28_ARGS>::read(uint32_t offset)
{
if (m_command_state != COMMAND_STATE_NONE) {
// Per the X28C256 datasheet regarding the command sequence,
@@ -201,7 +202,10 @@ uint8_t x28_device<AddressBits, PageSizeBytes, TBLCUsec, TWCUsec, ProgramOnRead>
}
}
- uint8_t data = m_storage[offset];
+ // If we're currently buffering a page, then reads from the page in question
+ // should be sourced from the page buffer.
+ bool read_from_buffer = m_buffering_page >= 0 && (offset & PAGE_MASK) == m_buffering_page;
+ uint8_t data = read_from_buffer ? m_page_buffer[offset & PAGE_OFFSET_MASK] : m_storage[storage_offset(offset)];
if (m_program_buffer_to_eeprom || (m_state == STATE_PROGRAMMING)) {
// "/DATA Polling"
@@ -217,11 +221,17 @@ uint8_t x28_device<AddressBits, PageSizeBytes, TBLCUsec, TWCUsec, ProgramOnRead>
// While programming or preparing to progam, bit 6 of what is returned
// alternates between 0 and 1.
- data = (data & ~TOGGLE_BIT) | m_toggle_bit;
+ data = data ^ m_toggle_bit;
m_toggle_bit ^= TOGGLE_BIT;
}
+ EE28LOGMASKED(EE28_LOG_DETAIL, "%s: eeprom28.read(%04x), have %02x in %s, returning %02x\n",
+ machine().describe_context(), offset,
+ read_from_buffer ? m_page_buffer[offset & PAGE_OFFSET_MASK] : m_storage[storage_offset(offset)],
+ read_from_buffer ? "buffer" : "storage",
+ data);
+
return data;
}
-#endif // MAME_MACHINE_X28_IPP
+#endif // MAME_MACHINE_EE28_IPP
diff --git a/src/devices/machine/x28.cpp b/src/devices/machine/x28.cpp
index d0912541bd8..13445a7441f 100644
--- a/src/devices/machine/x28.cpp
+++ b/src/devices/machine/x28.cpp
@@ -7,46 +7,17 @@
#include "emu.h"
#include "x28.h"
-// device type definitions
-DEFINE_DEVICE_TYPE(X28C64, x28c64_device, "x28c64", "X28C64 8Kx8 EEPROM")
-DEFINE_DEVICE_TYPE(X28C256, x28c256_device, "x28c256", "X28C256 32Kx8 EEPROM")
-DEFINE_DEVICE_TYPE(X28HC256, x28hc256_device, "x28hc256", "X28HC256 32Kx8 EEPROM")
-DEFINE_DEVICE_TYPE(X28C512, x28c512_device, "x28c512", "X28C512 64Kx8 EEPROM")
-DEFINE_DEVICE_TYPE(X28C010, x28c010_device, "x28c010", "X28C010 128Kx8 EEPROM")
-DEFINE_DEVICE_TYPE(XM28C020, xm28c020_device, "xm28c020", "XM28C020 256Kx8 EEPROM")
-DEFINE_DEVICE_TYPE(XM28C040, xm28c040_device, "xm28c040", "XM28C040 512Kx8 EEPROM")
-
-x28c64_device::x28c64_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
-: x28_device(mconfig, X28C64, tag, owner, clock)
-{
-}
-
-x28c256_device::x28c256_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
-: x28_device(mconfig, X28C256, tag, owner, clock)
-{
-}
-
-x28hc256_device::x28hc256_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
-: x28_device(mconfig, X28HC256, tag, owner, clock)
-{
-}
+#define DEFINE_X28_DEVICE_TYPES(Type,Class,ShortName,LongName) \
+DEFINE_DEVICE_TYPE(Type, Class##_device, ShortName, LongName) \
+DEFINE_DEVICE_TYPE(Type##_NVRAM, Class##_nvram_device, ShortName "_nvram", LongName " (nvram)")
-x28c512_device::x28c512_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
-: x28_device(mconfig, X28C512, tag, owner, clock)
-{
-}
-
-x28c010_device::x28c010_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
-: x28_device(mconfig, X28C010, tag, owner, clock)
-{
-}
-
-xm28c020_device::xm28c020_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
-: x28_device(mconfig, XM28C020, tag, owner, clock)
-{
-}
-
-xm28c040_device::xm28c040_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
-: x28_device(mconfig, XM28C040, tag, owner, clock)
-{
-}
+// device type definitions
+DEFINE_X28_DEVICE_TYPES(X28C64, x28c64, "x28c64", "X28C64 8Kx8 EEPROM")
+DEFINE_X28_DEVICE_TYPES(X28C256, x28c256, "x28c256", "X28C256 32Kx8 EEPROM")
+DEFINE_X28_DEVICE_TYPES(X28HC256, x28hc256, "x28hc256", "X28HC256 32Kx8 EEPROM")
+DEFINE_X28_DEVICE_TYPES(X28C512, x28c512, "x28c512", "X28C512 64Kx8 EEPROM")
+DEFINE_X28_DEVICE_TYPES(X28C010, x28c010, "x28c010", "X28C010 128Kx8 EEPROM")
+DEFINE_X28_DEVICE_TYPES(XM28C020, xm28c020, "xm28c020", "XM28C020 256Kx8 EEPROM")
+DEFINE_X28_DEVICE_TYPES(XM28C040, xm28c040, "xm28c040", "XM28C040 512Kx8 EEPROM")
+
+#undef DEFINE_X28_DEVICE_TYPES
diff --git a/src/devices/machine/x28.h b/src/devices/machine/x28.h
index 5d182839d62..4b97251bc6a 100644
--- a/src/devices/machine/x28.h
+++ b/src/devices/machine/x28.h
@@ -2,11 +2,7 @@
// copyright-holders:Christian Brunschen
/***************************************************************************
- Xicor 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.
+ Xicor 28-series Parallel EEPROMs
***************************************************************************/
@@ -15,375 +11,55 @@
#pragma once
-#include <algorithm>
-#include <array>
-#include <cstdint>
-
-//**************************************************************************
-// TYPE DEFINITIONS
-//**************************************************************************
-
-// x28_device: Xicor 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.
- */
-template<
- int AddressBits,
- uint32_t PageSizeBytes,
- uint32_t TBLCUsec,
- uint32_t TWCUsec,
- bool ProgramOnRead = false
->
-class x28_device : public device_t
-{
- using self = x28_device<AddressBits, PageSizeBytes, TBLCUsec, TWCUsec, ProgramOnRead>;
-
-public:
- static constexpr uint32_t ADDRESS_BITS = AddressBits;
- static constexpr uint32_t TOTAL_SIZE_BYTES = 1 << AddressBits;
- static constexpr uint32_t ADDRESS_MASK = TOTAL_SIZE_BYTES - 1;
- static constexpr uint32_t PAGE_SIZE_BYTES = PageSizeBytes;
- 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 PAGE_OFFSET_MASK = PageSizeBytes - 1;
- static constexpr uint32_t PAGE_MASK = ~(PAGE_OFFSET_MASK);
-
- static constexpr uint8_t INVERSE_DATA_BIT = 1 << 7;
- static constexpr uint8_t TOGGLE_BIT = 1 << 6;
-
- // construction/destruction
- x28_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 ~x28_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;
- }
-
-#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));
- }
-
- 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 = 0;
- }
-
- // Change State to a new internal state
- void change_to_state(int ns)
- {
- // LOG("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)
- {
- // LOG("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.
- };
-
- 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;
- int m_buffering_page = 0;
- 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;
-
- // Timer callbacks
- void start_programming_cycle(s32 param = 0)
- {
- change_to_state(STATE_PROGRAMMING);
-
- if (m_program_buffer_to_eeprom) {
- std::copy(std::begin(m_page_buffer), std::end(m_page_buffer), &(m_storage[m_buffering_page]));
- }
-
- 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)
- {
- change_to_state(STATE_IDLE);
-
- m_program_buffer_to_eeprom = false;
- // LOG("m_program_buffer_to_eeprom -> %d\r\n", m_program_buffer_to_eeprom);
- }
-};
+#include "eeprom28.h"
// Concrete devices
-// Real devices:
+#define DECLARE_X28_DEVICES_AND_TYPES(Type, Class, AddrBits, PageBytes, TBLC, TWC, ...) \
+/* Device Type - includes a forward declaration of the Class */ \
+DECLARE_DEVICE_TYPE(Type, Class##_device) \
+/* The Class, which can now refer to the Device Type when calling the superclass's constructor */ \
+class Class##_device \
+: public eeprom28_device<AddrBits, PageBytes, TBLC, TWC __VA_OPT__(,) __VA_ARGS__> \
+{ \
+public: \
+ Class##_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0) \
+ : eeprom28_device(mconfig, Type, tag, owner, clock) { } \
+}; \
+/* Device Type - includes a forward declaration of the Class */ \
+DECLARE_DEVICE_TYPE(Type##_NVRAM, Class##_nvram_device) \
+/* The Class, which can now refer to the Device Type when calling the superclass's constructor */ \
+class Class##_nvram_device \
+: public eeprom28_nvram_device<AddrBits, PageBytes, TBLC, TWC __VA_OPT__(,) __VA_ARGS__> \
+{ \
+public: \
+ Class##_nvram_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0) \
+ : eeprom28_nvram_device(mconfig, Type##_NVRAM, tag, owner, clock) { } \
+};
// X28C64: 64kbit == 8k bytes, 64 bytes per page
-class x28c64_device : public x28_device<13, 64, 100, 5000>
-{
-public:
- x28c64_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
-};
+DECLARE_X28_DEVICES_AND_TYPES(X28C64, x28c64, 13, 64, 100, 5000)
// X28C256: 256kbit == 32k bytes, 64 bytes per page
-class x28c256_device : public x28_device<15, 64, 100, 5000>
-{
-public:
- x28c256_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
-};
+DECLARE_X28_DEVICES_AND_TYPES(X28C256, x28c256, 15, 64, 100, 5000)
// X28HC256: 256kbit == 32k bytes, 64 bytes per page, T_WC = 3 ms.
-class x28hc256_device : public x28_device<15, 64, 100, 3000>
-{
-public:
- x28hc256_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
-};
+DECLARE_X28_DEVICES_AND_TYPES(X28HC256, x28hc256, 15, 64, 100, 3000)
// X28C512: 512kbit == 64k bytes, 128 bytes per page
-class x28c512_device : public x28_device<16, 128, 100, 5000>
-{
-public:
- x28c512_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
-};
+DECLARE_X28_DEVICES_AND_TYPES(X28C512, x28c512, 16, 128, 100, 5000)
// X28C010: 1Mbit = 128k bytes, 256 bytes per page
-class x28c010_device : public x28_device<17, 256, 100, 5000>
-{
-public:
- x28c010_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
-};
+DECLARE_X28_DEVICES_AND_TYPES(X28C010, x28c010, 17, 256, 100, 5000)
// XM28C020: 2Mbit = 256 kbytes, 128 bytes per page;
// comprised of 4 X28C513LCC:s on a single substrate
-class xm28c020_device : public x28_device<18, 128, 100, 5000>
-{
-public:
- xm28c020_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
-};
+DECLARE_X28_DEVICES_AND_TYPES(XM28C020, xm28c020, 18, 128, 100, 5000)
// XM28C040: 4Mbit = 512 kbytes, 256 bytes per page;
// comprised of 4 X28C010:s on a single substrate
-class xm28c040_device : public x28_device<19, 256, 100, 5000>
-{
-public:
- xm28c040_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
-};
-
-// device type declarations
-DECLARE_DEVICE_TYPE(X28C64, x28c64_device)
-DECLARE_DEVICE_TYPE(X28C256, x28c256_device)
-DECLARE_DEVICE_TYPE(X28HC256, x28hc256_device)
-DECLARE_DEVICE_TYPE(X28C512, x28c512_device)
-DECLARE_DEVICE_TYPE(X28C010, x28c010_device)
-DECLARE_DEVICE_TYPE(XM28C020, xm28c020_device)
-DECLARE_DEVICE_TYPE(XM28C040, xm28c040_device)
+DECLARE_X28_DEVICES_AND_TYPES(XM28C040, xm28c040, 19, 256, 100, 5000)
-#include "x28.ipp"
+#undef DECLARE_X28_DEVICES_AND_TYPES
#endif // MAME_MACHINE_X28_H
diff --git a/src/mame/ensoniq/vfxcart.h b/src/mame/ensoniq/vfxcart.h
index c926a123f87..0e0ab310fb7 100644
--- a/src/mame/ensoniq/vfxcart.h
+++ b/src/mame/ensoniq/vfxcart.h
@@ -53,7 +53,7 @@ private:
required_device<x28c256_device> m_eeprom;
- static constexpr uint32_t SIZE = x28c256_device::TOTAL_SIZE_BYTES;
+ static constexpr uint32_t SIZE = x28c256_device::DATA_SIZE_BYTES;
static constexpr uint32_t MASK = SIZE - 1;
bool m_is_loaded = false;