From c53bc9d241bb27e2e7b48122e7ce73c5811649ca Mon Sep 17 00:00:00 2001 From: Nishanth Sampath Kumar Date: Fri, 27 Mar 2026 14:48:36 -0700 Subject: [PATCH 1/3] at24: SMBus fallback for both 16-bit and 8-bit EEPROMs on PIIX4 AMD PIIX4 SMBus adapters (found on AMD SP5/EPYC platforms including Cisco 8000 series) support BYTE_DATA and WORD_DATA but lack I2C_FUNC_I2C and I2C_FUNC_SMBUS_I2C_BLOCK. For AT24_FLAG_ADDR16 EEPROMs this causes regmap_get_i2c_bus() to return -ENOTSUPP, making the EEPROM inaccessible. Add a smbus_addr16_fallback detection and two new helpers: - at24_smbus_read(): write_byte_data sets 16-bit pointer, repeated read_byte() fetches bytes (EEPROM auto-increments) - at24_smbus_write(): write_word_data encodes address+data in one WORD Force regmap_config.reg_bits=8 when fallback is active so regmap init succeeds; all actual I/O is handled by the SMBus helpers. Fixes EEPROM access on Cisco 8000-series with AMD SP5 on-die PIIX4 SMBus. Signed-off-by: Nishanth Sampath Kumar --- ...4-smbus-fallback-for-addr16-on-piix4.patch | 322 ++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 patches-sonic/driver-at24-smbus-fallback-for-addr16-on-piix4.patch diff --git a/patches-sonic/driver-at24-smbus-fallback-for-addr16-on-piix4.patch b/patches-sonic/driver-at24-smbus-fallback-for-addr16-on-piix4.patch new file mode 100644 index 000000000..ef08b0df6 --- /dev/null +++ b/patches-sonic/driver-at24-smbus-fallback-for-addr16-on-piix4.patch @@ -0,0 +1,322 @@ +From 07e943ccf6b98e941a39cd1f74db250e862151f0 Mon Sep 17 00:00:00 2001 +From: Nishanth Sampath Kumar +Date: Fri, 27 Mar 2026 14:48:36 -0700 +Subject: [PATCH] at24: SMBus fallback for both 16-bit and 8-bit EEPROMs on PIIX4 + +AMD PIIX4 SMBus adapters (found on AMD SP5/EPYC platforms including +Cisco 8000 series) support BYTE_DATA and 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 on SMB1-MUX-0): + regmap_get_i2c_bus() returns -ENOTSUPP for reg_bits=16. + +2. 8-bit addressed EEPROMs (e.g. 24c02 fan EEPROMs on PIIX4 mux children): + regmap picks regmap_i2c_smbus_i2c_block which fails at runtime. + +Add two fallback paths selected after chip flags are fully resolved +(i.e. after flags = cdata->flags and DT overrides): + + smbus_addr16_fallback (AT24_FLAG_ADDR16): + READ: write_byte_data(addr_hi, addr_lo) sets 16-bit pointer, + repeated read_byte() fetches bytes (EEPROM auto-increments). + WRITE: write_word_data encodes address+data in one WORD. + + smbus_addr8_fallback (no AT24_FLAG_ADDR16): + READ: read_byte_data(client, (u8)offset) per byte. + WRITE: write_byte_data(client, (u8)offset, data) per byte. + +Also add a regmap init retry with reg_bits=8/val_bits=8 so probe +succeeds on adapters that reject regmap init entirely. + +Key bug fixed vs v1: detection was done before flags = cdata->flags, +so AT24_FLAG_ADDR16 was never seen and all EEPROMs fell into the 8-bit +path, corrupting 24c64 reads. + +Signed-off-by: Nishanth Sampath Kumar +--- +--- 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,30 @@ + 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 +912,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)) From 4466e0f76dd880c405b7d1b0d31d096fb7fae4bc Mon Sep 17 00:00:00 2001 From: Nishanth Sampath Kumar Date: Fri, 3 Apr 2026 19:13:24 -0700 Subject: [PATCH 2/3] at24: fix smbus_config indentation and add blank line after declaration checkpatch.pl fixes: - correct indentation of smbus retry block to 1/2/3 tab nesting - add blank line after struct regmap_config smbus_config declaration Signed-off-by: Nishanth Sampath Kumar --- .../driver-at24-smbus-fallback-for-addr16-on-piix4.patch | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/patches-sonic/driver-at24-smbus-fallback-for-addr16-on-piix4.patch b/patches-sonic/driver-at24-smbus-fallback-for-addr16-on-piix4.patch index ef08b0df6..4fe536746 100644 --- a/patches-sonic/driver-at24-smbus-fallback-for-addr16-on-piix4.patch +++ b/patches-sonic/driver-at24-smbus-fallback-for-addr16-on-piix4.patch @@ -277,7 +277,7 @@ Signed-off-by: Nishanth Sampath Kumar err = device_property_read_u32(dev, "size", &byte_len); if (err) byte_len = cdata->byte_len; -@@ -681,10 +873,30 @@ +@@ -681,10 +873,31 @@ regmap_config.val_bits = 8; regmap_config.reg_bits = (flags & AT24_FLAG_ADDR16) ? 16 : 8; regmap_config.disable_locking = true; @@ -300,6 +300,7 @@ Signed-off-by: Nishanth Sampath Kumar + */ + 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); @@ -310,7 +311,7 @@ Signed-off-by: Nishanth Sampath Kumar at24 = devm_kzalloc(dev, struct_size(at24, client_regmaps, num_addresses), GFP_KERNEL); -@@ -700,6 +912,9 @@ +@@ -700,6 +913,9 @@ at24->num_addresses = num_addresses; at24->offset_adj = at24_get_offset_adj(flags, byte_len); at24->client_regmaps[0] = regmap; From 5fbdbff196d3ad07763c87f7aa68ee5db8bf4168 Mon Sep 17 00:00:00 2001 From: Nishanth Sampath Kumar Date: Thu, 9 Apr 2026 21:17:23 -0700 Subject: [PATCH 3/3] regmap-i2c: replace at24 SMBus fallback with regmap-level fix Remove the at24-specific SMBus fallback patch and replace it with a regmap-i2c core patch that adds a new regmap_smbus_byte_word_reg16 bus type for adapters with SMBUS_BYTE_DATA and SMBUS_WORD_DATA but lacking I2C_FUNC_I2C and I2C_FUNC_SMBUS_I2C_BLOCK (e.g. AMD PIIX4). This moves the fix to the regmap abstraction layer per upstream review feedback from Bartosz Golaszewski and Mark Brown, so all drivers (not just at24) benefit from the fallback. Signed-off-by: Nishanth Sampath Kumar --- ...sco-regmap-i2c-smbus-byte-word-reg16.patch | 112 ++++++ ...4-smbus-fallback-for-addr16-on-piix4.patch | 323 ------------------ 2 files changed, 112 insertions(+), 323 deletions(-) create mode 100755 patches-sonic/cisco-regmap-i2c-smbus-byte-word-reg16.patch delete mode 100644 patches-sonic/driver-at24-smbus-fallback-for-addr16-on-piix4.patch diff --git a/patches-sonic/cisco-regmap-i2c-smbus-byte-word-reg16.patch b/patches-sonic/cisco-regmap-i2c-smbus-byte-word-reg16.patch new file mode 100755 index 000000000..8f0beee99 --- /dev/null +++ b/patches-sonic/cisco-regmap-i2c-smbus-byte-word-reg16.patch @@ -0,0 +1,112 @@ +From: Nishanth Sampath Kumar +Date: Mon, 07 Apr 2026 12:00:00 -0700 +Subject: [PATCH v2] regmap-i2c: add SMBus byte/word reg16 bus for adapters + lacking I2C_FUNC_I2C + +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 +--- +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)) diff --git a/patches-sonic/driver-at24-smbus-fallback-for-addr16-on-piix4.patch b/patches-sonic/driver-at24-smbus-fallback-for-addr16-on-piix4.patch deleted file mode 100644 index 4fe536746..000000000 --- a/patches-sonic/driver-at24-smbus-fallback-for-addr16-on-piix4.patch +++ /dev/null @@ -1,323 +0,0 @@ -From 07e943ccf6b98e941a39cd1f74db250e862151f0 Mon Sep 17 00:00:00 2001 -From: Nishanth Sampath Kumar -Date: Fri, 27 Mar 2026 14:48:36 -0700 -Subject: [PATCH] at24: SMBus fallback for both 16-bit and 8-bit EEPROMs on PIIX4 - -AMD PIIX4 SMBus adapters (found on AMD SP5/EPYC platforms including -Cisco 8000 series) support BYTE_DATA and 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 on SMB1-MUX-0): - regmap_get_i2c_bus() returns -ENOTSUPP for reg_bits=16. - -2. 8-bit addressed EEPROMs (e.g. 24c02 fan EEPROMs on PIIX4 mux children): - regmap picks regmap_i2c_smbus_i2c_block which fails at runtime. - -Add two fallback paths selected after chip flags are fully resolved -(i.e. after flags = cdata->flags and DT overrides): - - smbus_addr16_fallback (AT24_FLAG_ADDR16): - READ: write_byte_data(addr_hi, addr_lo) sets 16-bit pointer, - repeated read_byte() fetches bytes (EEPROM auto-increments). - WRITE: write_word_data encodes address+data in one WORD. - - smbus_addr8_fallback (no AT24_FLAG_ADDR16): - READ: read_byte_data(client, (u8)offset) per byte. - WRITE: write_byte_data(client, (u8)offset, data) per byte. - -Also add a regmap init retry with reg_bits=8/val_bits=8 so probe -succeeds on adapters that reject regmap init entirely. - -Key bug fixed vs v1: detection was done before flags = cdata->flags, -so AT24_FLAG_ADDR16 was never seen and all EEPROMs fell into the 8-bit -path, corrupting 24c64 reads. - -Signed-off-by: Nishanth Sampath Kumar ---- ---- 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))