drivers/misc/eeprom/at24.c | 183 +++++++++++++++++++++++++++++++++++- 1 file changed, 182 insertions(+), 1 deletion(-)
AMD PIIX4 SMBus adapters, present on AMD SP5/EPYC-based platforms
(including Cisco 8000 series routers), support SMBUS_BYTE_DATA and
SMBUS_WORD_DATA but lack I2C_FUNC_I2C and I2C_FUNC_SMBUS_I2C_BLOCK.
This causes two distinct failures:
1. AT24_FLAG_ADDR16 EEPROMs (e.g. 24c64 syseeprom):
regmap_get_i2c_bus() returns -ENOTSUPP (-524) for reg_bits=16
because neither I2C_FUNC_I2C nor I2C_FUNC_SMBUS_I2C_BLOCK is set.
The driver probe fails entirely with:
at24 3-0055: probe with driver at24 failed with error -524
2. 8-bit addressed EEPROMs (e.g. 24c02 fan EEPROMs) behind a pca9548
mux on a PIIX4 child adapter: regmap selects regmap_i2c_smbus_i2c_block
which fails at runtime even though SMBUS_BYTE_DATA is available.
Add two SMBus-primitive fallback paths, selected after chip flags are
fully resolved (i.e. after flags = cdata->flags and DT address-width
overrides):
smbus_addr16_fallback (AT24_FLAG_ADDR16 set):
READ: i2c_smbus_write_byte_data(client, addr_hi, addr_lo) sets
the 16-bit address pointer; repeated i2c_smbus_read_byte()
fetches bytes sequentially (EEPROM auto-increments pointer).
WRITE: i2c_smbus_write_word_data() encodes address + data byte
in one SMBus WORD transaction.
smbus_addr8_fallback (AT24_FLAG_ADDR16 not set):
READ: i2c_smbus_read_byte_data(client, (u8)offset) per byte.
WRITE: i2c_smbus_write_byte_data(client, (u8)offset, data) per byte.
Both paths bypass regmap for I/O. A regmap init retry with reg_bits=8
is added so that probe succeeds on adapters that reject the initial
regmap configuration.
The fallback is only activated when i2c_check_functionality() confirms
both SMBUS_BYTE_DATA and SMBUS_WORD_DATA are present, ensuring no
regression on adapters that support full I2C transfers.
Signed-off-by: Nishanth Sampath Kumar <nissampa@cisco.com>
---
drivers/misc/eeprom/at24.c | 183 +++++++++++++++++++++++++++++++++++-
1 file changed, 182 insertions(+), 1 deletion(-)
--- a/drivers/misc/eeprom/at24.c
+++ b/drivers/misc/eeprom/at24.c
@@ -88,6 +88,11 @@
struct regulator *vcc_reg;
void (*read_post)(unsigned int off, char *buf, size_t count);
+ /* SMBus-only fallback for EEPROMs on PIIX4 adapters */
+ bool smbus_addr16_fallback;
+ bool smbus_addr8_fallback;
+ struct i2c_client *smbus_client;
+
/*
* Some chips tie up multiple I2C addresses; dummy devices reserve
* them for us.
@@ -343,6 +348,139 @@
return count;
}
+/* forward declaration needed by at24_smbus_write below */
+static size_t at24_adjust_write_count(struct at24_data *at24,
+ unsigned int offset, size_t count);
+
+/*
+ * SMBus primitive fallback for 16-bit addressed EEPROMs.
+ *
+ * PIIX4 SMBus supports BYTE_DATA and WORD_DATA but NOT I2C_FUNC_I2C or
+ * I2C_FUNC_SMBUS_I2C_BLOCK, so regmap_get_i2c_bus() returns -ENOTSUPP
+ * for reg_bits=16. We bypass regmap entirely for the I/O path:
+ *
+ * READ : write_byte_data(addr_hi, addr_lo) sets the 16-bit EEPROM
+ * address pointer, then read_byte() fetches bytes one-by-one
+ * (EEPROM auto-increments the pointer after each read).
+ *
+ * WRITE: write_word_data(addr_hi, cpu_to_le16((data<<8)|addr_lo))
+ * encodes the address and data byte in one SMBus word write.
+ */
+static ssize_t at24_smbus_read(struct at24_data *at24, char *buf,
+ unsigned int offset, size_t count)
+{
+ struct i2c_client *client = at24->smbus_client;
+ unsigned long timeout, read_time;
+ u8 addr_hi = (offset >> 8) & 0xff;
+ u8 addr_lo = offset & 0xff;
+ ssize_t ret;
+ size_t i;
+
+ count = at24_adjust_read_count(at24, offset, count);
+
+ timeout = jiffies + msecs_to_jiffies(at24_write_timeout);
+ do {
+ read_time = jiffies;
+
+ /* Set 16-bit address pointer: command=addr_hi, value=addr_lo */
+ ret = i2c_smbus_write_byte_data(client, addr_hi, addr_lo);
+ if (ret < 0)
+ goto retry;
+
+ /* Read bytes sequentially -- EEPROM auto-increments pointer */
+ for (i = 0; i < count; i++) {
+ ret = i2c_smbus_read_byte(client);
+ if (ret < 0)
+ goto retry;
+ buf[i] = (u8)ret;
+ }
+ dev_dbg(&client->dev, "smbus read %zu@%u --> ok\n", count, offset);
+ return count;
+retry:
+ usleep_range(1000, 1500);
+ } while (time_before(read_time, timeout));
+
+ return -ETIMEDOUT;
+}
+
+static ssize_t at24_smbus_write(struct at24_data *at24, const char *buf,
+ unsigned int offset, size_t count)
+{
+ struct i2c_client *client = at24->smbus_client;
+ unsigned long timeout, write_time;
+ size_t i;
+ int ret;
+
+ /* Write one byte at a time (page writes need raw I2C transfers) */
+ count = at24_adjust_write_count(at24, offset, count);
+
+ for (i = 0; i < count; i++, offset++) {
+ timeout = jiffies + msecs_to_jiffies(at24_write_timeout);
+ do {
+ write_time = jiffies;
+ /*
+ * command = addr[15:8]
+ * word LSB = addr[7:0]
+ * word MSB = data byte
+ * EEPROM: set 16-bit address then write one data byte.
+ */
+ ret = i2c_smbus_write_word_data(client,
+ (offset >> 8) & 0xff,
+ cpu_to_le16(((u16)(u8)buf[i] << 8) |
+ (offset & 0xff)));
+ if (!ret) {
+ dev_dbg(&client->dev, "smbus write 1@%u --> ok\n",
+ offset);
+ break;
+ }
+ usleep_range(1000, 1500);
+ } while (time_before(write_time, timeout));
+
+ if (ret)
+ return (i > 0) ? (ssize_t)i : -ETIMEDOUT;
+ }
+
+ return count;
+}
+
+static ssize_t at24_smbus8_read(struct at24_data *at24, char *buf,
+ unsigned int offset, size_t count)
+{
+ struct i2c_client *client = at24->smbus_client;
+ size_t i;
+ int ret;
+
+ count = at24_adjust_read_count(at24, offset, count);
+
+ for (i = 0; i < count; i++) {
+ ret = i2c_smbus_read_byte_data(client, (u8)(offset + i));
+ if (ret < 0)
+ return ret;
+ buf[i] = (u8)ret;
+ }
+ dev_dbg(&client->dev, "smbus8 read %zu@%u --> ok\n", count, offset);
+ return count;
+}
+
+static ssize_t at24_smbus8_write(struct at24_data *at24, const char *buf,
+ unsigned int offset, size_t count)
+{
+ struct i2c_client *client = at24->smbus_client;
+ size_t i;
+ int ret;
+
+ count = at24_adjust_write_count(at24, offset, count);
+
+ for (i = 0; i < count; i++) {
+ ret = i2c_smbus_write_byte_data(client, (u8)(offset + i),
+ (u8)buf[i]);
+ if (ret < 0)
+ return (i > 0) ? (ssize_t)i : ret;
+ }
+ dev_dbg(&client->dev, "smbus8 write %zu@%u --> ok\n", count, offset);
+ return count;
+}
+
static ssize_t at24_regmap_read(struct at24_data *at24, char *buf,
unsigned int offset, size_t count)
{
@@ -353,6 +491,12 @@
regmap = at24_translate_offset(at24, &offset);
count = at24_adjust_read_count(at24, offset, count);
+ if (at24->smbus_addr16_fallback)
+ return at24_smbus_read(at24, buf, offset, count);
+
+ if (at24->smbus_addr8_fallback)
+ return at24_smbus8_read(at24, buf, offset, count);
+
/* adjust offset for mac and serial read ops */
offset += at24->offset_adj;
@@ -411,6 +555,13 @@
regmap = at24_translate_offset(at24, &offset);
count = at24_adjust_write_count(at24, offset, count);
+
+ if (at24->smbus_addr16_fallback)
+ return at24_smbus_write(at24, buf, offset, count);
+
+ if (at24->smbus_addr8_fallback)
+ return at24_smbus8_write(at24, buf, offset, count);
+
timeout = jiffies + msecs_to_jiffies(at24_write_timeout);
do {
@@ -601,6 +752,8 @@
bool i2c_fn_i2c, i2c_fn_block;
unsigned int i, num_addresses;
struct at24_data *at24;
+ bool smbus_addr16_fallback;
+ bool smbus_addr8_fallback;
bool full_power;
struct regmap *regmap;
bool writable;
@@ -611,6 +764,18 @@
i2c_fn_block = i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_WRITE_I2C_BLOCK);
+ /*
+ * Detect SMBus-only adapters (e.g. AMD PIIX4) that lack true I2C
+ * transfer capability. Two cases:
+ *
+ * 1. No I2C_FUNC_I2C and no I2C_FUNC_SMBUS_I2C_BLOCK:
+ * For 16-bit addressed EEPROMs (AT24_FLAG_ADDR16), bypass regmap
+ * and use SMBus BYTE_DATA/WORD_DATA helpers.
+ *
+ * 2. No I2C_FUNC_I2C but I2C_FUNC_SMBUS_READ_I2C_BLOCK advertised
+ * (PIIX4 mux children): regmap picks the block-read path which
+ * fails at runtime. For 8-bit addressed EEPROMs use BYTE_DATA.
+ */
cdata = i2c_get_match_data(client);
if (!cdata)
return -ENODEV;
@@ -648,6 +813,33 @@
}
}
+ /*
+ * Detect SMBus-only adapters (e.g. AMD PIIX4) that lack true I2C
+ * capability. Detection is done here, after flags has been resolved
+ * from cdata and any device-tree overrides.
+ */
+ smbus_addr16_fallback = false;
+ smbus_addr8_fallback = false;
+ if (!i2c_fn_i2c) {
+ bool has_byte = i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA);
+ bool has_word = i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WORD_DATA);
+ if (has_byte && has_word) {
+ /*
+ * SMBus-only adapter (e.g. AMD PIIX4): no true I2C transfers.
+ * Choose fallback based on EEPROM address width.
+ */
+ if (flags & AT24_FLAG_ADDR16) {
+ smbus_addr16_fallback = true;
+ dev_dbg(dev, "SMBus-only adapter: enabling 16-bit addr SMBus fallback\n");
+ } else {
+ smbus_addr8_fallback = true;
+ dev_dbg(dev, "SMBus-only adapter: enabling 8-bit addr SMBus fallback\n");
+ }
+ }
+ }
+
err = device_property_read_u32(dev, "size", &byte_len);
if (err)
byte_len = cdata->byte_len;
@@ -681,10 +873,31 @@
regmap_config.val_bits = 8;
regmap_config.reg_bits = (flags & AT24_FLAG_ADDR16) ? 16 : 8;
regmap_config.disable_locking = true;
+ /*
+ * Force reg_bits=8 when the SMBus fallback path is active so that
+ * regmap_get_i2c_bus() can pick regmap_smbus_byte and succeed on PIIX4.
+ * The actual 16-bit address I/O is done entirely by our helpers.
+ */
+ if (smbus_addr16_fallback && (flags & AT24_FLAG_ADDR16))
+ regmap_config.reg_bits = 8;
regmap = devm_regmap_init_i2c(client, ®map_config);
- if (IS_ERR(regmap))
- return PTR_ERR(regmap);
+ if (IS_ERR(regmap)) {
+ /*
+ * For SMBus fallback paths the regmap is never used for I/O.
+ * If regmap init fails (e.g. adapter lacks I2C_FUNC_SMBUS_I2C_BLOCK
+ * at probe time), retry with a minimal byte config so probe succeeds.
+ */
+ if (smbus_addr16_fallback || smbus_addr8_fallback) {
+ struct regmap_config smbus_config = regmap_config;
+
+ smbus_config.reg_bits = 8;
+ smbus_config.val_bits = 8;
+ regmap = devm_regmap_init_i2c(client, &smbus_config);
+ }
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+ }
at24 = devm_kzalloc(dev, struct_size(at24, client_regmaps, num_addresses),
GFP_KERNEL);
@@ -700,6 +913,9 @@
at24->num_addresses = num_addresses;
at24->offset_adj = at24_get_offset_adj(flags, byte_len);
at24->client_regmaps[0] = regmap;
+ at24->smbus_addr16_fallback = smbus_addr16_fallback;
+ at24->smbus_addr8_fallback = smbus_addr8_fallback;
+ at24->smbus_client = client;
at24->vcc_reg = devm_regulator_get(dev, "vcc");
if (IS_ERR(at24->vcc_reg))
On Sat, Apr 4, 2026 at 4:19 AM Nishanth Sampath Kumar <nissampa@cisco.com> wrote: > > AMD PIIX4 SMBus adapters, present on AMD SP5/EPYC-based platforms > (including Cisco 8000 series routers), support SMBUS_BYTE_DATA and > SMBUS_WORD_DATA but lack I2C_FUNC_I2C and I2C_FUNC_SMBUS_I2C_BLOCK. > This causes two distinct failures: > > 1. AT24_FLAG_ADDR16 EEPROMs (e.g. 24c64 syseeprom): > regmap_get_i2c_bus() returns -ENOTSUPP (-524) for reg_bits=16 > because neither I2C_FUNC_I2C nor I2C_FUNC_SMBUS_I2C_BLOCK is set. > The driver probe fails entirely with: > at24 3-0055: probe with driver at24 failed with error -524 > > 2. 8-bit addressed EEPROMs (e.g. 24c02 fan EEPROMs) behind a pca9548 > mux on a PIIX4 child adapter: regmap selects regmap_i2c_smbus_i2c_block > which fails at runtime even though SMBUS_BYTE_DATA is available. > Cc'ing Mark and Wolfram (regmap and i2c maintainers respectively) as I'd like to get more eyes on this. To me this looks like something that should be fixed at the regmap level, not in an individual driver. This changeset defeats the entire purpose of the previous regmap conversion - abstracting away the implementation details of talking to the EEPROM over I2C. Is there anything that would stop us from extending i2c regmap to support buses without I2C_FUNC_I2C or I2C_FUNC_SMBUS_I2C_BLOCK? Bart
On Tue, Apr 07, 2026 at 12:54:49PM +0200, Bartosz Golaszewski wrote: > On Sat, Apr 4, 2026 at 4:19 AM Nishanth Sampath Kumar > > 1. AT24_FLAG_ADDR16 EEPROMs (e.g. 24c64 syseeprom): > > regmap_get_i2c_bus() returns -ENOTSUPP (-524) for reg_bits=16 > > because neither I2C_FUNC_I2C nor I2C_FUNC_SMBUS_I2C_BLOCK is set. > > The driver probe fails entirely with: > > at24 3-0055: probe with driver at24 failed with error -524 If you can implement a fallback for whatever configuration this device has then you should just implement that in the regmap core. > > 2. 8-bit addressed EEPROMs (e.g. 24c02 fan EEPROMs) behind a pca9548 > > mux on a PIIX4 child adapter: regmap selects regmap_i2c_smbus_i2c_block > > which fails at runtime even though SMBUS_BYTE_DATA is available. If the device is advertising capabilities that it does not actually support then the capability advertisment should be fixed.
AMD PIIX4 SMBus adapters, present on AMD SP5/EPYC-based platforms
(including Cisco 8000 series routers), support SMBUS_BYTE_DATA and
SMBUS_WORD_DATA but lack I2C_FUNC_I2C and I2C_FUNC_SMBUS_I2C_BLOCK.
When at24 (or any driver) requests a regmap with reg_bits=16 and
val_bits=8 on such an adapter, regmap_get_i2c_bus() finds no matching
bus and returns -ENOTSUPP. The existing regmap_i2c_smbus_i2c_block_reg16
bus type already implements 16-bit addressed reads using only
write_byte_data() + read_byte() primitives, but its selection is gated
on I2C_FUNC_SMBUS_I2C_BLOCK which these adapters lack.
Add a new regmap_smbus_byte_word_reg16 bus that:
READ: reuses regmap_i2c_smbus_i2c_read_reg16() -- sets the 16-bit
address via write_byte_data(addr_lo, addr_hi), then reads
bytes sequentially via read_byte() (EEPROM auto-increments).
Requires only SMBUS_BYTE_DATA.
WRITE: uses write_word_data(addr_hi, (data << 8) | addr_lo) to
encode one data byte per SMBus WORD transaction.
Requires only SMBUS_WORD_DATA. Single-byte writes only.
The new bus is selected in regmap_get_i2c_bus() when reg_bits=16,
val_bits=8, and the adapter has SMBUS_BYTE_DATA | SMBUS_WORD_DATA but
not I2C_FUNC_I2C or SMBUS_I2C_BLOCK. The branch is placed after the
existing I2C_BLOCK_reg16 check so adapters with full block support
continue to use the faster path.
This fixes at24 EEPROM probe failures on PIIX4:
at24 3-0055: probe with driver at24 failed with error -524
No driver changes are required -- at24 already passes reg_bits=16 to
devm_regmap_init_i2c(), which now succeeds.
Signed-off-by: Nishanth Sampath Kumar <nissampa@cisco.com>
---
v1 -> v2: Moved fix from at24 driver into regmap-i2c core per review
feedback from Bartosz Golaszewski and Mark Brown.
drivers/base/regmap/regmap-i2c.c | 49 ++++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)
--- a/drivers/base/regmap/regmap-i2c.c
+++ b/drivers/base/regmap/regmap-i2c.c
@@ -303,6 +303,50 @@
.max_raw_write = I2C_SMBUS_BLOCK_MAX - 2,
};
+/*
+ * SMBus byte/word reg16 support for adapters that have SMBUS_BYTE_DATA
+ * and SMBUS_WORD_DATA but lack I2C_FUNC_I2C and I2C_FUNC_SMBUS_I2C_BLOCK,
+ * such as the AMD PIIX4.
+ *
+ * READ: set 16-bit EEPROM address via write_byte_data(addr_lo, addr_hi),
+ * then sequentially read bytes via read_byte() (EEPROM auto-
+ * increments the address pointer). Same as the I2C-block reg16
+ * read path above.
+ *
+ * WRITE: encode the low address byte and data into a word transaction:
+ * write_word_data(addr_hi, (data_byte << 8) | addr_lo).
+ * Only single-byte writes are supported (one value per transaction).
+ */
+static int regmap_smbus_word_write_reg16(void *context, const void *data,
+ size_t count)
+{
+ struct device *dev = context;
+ struct i2c_client *i2c = to_i2c_client(dev);
+ u8 addr_hi, addr_lo, val;
+
+ /*
+ * data layout: [addr_hi, addr_lo, val0, val1, ...].
+ * Only single-byte value writes are supported; multi-byte would
+ * require raw I2C (or repeated word writes with incrementing address).
+ */
+ if (count != 3)
+ return -EINVAL;
+
+ addr_hi = ((u8 *)data)[0];
+ addr_lo = ((u8 *)data)[1];
+ val = ((u8 *)data)[2];
+
+ return i2c_smbus_write_word_data(i2c, addr_hi,
+ cpu_to_le16(((u16)val << 8) | addr_lo));
+}
+
+static const struct regmap_bus regmap_smbus_byte_word_reg16 = {
+ .write = regmap_smbus_word_write_reg16,
+ .read = regmap_i2c_smbus_i2c_read_reg16,
+ .max_raw_read = I2C_SMBUS_BLOCK_MAX - 2,
+ .max_raw_write = 1,
+};
+
static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
const struct regmap_config *config)
{
@@ -321,6 +365,11 @@
i2c_check_functionality(i2c->adapter,
I2C_FUNC_SMBUS_I2C_BLOCK))
bus = ®map_i2c_smbus_i2c_block_reg16;
+ else if (config->val_bits == 8 && config->reg_bits == 16 &&
+ i2c_check_functionality(i2c->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_WORD_DATA))
+ bus = ®map_smbus_byte_word_reg16;
else if (config->val_bits == 16 && config->reg_bits == 8 &&
i2c_check_functionality(i2c->adapter,
I2C_FUNC_SMBUS_WORD_DATA))
On Tue, 07 Apr 2026 16:39:27 -0700, Nishanth Sampath Kumar wrote:
> regmap-i2c: add SMBus byte/word reg16 bus for adapters lacking I2C_FUNC_I2C
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git for-7.1
Thanks!
[1/1] regmap-i2c: add SMBus byte/word reg16 bus for adapters lacking I2C_FUNC_I2C
https://git.kernel.org/broonie/sound/c/f7b242428c6c
All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying
to this mail.
Thanks,
Mark
On Tue, Apr 07, 2026 at 04:39:27PM -0700, Nishanth Sampath Kumar wrote:
> +static const struct regmap_bus regmap_smbus_byte_word_reg16 = {
> + .write = regmap_smbus_word_write_reg16,
> + .read = regmap_i2c_smbus_i2c_read_reg16,
Not an issue for this patch but the existing function is broken on big
endian systems, it needs to peer into reg with u8 pointers since the
core already put it into wire format.
© 2016 - 2026 Red Hat, Inc.