]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
regmap-i2c: add SMBus byte/word reg16 bus for adapters lacking I2C_FUNC_I2C
authorNishanth Sampath Kumar <nissampa@cisco.com>
Tue, 7 Apr 2026 23:39:27 +0000 (16:39 -0700)
committerMark Brown <broonie@kernel.org>
Mon, 13 Apr 2026 17:05:58 +0000 (18:05 +0100)
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>
Link: https://patch.msgid.link/20260407233927.498932-1-nissampa@cisco.com
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/base/regmap/regmap-i2c.c

index c9b39a02278e06c8183ba4f307e0540764a4c335..31e30dfced192f0c0c6b4c6a31b8aac24657e454 100644 (file)
@@ -303,6 +303,50 @@ static const struct regmap_bus regmap_i2c_smbus_i2c_block_reg16 = {
        .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 @@ static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
                i2c_check_functionality(i2c->adapter,
                                        I2C_FUNC_SMBUS_I2C_BLOCK))
                bus = &regmap_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 = &regmap_smbus_byte_word_reg16;
        else if (config->val_bits == 16 && config->reg_bits == 8 &&
                 i2c_check_functionality(i2c->adapter,
                                         I2C_FUNC_SMBUS_WORD_DATA))