From 2b110445b1dfdef34ea7c42c27ddc2ba1bee5753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Wed, 26 Nov 2025 11:46:25 +0100 Subject: [PATCH 01/46] i2c: designware: Optimize flag reading in i2c_dw_read() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Optimize the i2c_dw_read() function by reading the message flags only once per message, rather than for every byte. The message flags are accessed both in the outer loop and the inner loop, so move the declaration of the local flags variable to the outer loop. The message index is only modified by the outer loop, so reading the flags in the inner loop was always getting the same value. Reviewed-by: Andy Shevchenko Signed-off-by: Benoît Monin Acked-by: Mika Westerberg Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251126-i2c-dw-v4-2-b0654598e7c5@bootlin.com --- drivers/i2c/busses/i2c-designware-master.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index 45bfca05bb30..4493568e2fa3 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -593,11 +593,12 @@ i2c_dw_read(struct dw_i2c_dev *dev) unsigned int rx_valid; for (; dev->msg_read_idx < dev->msgs_num; dev->msg_read_idx++) { + u32 flags = msgs[dev->msg_read_idx].flags; unsigned int tmp; u32 len; u8 *buf; - if (!(msgs[dev->msg_read_idx].flags & I2C_M_RD)) + if (!(flags & I2C_M_RD)) continue; if (!(dev->status & STATUS_READ_IN_PROGRESS)) { @@ -611,8 +612,6 @@ i2c_dw_read(struct dw_i2c_dev *dev) regmap_read(dev->map, DW_IC_RXFLR, &rx_valid); for (; len > 0 && rx_valid > 0; len--, rx_valid--) { - u32 flags = msgs[dev->msg_read_idx].flags; - regmap_read(dev->map, DW_IC_DATA_CMD, &tmp); tmp &= DW_IC_DATA_CMD_DAT; /* Ensure length byte is a valid value */ From ea032b451134e5cc79ed1affc9a237ce5bdda9ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Wed, 26 Nov 2025 11:46:26 +0100 Subject: [PATCH 02/46] i2c: designware: Sort compatible strings in alphabetical order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorder the of_device_id structures so that they are in alphabetical order. Also drop the unneeded inner trailing comma in the "snps,designware-i2c" struct. Reviewed-by: Andy Shevchenko Signed-off-by: Benoît Monin Acked-by: Mika Westerberg Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251126-i2c-dw-v4-3-b0654598e7c5@bootlin.com --- drivers/i2c/busses/i2c-designware-platdrv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 7be99656a67d..077b34535ec7 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -334,9 +334,9 @@ static void dw_i2c_plat_remove(struct platform_device *pdev) } static const struct of_device_id dw_i2c_of_match[] = { - { .compatible = "snps,designware-i2c", }, - { .compatible = "mscc,ocelot-i2c", .data = (void *)MODEL_MSCC_OCELOT }, { .compatible = "baikal,bt1-sys-i2c", .data = (void *)MODEL_BAIKAL_BT1 }, + { .compatible = "mscc,ocelot-i2c", .data = (void *)MODEL_MSCC_OCELOT }, + { .compatible = "snps,designware-i2c" }, {} }; MODULE_DEVICE_TABLE(of, dw_i2c_of_match); From 6a28174326289834c6767bdeb1ba348aa9831e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Wed, 26 Nov 2025 11:46:27 +0100 Subject: [PATCH 03/46] i2c: designware: Add dedicated algorithm for AMD NAVI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apart from runtime PM, there is nothing in common between i2c_dw_xfer() and amd_i2c_dw_xfer_quirk(), so give AMD NAVI controller its own algorithm instead of calling the quirk from i2c_dw_xfer(). Add runtime PM handling to amd_i2c_dw_xfer_quirk() and a dedicated i2c_algorithm for AMD NAVI controllers. The adapter algorithm is set during probe based on the device model. This way we avoid checking for the device model at the start of every transfer. Reviewed-by: Andy Shevchenko Signed-off-by: Benoît Monin Acked-by: Mika Westerberg Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251126-i2c-dw-v4-4-b0654598e7c5@bootlin.com --- drivers/i2c/busses/i2c-designware-master.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index 4493568e2fa3..f247cf323207 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -361,6 +361,10 @@ static int amd_i2c_dw_xfer_quirk(struct i2c_adapter *adap, struct i2c_msg *msgs, u8 *tx_buf; unsigned int val; + ACQUIRE(pm_runtime_active_auto_try, pm)(dev->dev); + if (ACQUIRE_ERR(pm_runtime_active_auto_try, &pm)) + return -ENXIO; + /* * In order to enable the interrupt for UCSI i.e. AMD NAVI GPU card, * it is mandatory to set the right value in specific register @@ -820,14 +824,6 @@ i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) pm_runtime_get_sync(dev->dev); - switch (dev->flags & MODEL_MASK) { - case MODEL_AMD_NAVI_GPU: - ret = amd_i2c_dw_xfer_quirk(adap, msgs, num); - goto done_nolock; - default: - break; - } - reinit_completion(&dev->cmd_complete); dev->msgs = msgs; dev->msgs_num = num; @@ -917,6 +913,11 @@ static const struct i2c_algorithm i2c_dw_algo = { .functionality = i2c_dw_func, }; +static const struct i2c_algorithm amd_i2c_dw_algo = { + .xfer = amd_i2c_dw_xfer_quirk, + .functionality = i2c_dw_func, +}; + static const struct i2c_adapter_quirks i2c_dw_quirks = { .flags = I2C_AQ_NO_ZERO_LEN, }; @@ -1052,7 +1053,10 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev) scnprintf(adap->name, sizeof(adap->name), "Synopsys DesignWare I2C adapter"); adap->retries = 3; - adap->algo = &i2c_dw_algo; + if ((dev->flags & MODEL_MASK) == MODEL_AMD_NAVI_GPU) + adap->algo = &amd_i2c_dw_algo; + else + adap->algo = &i2c_dw_algo; adap->quirks = &i2c_dw_quirks; adap->dev.parent = dev->dev; i2c_set_adapdata(adap, dev); From 41acc4dd8a04af332416b59a4cdb4780b7716ff1 Mon Sep 17 00:00:00 2001 From: FUKAUMI Naoki Date: Tue, 2 Dec 2025 08:49:39 +0000 Subject: [PATCH 04/46] dt-bindings: eeprom: at24: Add compatible for Belling BL24C04A/BL24C16F Add the compatible for Belling BL24C04A 4Kb EEPROM and BL24C16F 16Kb EEPROM. Signed-off-by: FUKAUMI Naoki Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20251202084941.1785-2-naoki@radxa.com Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/eeprom/at24.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/eeprom/at24.yaml b/Documentation/devicetree/bindings/eeprom/at24.yaml index c21282634780..8f16fa2ba17a 100644 --- a/Documentation/devicetree/bindings/eeprom/at24.yaml +++ b/Documentation/devicetree/bindings/eeprom/at24.yaml @@ -116,6 +116,7 @@ properties: - const: atmel,24c02 - items: - enum: + - belling,bl24c04a - giantec,gt24c04a - onnn,cat24c04 - onnn,cat24c05 @@ -124,6 +125,7 @@ properties: - items: - enum: - belling,bl24c16a + - belling,bl24c16f - renesas,r1ex24016 - const: atmel,24c16 - items: From 30116121412b1aef99899bacb51f7ccf2511f223 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Wed, 10 Dec 2025 10:05:27 +0900 Subject: [PATCH 05/46] dt-bindings: eeprom: at24: Add compatible for Giantec GT24P64A Add the compatible for another 64Kb EEPROM from Giantec. Signed-off-by: Luca Weiss Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20251210-fp4-cam-prep-v1-1-0eacbff271ec@fairphone.com Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/eeprom/at24.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/eeprom/at24.yaml b/Documentation/devicetree/bindings/eeprom/at24.yaml index 8f16fa2ba17a..95ac2f15f601 100644 --- a/Documentation/devicetree/bindings/eeprom/at24.yaml +++ b/Documentation/devicetree/bindings/eeprom/at24.yaml @@ -134,6 +134,7 @@ properties: - items: - enum: - belling,bl24s64 + - giantec,gt24p64a - onnn,n24s64b - puya,p24c64f - const: atmel,24c64 From 7a29af24b288eace769ccd9eb8044742dfbd5944 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 12 Dec 2025 04:26:46 +0100 Subject: [PATCH 06/46] eeprom: at24: use dev_err_probe() consistently Save some lines by consistently using dev_err_probe() when bailing out with an error message. Link: https://lore.kernel.org/r/20251212032646.49336-1-bartosz.golaszewski@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/misc/eeprom/at24.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c index f721825199ce..0200288d3a7a 100644 --- a/drivers/misc/eeprom/at24.c +++ b/drivers/misc/eeprom/at24.c @@ -657,10 +657,8 @@ static int at24_probe(struct i2c_client *client) if (!i2c_fn_i2c && !i2c_fn_block) page_size = 1; - if (!page_size) { - dev_err(dev, "page_size must not be 0!\n"); - return -EINVAL; - } + if (!page_size) + return dev_err_probe(dev, -EINVAL, "page_size must not be 0!\n"); if (!is_power_of_2(page_size)) dev_warn(dev, "page_size looks suspicious (no power of 2)!\n"); @@ -674,11 +672,9 @@ static int at24_probe(struct i2c_client *client) (flags & AT24_FLAG_ADDR16) ? 65536 : 256); } - if ((flags & AT24_FLAG_SERIAL) && (flags & AT24_FLAG_MAC)) { - dev_err(dev, - "invalid device data - cannot have both AT24_FLAG_SERIAL & AT24_FLAG_MAC."); - return -EINVAL; - } + if ((flags & AT24_FLAG_SERIAL) && (flags & AT24_FLAG_MAC)) + return dev_err_probe(dev, -EINVAL, + "invalid device data - cannot have both AT24_FLAG_SERIAL & AT24_FLAG_MAC."); regmap_config.val_bits = 8; regmap_config.reg_bits = (flags & AT24_FLAG_ADDR16) ? 16 : 8; @@ -759,10 +755,8 @@ static int at24_probe(struct i2c_client *client) full_power = acpi_dev_state_d0(&client->dev); if (full_power) { err = regulator_enable(at24->vcc_reg); - if (err) { - dev_err(dev, "Failed to enable vcc regulator\n"); - return err; - } + if (err) + return dev_err_probe(dev, err, "Failed to enable vcc regulator\n"); pm_runtime_set_active(dev); } From 9f65f8fa18bbfb7d8756f1bfd6d1cbaffe2661df Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Thu, 18 Dec 2025 16:15:00 +0100 Subject: [PATCH 07/46] i2c: designware: Remove useless driver specific option for I2C target The generic option for I2C target is already user selectable, which makes the DesignWare specific option completely unnecessary. The DesignWare option also silently selected I2C_SLAVE instead of depending on it without any real need for it. Reviewed-by: Andy Shevchenko Signed-off-by: Heikki Krogerus Acked-by: Mika Westerberg Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251218151509.361617-2-heikki.krogerus@linux.intel.com --- drivers/i2c/busses/Kconfig | 10 ++-------- drivers/i2c/busses/Makefile | 2 +- drivers/i2c/busses/i2c-designware-core.h | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index cea87fcb4a1a..f2df105d69ac 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -568,20 +568,14 @@ config I2C_DESIGNWARE_CORE help This option enables support for the Synopsys DesignWare I2C adapter. This driver includes support for the I2C host on the Synopsys - Designware I2C adapter. + Designware I2C adapter, and the I2C slave when enabled (select + I2C_SLAVE). To compile the driver as a module, choose M here: the module will be called i2c-designware-core. if I2C_DESIGNWARE_CORE -config I2C_DESIGNWARE_SLAVE - bool "Synopsys DesignWare Slave" - select I2C_SLAVE - help - If you say yes to this option, support will be included for the - Synopsys DesignWare I2C slave adapter. - config I2C_DESIGNWARE_PLATFORM tristate "Synopsys DesignWare Platform driver" depends on (ACPI && COMMON_CLK) || !ACPI diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index fb985769f5ff..547123ab351f 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -53,7 +53,7 @@ obj-$(CONFIG_I2C_DAVINCI) += i2c-davinci.o obj-$(CONFIG_I2C_DESIGNWARE_CORE) += i2c-designware-core.o i2c-designware-core-y := i2c-designware-common.o i2c-designware-core-y += i2c-designware-master.o -i2c-designware-core-$(CONFIG_I2C_DESIGNWARE_SLAVE) += i2c-designware-slave.o +i2c-designware-core-$(CONFIG_I2C_SLAVE) += i2c-designware-slave.o obj-$(CONFIG_I2C_DESIGNWARE_PLATFORM) += i2c-designware-platform.o i2c-designware-platform-y := i2c-designware-platdrv.o i2c-designware-platform-$(CONFIG_I2C_DESIGNWARE_AMDPSP) += i2c-designware-amdpsp.o diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index bb5ce0a382f9..2a7decc24931 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -386,7 +386,7 @@ void i2c_dw_disable(struct dw_i2c_dev *dev); extern void i2c_dw_configure_master(struct dw_i2c_dev *dev); extern int i2c_dw_probe_master(struct dw_i2c_dev *dev); -#if IS_ENABLED(CONFIG_I2C_DESIGNWARE_SLAVE) +#if IS_ENABLED(CONFIG_I2C_SLAVE) extern void i2c_dw_configure_slave(struct dw_i2c_dev *dev); extern int i2c_dw_probe_slave(struct dw_i2c_dev *dev); #else From a7b79464a5e4b95adf7c3da66d287b1944824b91 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Thu, 18 Dec 2025 16:15:01 +0100 Subject: [PATCH 08/46] i2c: designware: Remove unnecessary function exports The master and slave probe functions are only called from the core. Reviewed-by: Andy Shevchenko Signed-off-by: Heikki Krogerus Acked-by: Mika Westerberg Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251218151509.361617-3-heikki.krogerus@linux.intel.com --- drivers/i2c/busses/i2c-designware-master.c | 1 - drivers/i2c/busses/i2c-designware-slave.c | 1 - 2 files changed, 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index f247cf323207..15b3a46f0132 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -1101,7 +1101,6 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev) return ret; } -EXPORT_SYMBOL_GPL(i2c_dw_probe_master); MODULE_DESCRIPTION("Synopsys DesignWare I2C bus master adapter"); MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-designware-slave.c b/drivers/i2c/busses/i2c-designware-slave.c index 6eb16b7d75a6..1995be79544d 100644 --- a/drivers/i2c/busses/i2c-designware-slave.c +++ b/drivers/i2c/busses/i2c-designware-slave.c @@ -277,7 +277,6 @@ int i2c_dw_probe_slave(struct dw_i2c_dev *dev) return ret; } -EXPORT_SYMBOL_GPL(i2c_dw_probe_slave); MODULE_AUTHOR("Luis Oliveira "); MODULE_DESCRIPTION("Synopsys DesignWare I2C bus slave adapter"); From ad0876a84631fee7b0ad4cd8118b9696aa566671 Mon Sep 17 00:00:00 2001 From: Encrow Thorne Date: Tue, 30 Dec 2025 23:06:51 +0800 Subject: [PATCH 09/46] dt-bindings: i2c: spacemit: add optional resets The I2C controller requires a reset to ensure it starts from a clean state. Add the 'resets' property to support this hardware requirement. Signed-off-by: Encrow Thorne Reviewed-by: Troy Mitchell Acked-by: Rob Herring (Arm) Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251230150653.42097-1-jyc0019@gmail.com --- Documentation/devicetree/bindings/i2c/spacemit,k1-i2c.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/i2c/spacemit,k1-i2c.yaml b/Documentation/devicetree/bindings/i2c/spacemit,k1-i2c.yaml index b7220fff2235..5896fb120501 100644 --- a/Documentation/devicetree/bindings/i2c/spacemit,k1-i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/spacemit,k1-i2c.yaml @@ -41,6 +41,9 @@ properties: default: 400000 maximum: 3300000 + resets: + maxItems: 1 + required: - compatible - reg From b96259551b337225bb0e7afb3452b98435dd8b81 Mon Sep 17 00:00:00 2001 From: Encrow Thorne Date: Tue, 30 Dec 2025 23:06:52 +0800 Subject: [PATCH 10/46] i2c: k1: add reset support The K1 I2C controller provides a reset line that needs to be deasserted before the controller can be accessed. Add reset support to the driver to ensure the controller starts in the required state. Signed-off-by: Encrow Thorne Reviewed-by: Troy Mitchell Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251230150653.42097-2-jyc0019@gmail.com --- drivers/i2c/busses/i2c-k1.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c index d42c03ef5db5..23661c7ddb67 100644 --- a/drivers/i2c/busses/i2c-k1.c +++ b/drivers/i2c/busses/i2c-k1.c @@ -10,6 +10,7 @@ #include #include #include + #include /* spacemit i2c registers */ #define SPACEMIT_ICR 0x0 /* Control register */ @@ -534,6 +535,7 @@ static int spacemit_i2c_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct device_node *of_node = pdev->dev.of_node; struct spacemit_i2c_dev *i2c; + struct reset_control *rst; int ret; i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL); @@ -578,6 +580,11 @@ static int spacemit_i2c_probe(struct platform_device *pdev) if (IS_ERR(clk)) return dev_err_probe(dev, PTR_ERR(clk), "failed to enable bus clock"); + rst = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), + "failed to acquire deasserted reset\n"); + spacemit_i2c_reset(i2c); i2c_set_adapdata(&i2c->adapt, i2c); From e2370b8b2cf1f60747594ec6b52b7c5542523549 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 29 Dec 2025 19:37:46 +0100 Subject: [PATCH 11/46] dt-bindings: i2c: atmel,at91sam: add microchip,lan9691-i2c Document Microchip LAN969x I2C compatible. Signed-off-by: Robert Marko Acked-by: Conor Dooley Acked-by: Andi Shyti Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251229184004.571837-6-robert.marko@sartura.hr --- Documentation/devicetree/bindings/i2c/atmel,at91sam-i2c.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/i2c/atmel,at91sam-i2c.yaml b/Documentation/devicetree/bindings/i2c/atmel,at91sam-i2c.yaml index e61cdb5b16ef..c83674c3183b 100644 --- a/Documentation/devicetree/bindings/i2c/atmel,at91sam-i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/atmel,at91sam-i2c.yaml @@ -26,6 +26,7 @@ properties: - microchip,sam9x60-i2c - items: - enum: + - microchip,lan9691-i2c - microchip,sama7d65-i2c - microchip,sama7g5-i2c - microchip,sam9x7-i2c From 5083dba0fde5446c00ee1a82a3911c8f88a2c72e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jan 2026 14:46:09 +0100 Subject: [PATCH 12/46] units: Add HZ_PER_GHZ The is going to be a new user of the HZ_PER_GHZ definition besides possibly existing ones. Add that one to the header. While at it, split Hz and kHz groups of the multipliers for better maintenance and readability. Signed-off-by: Andy Shevchenko Reviewed-by: Andi Shyti Reviewed-by: Linus Walleij Reviewed-by: Wolfram Sang Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260112134900.4142954-2-andriy.shevchenko@linux.intel.com --- include/linux/units.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/units.h b/include/linux/units.h index 00e15de33eca..0c296a004e89 100644 --- a/include/linux/units.h +++ b/include/linux/units.h @@ -25,9 +25,12 @@ #define MICROHZ_PER_HZ 1000000UL #define MILLIHZ_PER_HZ 1000UL +/* Hz based multipliers */ #define HZ_PER_KHZ 1000UL #define HZ_PER_MHZ 1000000UL +#define HZ_PER_GHZ 1000000000UL +/* kHz based multipliers */ #define KHZ_PER_MHZ 1000UL #define KHZ_PER_GHZ 1000000UL From f23669f874c0147727f87db61f50224d9b4f3071 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jan 2026 14:46:10 +0100 Subject: [PATCH 13/46] i2c: mlxbf: Use HZ_PER_GHZ constant instead of custom one Use HZ_PER_GHZ constant instead of custom one. No functional changes. Signed-off-by: Andy Shevchenko Reviewed-by: Wolfram Sang Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260112134900.4142954-3-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/i2c-mlxbf.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/i2c/busses/i2c-mlxbf.c b/drivers/i2c/busses/i2c-mlxbf.c index 8345f7e6385d..746f65989138 100644 --- a/drivers/i2c/busses/i2c-mlxbf.c +++ b/drivers/i2c/busses/i2c-mlxbf.c @@ -20,6 +20,7 @@ #include #include #include +#include /* Defines what functionality is present. */ #define MLXBF_I2C_FUNC_SMBUS_BLOCK \ @@ -72,8 +73,6 @@ /* Constant used to determine the PLL frequency. */ #define MLNXBF_I2C_COREPLL_CONST 16384ULL -#define MLXBF_I2C_FREQUENCY_1GHZ 1000000000ULL - /* PLL registers. */ #define MLXBF_I2C_CORE_PLL_REG1 0x4 #define MLXBF_I2C_CORE_PLL_REG2 0x8 @@ -1083,7 +1082,7 @@ static u32 mlxbf_i2c_get_ticks(struct mlxbf_i2c_priv *priv, u64 nanoseconds, * Frequency */ frequency = priv->frequency; - ticks = div_u64(nanoseconds * frequency, MLXBF_I2C_FREQUENCY_1GHZ); + ticks = div_u64(nanoseconds * frequency, HZ_PER_GHZ); /* * The number of ticks is rounded down and if minimum is equal to 1 * then add one tick. From ec416d46910153b4faf41eef2be72778c18c7adb Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jan 2026 14:46:11 +0100 Subject: [PATCH 14/46] i2c: mt65xx: Use HZ_PER_GHZ constant instead of plain number Use defined constant to avoid the possible mistakes and to provide an additional information on the units. Signed-off-by: Andy Shevchenko Reviewed-by: Wolfram Sang Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260112134900.4142954-4-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/i2c-mt65xx.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/i2c/busses/i2c-mt65xx.c b/drivers/i2c/busses/i2c-mt65xx.c index aefdbee1f03c..cb4d3aa709d0 100644 --- a/drivers/i2c/busses/i2c-mt65xx.c +++ b/drivers/i2c/busses/i2c-mt65xx.c @@ -24,6 +24,7 @@ #include #include #include +#include #define I2C_RS_TRANSFER (1 << 4) #define I2C_ARB_LOST (1 << 3) @@ -685,7 +686,7 @@ static int mtk_i2c_get_clk_div_restri(struct mtk_i2c *i2c, * Check and Calculate i2c ac-timing * * Hardware design: - * sample_ns = (1000000000 * (sample_cnt + 1)) / clk_src + * sample_ns = (HZ_PER_GHZ * (sample_cnt + 1)) / clk_src * xxx_cnt_div = spec->min_xxx_ns / sample_ns * * Sample_ns is rounded down for xxx_cnt_div would be greater @@ -701,9 +702,8 @@ static int mtk_i2c_check_ac_timing(struct mtk_i2c *i2c, { const struct i2c_spec_values *spec; unsigned int su_sta_cnt, low_cnt, high_cnt, max_step_cnt; - unsigned int sda_max, sda_min, clk_ns, max_sta_cnt = 0x3f; - unsigned int sample_ns = div_u64(1000000000ULL * (sample_cnt + 1), - clk_src); + unsigned int sda_max, sda_min, max_sta_cnt = 0x3f; + unsigned int clk_ns, sample_ns; if (!i2c->dev_comp->timing_adjust) return 0; @@ -713,8 +713,9 @@ static int mtk_i2c_check_ac_timing(struct mtk_i2c *i2c, spec = mtk_i2c_get_spec(check_speed); + sample_ns = div_u64(1ULL * HZ_PER_GHZ * (sample_cnt + 1), clk_src); if (i2c->dev_comp->ltiming_adjust) - clk_ns = 1000000000 / clk_src; + clk_ns = HZ_PER_GHZ / clk_src; else clk_ns = sample_ns / 2; From f83aa451460683c33824571269216e52154615da Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jan 2026 14:46:12 +0100 Subject: [PATCH 15/46] i2c: nomadik: Use HZ_PER_GHZ constant instead of plain number Use defined constant to avoid the possible mistakes and to provide an additional information on the units. While at it, drop unneeded 64-bit division, all operands fit 32-bit. Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Reviewed-by: Wolfram Sang Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260112134900.4142954-5-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/i2c-nomadik.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index 19b648fc094d..b63ee51c1652 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -31,6 +31,7 @@ #include #include #include +#include #define DRIVER_NAME "nmk-i2c" @@ -419,10 +420,10 @@ static void setup_i2c_controller(struct nmk_i2c_dev *priv) * modes are 250ns, 100ns, 10ns respectively. * * As the time for one cycle T in nanoseconds is - * T = (1/f) * 1000000000 => - * slsu = cycles / (1000000000 / f) + 1 + * T = (1/f) * HZ_PER_GHZ => + * slsu = cycles / (HZ_PER_GHZ / f) + 1 */ - ns = DIV_ROUND_UP_ULL(1000000000ULL, i2c_clk); + ns = DIV_ROUND_UP(HZ_PER_GHZ, i2c_clk); switch (priv->sm) { case I2C_FREQ_MODE_FAST: case I2C_FREQ_MODE_FAST_PLUS: From 65db3bf4f6afb9910cd55e98b547d844130c4d82 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jan 2026 14:46:13 +0100 Subject: [PATCH 16/46] i2c: rk3x: Use HZ_PER_GHZ constant instead of plain number Use defined constant to avoid the possible mistakes and to provide an additional information on the units. Signed-off-by: Andy Shevchenko Reviewed-by: Heiko Stuebner Reviewed-by: Wolfram Sang Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260112134900.4142954-6-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/i2c-rk3x.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/i2c/busses/i2c-rk3x.c b/drivers/i2c/busses/i2c-rk3x.c index d4e9196445c0..fcede9f6ed54 100644 --- a/drivers/i2c/busses/i2c-rk3x.c +++ b/drivers/i2c/busses/i2c-rk3x.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -896,13 +897,12 @@ static void rk3x_i2c_adapt_div(struct rk3x_i2c *i2c, unsigned long clk_rate) clk_disable(i2c->pclk); - t_low_ns = div_u64(((u64)calc.div_low + 1) * 8 * 1000000000, clk_rate); - t_high_ns = div_u64(((u64)calc.div_high + 1) * 8 * 1000000000, - clk_rate); + t_low_ns = div_u64(8ULL * HZ_PER_GHZ * (calc.div_low + 1), clk_rate); + t_high_ns = div_u64(8ULL * HZ_PER_GHZ * (calc.div_high + 1), clk_rate); dev_dbg(i2c->dev, - "CLK %lukhz, Req %uns, Act low %lluns high %lluns\n", - clk_rate / 1000, - 1000000000 / t->bus_freq_hz, + "CLK %lukHz, Req %luns, Act low %lluns high %lluns\n", + clk_rate / HZ_PER_KHZ, + HZ_PER_GHZ / t->bus_freq_hz, t_low_ns, t_high_ns); } From b77f0370b072af3275970e0b314cc20a159ca1c1 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jan 2026 14:46:14 +0100 Subject: [PATCH 17/46] i2c: st: Use HZ_PER_GHZ constant instead of plain number Use defined constant to avoid the possible mistakes and to provide an additional information on the units. Signed-off-by: Andy Shevchenko Reviewed-by: Patrice Chotard Reviewed-by: Wolfram Sang Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260112134900.4142954-7-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/i2c-st.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-st.c b/drivers/i2c/busses/i2c-st.c index 97d70e667227..751ea421caaf 100644 --- a/drivers/i2c/busses/i2c-st.c +++ b/drivers/i2c/busses/i2c-st.c @@ -20,6 +20,7 @@ #include #include #include +#include /* SSC registers */ #define SSC_BRG 0x000 @@ -285,7 +286,7 @@ static void st_i2c_hw_config(struct st_i2c_dev *i2c_dev) writel_relaxed(val, i2c_dev->base + SSC_CTL); rate = clk_get_rate(i2c_dev->clk); - ns_per_clk = 1000000000 / rate; + ns_per_clk = HZ_PER_GHZ / rate; /* Baudrate */ val = rate / (2 * t->rate); From 361ad74a549d99c613d423e1ed0baebfbc493503 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jan 2026 14:46:15 +0100 Subject: [PATCH 18/46] i2c: synquacer: Use HZ_PER_GHZ constant instead of plain number Use defined constant to avoid the possible mistakes and to provide an additional information on the units. Signed-off-by: Andy Shevchenko Reviewed-by: Wolfram Sang Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260112134900.4142954-8-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/i2c-synquacer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-synquacer.c b/drivers/i2c/busses/i2c-synquacer.c index 1230f51e1624..4891d68bf0ee 100644 --- a/drivers/i2c/busses/i2c-synquacer.c +++ b/drivers/i2c/busses/i2c-synquacer.c @@ -18,9 +18,10 @@ #include #include #include +#include #define WAIT_PCLK(n, rate) \ - ndelay(DIV_ROUND_UP(DIV_ROUND_UP(1000000000, rate), n) + 10) + ndelay(DIV_ROUND_UP(DIV_ROUND_UP(HZ_PER_GHZ, rate), n) + 10) /* I2C register address definitions */ #define SYNQUACER_I2C_REG_BSR (0x00 << 2) // Bus Status From b53232fd220ad5ecc29b1cb4d4e1355365bc5026 Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Tue, 18 Nov 2025 19:36:15 +0530 Subject: [PATCH 19/46] i2c: tegra: Do not configure DMA if not supported On Tegra264, not all I2C controllers have the necessary interface to GPC DMA, this causes failures when function tegra_i2c_init_dma() is called. Ensure that "dmas" device-tree property is present before initializing DMA in function tegra_i2c_init_dma(). Signed-off-by: Kartik Rajput Reviewed-by: Jon Hunter Acked-by: Thierry Reding Signed-off-by: Wolfram Sang --- drivers/i2c/busses/i2c-tegra.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index e533460bccc3..bd26b232ffb3 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -449,6 +449,11 @@ static int tegra_i2c_init_dma(struct tegra_i2c_dev *i2c_dev) if (IS_VI(i2c_dev)) return 0; + if (!of_property_present(i2c_dev->dev->of_node, "dmas")) { + dev_dbg(i2c_dev->dev, "DMA not available, falling back to PIO\n"); + return 0; + } + if (i2c_dev->hw->has_apb_dma) { if (!IS_ENABLED(CONFIG_TEGRA20_APB_DMA)) { dev_dbg(i2c_dev->dev, "APB DMA support not enabled\n"); From 8b80b61e6f4fea1fab4f48ed6af2d9b8946f8049 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 18 Nov 2025 19:36:16 +0530 Subject: [PATCH 20/46] i2c: tegra: Use separate variables for fast and fastplus The current implementation uses a single value of THIGH, TLOW and setup hold time for both fast and fastplus. But these values can be different for each speed mode and should be using separate variables. Split the variables used for fast and fast plus mode. Signed-off-by: Akhil R Reviewed-by: Jon Hunter Acked-by: Thierry Reding Signed-off-by: Wolfram Sang --- drivers/i2c/busses/i2c-tegra.c | 119 ++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 46 deletions(-) diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index bd26b232ffb3..c0382c9a0430 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -196,12 +196,16 @@ enum msg_end_type { * @has_apb_dma: Support of APBDMA on corresponding Tegra chip. * @tlow_std_mode: Low period of the clock in standard mode. * @thigh_std_mode: High period of the clock in standard mode. - * @tlow_fast_fastplus_mode: Low period of the clock in fast/fast-plus modes. - * @thigh_fast_fastplus_mode: High period of the clock in fast/fast-plus modes. + * @tlow_fast_mode: Low period of the clock in fast mode. + * @thigh_fast_mode: High period of the clock in fast mode. + * @tlow_fastplus_mode: Low period of the clock in fast-plus mode. + * @thigh_fastplus_mode: High period of the clock in fast-plus mode. * @setup_hold_time_std_mode: Setup and hold time for start and stop conditions * in standard mode. - * @setup_hold_time_fast_fast_plus_mode: Setup and hold time for start and stop - * conditions in fast/fast-plus modes. + * @setup_hold_time_fast_mode: Setup and hold time for start and stop + * conditions in fast mode. + * @setup_hold_time_fastplus_mode: Setup and hold time for start and stop + * conditions in fast-plus mode. * @setup_hold_time_hs_mode: Setup and hold time for start and stop conditions * in HS mode. * @has_interface_timing_reg: Has interface timing register to program the tuned @@ -224,10 +228,13 @@ struct tegra_i2c_hw_feature { bool has_apb_dma; u32 tlow_std_mode; u32 thigh_std_mode; - u32 tlow_fast_fastplus_mode; - u32 thigh_fast_fastplus_mode; + u32 tlow_fast_mode; + u32 thigh_fast_mode; + u32 tlow_fastplus_mode; + u32 thigh_fastplus_mode; u32 setup_hold_time_std_mode; - u32 setup_hold_time_fast_fast_plus_mode; + u32 setup_hold_time_fast_mode; + u32 setup_hold_time_fastplus_mode; u32 setup_hold_time_hs_mode; bool has_interface_timing_reg; }; @@ -677,25 +684,21 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) if (IS_VI(i2c_dev)) tegra_i2c_vi_init(i2c_dev); - switch (t->bus_freq_hz) { - case I2C_MAX_STANDARD_MODE_FREQ + 1 ... I2C_MAX_FAST_MODE_PLUS_FREQ: - default: - tlow = i2c_dev->hw->tlow_fast_fastplus_mode; - thigh = i2c_dev->hw->thigh_fast_fastplus_mode; - tsu_thd = i2c_dev->hw->setup_hold_time_fast_fast_plus_mode; - - if (t->bus_freq_hz > I2C_MAX_FAST_MODE_FREQ) - non_hs_mode = i2c_dev->hw->clk_divisor_fast_plus_mode; - else - non_hs_mode = i2c_dev->hw->clk_divisor_fast_mode; - break; - - case 0 ... I2C_MAX_STANDARD_MODE_FREQ: + if (t->bus_freq_hz <= I2C_MAX_STANDARD_MODE_FREQ) { tlow = i2c_dev->hw->tlow_std_mode; thigh = i2c_dev->hw->thigh_std_mode; tsu_thd = i2c_dev->hw->setup_hold_time_std_mode; non_hs_mode = i2c_dev->hw->clk_divisor_std_mode; - break; + } else if (t->bus_freq_hz <= I2C_MAX_FAST_MODE_FREQ) { + tlow = i2c_dev->hw->tlow_fast_mode; + thigh = i2c_dev->hw->thigh_fast_mode; + tsu_thd = i2c_dev->hw->setup_hold_time_fast_mode; + non_hs_mode = i2c_dev->hw->clk_divisor_fast_mode; + } else { + tlow = i2c_dev->hw->tlow_fastplus_mode; + thigh = i2c_dev->hw->thigh_fastplus_mode; + tsu_thd = i2c_dev->hw->setup_hold_time_fastplus_mode; + non_hs_mode = i2c_dev->hw->clk_divisor_fast_plus_mode; } /* make sure clock divisor programmed correctly */ @@ -1496,10 +1499,13 @@ static const struct tegra_i2c_hw_feature tegra20_i2c_hw = { .has_apb_dma = true, .tlow_std_mode = 0x4, .thigh_std_mode = 0x2, - .tlow_fast_fastplus_mode = 0x4, - .thigh_fast_fastplus_mode = 0x2, + .tlow_fast_mode = 0x4, + .thigh_fast_mode = 0x2, + .tlow_fastplus_mode = 0x4, + .thigh_fastplus_mode = 0x2, .setup_hold_time_std_mode = 0x0, - .setup_hold_time_fast_fast_plus_mode = 0x0, + .setup_hold_time_fast_mode = 0x0, + .setup_hold_time_fastplus_mode = 0x0, .setup_hold_time_hs_mode = 0x0, .has_interface_timing_reg = false, }; @@ -1521,10 +1527,13 @@ static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { .has_apb_dma = true, .tlow_std_mode = 0x4, .thigh_std_mode = 0x2, - .tlow_fast_fastplus_mode = 0x4, - .thigh_fast_fastplus_mode = 0x2, + .tlow_fast_mode = 0x4, + .thigh_fast_mode = 0x2, + .tlow_fastplus_mode = 0x4, + .thigh_fastplus_mode = 0x2, .setup_hold_time_std_mode = 0x0, - .setup_hold_time_fast_fast_plus_mode = 0x0, + .setup_hold_time_fast_mode = 0x0, + .setup_hold_time_fastplus_mode = 0x0, .setup_hold_time_hs_mode = 0x0, .has_interface_timing_reg = false, }; @@ -1546,10 +1555,13 @@ static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { .has_apb_dma = true, .tlow_std_mode = 0x4, .thigh_std_mode = 0x2, - .tlow_fast_fastplus_mode = 0x4, - .thigh_fast_fastplus_mode = 0x2, + .tlow_fast_mode = 0x4, + .thigh_fast_mode = 0x2, + .tlow_fastplus_mode = 0x4, + .thigh_fastplus_mode = 0x2, .setup_hold_time_std_mode = 0x0, - .setup_hold_time_fast_fast_plus_mode = 0x0, + .setup_hold_time_fast_mode = 0x0, + .setup_hold_time_fastplus_mode = 0x0, .setup_hold_time_hs_mode = 0x0, .has_interface_timing_reg = false, }; @@ -1571,10 +1583,13 @@ static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { .has_apb_dma = true, .tlow_std_mode = 0x4, .thigh_std_mode = 0x2, - .tlow_fast_fastplus_mode = 0x4, - .thigh_fast_fastplus_mode = 0x2, + .tlow_fast_mode = 0x4, + .thigh_fast_mode = 0x2, + .tlow_fastplus_mode = 0x4, + .thigh_fastplus_mode = 0x2, .setup_hold_time_std_mode = 0x0, - .setup_hold_time_fast_fast_plus_mode = 0x0, + .setup_hold_time_fast_mode = 0x0, + .setup_hold_time_fastplus_mode = 0x0, .setup_hold_time_hs_mode = 0x0, .has_interface_timing_reg = true, }; @@ -1596,10 +1611,13 @@ static const struct tegra_i2c_hw_feature tegra210_i2c_hw = { .has_apb_dma = true, .tlow_std_mode = 0x4, .thigh_std_mode = 0x2, - .tlow_fast_fastplus_mode = 0x4, - .thigh_fast_fastplus_mode = 0x2, + .tlow_fast_mode = 0x4, + .thigh_fast_mode = 0x2, + .tlow_fastplus_mode = 0x4, + .thigh_fastplus_mode = 0x2, .setup_hold_time_std_mode = 0, - .setup_hold_time_fast_fast_plus_mode = 0, + .setup_hold_time_fast_mode = 0, + .setup_hold_time_fastplus_mode = 0, .setup_hold_time_hs_mode = 0, .has_interface_timing_reg = true, }; @@ -1621,10 +1639,13 @@ static const struct tegra_i2c_hw_feature tegra186_i2c_hw = { .has_apb_dma = false, .tlow_std_mode = 0x4, .thigh_std_mode = 0x3, - .tlow_fast_fastplus_mode = 0x4, - .thigh_fast_fastplus_mode = 0x2, + .tlow_fast_mode = 0x4, + .thigh_fast_mode = 0x2, + .tlow_fastplus_mode = 0x4, + .thigh_fastplus_mode = 0x2, .setup_hold_time_std_mode = 0, - .setup_hold_time_fast_fast_plus_mode = 0, + .setup_hold_time_fast_mode = 0, + .setup_hold_time_fastplus_mode = 0, .setup_hold_time_hs_mode = 0, .has_interface_timing_reg = true, }; @@ -1646,10 +1667,13 @@ static const struct tegra_i2c_hw_feature tegra194_i2c_hw = { .has_apb_dma = false, .tlow_std_mode = 0x8, .thigh_std_mode = 0x7, - .tlow_fast_fastplus_mode = 0x2, - .thigh_fast_fastplus_mode = 0x2, + .tlow_fast_mode = 0x2, + .thigh_fast_mode = 0x2, + .tlow_fastplus_mode = 0x2, + .thigh_fastplus_mode = 0x2, .setup_hold_time_std_mode = 0x08080808, - .setup_hold_time_fast_fast_plus_mode = 0x02020202, + .setup_hold_time_fast_mode = 0x02020202, + .setup_hold_time_fastplus_mode = 0x02020202, .setup_hold_time_hs_mode = 0x090909, .has_interface_timing_reg = true, }; @@ -1671,10 +1695,13 @@ static const struct tegra_i2c_hw_feature tegra256_i2c_hw = { .has_apb_dma = false, .tlow_std_mode = 0x8, .thigh_std_mode = 0x7, - .tlow_fast_fastplus_mode = 0x3, - .thigh_fast_fastplus_mode = 0x3, + .tlow_fast_mode = 0x3, + .thigh_fast_mode = 0x3, + .tlow_fastplus_mode = 0x3, + .thigh_fastplus_mode = 0x3, .setup_hold_time_std_mode = 0x08080808, - .setup_hold_time_fast_fast_plus_mode = 0x02020202, + .setup_hold_time_fast_mode = 0x02020202, + .setup_hold_time_fastplus_mode = 0x02020202, .setup_hold_time_hs_mode = 0x090909, .has_interface_timing_reg = true, }; From 81d4c5350f0148543bd4541a6d1fb06d9c835aeb Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 18 Nov 2025 19:36:17 +0530 Subject: [PATCH 21/46] i2c: tegra: Update Tegra256 timing parameters Update the timing parameters of Tegra256 so that the signals are complaint with the I2C specification for SCL low time. Signed-off-by: Akhil R Reviewed-by: Jon Hunter Acked-by: Thierry Reding Signed-off-by: Wolfram Sang --- drivers/i2c/busses/i2c-tegra.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index c0382c9a0430..470d0d32d571 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -1684,7 +1684,7 @@ static const struct tegra_i2c_hw_feature tegra256_i2c_hw = { .clk_divisor_hs_mode = 7, .clk_divisor_std_mode = 0x7a, .clk_divisor_fast_mode = 0x40, - .clk_divisor_fast_plus_mode = 0x19, + .clk_divisor_fast_plus_mode = 0x14, .has_config_load_reg = true, .has_multi_master_mode = true, .has_slcg_override_reg = true, @@ -1695,14 +1695,13 @@ static const struct tegra_i2c_hw_feature tegra256_i2c_hw = { .has_apb_dma = false, .tlow_std_mode = 0x8, .thigh_std_mode = 0x7, - .tlow_fast_mode = 0x3, - .thigh_fast_mode = 0x3, - .tlow_fastplus_mode = 0x3, - .thigh_fastplus_mode = 0x3, + .tlow_fast_mode = 0x4, + .thigh_fast_mode = 0x2, + .tlow_fastplus_mode = 0x4, + .thigh_fastplus_mode = 0x4, .setup_hold_time_std_mode = 0x08080808, - .setup_hold_time_fast_mode = 0x02020202, - .setup_hold_time_fastplus_mode = 0x02020202, - .setup_hold_time_hs_mode = 0x090909, + .setup_hold_time_fast_mode = 0x04010101, + .setup_hold_time_fastplus_mode = 0x04020202, .has_interface_timing_reg = true, }; From 978b3ccbbac326cc5fe48c98a0440bdc79d9fd93 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 18 Nov 2025 19:36:18 +0530 Subject: [PATCH 22/46] i2c: tegra: Add HS mode support Add support for High Speed (HS) mode transfers for Tegra194 and later chips. While HS mode has been documented in the technical reference manuals since Tegra20, the hardware implementation appears to be broken on all chips prior to Tegra194. When HS mode is not supported, set the frequency to FM+ instead. Signed-off-by: Akhil R Signed-off-by: Kartik Rajput Reviewed-by: Jon Hunter Acked-by: Thierry Reding Signed-off-by: Wolfram Sang --- drivers/i2c/busses/i2c-tegra.c | 59 ++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index 470d0d32d571..3cbda0363316 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -85,6 +85,7 @@ #define PACKET_HEADER0_PROTOCOL GENMASK(7, 4) #define PACKET_HEADER0_PROTOCOL_I2C 1 +#define I2C_HEADER_HS_MODE BIT(22) #define I2C_HEADER_CONT_ON_NAK BIT(21) #define I2C_HEADER_READ BIT(19) #define I2C_HEADER_10BIT_ADDR BIT(18) @@ -200,6 +201,8 @@ enum msg_end_type { * @thigh_fast_mode: High period of the clock in fast mode. * @tlow_fastplus_mode: Low period of the clock in fast-plus mode. * @thigh_fastplus_mode: High period of the clock in fast-plus mode. + * @tlow_hs_mode: Low period of the clock in HS mode. + * @thigh_hs_mode: High period of the clock in HS mode. * @setup_hold_time_std_mode: Setup and hold time for start and stop conditions * in standard mode. * @setup_hold_time_fast_mode: Setup and hold time for start and stop @@ -210,6 +213,7 @@ enum msg_end_type { * in HS mode. * @has_interface_timing_reg: Has interface timing register to program the tuned * timing settings. + * @enable_hs_mode_support: Enable support for high speed (HS) mode transfers. */ struct tegra_i2c_hw_feature { bool has_continue_xfer_support; @@ -232,11 +236,14 @@ struct tegra_i2c_hw_feature { u32 thigh_fast_mode; u32 tlow_fastplus_mode; u32 thigh_fastplus_mode; + u32 tlow_hs_mode; + u32 thigh_hs_mode; u32 setup_hold_time_std_mode; u32 setup_hold_time_fast_mode; u32 setup_hold_time_fastplus_mode; u32 setup_hold_time_hs_mode; bool has_interface_timing_reg; + bool enable_hs_mode_support; }; /** @@ -646,6 +653,7 @@ static int tegra_i2c_master_reset(struct tegra_i2c_dev *i2c_dev) static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) { u32 val, clk_divisor, clk_multiplier, tsu_thd, tlow, thigh, non_hs_mode; + u32 max_bus_freq_hz; struct i2c_timings *t = &i2c_dev->timings; int err; @@ -684,6 +692,14 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) if (IS_VI(i2c_dev)) tegra_i2c_vi_init(i2c_dev); + if (i2c_dev->hw->enable_hs_mode_support) + max_bus_freq_hz = I2C_MAX_HIGH_SPEED_MODE_FREQ; + else + max_bus_freq_hz = I2C_MAX_FAST_MODE_PLUS_FREQ; + + if (WARN_ON(t->bus_freq_hz > max_bus_freq_hz)) + t->bus_freq_hz = max_bus_freq_hz; + if (t->bus_freq_hz <= I2C_MAX_STANDARD_MODE_FREQ) { tlow = i2c_dev->hw->tlow_std_mode; thigh = i2c_dev->hw->thigh_std_mode; @@ -694,11 +710,22 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) thigh = i2c_dev->hw->thigh_fast_mode; tsu_thd = i2c_dev->hw->setup_hold_time_fast_mode; non_hs_mode = i2c_dev->hw->clk_divisor_fast_mode; - } else { + } else if (t->bus_freq_hz <= I2C_MAX_FAST_MODE_PLUS_FREQ) { tlow = i2c_dev->hw->tlow_fastplus_mode; thigh = i2c_dev->hw->thigh_fastplus_mode; tsu_thd = i2c_dev->hw->setup_hold_time_fastplus_mode; non_hs_mode = i2c_dev->hw->clk_divisor_fast_plus_mode; + } else { + /* + * When using HS mode, i.e. when the bus frequency is greater than fast plus mode, + * the non-hs timing registers will be used for sending the master code byte for + * transition to HS mode. Configure the non-hs timing registers for Fast Mode to + * send the master code byte at 400kHz. + */ + tlow = i2c_dev->hw->tlow_fast_mode; + thigh = i2c_dev->hw->thigh_fast_mode; + tsu_thd = i2c_dev->hw->setup_hold_time_fast_mode; + non_hs_mode = i2c_dev->hw->clk_divisor_fast_mode; } /* make sure clock divisor programmed correctly */ @@ -720,6 +747,18 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) if (i2c_dev->hw->has_interface_timing_reg && tsu_thd) i2c_writel(i2c_dev, tsu_thd, I2C_INTERFACE_TIMING_1); + /* Write HS mode registers. These will get used only for HS mode*/ + if (i2c_dev->hw->enable_hs_mode_support) { + tlow = i2c_dev->hw->tlow_hs_mode; + thigh = i2c_dev->hw->thigh_hs_mode; + tsu_thd = i2c_dev->hw->setup_hold_time_hs_mode; + + val = FIELD_PREP(I2C_HS_INTERFACE_TIMING_THIGH, thigh) | + FIELD_PREP(I2C_HS_INTERFACE_TIMING_TLOW, tlow); + i2c_writel(i2c_dev, val, I2C_HS_INTERFACE_TIMING_0); + i2c_writel(i2c_dev, tsu_thd, I2C_HS_INTERFACE_TIMING_1); + } + clk_multiplier = (tlow + thigh + 2) * (non_hs_mode + 1); err = clk_set_rate(i2c_dev->div_clk, @@ -1217,6 +1256,9 @@ static void tegra_i2c_push_packet_header(struct tegra_i2c_dev *i2c_dev, if (msg->flags & I2C_M_RD) packet_header |= I2C_HEADER_READ; + if (i2c_dev->timings.bus_freq_hz > I2C_MAX_FAST_MODE_PLUS_FREQ) + packet_header |= I2C_HEADER_HS_MODE; + if (i2c_dev->dma_mode && !i2c_dev->msg_read) *dma_buf++ = packet_header; else @@ -1508,6 +1550,7 @@ static const struct tegra_i2c_hw_feature tegra20_i2c_hw = { .setup_hold_time_fastplus_mode = 0x0, .setup_hold_time_hs_mode = 0x0, .has_interface_timing_reg = false, + .enable_hs_mode_support = false, }; static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { @@ -1536,6 +1579,7 @@ static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { .setup_hold_time_fastplus_mode = 0x0, .setup_hold_time_hs_mode = 0x0, .has_interface_timing_reg = false, + .enable_hs_mode_support = false, }; static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { @@ -1564,6 +1608,7 @@ static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { .setup_hold_time_fastplus_mode = 0x0, .setup_hold_time_hs_mode = 0x0, .has_interface_timing_reg = false, + .enable_hs_mode_support = false, }; static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { @@ -1592,6 +1637,7 @@ static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { .setup_hold_time_fastplus_mode = 0x0, .setup_hold_time_hs_mode = 0x0, .has_interface_timing_reg = true, + .enable_hs_mode_support = false, }; static const struct tegra_i2c_hw_feature tegra210_i2c_hw = { @@ -1620,6 +1666,7 @@ static const struct tegra_i2c_hw_feature tegra210_i2c_hw = { .setup_hold_time_fastplus_mode = 0, .setup_hold_time_hs_mode = 0, .has_interface_timing_reg = true, + .enable_hs_mode_support = false, }; static const struct tegra_i2c_hw_feature tegra186_i2c_hw = { @@ -1648,6 +1695,7 @@ static const struct tegra_i2c_hw_feature tegra186_i2c_hw = { .setup_hold_time_fastplus_mode = 0, .setup_hold_time_hs_mode = 0, .has_interface_timing_reg = true, + .enable_hs_mode_support = false, }; static const struct tegra_i2c_hw_feature tegra194_i2c_hw = { @@ -1671,17 +1719,20 @@ static const struct tegra_i2c_hw_feature tegra194_i2c_hw = { .thigh_fast_mode = 0x2, .tlow_fastplus_mode = 0x2, .thigh_fastplus_mode = 0x2, + .tlow_hs_mode = 0x8, + .thigh_hs_mode = 0x3, .setup_hold_time_std_mode = 0x08080808, .setup_hold_time_fast_mode = 0x02020202, .setup_hold_time_fastplus_mode = 0x02020202, .setup_hold_time_hs_mode = 0x090909, .has_interface_timing_reg = true, + .enable_hs_mode_support = true, }; static const struct tegra_i2c_hw_feature tegra256_i2c_hw = { .has_continue_xfer_support = true, .has_per_pkt_xfer_complete_irq = true, - .clk_divisor_hs_mode = 7, + .clk_divisor_hs_mode = 9, .clk_divisor_std_mode = 0x7a, .clk_divisor_fast_mode = 0x40, .clk_divisor_fast_plus_mode = 0x14, @@ -1699,10 +1750,14 @@ static const struct tegra_i2c_hw_feature tegra256_i2c_hw = { .thigh_fast_mode = 0x2, .tlow_fastplus_mode = 0x4, .thigh_fastplus_mode = 0x4, + .tlow_hs_mode = 0x3, + .thigh_hs_mode = 0x2, .setup_hold_time_std_mode = 0x08080808, .setup_hold_time_fast_mode = 0x04010101, .setup_hold_time_fastplus_mode = 0x04020202, + .setup_hold_time_hs_mode = 0x030303, .has_interface_timing_reg = true, + .enable_hs_mode_support = true, }; static const struct of_device_id tegra_i2c_of_match[] = { From 6077cfd716fbd4d1f2a3702e49ae8bf65c072685 Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Tue, 18 Nov 2025 19:36:19 +0530 Subject: [PATCH 23/46] i2c: tegra: Add support for SW mutex register Add support for SW mutex register introduced in Tegra264 to provide an option to share the interface between multiple firmwares and/or VMs. This involves following steps: - A firmware/OS writes its unique ID to the mutex REQUEST field. - Ownership is established when reading the GRANT field returns the same ID. - If GRANT shows a different non-zero ID, the firmware/OS retries until timeout. - After completing access, it releases the mutex by writing 0. However, the hardware does not ensure any protection based on the values. The driver/firmware should honor the peer who already holds the mutex. Signed-off-by: Kartik Rajput Signed-off-by: Akhil R Reviewed-by: Jon Hunter Acked-by: Thierry Reding Signed-off-by: Wolfram Sang --- drivers/i2c/busses/i2c-tegra.c | 93 ++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index 3cbda0363316..84f2d5f4b794 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -137,6 +137,14 @@ #define I2C_MASTER_RESET_CNTRL 0x0a8 +#define I2C_SW_MUTEX 0x0ec +#define I2C_SW_MUTEX_REQUEST GENMASK(3, 0) +#define I2C_SW_MUTEX_GRANT GENMASK(7, 4) +#define I2C_SW_MUTEX_ID_CCPLEX 9 + +/* SW mutex acquire timeout value in microseconds. */ +#define I2C_SW_MUTEX_TIMEOUT_US (25 * USEC_PER_MSEC) + /* configuration load timeout in microseconds */ #define I2C_CONFIG_LOAD_TIMEOUT 1000000 @@ -214,6 +222,7 @@ enum msg_end_type { * @has_interface_timing_reg: Has interface timing register to program the tuned * timing settings. * @enable_hs_mode_support: Enable support for high speed (HS) mode transfers. + * @has_mutex: Has mutex register for mutual exclusion with other firmwares or VMs. */ struct tegra_i2c_hw_feature { bool has_continue_xfer_support; @@ -244,6 +253,7 @@ struct tegra_i2c_hw_feature { u32 setup_hold_time_hs_mode; bool has_interface_timing_reg; bool enable_hs_mode_support; + bool has_mutex; }; /** @@ -388,6 +398,76 @@ static void i2c_readsl(struct tegra_i2c_dev *i2c_dev, void *data, readsl(i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg), data, len); } +static bool tegra_i2c_mutex_acquired(struct tegra_i2c_dev *i2c_dev) +{ + unsigned int reg = tegra_i2c_reg_addr(i2c_dev, I2C_SW_MUTEX); + u32 val, id; + + val = readl(i2c_dev->base + reg); + id = FIELD_GET(I2C_SW_MUTEX_GRANT, val); + + return id == I2C_SW_MUTEX_ID_CCPLEX; +} + +static bool tegra_i2c_mutex_trylock(struct tegra_i2c_dev *i2c_dev) +{ + unsigned int reg = tegra_i2c_reg_addr(i2c_dev, I2C_SW_MUTEX); + u32 val, id; + + val = readl(i2c_dev->base + reg); + id = FIELD_GET(I2C_SW_MUTEX_GRANT, val); + if (id != 0 && id != I2C_SW_MUTEX_ID_CCPLEX) + return false; + + val = FIELD_PREP(I2C_SW_MUTEX_REQUEST, I2C_SW_MUTEX_ID_CCPLEX); + writel(val, i2c_dev->base + reg); + + return tegra_i2c_mutex_acquired(i2c_dev); +} + +static int tegra_i2c_mutex_lock(struct tegra_i2c_dev *i2c_dev) +{ + bool locked; + int ret; + + if (!i2c_dev->hw->has_mutex) + return 0; + + if (i2c_dev->atomic_mode) + ret = read_poll_timeout_atomic(tegra_i2c_mutex_trylock, locked, locked, + USEC_PER_MSEC, I2C_SW_MUTEX_TIMEOUT_US, + false, i2c_dev); + else + ret = read_poll_timeout(tegra_i2c_mutex_trylock, locked, locked, USEC_PER_MSEC, + I2C_SW_MUTEX_TIMEOUT_US, false, i2c_dev); + + if (ret) + dev_warn(i2c_dev->dev, "failed to acquire mutex\n"); + + return ret; +} + +static int tegra_i2c_mutex_unlock(struct tegra_i2c_dev *i2c_dev) +{ + unsigned int reg = tegra_i2c_reg_addr(i2c_dev, I2C_SW_MUTEX); + u32 val, id; + + if (!i2c_dev->hw->has_mutex) + return 0; + + val = readl(i2c_dev->base + reg); + + id = FIELD_GET(I2C_SW_MUTEX_GRANT, val); + if (id && id != I2C_SW_MUTEX_ID_CCPLEX) { + dev_warn(i2c_dev->dev, "unable to unlock mutex, mutex is owned by: %u\n", id); + return -EPERM; + } + + writel(0, i2c_dev->base + reg); + + return 0; +} + static void tegra_i2c_mask_irq(struct tegra_i2c_dev *i2c_dev, u32 mask) { u32 int_mask; @@ -1443,6 +1523,10 @@ static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], return ret; } + ret = tegra_i2c_mutex_lock(i2c_dev); + if (ret) + return ret; + for (i = 0; i < num; i++) { enum msg_end_type end_type = MSG_END_STOP; @@ -1472,6 +1556,7 @@ static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], break; } + ret = tegra_i2c_mutex_unlock(i2c_dev); pm_runtime_put(i2c_dev->dev); return ret ?: i; @@ -1551,6 +1636,7 @@ static const struct tegra_i2c_hw_feature tegra20_i2c_hw = { .setup_hold_time_hs_mode = 0x0, .has_interface_timing_reg = false, .enable_hs_mode_support = false, + .has_mutex = false, }; static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { @@ -1580,6 +1666,7 @@ static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { .setup_hold_time_hs_mode = 0x0, .has_interface_timing_reg = false, .enable_hs_mode_support = false, + .has_mutex = false, }; static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { @@ -1609,6 +1696,7 @@ static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { .setup_hold_time_hs_mode = 0x0, .has_interface_timing_reg = false, .enable_hs_mode_support = false, + .has_mutex = false, }; static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { @@ -1638,6 +1726,7 @@ static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { .setup_hold_time_hs_mode = 0x0, .has_interface_timing_reg = true, .enable_hs_mode_support = false, + .has_mutex = false, }; static const struct tegra_i2c_hw_feature tegra210_i2c_hw = { @@ -1667,6 +1756,7 @@ static const struct tegra_i2c_hw_feature tegra210_i2c_hw = { .setup_hold_time_hs_mode = 0, .has_interface_timing_reg = true, .enable_hs_mode_support = false, + .has_mutex = false, }; static const struct tegra_i2c_hw_feature tegra186_i2c_hw = { @@ -1696,6 +1786,7 @@ static const struct tegra_i2c_hw_feature tegra186_i2c_hw = { .setup_hold_time_hs_mode = 0, .has_interface_timing_reg = true, .enable_hs_mode_support = false, + .has_mutex = false, }; static const struct tegra_i2c_hw_feature tegra194_i2c_hw = { @@ -1727,6 +1818,7 @@ static const struct tegra_i2c_hw_feature tegra194_i2c_hw = { .setup_hold_time_hs_mode = 0x090909, .has_interface_timing_reg = true, .enable_hs_mode_support = true, + .has_mutex = false, }; static const struct tegra_i2c_hw_feature tegra256_i2c_hw = { @@ -1758,6 +1850,7 @@ static const struct tegra_i2c_hw_feature tegra256_i2c_hw = { .setup_hold_time_hs_mode = 0x030303, .has_interface_timing_reg = true, .enable_hs_mode_support = true, + .has_mutex = true, }; static const struct of_device_id tegra_i2c_of_match[] = { From 1ac9e16dd226a26a479c7392c9ec28f1c9eec61c Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 18 Nov 2025 19:36:20 +0530 Subject: [PATCH 24/46] i2c: tegra: Add Tegra264 support Add support for Tegra264 SoC which supports 17 generic I2C controllers, two of which are in the AON (always-on) partition of the SoC. In addition to the features supported by Tegra194 it also supports a SW mutex register to allow sharing the same I2C instance across multiple firmware. Signed-off-by: Akhil R Signed-off-by: Kartik Rajput Reviewed-by: Jon Hunter Acked-by: Thierry Reding Signed-off-by: Wolfram Sang --- drivers/i2c/busses/i2c-tegra.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index 84f2d5f4b794..d05015ef425d 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -1853,7 +1853,40 @@ static const struct tegra_i2c_hw_feature tegra256_i2c_hw = { .has_mutex = true, }; +static const struct tegra_i2c_hw_feature tegra264_i2c_hw = { + .has_continue_xfer_support = true, + .has_per_pkt_xfer_complete_irq = true, + .clk_divisor_hs_mode = 1, + .clk_divisor_std_mode = 0x1d, + .clk_divisor_fast_mode = 0x15, + .clk_divisor_fast_plus_mode = 0x8, + .has_config_load_reg = true, + .has_multi_master_mode = true, + .has_slcg_override_reg = true, + .has_mst_fifo = true, + .has_mst_reset = true, + .quirks = &tegra194_i2c_quirks, + .supports_bus_clear = true, + .has_apb_dma = false, + .tlow_std_mode = 0x8, + .thigh_std_mode = 0x7, + .tlow_fast_mode = 0x2, + .thigh_fast_mode = 0x2, + .tlow_fastplus_mode = 0x2, + .thigh_fastplus_mode = 0x2, + .tlow_hs_mode = 0x4, + .thigh_hs_mode = 0x2, + .setup_hold_time_std_mode = 0x08080808, + .setup_hold_time_fast_mode = 0x02020202, + .setup_hold_time_fastplus_mode = 0x02020202, + .setup_hold_time_hs_mode = 0x090909, + .has_interface_timing_reg = true, + .enable_hs_mode_support = true, + .has_mutex = true, +}; + static const struct of_device_id tegra_i2c_of_match[] = { + { .compatible = "nvidia,tegra264-i2c", .data = &tegra264_i2c_hw, }, { .compatible = "nvidia,tegra256-i2c", .data = &tegra256_i2c_hw, }, { .compatible = "nvidia,tegra194-i2c", .data = &tegra194_i2c_hw, }, { .compatible = "nvidia,tegra186-i2c", .data = &tegra186_i2c_hw, }, From aa1292d109a65c9145e3311057ee28098fad78a8 Mon Sep 17 00:00:00 2001 From: Louis-Alexis Eyraud Date: Thu, 30 Oct 2025 08:56:29 +0100 Subject: [PATCH 25/46] dt-bindings: i2c: i2c-mt65xx: Add compatible for MT8189 SoC Add compatible string for MT8189 SoC. Its multiple I2C controller instances are compatible with the ones found in the MT8188 SoC. Signed-off-by: Louis-Alexis Eyraud Reviewed-by: AngeloGioacchino Del Regno Acked-by: Conor Dooley Signed-off-by: Wolfram Sang --- Documentation/devicetree/bindings/i2c/i2c-mt65xx.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/i2c/i2c-mt65xx.yaml b/Documentation/devicetree/bindings/i2c/i2c-mt65xx.yaml index 3562ce0c0f7e..ecd5783f001b 100644 --- a/Documentation/devicetree/bindings/i2c/i2c-mt65xx.yaml +++ b/Documentation/devicetree/bindings/i2c/i2c-mt65xx.yaml @@ -54,6 +54,7 @@ properties: - enum: - mediatek,mt6878-i2c - mediatek,mt6991-i2c + - mediatek,mt8189-i2c - mediatek,mt8196-i2c - const: mediatek,mt8188-i2c - items: From 1a1c74b66af815c5eacf73d5ec6e79e21e102fcf Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jan 2026 14:22:40 +0100 Subject: [PATCH 26/46] i2c: core: Check for error pointer for fwnode Theoretically it's possible that fwnode is returned by some API, that may return an error pointer (and we have, for example, fwnode_find_reference() which does that). If such an fwnode is supplied to the i2c core APIs the functions will perform unneeded loops and checks. Avoid this by preventively checking for an error pointer and bail out immediately. Signed-off-by: Andy Shevchenko Signed-off-by: Wolfram Sang --- drivers/i2c/i2c-core-base.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index ae7e9c8b65a6..41c2e46ffb24 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -1090,7 +1090,7 @@ struct i2c_client *i2c_find_device_by_fwnode(struct fwnode_handle *fwnode) struct i2c_client *client; struct device *dev; - if (!fwnode) + if (IS_ERR_OR_NULL(fwnode)) return NULL; dev = bus_find_device_by_fwnode(&i2c_bus_type, fwnode); @@ -1875,7 +1875,7 @@ struct i2c_adapter *i2c_find_adapter_by_fwnode(struct fwnode_handle *fwnode) struct i2c_adapter *adapter; struct device *dev; - if (!fwnode) + if (IS_ERR_OR_NULL(fwnode)) return NULL; dev = bus_find_device(&i2c_bus_type, NULL, fwnode, From 861e0f8d81d727389311b58539952d0ca2095b9d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jan 2026 14:22:41 +0100 Subject: [PATCH 27/46] i2c: core: Replace custom implementation of device_match_fwnode() Replace custom implementation of the device_match_fwnode(). Signed-off-by: Andy Shevchenko Signed-off-by: Wolfram Sang --- drivers/i2c/i2c-core-base.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index 41c2e46ffb24..b9b44bed3243 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -1852,10 +1852,10 @@ EXPORT_SYMBOL_GPL(devm_i2c_add_adapter); static int i2c_dev_or_parent_fwnode_match(struct device *dev, const void *data) { - if (dev_fwnode(dev) == data) + if (device_match_fwnode(dev, data)) return 1; - if (dev->parent && dev_fwnode(dev->parent) == data) + if (dev->parent && device_match_fwnode(dev->parent, data)) return 1; return 0; From 71ebc45fa052c2c20e4255f01176a54c437eecd2 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jan 2026 14:22:42 +0100 Subject: [PATCH 28/46] i2c: core: Use dev_fwnode() irq_domain_create_linear() takes fwnode as the first argument. It can be extracted from the struct device using dev_fwnode() helper instead of using direct dereference(). So use the dev_fwnode() helper. Signed-off-by: Andy Shevchenko Signed-off-by: Wolfram Sang --- drivers/i2c/i2c-core-base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index b9b44bed3243..f0fb0cfd56e0 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -1476,7 +1476,7 @@ static int i2c_setup_host_notify_irq_domain(struct i2c_adapter *adap) if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_HOST_NOTIFY)) return 0; - domain = irq_domain_create_linear(adap->dev.parent->fwnode, + domain = irq_domain_create_linear(dev_fwnode(adap->dev.parent), I2C_ADDR_7BITS_COUNT, &i2c_host_notify_irq_ops, adap); if (!domain) From bc78670a29769458e31536693170dcfb755cf4d2 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jan 2026 14:55:10 +0100 Subject: [PATCH 29/46] i2c: mlxbf: Remove unused bus speed definitions The driver had been converted to use standard constants for the bus speed a long time ago. Remove the leftover definitions. Signed-off-by: Andy Shevchenko Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260112135603.4150952-2-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/i2c-mlxbf.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/i2c/busses/i2c-mlxbf.c b/drivers/i2c/busses/i2c-mlxbf.c index 746f65989138..9f0048044112 100644 --- a/drivers/i2c/busses/i2c-mlxbf.c +++ b/drivers/i2c/busses/i2c-mlxbf.c @@ -324,12 +324,6 @@ .name = (str) \ } -enum { - MLXBF_I2C_TIMING_100KHZ = 100000, - MLXBF_I2C_TIMING_400KHZ = 400000, - MLXBF_I2C_TIMING_1000KHZ = 1000000, -}; - enum { MLXBF_I2C_F_READ = BIT(0), MLXBF_I2C_F_WRITE = BIT(1), From 8c4ef23bbc60123d7b566672ec2da564f5cd545c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jan 2026 14:55:11 +0100 Subject: [PATCH 30/46] i2c: mlxbf: Use HZ_PER_KHZ in the driver Use predefined HZ_PER_MHZ constant where it is appropriate. Signed-off-by: Andy Shevchenko Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260112135603.4150952-3-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/i2c-mlxbf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-mlxbf.c b/drivers/i2c/busses/i2c-mlxbf.c index 9f0048044112..6c1cfe9ec8ac 100644 --- a/drivers/i2c/busses/i2c-mlxbf.c +++ b/drivers/i2c/busses/i2c-mlxbf.c @@ -66,7 +66,7 @@ * strongly dependent on the core clock frequency of the SMBus * Master. Default value is set to 400MHz. */ -#define MLXBF_I2C_TYU_PLL_OUT_FREQ (400 * 1000 * 1000) +#define MLXBF_I2C_TYU_PLL_OUT_FREQ (400 * HZ_PER_MHZ) /* Reference clock for Bluefield - 156 MHz. */ #define MLXBF_I2C_PLL_IN_FREQ 156250000ULL From 7b5073f9897f67af58b5bf17232bf60fc42e7ecd Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Fri, 26 Dec 2025 16:31:59 +0800 Subject: [PATCH 31/46] i2c: spacemit: drop useless spaces Previously, the I2C driver had an extra leading space in column 0 of included header lines. This commit removes the redundant whitespace. Signed-off-by: Troy Mitchell Reviewed-by: Alex Elder Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251226-k1-i2c-ilcr-v5-1-b5807b7dd0e6@linux.spacemit.com --- drivers/i2c/busses/i2c-k1.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c index 23661c7ddb67..a8310d6efe0c 100644 --- a/drivers/i2c/busses/i2c-k1.c +++ b/drivers/i2c/busses/i2c-k1.c @@ -4,13 +4,13 @@ */ #include - #include - #include - #include - #include - #include - #include - #include +#include +#include +#include +#include +#include +#include +#include /* spacemit i2c registers */ #define SPACEMIT_ICR 0x0 /* Control register */ From d70f60ad964dc773bfcf73d52099472228629cac Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 14 Jan 2026 09:17:50 +0100 Subject: [PATCH 32/46] i2c: designware: Remove not-going-to-be-supported code for Baikal SoC As noticed in the discussion [1] the Baikal SoC and platforms are not going to be finalized, hence remove stale code. Link: https://lore.kernel.org/lkml/22b92ddf-6321-41b5-8073-f9c7064d3432@infradead.org/ [1] Signed-off-by: Andy Shevchenko Acked-by: Mika Westerberg Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260114081954.252160-2-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/Kconfig | 1 - drivers/i2c/busses/i2c-designware-core.h | 1 - drivers/i2c/busses/i2c-designware-platdrv.c | 68 --------------------- 3 files changed, 70 deletions(-) diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index f2df105d69ac..c796b45d8d99 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -579,7 +579,6 @@ if I2C_DESIGNWARE_CORE config I2C_DESIGNWARE_PLATFORM tristate "Synopsys DesignWare Platform driver" depends on (ACPI && COMMON_CLK) || !ACPI - select MFD_SYSCON if MIPS_BAIKAL_T1 default I2C_DESIGNWARE_CORE help If you say yes to this option, support will be included for the diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index 2a7decc24931..cf0364079b55 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -314,7 +314,6 @@ struct dw_i2c_dev { #define ACCESS_POLLING BIT(3) #define MODEL_MSCC_OCELOT BIT(8) -#define MODEL_BAIKAL_BT1 BIT(9) #define MODEL_AMD_NAVI_GPU BIT(10) #define MODEL_WANGXUN_SP BIT(11) #define MODEL_MASK GENMASK(11, 8) diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 077b34535ec7..2e532f16691b 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -37,70 +37,6 @@ static u32 i2c_dw_get_clk_rate_khz(struct dw_i2c_dev *dev) return clk_get_rate(dev->clk) / HZ_PER_KHZ; } -#ifdef CONFIG_OF -#define BT1_I2C_CTL 0x100 -#define BT1_I2C_CTL_ADDR_MASK GENMASK(7, 0) -#define BT1_I2C_CTL_WR BIT(8) -#define BT1_I2C_CTL_GO BIT(31) -#define BT1_I2C_DI 0x104 -#define BT1_I2C_DO 0x108 - -static int bt1_i2c_read(void *context, unsigned int reg, unsigned int *val) -{ - struct dw_i2c_dev *dev = context; - int ret; - - /* - * Note these methods shouldn't ever fail because the system controller - * registers are memory mapped. We check the return value just in case. - */ - ret = regmap_write(dev->sysmap, BT1_I2C_CTL, - BT1_I2C_CTL_GO | (reg & BT1_I2C_CTL_ADDR_MASK)); - if (ret) - return ret; - - return regmap_read(dev->sysmap, BT1_I2C_DO, val); -} - -static int bt1_i2c_write(void *context, unsigned int reg, unsigned int val) -{ - struct dw_i2c_dev *dev = context; - int ret; - - ret = regmap_write(dev->sysmap, BT1_I2C_DI, val); - if (ret) - return ret; - - return regmap_write(dev->sysmap, BT1_I2C_CTL, - BT1_I2C_CTL_GO | BT1_I2C_CTL_WR | (reg & BT1_I2C_CTL_ADDR_MASK)); -} - -static const struct regmap_config bt1_i2c_cfg = { - .reg_bits = 32, - .val_bits = 32, - .reg_stride = 4, - .fast_io = true, - .reg_read = bt1_i2c_read, - .reg_write = bt1_i2c_write, - .max_register = DW_IC_COMP_TYPE, -}; - -static int bt1_i2c_request_regs(struct dw_i2c_dev *dev) -{ - dev->sysmap = syscon_node_to_regmap(dev->dev->of_node->parent); - if (IS_ERR(dev->sysmap)) - return PTR_ERR(dev->sysmap); - - dev->map = devm_regmap_init(dev->dev, NULL, dev, &bt1_i2c_cfg); - return PTR_ERR_OR_ZERO(dev->map); -} -#else -static int bt1_i2c_request_regs(struct dw_i2c_dev *dev) -{ - return -ENODEV; -} -#endif - static int dw_i2c_get_parent_regmap(struct dw_i2c_dev *dev) { dev->map = dev_get_regmap(dev->dev->parent, NULL); @@ -127,9 +63,6 @@ static int dw_i2c_plat_request_regs(struct dw_i2c_dev *dev) return dw_i2c_get_parent_regmap(dev); switch (dev->flags & MODEL_MASK) { - case MODEL_BAIKAL_BT1: - ret = bt1_i2c_request_regs(dev); - break; case MODEL_WANGXUN_SP: ret = dw_i2c_get_parent_regmap(dev); break; @@ -334,7 +267,6 @@ static void dw_i2c_plat_remove(struct platform_device *pdev) } static const struct of_device_id dw_i2c_of_match[] = { - { .compatible = "baikal,bt1-sys-i2c", .data = (void *)MODEL_BAIKAL_BT1 }, { .compatible = "mscc,ocelot-i2c", .data = (void *)MODEL_MSCC_OCELOT }, { .compatible = "snps,designware-i2c" }, {} From bdc0c634612e3a9ecf318387a2c9cc0321ebf212 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Wed, 17 Dec 2025 16:16:01 +0800 Subject: [PATCH 33/46] i2c: tegra: remove unused rst Since commit 56344e241c54 ("i2c: tegra: Fix reset error handling with ACPI") replace reset_control_reset() with device_reset(), the rst is no longer used, remove it. Signed-off-by: Guixin Liu Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251217081601.93856-1-kanie@linux.alibaba.com --- drivers/i2c/busses/i2c-tegra.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index e533460bccc3..9e39ac7a0a69 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -240,7 +240,6 @@ struct tegra_i2c_hw_feature { * @div_clk: clock reference for div clock of I2C controller * @clocks: array of I2C controller clocks * @nclocks: number of clocks in the array - * @rst: reset control for the I2C controller * @base: ioremapped registers cookie * @base_phys: physical base address of the I2C controller * @cont_id: I2C controller ID, used for packet header @@ -269,7 +268,6 @@ struct tegra_i2c_dev { struct i2c_adapter adapter; const struct tegra_i2c_hw_feature *hw; - struct reset_control *rst; unsigned int cont_id; unsigned int irq; From fc31008d5f57e71afa124550ca01b4399434435e Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 16 Dec 2025 22:30:26 -0800 Subject: [PATCH 34/46] i2c: rtl9300: remove const cast These casts are used to remove const for no good reason. Fix the types instead. Signed-off-by: Rosen Penev Reviewed-by: Chris Packham Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251217063027.37987-2-rosenp@gmail.com --- drivers/i2c/busses/i2c-rtl9300.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/i2c/busses/i2c-rtl9300.c b/drivers/i2c/busses/i2c-rtl9300.c index 4723e48cfe18..f2aa341a7cdd 100644 --- a/drivers/i2c/busses/i2c-rtl9300.c +++ b/drivers/i2c/busses/i2c-rtl9300.c @@ -129,7 +129,7 @@ static int rtl9310_i2c_select_scl(struct rtl9300_i2c *i2c, u8 scl) static int rtl9300_i2c_config_chan(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan) { - struct rtl9300_i2c_drv_data *drv_data; + const struct rtl9300_i2c_drv_data *drv_data; int ret; if (i2c->sda_num == chan->sda_num) @@ -139,7 +139,7 @@ static int rtl9300_i2c_config_chan(struct rtl9300_i2c *i2c, struct rtl9300_i2c_c if (ret) return ret; - drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev); + drv_data = device_get_match_data(i2c->dev); ret = drv_data->select_scl(i2c, i2c->scl_num); if (ret) return ret; @@ -372,7 +372,7 @@ static int rtl9300_i2c_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct rtl9300_i2c *i2c; struct fwnode_handle *child; - struct rtl9300_i2c_drv_data *drv_data; + const struct rtl9300_i2c_drv_data *drv_data; struct reg_field fields[F_NUM_FIELDS]; u32 clock_freq, scl_num, sda_num; int ret, i = 0; @@ -399,7 +399,7 @@ static int rtl9300_i2c_probe(struct platform_device *pdev) platform_set_drvdata(pdev, i2c); - drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev); + drv_data = device_get_match_data(i2c->dev); if (device_get_child_node_count(dev) > drv_data->max_nchan) return dev_err_probe(dev, -EINVAL, "Too many channels\n"); From f6551f7861aca09cb2fdf675d6bb9ca2ffa9038a Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 16 Dec 2025 22:30:27 -0800 Subject: [PATCH 35/46] i2c: rtl9300: use of instead of fwnode Avoids having to use to_of_node and just assign directly. This is an OF only driver anyway. Use _scoped for the for each loop to avoid refcount leaks. Signed-off-by: Rosen Penev Reviewed-by: Chris Packham Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251217063027.37987-3-rosenp@gmail.com --- drivers/i2c/busses/i2c-rtl9300.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/i2c/busses/i2c-rtl9300.c b/drivers/i2c/busses/i2c-rtl9300.c index f2aa341a7cdd..672cb978066d 100644 --- a/drivers/i2c/busses/i2c-rtl9300.c +++ b/drivers/i2c/busses/i2c-rtl9300.c @@ -371,7 +371,6 @@ static int rtl9300_i2c_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rtl9300_i2c *i2c; - struct fwnode_handle *child; const struct rtl9300_i2c_drv_data *drv_data; struct reg_field fields[F_NUM_FIELDS]; u32 clock_freq, scl_num, sda_num; @@ -415,15 +414,15 @@ static int rtl9300_i2c_probe(struct platform_device *pdev) return ret; i = 0; - device_for_each_child_node(dev, child) { + for_each_child_of_node_scoped(dev->of_node, child) { struct rtl9300_i2c_chan *chan = &i2c->chans[i]; struct i2c_adapter *adap = &chan->adap; - ret = fwnode_property_read_u32(child, "reg", &sda_num); + ret = of_property_read_u32(child, "reg", &sda_num); if (ret) return ret; - ret = fwnode_property_read_u32(child, "clock-frequency", &clock_freq); + ret = of_property_read_u32(child, "clock-frequency", &clock_freq); if (ret) clock_freq = I2C_MAX_STANDARD_MODE_FREQ; @@ -449,7 +448,7 @@ static int rtl9300_i2c_probe(struct platform_device *pdev) adap->retries = 3; adap->dev.parent = dev; i2c_set_adapdata(adap, chan); - adap->dev.of_node = to_of_node(child); + adap->dev.of_node = child; snprintf(adap->name, sizeof(adap->name), "%s SDA%d\n", dev_name(dev), sda_num); i++; From 518edab3ad4f61204af788b3dcf9ed4087cdc275 Mon Sep 17 00:00:00 2001 From: Artem Shimko Date: Thu, 11 Dec 2025 15:29:47 +0300 Subject: [PATCH 36/46] i2c: designware: Replace magic numbers with named constants Replace various magic numbers with properly named constants to improve code readability and maintainability. This includes constants for register access, timing adjustments, timeouts, FIFO parameters, and default values. This makes the code more self-documenting without altering any functionality. Signed-off-by: Artem Shimko Acked-by: Mika Westerberg Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251211122947.1469666-1-a.shimko.dev@gmail.com --- drivers/i2c/busses/i2c-designware-common.c | 29 ++++++++++++++-------- drivers/i2c/busses/i2c-designware-core.h | 13 ++++++++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index 5b1e8f74c4ac..6671e98691ee 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -12,6 +12,7 @@ #define DEFAULT_SYMBOL_NAMESPACE "I2C_DW_COMMON" #include +#include #include #include #include @@ -34,6 +35,10 @@ #include "i2c-designware-core.h" +#define DW_IC_DEFAULT_BUS_CAPACITANCE_pF 100 +#define DW_IC_ABORT_TIMEOUT_US 10 +#define DW_IC_BUSY_POLL_TIMEOUT_US (1 * USEC_PER_MSEC) + static const char *const abort_sources[] = { [ABRT_7B_ADDR_NOACK] = "slave address not acknowledged (7bit mode)", @@ -106,7 +111,7 @@ static int dw_reg_read_word(void *context, unsigned int reg, unsigned int *val) struct dw_i2c_dev *dev = context; *val = readw(dev->base + reg) | - (readw(dev->base + reg + 2) << 16); + (readw(dev->base + reg + DW_IC_REG_STEP_BYTES) << DW_IC_REG_WORD_SHIFT); return 0; } @@ -116,7 +121,7 @@ static int dw_reg_write_word(void *context, unsigned int reg, unsigned int val) struct dw_i2c_dev *dev = context; writew(val, dev->base + reg); - writew(val >> 16, dev->base + reg + 2); + writew(val >> DW_IC_REG_WORD_SHIFT, dev->base + reg + DW_IC_REG_STEP_BYTES); return 0; } @@ -165,7 +170,7 @@ int i2c_dw_init_regmap(struct dw_i2c_dev *dev) if (reg == swab32(DW_IC_COMP_TYPE_VALUE)) { map_cfg.reg_read = dw_reg_read_swab; map_cfg.reg_write = dw_reg_write_swab; - } else if (reg == (DW_IC_COMP_TYPE_VALUE & 0x0000ffff)) { + } else if (reg == lower_16_bits(DW_IC_COMP_TYPE_VALUE)) { map_cfg.reg_read = dw_reg_read_word; map_cfg.reg_write = dw_reg_write_word; } else if (reg != DW_IC_COMP_TYPE_VALUE) { @@ -384,7 +389,7 @@ int i2c_dw_fw_parse_and_configure(struct dw_i2c_dev *dev) i2c_parse_fw_timings(device, t, false); if (device_property_read_u32(device, "snps,bus-capacitance-pf", &dev->bus_capacitance_pF)) - dev->bus_capacitance_pF = 100; + dev->bus_capacitance_pF = DW_IC_DEFAULT_BUS_CAPACITANCE_pF; dev->clk_freq_optimized = device_property_read_bool(device, "snps,clk-freq-optimized"); @@ -539,8 +544,9 @@ void __i2c_dw_disable(struct dw_i2c_dev *dev) regmap_write(dev->map, DW_IC_ENABLE, enable | DW_IC_ENABLE_ABORT); ret = regmap_read_poll_timeout(dev->map, DW_IC_ENABLE, enable, - !(enable & DW_IC_ENABLE_ABORT), 10, - 100); + !(enable & DW_IC_ENABLE_ABORT), + DW_IC_ABORT_TIMEOUT_US, + 10 * DW_IC_ABORT_TIMEOUT_US); if (ret) dev_err(dev->dev, "timeout while trying to abort current transfer\n"); } @@ -552,7 +558,7 @@ void __i2c_dw_disable(struct dw_i2c_dev *dev) * in that case this test reads zero and exits the loop. */ regmap_read(dev->map, DW_IC_ENABLE_STATUS, &status); - if ((status & 1) == 0) + if (!(status & 1)) return; /* @@ -635,7 +641,8 @@ int i2c_dw_wait_bus_not_busy(struct dw_i2c_dev *dev) ret = regmap_read_poll_timeout(dev->map, DW_IC_STATUS, status, !(status & DW_IC_STATUS_ACTIVITY), - 1100, 20000); + DW_IC_BUSY_POLL_TIMEOUT_US, + 20 * DW_IC_BUSY_POLL_TIMEOUT_US); if (ret) { dev_warn(dev->dev, "timeout waiting for bus ready\n"); @@ -699,12 +706,12 @@ int i2c_dw_set_fifo_size(struct dw_i2c_dev *dev) if (ret) return ret; - tx_fifo_depth = ((param >> 16) & 0xff) + 1; - rx_fifo_depth = ((param >> 8) & 0xff) + 1; + tx_fifo_depth = FIELD_GET(DW_IC_FIFO_TX_FIELD, param) + 1; + rx_fifo_depth = FIELD_GET(DW_IC_FIFO_RX_FIELD, param) + 1; if (!dev->tx_fifo_depth) { dev->tx_fifo_depth = tx_fifo_depth; dev->rx_fifo_depth = rx_fifo_depth; - } else if (tx_fifo_depth >= 2) { + } else if (tx_fifo_depth >= DW_IC_FIFO_MIN_DEPTH) { dev->tx_fifo_depth = min_t(u32, dev->tx_fifo_depth, tx_fifo_depth); dev->rx_fifo_depth = min_t(u32, dev->rx_fifo_depth, diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index cf0364079b55..ba7e307f0791 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -41,6 +41,19 @@ #define DW_IC_DATA_CMD_DAT GENMASK(7, 0) #define DW_IC_DATA_CMD_FIRST_DATA_BYTE BIT(11) +/* + * Register access parameters + */ +#define DW_IC_REG_STEP_BYTES 2 +#define DW_IC_REG_WORD_SHIFT 16 + +/* + * FIFO depth configuration + */ +#define DW_IC_FIFO_TX_FIELD GENMASK(23, 16) +#define DW_IC_FIFO_RX_FIELD GENMASK(15, 8) +#define DW_IC_FIFO_MIN_DEPTH 2 + /* * Registers offset */ From 7021f6c038d0ecd1535e279dfd18dc4bf2dd899e Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 10 Dec 2025 13:02:58 +0900 Subject: [PATCH 37/46] i2c: amd-mp2: clean up amd_mp2_find_device() Rename the driver data pointer for consistency with the rest of the driver and drop a redundant cast. Signed-off-by: Johan Hovold Reviewed-by: Shyam Sundar S K Link: https://lore.kernel.org/r/20251210040258.60106-1-johan@kernel.org Signed-off-by: Andi Shyti --- drivers/i2c/busses/i2c-amd-mp2-pci.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/i2c/busses/i2c-amd-mp2-pci.c b/drivers/i2c/busses/i2c-amd-mp2-pci.c index 60edbabc2986..5b41d18b62d3 100644 --- a/drivers/i2c/busses/i2c-amd-mp2-pci.c +++ b/drivers/i2c/busses/i2c-amd-mp2-pci.c @@ -456,18 +456,20 @@ module_pci_driver(amd_mp2_pci_driver); struct amd_mp2_dev *amd_mp2_find_device(void) { + struct amd_mp2_dev *privdata; struct device *dev; struct pci_dev *pci_dev; - struct amd_mp2_dev *mp2_dev; dev = driver_find_next_device(&amd_mp2_pci_driver.driver, NULL); if (!dev) return NULL; pci_dev = to_pci_dev(dev); - mp2_dev = (struct amd_mp2_dev *)pci_get_drvdata(pci_dev); + privdata = pci_get_drvdata(pci_dev); + put_device(dev); - return mp2_dev; + + return privdata; } EXPORT_SYMBOL_GPL(amd_mp2_find_device); From 949f647eff76a1e759af7b1c0295db5b5640928a Mon Sep 17 00:00:00 2001 From: Carlos Song Date: Tue, 25 Nov 2025 16:47:18 +0800 Subject: [PATCH 38/46] i2c: imx-lpi2c: Add runtime PM support for IRQ and clock management on i.MX8QXP/8QM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On i.MX8QXP/8QM SoCs, both the lvds/mipi and lvds/mipi-lpi2c power domains must enter low-power mode during runtime suspend to achieve deep power savings. LPI2C resides in the lvds-lpi2c/mipi-lpi2c power domain, while its IRQ is routed through an irqsteer located in the lvds/mipi power domain. The LPI2C clock source comes from an LPCG within the lvds-lpi2c domain. For example, the hierarchy for lvds0 and lvds0-lpi2c0 domains is: ┌───────────────────────┐ │ pm-domain : lvds0 │ │ │ │ ┌──────────────┐ │ │ │ irqsteer │ │ │ └───────▲──────┘ │ │ │irq │ │ │ │ └────────────┼──────────┘ ┌────────────┼──────────┐ │ ┌───┼───┐ │ │ │lpi2c0 │ │ │ └───┬───┘clk │ │ ┌────────┼───────┐ │ │ │ LPCG │ │ │ └────────────────┘ │ │pm-domain:lvds0-lpi2c0 │ └───────────────────────┘ To allow these domains to power down in system runtime suspend: - All irqsteer clients must release IRQs. - All LPCG clients must disable and unprepare clocks. Thus, LPI2C must: - Free its IRQ during runtime suspend and re-request it on resume. - Disable and unprepare all clocks during runtime suspend and prepare and rne ble them on resume. This enables the lvds/mipi domains to enter deep low-power mode, significantly reducing power consumption compared to active mode. Signed-off-by: Carlos Song Reviewed-by: Frank Li Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251125084718.2156168-1-carlos.song@nxp.com --- drivers/i2c/busses/i2c-imx-lpi2c.c | 84 +++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 13 deletions(-) diff --git a/drivers/i2c/busses/i2c-imx-lpi2c.c b/drivers/i2c/busses/i2c-imx-lpi2c.c index 2a0962a0b441..be7134eefc2f 100644 --- a/drivers/i2c/busses/i2c-imx-lpi2c.c +++ b/drivers/i2c/busses/i2c-imx-lpi2c.c @@ -131,6 +131,7 @@ #define CHUNK_DATA 256 #define I2C_PM_TIMEOUT 10 /* ms */ +#define I2C_PM_LONG_TIMEOUT_MS 1000 /* Avoid dead lock caused by big clock prepare lock */ #define I2C_DMA_THRESHOLD 8 /* bytes */ enum lpi2c_imx_mode { @@ -148,6 +149,11 @@ enum lpi2c_imx_pincfg { FOUR_PIN_PP, }; +struct imx_lpi2c_hwdata { + bool need_request_free_irq; /* Needed by irqsteer */ + bool need_prepare_unprepare_clk; /* Needed by LPCG */ +}; + struct lpi2c_imx_dma { bool using_pio_mode; u8 rx_cmd_buf_len; @@ -186,6 +192,21 @@ struct lpi2c_imx_struct { bool can_use_dma; struct lpi2c_imx_dma *dma; struct i2c_client *target; + int irq; + const struct imx_lpi2c_hwdata *hwdata; +}; + +static const struct imx_lpi2c_hwdata imx7ulp_lpi2c_hwdata = { +}; + +static const struct imx_lpi2c_hwdata imx8qxp_lpi2c_hwdata = { + .need_request_free_irq = true, + .need_prepare_unprepare_clk = true, +}; + +static const struct imx_lpi2c_hwdata imx8qm_lpi2c_hwdata = { + .need_request_free_irq = true, + .need_prepare_unprepare_clk = true, }; #define lpi2c_imx_read_msr_poll_timeout(atomic, val, cond) \ @@ -1363,7 +1384,9 @@ static const struct i2c_algorithm lpi2c_imx_algo = { }; static const struct of_device_id lpi2c_imx_of_match[] = { - { .compatible = "fsl,imx7ulp-lpi2c" }, + { .compatible = "fsl,imx7ulp-lpi2c", .data = &imx7ulp_lpi2c_hwdata,}, + { .compatible = "fsl,imx8qxp-lpi2c", .data = &imx8qxp_lpi2c_hwdata,}, + { .compatible = "fsl,imx8qm-lpi2c", .data = &imx8qm_lpi2c_hwdata,}, { } }; MODULE_DEVICE_TABLE(of, lpi2c_imx_of_match); @@ -1374,19 +1397,23 @@ static int lpi2c_imx_probe(struct platform_device *pdev) struct resource *res; dma_addr_t phy_addr; unsigned int temp; - int irq, ret; + int ret; lpi2c_imx = devm_kzalloc(&pdev->dev, sizeof(*lpi2c_imx), GFP_KERNEL); if (!lpi2c_imx) return -ENOMEM; + lpi2c_imx->hwdata = of_device_get_match_data(&pdev->dev); + if (!lpi2c_imx->hwdata) + return -ENODEV; + lpi2c_imx->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(lpi2c_imx->base)) return PTR_ERR(lpi2c_imx->base); - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; + lpi2c_imx->irq = platform_get_irq(pdev, 0); + if (lpi2c_imx->irq < 0) + return lpi2c_imx->irq; lpi2c_imx->adapter.owner = THIS_MODULE; lpi2c_imx->adapter.algo = &lpi2c_imx_algo; @@ -1406,10 +1433,10 @@ static int lpi2c_imx_probe(struct platform_device *pdev) if (ret) lpi2c_imx->bitrate = I2C_MAX_STANDARD_MODE_FREQ; - ret = devm_request_irq(&pdev->dev, irq, lpi2c_imx_isr, IRQF_NO_SUSPEND, + ret = devm_request_irq(&pdev->dev, lpi2c_imx->irq, lpi2c_imx_isr, IRQF_NO_SUSPEND, pdev->name, lpi2c_imx); if (ret) - return dev_err_probe(&pdev->dev, ret, "can't claim irq %d\n", irq); + return dev_err_probe(&pdev->dev, ret, "can't claim irq %d\n", lpi2c_imx->irq); i2c_set_adapdata(&lpi2c_imx->adapter, lpi2c_imx); platform_set_drvdata(pdev, lpi2c_imx); @@ -1432,7 +1459,11 @@ static int lpi2c_imx_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, -EINVAL, "can't get I2C peripheral clock rate\n"); - pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_TIMEOUT); + if (lpi2c_imx->hwdata->need_prepare_unprepare_clk) + pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_LONG_TIMEOUT_MS); + else + pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_TIMEOUT); + pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); @@ -1487,8 +1518,16 @@ static void lpi2c_imx_remove(struct platform_device *pdev) static int __maybe_unused lpi2c_runtime_suspend(struct device *dev) { struct lpi2c_imx_struct *lpi2c_imx = dev_get_drvdata(dev); + bool need_prepare_unprepare_clk = lpi2c_imx->hwdata->need_prepare_unprepare_clk; + bool need_request_free_irq = lpi2c_imx->hwdata->need_request_free_irq; - clk_bulk_disable(lpi2c_imx->num_clks, lpi2c_imx->clks); + if (need_request_free_irq) + devm_free_irq(dev, lpi2c_imx->irq, lpi2c_imx); + + if (need_prepare_unprepare_clk) + clk_bulk_disable_unprepare(lpi2c_imx->num_clks, lpi2c_imx->clks); + else + clk_bulk_disable(lpi2c_imx->num_clks, lpi2c_imx->clks); pinctrl_pm_select_sleep_state(dev); return 0; @@ -1497,13 +1536,32 @@ static int __maybe_unused lpi2c_runtime_suspend(struct device *dev) static int __maybe_unused lpi2c_runtime_resume(struct device *dev) { struct lpi2c_imx_struct *lpi2c_imx = dev_get_drvdata(dev); + bool need_prepare_unprepare_clk = lpi2c_imx->hwdata->need_prepare_unprepare_clk; + bool need_request_free_irq = lpi2c_imx->hwdata->need_request_free_irq; int ret; pinctrl_pm_select_default_state(dev); - ret = clk_bulk_enable(lpi2c_imx->num_clks, lpi2c_imx->clks); - if (ret) { - dev_err(dev, "failed to enable I2C clock, ret=%d\n", ret); - return ret; + if (need_prepare_unprepare_clk) { + ret = clk_bulk_prepare_enable(lpi2c_imx->num_clks, lpi2c_imx->clks); + if (ret) { + dev_err(dev, "failed to enable I2C clock, ret=%d\n", ret); + return ret; + } + } else { + ret = clk_bulk_enable(lpi2c_imx->num_clks, lpi2c_imx->clks); + if (ret) { + dev_err(dev, "failed to enable clock %d\n", ret); + return ret; + } + } + + if (need_request_free_irq) { + ret = devm_request_irq(dev, lpi2c_imx->irq, lpi2c_imx_isr, IRQF_NO_SUSPEND, + dev_name(dev), lpi2c_imx); + if (ret) { + dev_err(dev, "can't claim irq %d\n", lpi2c_imx->irq); + return ret; + } } return 0; From fae88e03f45893cfb63c608761220a489cc6e25e Mon Sep 17 00:00:00 2001 From: David Laight Date: Wed, 19 Nov 2025 22:41:16 +0000 Subject: [PATCH 39/46] drivers/i2c/busses: use min() instead of min_t() min_t(u8, a, b) casts both its arguments to u8 potentially discarding signifinact bits. Use min(a, b) instead as it cannot discard significant bits. Detected by an extra check added to min_t(). Signed-off-by: David Laight Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251119224140.8616-21-david.laight.linux@gmail.com --- drivers/i2c/busses/i2c-designware-master.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index 15b3a46f0132..18f9a587732c 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -575,7 +575,7 @@ i2c_dw_recv_len(struct dw_i2c_dev *dev, u8 len) * after receiving the first byte. */ len += (flags & I2C_CLIENT_PEC) ? 2 : 1; - dev->tx_buf_len = len - min_t(u8, len, dev->rx_outstanding); + dev->tx_buf_len = len - min(len, dev->rx_outstanding); msgs[dev->msg_read_idx].len = len; msgs[dev->msg_read_idx].flags &= ~I2C_M_RECV_LEN; From de284988c270cc16a3fb41f8f6955394d4af2a12 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Fri, 16 Jan 2026 14:38:55 +0100 Subject: [PATCH 40/46] dt-bindings: eeprom: at24: Add compatible for Puya P24C128F Add the compatible for an 128Kb EEPROM from Puya. Signed-off-by: Luca Weiss Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260116-milos-cci-v1-1-28e01128da9c@fairphone.com Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/eeprom/at24.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/eeprom/at24.yaml b/Documentation/devicetree/bindings/eeprom/at24.yaml index 95ac2f15f601..ef88f46928a4 100644 --- a/Documentation/devicetree/bindings/eeprom/at24.yaml +++ b/Documentation/devicetree/bindings/eeprom/at24.yaml @@ -142,6 +142,7 @@ properties: - enum: - giantec,gt24p128e - giantec,gt24p128f + - puya,p24c128f - renesas,r1ex24128 - samsung,s524ad0xd1 - const: atmel,24c128 From 2c7aa2683bfa80670dc45d310d259544240daea4 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 14 Jan 2026 09:17:51 +0100 Subject: [PATCH 41/46] i2c: designware: Use device_is_compatible() instead of custom approach We use MODEL_MSCC_OCELOT effectively as a flag for comparing against "compatible" property. Use device_is_compatible() directly to make it clear. Signed-off-by: Andy Shevchenko Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260114081954.252160-3-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/i2c-designware-common.c | 6 +----- drivers/i2c/busses/i2c-designware-core.h | 1 - drivers/i2c/busses/i2c-designware-platdrv.c | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index 6671e98691ee..6330cc2becce 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -243,14 +243,10 @@ static void i2c_dw_of_configure(struct device *device) struct platform_device *pdev = to_platform_device(device); struct dw_i2c_dev *dev = dev_get_drvdata(device); - switch (dev->flags & MODEL_MASK) { - case MODEL_MSCC_OCELOT: + if (device_is_compatible(dev->dev, "mscc,ocelot-i2c")) { dev->ext = devm_platform_ioremap_resource(pdev, 1); if (!IS_ERR(dev->ext)) dev->set_sda_hold_time = mscc_twi_set_sda_hold_time; - break; - default: - break; } } diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index ba7e307f0791..67f13daf37b3 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -326,7 +326,6 @@ struct dw_i2c_dev { #define ARBITRATION_SEMAPHORE BIT(2) #define ACCESS_POLLING BIT(3) -#define MODEL_MSCC_OCELOT BIT(8) #define MODEL_AMD_NAVI_GPU BIT(10) #define MODEL_WANGXUN_SP BIT(11) #define MODEL_MASK GENMASK(11, 8) diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 2e532f16691b..4e6fe3b55322 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -267,7 +267,7 @@ static void dw_i2c_plat_remove(struct platform_device *pdev) } static const struct of_device_id dw_i2c_of_match[] = { - { .compatible = "mscc,ocelot-i2c", .data = (void *)MODEL_MSCC_OCELOT }, + { .compatible = "mscc,ocelot-i2c" }, { .compatible = "snps,designware-i2c" }, {} }; From 6062443a0593a0e1d36c3af939dde170a396f1a0 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 20 Jan 2026 14:07:25 +0100 Subject: [PATCH 42/46] i2c: designware: Combine some of the common functions The adapter can be registered just in the core instead of separately in the master and slave drivers. The same applies to the interrupt. The dedicated "target only" (slave only) configuration for this controller will be removed so that host mode (master mode) will always be supported together with the target mode. Therefore the descrption for the "target only" configuration that appears in the "name" sysfs attribute file is also dropped while at it. Signed-off-by: Heikki Krogerus Acked-by: Mika Westerberg Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260120130729.1679560-2-heikki.krogerus@linux.intel.com --- drivers/i2c/busses/i2c-designware-common.c | 108 +++++++++++++++++++-- drivers/i2c/busses/i2c-designware-core.h | 11 ++- drivers/i2c/busses/i2c-designware-master.c | 97 +++--------------- drivers/i2c/busses/i2c-designware-slave.c | 53 ++-------- 4 files changed, 126 insertions(+), 143 deletions(-) diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index 6330cc2becce..7066d05e5e43 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -136,7 +136,7 @@ static int dw_reg_write_word(void *context, unsigned int reg, unsigned int val) * * Return: 0 on success, or negative errno otherwise. */ -int i2c_dw_init_regmap(struct dw_i2c_dev *dev) +static int i2c_dw_init_regmap(struct dw_i2c_dev *dev) { struct regmap_config map_cfg = { .reg_bits = 32, @@ -458,7 +458,7 @@ u32 i2c_dw_scl_lcnt(struct dw_i2c_dev *dev, unsigned int reg, u32 ic_clk, return DIV_ROUND_CLOSEST_ULL((u64)ic_clk * (tLOW + tf), MICRO) - 1 + offset; } -int i2c_dw_set_sda_hold(struct dw_i2c_dev *dev) +static int i2c_dw_set_sda_hold(struct dw_i2c_dev *dev) { unsigned int reg; int ret; @@ -675,7 +675,7 @@ int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev) return -EIO; } -int i2c_dw_set_fifo_size(struct dw_i2c_dev *dev) +static int i2c_dw_set_fifo_size(struct dw_i2c_dev *dev) { u32 tx_fifo_depth, rx_fifo_depth; unsigned int param; @@ -744,19 +744,113 @@ void i2c_dw_disable(struct dw_i2c_dev *dev) } EXPORT_SYMBOL_GPL(i2c_dw_disable); +static irqreturn_t i2c_dw_isr(int this_irq, void *dev_id) +{ + struct dw_i2c_dev *dev = dev_id; + + if (dev->mode == DW_IC_SLAVE) + return i2c_dw_isr_slave(dev); + + return i2c_dw_isr_master(dev); +} + +static const struct i2c_algorithm i2c_dw_algo = { + .xfer = i2c_dw_xfer, + .functionality = i2c_dw_func, +#if IS_ENABLED(CONFIG_I2C_SLAVE) + .reg_slave = i2c_dw_reg_slave, + .unreg_slave = i2c_dw_unreg_slave, +#endif +}; + +static const struct i2c_adapter_quirks i2c_dw_quirks = { + .flags = I2C_AQ_NO_ZERO_LEN, +}; + int i2c_dw_probe(struct dw_i2c_dev *dev) { + struct i2c_adapter *adap = &dev->adapter; + unsigned long irq_flags; + int ret; + device_set_node(&dev->adapter.dev, dev_fwnode(dev->dev)); + ret = i2c_dw_init_regmap(dev); + if (ret) + return ret; + + ret = i2c_dw_set_sda_hold(dev); + if (ret) + return ret; + + ret = i2c_dw_set_fifo_size(dev); + if (ret) + return ret; + switch (dev->mode) { case DW_IC_SLAVE: - return i2c_dw_probe_slave(dev); + ret = i2c_dw_probe_slave(dev); + break; case DW_IC_MASTER: - return i2c_dw_probe_master(dev); + ret = i2c_dw_probe_master(dev); + break; default: - dev_err(dev->dev, "Wrong operation mode: %d\n", dev->mode); - return -EINVAL; + ret = -EINVAL; + break; } + if (ret) + return ret; + + ret = dev->init(dev); + if (ret) + return ret; + + if (!adap->name[0]) + strscpy(adap->name, "Synopsys DesignWare I2C adapter"); + + adap->retries = 3; + adap->algo = &i2c_dw_algo; + adap->quirks = &i2c_dw_quirks; + adap->dev.parent = dev->dev; + i2c_set_adapdata(adap, dev); + + /* + * REVISIT: The mode check may not be necessary. + * For now keeping the flags as they were originally. + */ + if (dev->mode == DW_IC_SLAVE) + irq_flags = IRQF_SHARED; + else if (dev->flags & ACCESS_NO_IRQ_SUSPEND) + irq_flags = IRQF_NO_SUSPEND; + else + irq_flags = IRQF_SHARED | IRQF_COND_SUSPEND; + + ret = i2c_dw_acquire_lock(dev); + if (ret) + return ret; + + __i2c_dw_write_intr_mask(dev, 0); + i2c_dw_release_lock(dev); + + if (!(dev->flags & ACCESS_POLLING)) { + ret = devm_request_irq(dev->dev, dev->irq, i2c_dw_isr, + irq_flags, dev_name(dev->dev), dev); + if (ret) + return ret; + } + + /* + * Increment PM usage count during adapter registration in order to + * avoid possible spurious runtime suspend when adapter device is + * registered to the device core and immediate resume in case bus has + * registered I2C slaves that do I2C transfers in their probe. + */ + ACQUIRE(pm_runtime_noresume, pm)(dev->dev); + ret = ACQUIRE_ERR(pm_runtime_noresume, &pm); + if (ret) + return ret; + + return i2c_add_numbered_adapter(adap); } EXPORT_SYMBOL_GPL(i2c_dw_probe); diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index 67f13daf37b3..9727143f5419 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -344,20 +345,18 @@ struct i2c_dw_semaphore_callbacks { int (*probe)(struct dw_i2c_dev *dev); }; -int i2c_dw_init_regmap(struct dw_i2c_dev *dev); u32 i2c_dw_scl_hcnt(struct dw_i2c_dev *dev, unsigned int reg, u32 ic_clk, u32 tSYMBOL, u32 tf, int offset); u32 i2c_dw_scl_lcnt(struct dw_i2c_dev *dev, unsigned int reg, u32 ic_clk, u32 tLOW, u32 tf, int offset); -int i2c_dw_set_sda_hold(struct dw_i2c_dev *dev); u32 i2c_dw_clk_rate(struct dw_i2c_dev *dev); int i2c_dw_prepare_clk(struct dw_i2c_dev *dev, bool prepare); int i2c_dw_acquire_lock(struct dw_i2c_dev *dev); void i2c_dw_release_lock(struct dw_i2c_dev *dev); int i2c_dw_wait_bus_not_busy(struct dw_i2c_dev *dev); int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev); -int i2c_dw_set_fifo_size(struct dw_i2c_dev *dev); u32 i2c_dw_func(struct i2c_adapter *adap); +irqreturn_t i2c_dw_isr_master(struct dw_i2c_dev *dev); extern const struct dev_pm_ops i2c_dw_dev_pm_ops; @@ -397,12 +396,18 @@ void i2c_dw_disable(struct dw_i2c_dev *dev); extern void i2c_dw_configure_master(struct dw_i2c_dev *dev); extern int i2c_dw_probe_master(struct dw_i2c_dev *dev); +int i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); + #if IS_ENABLED(CONFIG_I2C_SLAVE) extern void i2c_dw_configure_slave(struct dw_i2c_dev *dev); extern int i2c_dw_probe_slave(struct dw_i2c_dev *dev); +irqreturn_t i2c_dw_isr_slave(struct dw_i2c_dev *dev); +int i2c_dw_reg_slave(struct i2c_client *client); +int i2c_dw_unreg_slave(struct i2c_client *client); #else static inline void i2c_dw_configure_slave(struct dw_i2c_dev *dev) { } static inline int i2c_dw_probe_slave(struct dw_i2c_dev *dev) { return -EINVAL; } +static inline irqreturn_t i2c_dw_isr_slave(struct dw_i2c_dev *dev) { return IRQ_NONE; } #endif static inline void i2c_dw_configure(struct dw_i2c_dev *dev) diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index 18f9a587732c..fdd75e300176 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -191,10 +191,6 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) dev->hs_hcnt, dev->hs_lcnt); } - ret = i2c_dw_set_sda_hold(dev); - if (ret) - return ret; - dev_dbg(dev->dev, "Bus speed: %s\n", i2c_freq_mode_string(t->bus_freq_hz)); return 0; } @@ -353,9 +349,8 @@ static int i2c_dw_status(struct dw_i2c_dev *dev) * Initiate and continue master read/write transaction with polling * based transfer routine afterward write messages into the Tx buffer. */ -static int amd_i2c_dw_xfer_quirk(struct i2c_adapter *adap, struct i2c_msg *msgs, int num_msgs) +static int amd_i2c_dw_xfer_quirk(struct dw_i2c_dev *dev, struct i2c_msg *msgs, int num_msgs) { - struct dw_i2c_dev *dev = i2c_get_adapdata(adap); int msg_wrt_idx, msg_itr_lmt, buf_len, data_idx; int cmd = 0, status; u8 *tx_buf; @@ -752,9 +747,8 @@ tx_aborted: * Interrupt service routine. This gets called whenever an I2C master interrupt * occurs. */ -static irqreturn_t i2c_dw_isr(int this_irq, void *dev_id) +irqreturn_t i2c_dw_isr_master(struct dw_i2c_dev *dev) { - struct dw_i2c_dev *dev = dev_id; unsigned int stat, enabled; regmap_read(dev->map, DW_IC_ENABLE, &enabled); @@ -815,9 +809,8 @@ static int i2c_dw_wait_transfer(struct dw_i2c_dev *dev) * Prepare controller for a transaction and call i2c_dw_xfer_msg. */ static int -i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num) { - struct dw_i2c_dev *dev = i2c_get_adapdata(adap); int ret; dev_dbg(dev->dev, "%s: msgs: %d\n", __func__, num); @@ -908,19 +901,15 @@ done_nolock: return ret; } -static const struct i2c_algorithm i2c_dw_algo = { - .xfer = i2c_dw_xfer, - .functionality = i2c_dw_func, -}; +int i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct dw_i2c_dev *dev = i2c_get_adapdata(adap); -static const struct i2c_algorithm amd_i2c_dw_algo = { - .xfer = amd_i2c_dw_xfer_quirk, - .functionality = i2c_dw_func, -}; + if ((dev->flags & MODEL_MASK) == MODEL_AMD_NAVI_GPU) + return amd_i2c_dw_xfer_quirk(dev, msgs, num); -static const struct i2c_adapter_quirks i2c_dw_quirks = { - .flags = I2C_AQ_NO_ZERO_LEN, -}; + return i2c_dw_xfer_common(dev, msgs, num); +} void i2c_dw_configure_master(struct dw_i2c_dev *dev) { @@ -1005,8 +994,6 @@ static int i2c_dw_init_recovery_info(struct dw_i2c_dev *dev) int i2c_dw_probe_master(struct dw_i2c_dev *dev) { - struct i2c_adapter *adap = &dev->adapter; - unsigned long irq_flags; unsigned int ic_con; int ret; @@ -1014,18 +1001,10 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev) dev->init = i2c_dw_init_master; - ret = i2c_dw_init_regmap(dev); - if (ret) - return ret; - ret = i2c_dw_set_timings_master(dev); if (ret) return ret; - ret = i2c_dw_set_fifo_size(dev); - if (ret) - return ret; - /* Lock the bus for accessing DW_IC_CON */ ret = i2c_dw_acquire_lock(dev); if (ret) @@ -1045,61 +1024,7 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev) if (ic_con & DW_IC_CON_BUS_CLEAR_CTRL) dev->master_cfg |= DW_IC_CON_BUS_CLEAR_CTRL; - ret = dev->init(dev); - if (ret) - return ret; - - if (!adap->name[0]) - scnprintf(adap->name, sizeof(adap->name), - "Synopsys DesignWare I2C adapter"); - adap->retries = 3; - if ((dev->flags & MODEL_MASK) == MODEL_AMD_NAVI_GPU) - adap->algo = &amd_i2c_dw_algo; - else - adap->algo = &i2c_dw_algo; - adap->quirks = &i2c_dw_quirks; - adap->dev.parent = dev->dev; - i2c_set_adapdata(adap, dev); - - if (dev->flags & ACCESS_NO_IRQ_SUSPEND) { - irq_flags = IRQF_NO_SUSPEND; - } else { - irq_flags = IRQF_SHARED | IRQF_COND_SUSPEND; - } - - ret = i2c_dw_acquire_lock(dev); - if (ret) - return ret; - - __i2c_dw_write_intr_mask(dev, 0); - i2c_dw_release_lock(dev); - - if (!(dev->flags & ACCESS_POLLING)) { - ret = devm_request_irq(dev->dev, dev->irq, i2c_dw_isr, - irq_flags, dev_name(dev->dev), dev); - if (ret) - return dev_err_probe(dev->dev, ret, - "failure requesting irq %i: %d\n", - dev->irq, ret); - } - - ret = i2c_dw_init_recovery_info(dev); - if (ret) - return ret; - - /* - * Increment PM usage count during adapter registration in order to - * avoid possible spurious runtime suspend when adapter device is - * registered to the device core and immediate resume in case bus has - * registered I2C slaves that do I2C transfers in their probe. - */ - pm_runtime_get_noresume(dev->dev); - ret = i2c_add_numbered_adapter(adap); - if (ret) - dev_err(dev->dev, "failure adding adapter: %d\n", ret); - pm_runtime_put_noidle(dev->dev); - - return ret; + return i2c_dw_init_recovery_info(dev); } MODULE_DESCRIPTION("Synopsys DesignWare I2C bus master adapter"); diff --git a/drivers/i2c/busses/i2c-designware-slave.c b/drivers/i2c/busses/i2c-designware-slave.c index 1995be79544d..c0baf53e97d8 100644 --- a/drivers/i2c/busses/i2c-designware-slave.c +++ b/drivers/i2c/busses/i2c-designware-slave.c @@ -63,7 +63,7 @@ static int i2c_dw_init_slave(struct dw_i2c_dev *dev) return 0; } -static int i2c_dw_reg_slave(struct i2c_client *slave) +int i2c_dw_reg_slave(struct i2c_client *slave) { struct dw_i2c_dev *dev = i2c_get_adapdata(slave->adapter); @@ -88,7 +88,7 @@ static int i2c_dw_reg_slave(struct i2c_client *slave) return 0; } -static int i2c_dw_unreg_slave(struct i2c_client *slave) +int i2c_dw_unreg_slave(struct i2c_client *slave) { struct dw_i2c_dev *dev = i2c_get_adapdata(slave->adapter); @@ -152,9 +152,8 @@ static u32 i2c_dw_read_clear_intrbits_slave(struct dw_i2c_dev *dev) * Interrupt service routine. This gets called whenever an I2C slave interrupt * occurs. */ -static irqreturn_t i2c_dw_isr_slave(int this_irq, void *dev_id) +irqreturn_t i2c_dw_isr_slave(struct dw_i2c_dev *dev) { - struct dw_i2c_dev *dev = dev_id; unsigned int raw_stat, stat, enabled, tmp; u8 val = 0, slave_activity; @@ -217,12 +216,6 @@ static irqreturn_t i2c_dw_isr_slave(int this_irq, void *dev_id) return IRQ_HANDLED; } -static const struct i2c_algorithm i2c_dw_algo = { - .functionality = i2c_dw_func, - .reg_slave = i2c_dw_reg_slave, - .unreg_slave = i2c_dw_unreg_slave, -}; - void i2c_dw_configure_slave(struct dw_i2c_dev *dev) { dev->functionality = I2C_FUNC_SLAVE; @@ -236,46 +229,12 @@ EXPORT_SYMBOL_GPL(i2c_dw_configure_slave); int i2c_dw_probe_slave(struct dw_i2c_dev *dev) { - struct i2c_adapter *adap = &dev->adapter; - int ret; + if (dev->flags & ACCESS_POLLING) + return -EOPNOTSUPP; dev->init = i2c_dw_init_slave; - ret = i2c_dw_init_regmap(dev); - if (ret) - return ret; - - ret = i2c_dw_set_sda_hold(dev); - if (ret) - return ret; - - ret = i2c_dw_set_fifo_size(dev); - if (ret) - return ret; - - ret = dev->init(dev); - if (ret) - return ret; - - snprintf(adap->name, sizeof(adap->name), - "Synopsys DesignWare I2C Slave adapter"); - adap->retries = 3; - adap->algo = &i2c_dw_algo; - adap->dev.parent = dev->dev; - i2c_set_adapdata(adap, dev); - - ret = devm_request_irq(dev->dev, dev->irq, i2c_dw_isr_slave, - IRQF_SHARED, dev_name(dev->dev), dev); - if (ret) - return dev_err_probe(dev->dev, ret, - "failure requesting IRQ %i: %d\n", - dev->irq, ret); - - ret = i2c_add_numbered_adapter(adap); - if (ret) - dev_err(dev->dev, "failure adding adapter: %d\n", ret); - - return ret; + return 0; } MODULE_AUTHOR("Luis Oliveira "); From 38fa29b01a6a295aedb69d1bbdad70acd7d204c6 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 20 Jan 2026 14:07:26 +0100 Subject: [PATCH 43/46] i2c: designware: Combine the init functions Providing a single function for controller initialisation. The controller initialisation has the same steps for master and slave modes, except the timing parameters are only needed in master mode. Signed-off-by: Heikki Krogerus Acked-by: Mika Westerberg Reviewed-by: Andy Shevchenko Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260120130729.1679560-3-heikki.krogerus@linux.intel.com --- drivers/i2c/busses/i2c-designware-amdisp.c | 4 +- drivers/i2c/busses/i2c-designware-common.c | 81 +++++++++++++++++++++- drivers/i2c/busses/i2c-designware-core.h | 3 +- drivers/i2c/busses/i2c-designware-master.c | 70 +------------------ drivers/i2c/busses/i2c-designware-slave.c | 44 ------------ 5 files changed, 85 insertions(+), 117 deletions(-) diff --git a/drivers/i2c/busses/i2c-designware-amdisp.c b/drivers/i2c/busses/i2c-designware-amdisp.c index 450793d5f839..ec9259dd2a4f 100644 --- a/drivers/i2c/busses/i2c-designware-amdisp.c +++ b/drivers/i2c/busses/i2c-designware-amdisp.c @@ -163,8 +163,8 @@ static int amd_isp_dw_i2c_plat_runtime_resume(struct device *dev) if (!i_dev->shared_with_punit) i2c_dw_prepare_clk(i_dev, true); - if (i_dev->init) - i_dev->init(i_dev); + + i2c_dw_init(i_dev); return 0; } diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index 7066d05e5e43..17affdecbe30 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -359,6 +359,83 @@ static inline u32 i2c_dw_acpi_round_bus_speed(struct device *device) { return 0; #endif /* CONFIG_ACPI */ +static void i2c_dw_configure_mode(struct dw_i2c_dev *dev) +{ + switch (dev->mode) { + case DW_IC_MASTER: + regmap_write(dev->map, DW_IC_TX_TL, dev->tx_fifo_depth / 2); + regmap_write(dev->map, DW_IC_RX_TL, 0); + regmap_write(dev->map, DW_IC_CON, dev->master_cfg); + break; + case DW_IC_SLAVE: + regmap_write(dev->map, DW_IC_TX_TL, 0); + regmap_write(dev->map, DW_IC_RX_TL, 0); + regmap_write(dev->map, DW_IC_CON, dev->slave_cfg); + regmap_write(dev->map, DW_IC_INTR_MASK, DW_IC_INTR_SLAVE_MASK); + break; + default: + return; + } +} + +static void i2c_dw_write_timings(struct dw_i2c_dev *dev) +{ + /* Write standard speed timing parameters */ + regmap_write(dev->map, DW_IC_SS_SCL_HCNT, dev->ss_hcnt); + regmap_write(dev->map, DW_IC_SS_SCL_LCNT, dev->ss_lcnt); + + /* Write fast mode/fast mode plus timing parameters */ + regmap_write(dev->map, DW_IC_FS_SCL_HCNT, dev->fs_hcnt); + regmap_write(dev->map, DW_IC_FS_SCL_LCNT, dev->fs_lcnt); + + /* Write high speed timing parameters if supported */ + if (dev->hs_hcnt && dev->hs_lcnt) { + regmap_write(dev->map, DW_IC_HS_SCL_HCNT, dev->hs_hcnt); + regmap_write(dev->map, DW_IC_HS_SCL_LCNT, dev->hs_lcnt); + } +} + +/** + * i2c_dw_init() - Initialize the DesignWare I2C hardware + * @dev: device private data + * + * This functions configures and enables the DesigWare I2C hardware. + * + * Return: 0 on success, or negative errno otherwise. + */ +int i2c_dw_init(struct dw_i2c_dev *dev) +{ + int ret; + + ret = i2c_dw_acquire_lock(dev); + if (ret) + return ret; + + /* Disable the adapter */ + __i2c_dw_disable(dev); + + /* + * Mask SMBus interrupts to block storms from broken + * firmware that leaves IC_SMBUS=1; the handler never + * services them. + */ + regmap_write(dev->map, DW_IC_SMBUS_INTR_MASK, 0); + + if (dev->mode == DW_IC_MASTER) + i2c_dw_write_timings(dev); + + /* Write SDA hold time if supported */ + if (dev->sda_hold_time) + regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time); + + i2c_dw_configure_mode(dev); + + i2c_dw_release_lock(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(i2c_dw_init); + static void i2c_dw_adjust_bus_speed(struct dw_i2c_dev *dev) { u32 acpi_speed = i2c_dw_acpi_round_bus_speed(dev->dev); @@ -801,7 +878,7 @@ int i2c_dw_probe(struct dw_i2c_dev *dev) if (ret) return ret; - ret = dev->init(dev); + ret = i2c_dw_init(dev); if (ret) return ret; @@ -894,7 +971,7 @@ static int i2c_dw_runtime_resume(struct device *device) if (!dev->shared_with_punit) i2c_dw_prepare_clk(dev, true); - dev->init(dev); + i2c_dw_init(dev); return 0; } diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index 9727143f5419..63a59bb03163 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -253,7 +253,6 @@ struct reset_control; * @semaphore_idx: Index of table with semaphore type attached to the bus. It's * -1 if there is no semaphore. * @shared_with_punit: true if this bus is shared with the SoC's PUNIT - * @init: function to initialize the I2C hardware * @set_sda_hold_time: callback to retrieve IP specific SDA hold timing * @mode: operation mode - DW_IC_MASTER or DW_IC_SLAVE * @rinfo: I²C GPIO recovery information @@ -314,7 +313,6 @@ struct dw_i2c_dev { void (*release_lock)(void); int semaphore_idx; bool shared_with_punit; - int (*init)(struct dw_i2c_dev *dev); int (*set_sda_hold_time)(struct dw_i2c_dev *dev); int mode; struct i2c_bus_recovery_info rinfo; @@ -419,6 +417,7 @@ static inline void i2c_dw_configure(struct dw_i2c_dev *dev) } int i2c_dw_probe(struct dw_i2c_dev *dev); +int i2c_dw_init(struct dw_i2c_dev *dev); #if IS_ENABLED(CONFIG_I2C_DESIGNWARE_BAYTRAIL) int i2c_dw_baytrail_probe_lock_support(struct dw_i2c_dev *dev); diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index fdd75e300176..22c1bb463c2c 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -31,16 +31,6 @@ #define AMD_TIMEOUT_MAX_US 250 #define AMD_MASTERCFG_MASK GENMASK(15, 0) -static void i2c_dw_configure_fifo_master(struct dw_i2c_dev *dev) -{ - /* Configure Tx/Rx FIFO threshold levels */ - regmap_write(dev->map, DW_IC_TX_TL, dev->tx_fifo_depth / 2); - regmap_write(dev->map, DW_IC_RX_TL, 0); - - /* Configure the I2C master */ - regmap_write(dev->map, DW_IC_CON, dev->master_cfg); -} - static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) { unsigned int comp_param1; @@ -195,58 +185,6 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) return 0; } -/** - * i2c_dw_init_master() - Initialize the DesignWare I2C master hardware - * @dev: device private data - * - * This functions configures and enables the I2C master. - * This function is called during I2C init function, and in case of timeout at - * run time. - * - * Return: 0 on success, or negative errno otherwise. - */ -static int i2c_dw_init_master(struct dw_i2c_dev *dev) -{ - int ret; - - ret = i2c_dw_acquire_lock(dev); - if (ret) - return ret; - - /* Disable the adapter */ - __i2c_dw_disable(dev); - - /* - * Mask SMBus interrupts to block storms from broken - * firmware that leaves IC_SMBUS=1; the handler never - * services them. - */ - regmap_write(dev->map, DW_IC_SMBUS_INTR_MASK, 0); - - /* Write standard speed timing parameters */ - regmap_write(dev->map, DW_IC_SS_SCL_HCNT, dev->ss_hcnt); - regmap_write(dev->map, DW_IC_SS_SCL_LCNT, dev->ss_lcnt); - - /* Write fast mode/fast mode plus timing parameters */ - regmap_write(dev->map, DW_IC_FS_SCL_HCNT, dev->fs_hcnt); - regmap_write(dev->map, DW_IC_FS_SCL_LCNT, dev->fs_lcnt); - - /* Write high speed timing parameters if supported */ - if (dev->hs_hcnt && dev->hs_lcnt) { - regmap_write(dev->map, DW_IC_HS_SCL_HCNT, dev->hs_hcnt); - regmap_write(dev->map, DW_IC_HS_SCL_LCNT, dev->hs_lcnt); - } - - /* Write SDA hold time if supported */ - if (dev->sda_hold_time) - regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time); - - i2c_dw_configure_fifo_master(dev); - i2c_dw_release_lock(dev); - - return 0; -} - static void i2c_dw_xfer_init(struct dw_i2c_dev *dev) { struct i2c_msg *msgs = dev->msgs; @@ -843,9 +781,9 @@ i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num) ret = i2c_dw_wait_transfer(dev); if (ret) { dev_err(dev->dev, "controller timed out\n"); - /* i2c_dw_init_master() implicitly disables the adapter */ + /* i2c_dw_init() implicitly disables the adapter */ i2c_recover_bus(&dev->adapter); - i2c_dw_init_master(dev); + i2c_dw_init(dev); goto done; } @@ -950,7 +888,7 @@ static void i2c_dw_unprepare_recovery(struct i2c_adapter *adap) i2c_dw_prepare_clk(dev, true); reset_control_deassert(dev->rst); - i2c_dw_init_master(dev); + i2c_dw_init(dev); } static int i2c_dw_init_recovery_info(struct dw_i2c_dev *dev) @@ -999,8 +937,6 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev) init_completion(&dev->cmd_complete); - dev->init = i2c_dw_init_master; - ret = i2c_dw_set_timings_master(dev); if (ret) return ret; diff --git a/drivers/i2c/busses/i2c-designware-slave.c b/drivers/i2c/busses/i2c-designware-slave.c index c0baf53e97d8..9fc8faa33735 100644 --- a/drivers/i2c/busses/i2c-designware-slave.c +++ b/drivers/i2c/busses/i2c-designware-slave.c @@ -21,48 +21,6 @@ #include "i2c-designware-core.h" -static void i2c_dw_configure_fifo_slave(struct dw_i2c_dev *dev) -{ - /* Configure Tx/Rx FIFO threshold levels. */ - regmap_write(dev->map, DW_IC_TX_TL, 0); - regmap_write(dev->map, DW_IC_RX_TL, 0); - - /* Configure the I2C slave. */ - regmap_write(dev->map, DW_IC_CON, dev->slave_cfg); - regmap_write(dev->map, DW_IC_INTR_MASK, DW_IC_INTR_SLAVE_MASK); -} - -/** - * i2c_dw_init_slave() - Initialize the DesignWare i2c slave hardware - * @dev: device private data - * - * This function configures and enables the I2C in slave mode. - * This function is called during I2C init function, and in case of timeout at - * run time. - * - * Return: 0 on success, or negative errno otherwise. - */ -static int i2c_dw_init_slave(struct dw_i2c_dev *dev) -{ - int ret; - - ret = i2c_dw_acquire_lock(dev); - if (ret) - return ret; - - /* Disable the adapter. */ - __i2c_dw_disable(dev); - - /* Write SDA hold time if supported */ - if (dev->sda_hold_time) - regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time); - - i2c_dw_configure_fifo_slave(dev); - i2c_dw_release_lock(dev); - - return 0; -} - int i2c_dw_reg_slave(struct i2c_client *slave) { struct dw_i2c_dev *dev = i2c_get_adapdata(slave->adapter); @@ -232,8 +190,6 @@ int i2c_dw_probe_slave(struct dw_i2c_dev *dev) if (dev->flags & ACCESS_POLLING) return -EOPNOTSUPP; - dev->init = i2c_dw_init_slave; - return 0; } From cfbcc20d5c02d6d9e5218ea493fb231b58efe6b3 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 20 Jan 2026 14:07:27 +0100 Subject: [PATCH 44/46] i2c: designware: Enable mode swapping The DesignWare I2C can not be operated as I2C master and I2C slave simultaneously, but that does not actually mean master and slave modes can not be supported at the same time. It just means an explicit mode swap needs to be executed when the mode is changed. The DesignWare I2C documentation actually describes a couple of cases where the mode is excepted to be changed. The I2C master will now always be supported. Both modes are now always configured in i2c_dw_configure(), but the slave mode will continue to be available only when the Kconfig option I2C_SLAVE is enabled. The driver will now start in master mode and then swap to slave mode when a slave device is registered. After a slave device is registered, the controller is swapped to master mode when a transfer in master mode is started and then back to slave mode again after the transfer is completed. The DesignWare I2C can now be used with protocols such as MCTP (drivers/net/mctp/mctp-i2c.c) and IPMI (drivers/char/ipmi/) that require support for both I2C master and I2C slave. It is now also possible to support the SMBus Host Notification Protocol as I2C master if needed. Signed-off-by: Heikki Krogerus Acked-by: Mika Westerberg Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260120130729.1679560-4-heikki.krogerus@linux.intel.com --- drivers/i2c/busses/i2c-designware-common.c | 50 +++++++++++++++------- drivers/i2c/busses/i2c-designware-core.h | 9 ++-- drivers/i2c/busses/i2c-designware-master.c | 6 ++- drivers/i2c/busses/i2c-designware-slave.c | 35 +++++++-------- 4 files changed, 57 insertions(+), 43 deletions(-) diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index 17affdecbe30..53d9337db5e2 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -359,21 +359,25 @@ static inline u32 i2c_dw_acpi_round_bus_speed(struct device *device) { return 0; #endif /* CONFIG_ACPI */ -static void i2c_dw_configure_mode(struct dw_i2c_dev *dev) +static void i2c_dw_configure_mode(struct dw_i2c_dev *dev, int mode) { - switch (dev->mode) { + switch (mode) { case DW_IC_MASTER: regmap_write(dev->map, DW_IC_TX_TL, dev->tx_fifo_depth / 2); regmap_write(dev->map, DW_IC_RX_TL, 0); regmap_write(dev->map, DW_IC_CON, dev->master_cfg); break; case DW_IC_SLAVE: + dev->status = 0; regmap_write(dev->map, DW_IC_TX_TL, 0); regmap_write(dev->map, DW_IC_RX_TL, 0); regmap_write(dev->map, DW_IC_CON, dev->slave_cfg); + regmap_write(dev->map, DW_IC_SAR, dev->slave->addr); regmap_write(dev->map, DW_IC_INTR_MASK, DW_IC_INTR_SLAVE_MASK); + __i2c_dw_enable(dev); break; default: + WARN(1, "Invalid mode %d\n", mode); return; } } @@ -395,6 +399,31 @@ static void i2c_dw_write_timings(struct dw_i2c_dev *dev) } } +/** + * i2c_dw_set_mode() - Select the controller mode of operation - master or slave + * @dev: device private data + * @mode: I2C mode of operation + * + * Configures the controller to operate in @mode. This function needs to be + * called when ever a mode swap is required. + * + * Setting the slave mode does not have an effect before a slave device is + * registered. So before the slave device is registered, the controller is kept + * in master mode regardless of @mode. + * + * The controller must be disabled before this function is called. + */ +void i2c_dw_set_mode(struct dw_i2c_dev *dev, int mode) +{ + if (mode == DW_IC_SLAVE && !dev->slave) + mode = DW_IC_MASTER; + if (dev->mode == mode) + return; + + i2c_dw_configure_mode(dev, mode); + dev->mode = mode; +} + /** * i2c_dw_init() - Initialize the DesignWare I2C hardware * @dev: device private data @@ -421,14 +450,13 @@ int i2c_dw_init(struct dw_i2c_dev *dev) */ regmap_write(dev->map, DW_IC_SMBUS_INTR_MASK, 0); - if (dev->mode == DW_IC_MASTER) - i2c_dw_write_timings(dev); + i2c_dw_write_timings(dev); /* Write SDA hold time if supported */ if (dev->sda_hold_time) regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time); - i2c_dw_configure_mode(dev); + i2c_dw_configure_mode(dev, dev->mode); i2c_dw_release_lock(dev); @@ -864,17 +892,7 @@ int i2c_dw_probe(struct dw_i2c_dev *dev) if (ret) return ret; - switch (dev->mode) { - case DW_IC_SLAVE: - ret = i2c_dw_probe_slave(dev); - break; - case DW_IC_MASTER: - ret = i2c_dw_probe_master(dev); - break; - default: - ret = -EINVAL; - break; - } + ret = i2c_dw_probe_master(dev); if (ret) return ret; diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index 63a59bb03163..a49263a36023 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -398,26 +398,23 @@ int i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); #if IS_ENABLED(CONFIG_I2C_SLAVE) extern void i2c_dw_configure_slave(struct dw_i2c_dev *dev); -extern int i2c_dw_probe_slave(struct dw_i2c_dev *dev); irqreturn_t i2c_dw_isr_slave(struct dw_i2c_dev *dev); int i2c_dw_reg_slave(struct i2c_client *client); int i2c_dw_unreg_slave(struct i2c_client *client); #else static inline void i2c_dw_configure_slave(struct dw_i2c_dev *dev) { } -static inline int i2c_dw_probe_slave(struct dw_i2c_dev *dev) { return -EINVAL; } static inline irqreturn_t i2c_dw_isr_slave(struct dw_i2c_dev *dev) { return IRQ_NONE; } #endif static inline void i2c_dw_configure(struct dw_i2c_dev *dev) { - if (i2c_detect_slave_mode(dev->dev)) - i2c_dw_configure_slave(dev); - else - i2c_dw_configure_master(dev); + i2c_dw_configure_slave(dev); + i2c_dw_configure_master(dev); } int i2c_dw_probe(struct dw_i2c_dev *dev); int i2c_dw_init(struct dw_i2c_dev *dev); +void i2c_dw_set_mode(struct dw_i2c_dev *dev, int mode); #if IS_ENABLED(CONFIG_I2C_DESIGNWARE_BAYTRAIL) int i2c_dw_baytrail_probe_lock_support(struct dw_i2c_dev *dev); diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index 22c1bb463c2c..8ca254cbb2f8 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -194,6 +194,8 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev) /* Disable the adapter */ __i2c_dw_disable(dev); + i2c_dw_set_mode(dev, DW_IC_MASTER); + /* If the slave address is ten bit address, enable 10BITADDR */ if (msgs[dev->msg_write_idx].flags & I2C_M_TEN) { ic_con = DW_IC_CON_10BITADDR_MASTER; @@ -831,6 +833,8 @@ i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num) ret = -EIO; done: + i2c_dw_set_mode(dev, DW_IC_SLAVE); + i2c_dw_release_lock(dev); done_nolock: @@ -853,7 +857,7 @@ void i2c_dw_configure_master(struct dw_i2c_dev *dev) { struct i2c_timings *t = &dev->timings; - dev->functionality = I2C_FUNC_10BIT_ADDR | DW_IC_DEFAULT_FUNCTIONALITY; + dev->functionality |= I2C_FUNC_10BIT_ADDR | DW_IC_DEFAULT_FUNCTIONALITY; dev->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE | DW_IC_CON_RESTART_EN; diff --git a/drivers/i2c/busses/i2c-designware-slave.c b/drivers/i2c/busses/i2c-designware-slave.c index 9fc8faa33735..ad0d5fbfa6d5 100644 --- a/drivers/i2c/busses/i2c-designware-slave.c +++ b/drivers/i2c/busses/i2c-designware-slave.c @@ -24,24 +24,25 @@ int i2c_dw_reg_slave(struct i2c_client *slave) { struct dw_i2c_dev *dev = i2c_get_adapdata(slave->adapter); + int ret; + if (!i2c_check_functionality(slave->adapter, I2C_FUNC_SLAVE)) + return -EOPNOTSUPP; if (dev->slave) return -EBUSY; if (slave->flags & I2C_CLIENT_TEN) return -EAFNOSUPPORT; + + ret = i2c_dw_acquire_lock(dev); + if (ret) + return ret; + pm_runtime_get_sync(dev->dev); - - /* - * Set slave address in the IC_SAR register, - * the address to which the DW_apb_i2c responds. - */ __i2c_dw_disable_nowait(dev); - regmap_write(dev->map, DW_IC_SAR, slave->addr); dev->slave = slave; + i2c_dw_set_mode(dev, DW_IC_SLAVE); - __i2c_dw_enable(dev); - - dev->status = 0; + i2c_dw_release_lock(dev); return 0; } @@ -54,6 +55,7 @@ int i2c_dw_unreg_slave(struct i2c_client *slave) i2c_dw_disable(dev); synchronize_irq(dev->irq); dev->slave = NULL; + i2c_dw_set_mode(dev, DW_IC_MASTER); pm_runtime_put_sync_suspend(dev->dev); return 0; @@ -176,23 +178,16 @@ irqreturn_t i2c_dw_isr_slave(struct dw_i2c_dev *dev) void i2c_dw_configure_slave(struct dw_i2c_dev *dev) { - dev->functionality = I2C_FUNC_SLAVE; + if (dev->flags & ACCESS_POLLING) + return; + + dev->functionality |= I2C_FUNC_SLAVE; dev->slave_cfg = DW_IC_CON_RX_FIFO_FULL_HLD_CTRL | DW_IC_CON_RESTART_EN | DW_IC_CON_STOP_DET_IFADDRESSED; - - dev->mode = DW_IC_SLAVE; } EXPORT_SYMBOL_GPL(i2c_dw_configure_slave); -int i2c_dw_probe_slave(struct dw_i2c_dev *dev) -{ - if (dev->flags & ACCESS_POLLING) - return -EOPNOTSUPP; - - return 0; -} - MODULE_AUTHOR("Luis Oliveira "); MODULE_DESCRIPTION("Synopsys DesignWare I2C bus slave adapter"); MODULE_LICENSE("GPL v2"); From 51e8ce3630878fa6083e1eec84f58f49ec85089b Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 20 Jan 2026 14:07:28 +0100 Subject: [PATCH 45/46] i2c: designware: Remove an unnecessary condition Writing also the high speed timing registers unconditionally. The reset value for these registers is 0, so this should always be safe. Suggested-by: Andy Shevchenko Signed-off-by: Heikki Krogerus Acked-by: Mika Westerberg Reviewed-by: Andy Shevchenko Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260120130729.1679560-5-heikki.krogerus@linux.intel.com --- drivers/i2c/busses/i2c-designware-common.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index 53d9337db5e2..64654dabbb21 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -392,11 +392,9 @@ static void i2c_dw_write_timings(struct dw_i2c_dev *dev) regmap_write(dev->map, DW_IC_FS_SCL_HCNT, dev->fs_hcnt); regmap_write(dev->map, DW_IC_FS_SCL_LCNT, dev->fs_lcnt); - /* Write high speed timing parameters if supported */ - if (dev->hs_hcnt && dev->hs_lcnt) { - regmap_write(dev->map, DW_IC_HS_SCL_HCNT, dev->hs_hcnt); - regmap_write(dev->map, DW_IC_HS_SCL_LCNT, dev->hs_lcnt); - } + /* Write high speed timing parameters */ + regmap_write(dev->map, DW_IC_HS_SCL_HCNT, dev->hs_hcnt); + regmap_write(dev->map, DW_IC_HS_SCL_LCNT, dev->hs_lcnt); } /** From efdc383d1cc28d45cbf5a23b5ffa997010aaacb4 Mon Sep 17 00:00:00 2001 From: Carlos Song Date: Fri, 23 Jan 2026 18:54:58 +0800 Subject: [PATCH 46/46] i2c: imx-lpi2c: fix SMBus block read NACK after byte count The LPI2C controller sends a NACK at the end of a receive command unless another receive command is already queued in MTDR. During SMBus block reads, this causes the controller to NACK immediately after receiving the block length byte, aborting the transfer before the data bytes are read. Fix this by queueing a second receive command as soon as the block length byte is received, keeping MTDR non-empty and ensuring continuous ACKs. The initial receive command reads the block length, and the subsequent command reads the remaining data bytes according to the reported length. Fixes: a55fa9d0e42e ("i2c: imx-lpi2c: add low power i2c bus driver") Signed-off-by: Carlos Song Cc: # v4.10+ Reviewed-by: Frank Li Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260123105459.3448822-1-carlos.song@nxp.com Signed-off-by: Wolfram Sang --- drivers/i2c/busses/i2c-imx-lpi2c.c | 107 ++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 24 deletions(-) diff --git a/drivers/i2c/busses/i2c-imx-lpi2c.c b/drivers/i2c/busses/i2c-imx-lpi2c.c index be7134eefc2f..f97f73faec82 100644 --- a/drivers/i2c/busses/i2c-imx-lpi2c.c +++ b/drivers/i2c/busses/i2c-imx-lpi2c.c @@ -5,6 +5,7 @@ * Copyright 2016 Freescale Semiconductor, Inc. */ +#include #include #include #include @@ -90,6 +91,7 @@ #define MRDR_RXEMPTY BIT(14) #define MDER_TDDE BIT(0) #define MDER_RDDE BIT(1) +#define MSR_RDF_ASSERTED(x) FIELD_GET(MSR_RDF, (x)) #define SCR_SEN BIT(0) #define SCR_RST BIT(1) @@ -482,7 +484,7 @@ static bool lpi2c_imx_write_txfifo(struct lpi2c_imx_struct *lpi2c_imx, bool atom static bool lpi2c_imx_read_rxfifo(struct lpi2c_imx_struct *lpi2c_imx, bool atomic) { - unsigned int blocklen, remaining; + unsigned int remaining; unsigned int temp, data; do { @@ -493,15 +495,6 @@ static bool lpi2c_imx_read_rxfifo(struct lpi2c_imx_struct *lpi2c_imx, bool atomi lpi2c_imx->rx_buf[lpi2c_imx->delivered++] = data & 0xff; } while (1); - /* - * First byte is the length of remaining packet in the SMBus block - * data read. Add it to msgs->len. - */ - if (lpi2c_imx->block_data) { - blocklen = lpi2c_imx->rx_buf[0]; - lpi2c_imx->msglen += blocklen; - } - remaining = lpi2c_imx->msglen - lpi2c_imx->delivered; if (!remaining) { @@ -514,12 +507,7 @@ static bool lpi2c_imx_read_rxfifo(struct lpi2c_imx_struct *lpi2c_imx, bool atomi lpi2c_imx_set_rx_watermark(lpi2c_imx); /* multiple receive commands */ - if (lpi2c_imx->block_data) { - lpi2c_imx->block_data = 0; - temp = remaining; - temp |= (RECV_DATA << 8); - writel(temp, lpi2c_imx->base + LPI2C_MTDR); - } else if (!(lpi2c_imx->delivered & 0xff)) { + if (!(lpi2c_imx->delivered & 0xff)) { temp = (remaining > CHUNK_DATA ? CHUNK_DATA : remaining) - 1; temp |= (RECV_DATA << 8); writel(temp, lpi2c_imx->base + LPI2C_MTDR); @@ -557,18 +545,77 @@ static int lpi2c_imx_write_atomic(struct lpi2c_imx_struct *lpi2c_imx, return err; } -static void lpi2c_imx_read_init(struct lpi2c_imx_struct *lpi2c_imx, - struct i2c_msg *msgs) +static unsigned int lpi2c_SMBus_block_read_length_byte(struct lpi2c_imx_struct *lpi2c_imx) { - unsigned int temp; + unsigned int data; + + data = readl(lpi2c_imx->base + LPI2C_MRDR); + lpi2c_imx->rx_buf[lpi2c_imx->delivered++] = data & 0xff; + + return data; +} + +static int lpi2c_imx_read_init(struct lpi2c_imx_struct *lpi2c_imx, + struct i2c_msg *msgs) +{ + unsigned int temp, val, block_len; + int ret; lpi2c_imx->rx_buf = msgs->buf; lpi2c_imx->block_data = msgs->flags & I2C_M_RECV_LEN; lpi2c_imx_set_rx_watermark(lpi2c_imx); - temp = msgs->len > CHUNK_DATA ? CHUNK_DATA - 1 : msgs->len - 1; - temp |= (RECV_DATA << 8); - writel(temp, lpi2c_imx->base + LPI2C_MTDR); + + if (!lpi2c_imx->block_data) { + temp = msgs->len > CHUNK_DATA ? CHUNK_DATA - 1 : msgs->len - 1; + temp |= (RECV_DATA << 8); + writel(temp, lpi2c_imx->base + LPI2C_MTDR); + } else { + /* + * The LPI2C controller automatically sends a NACK after the last byte of a + * receive command, unless the next command in MTDR is also a receive command. + * If MTDR is empty when a receive completes, a NACK is sent by default. + * + * To comply with the SMBus block read spec, we start with a 2-byte read: + * The first byte in RXFIFO is the block length. Once this byte arrives, the + * controller immediately updates MTDR with the next read command, ensuring + * continuous ACK instead of NACK. + * + * The second byte is the first block data byte. Therefore, the subsequent + * read command should request (block_len - 1) bytes, since one data byte + * has already been read. + */ + + writel((RECV_DATA << 8) | 0x01, lpi2c_imx->base + LPI2C_MTDR); + + ret = readl_poll_timeout(lpi2c_imx->base + LPI2C_MSR, val, + MSR_RDF_ASSERTED(val), 1, 1000); + if (ret) { + dev_err(&lpi2c_imx->adapter.dev, "SMBus read count failed %d\n", ret); + return ret; + } + + /* Read block length byte and confirm this SMBus transfer meets protocol */ + block_len = lpi2c_SMBus_block_read_length_byte(lpi2c_imx); + if (block_len == 0 || block_len > I2C_SMBUS_BLOCK_MAX) { + dev_err(&lpi2c_imx->adapter.dev, "Invalid SMBus block read length\n"); + return -EPROTO; + } + + /* + * When block_len shows more bytes need to be read, update second read command to + * keep MTDR non-empty and ensuring continuous ACKs. Only update command register + * here. All block bytes will be read out at IRQ handler or lpi2c_imx_read_atomic() + * function. + */ + if (block_len > 1) + writel((RECV_DATA << 8) | (block_len - 2), lpi2c_imx->base + LPI2C_MTDR); + + lpi2c_imx->msglen += block_len; + msgs->len += block_len; + } + + return 0; } static bool lpi2c_imx_read_chunk_atomic(struct lpi2c_imx_struct *lpi2c_imx) @@ -613,6 +660,10 @@ static bool is_use_dma(struct lpi2c_imx_struct *lpi2c_imx, struct i2c_msg *msg) if (!lpi2c_imx->can_use_dma) return false; + /* DMA is not suitable for SMBus block read */ + if (msg->flags & I2C_M_RECV_LEN) + return false; + /* * When the length of data is less than I2C_DMA_THRESHOLD, * cpu mode is used directly to avoid low performance. @@ -623,10 +674,14 @@ static bool is_use_dma(struct lpi2c_imx_struct *lpi2c_imx, struct i2c_msg *msg) static int lpi2c_imx_pio_xfer(struct lpi2c_imx_struct *lpi2c_imx, struct i2c_msg *msg) { + int ret; + reinit_completion(&lpi2c_imx->complete); if (msg->flags & I2C_M_RD) { - lpi2c_imx_read_init(lpi2c_imx, msg); + ret = lpi2c_imx_read_init(lpi2c_imx, msg); + if (ret) + return ret; lpi2c_imx_intctrl(lpi2c_imx, MIER_RDIE | MIER_NDIE); } else { lpi2c_imx_write(lpi2c_imx, msg); @@ -638,8 +693,12 @@ static int lpi2c_imx_pio_xfer(struct lpi2c_imx_struct *lpi2c_imx, static int lpi2c_imx_pio_xfer_atomic(struct lpi2c_imx_struct *lpi2c_imx, struct i2c_msg *msg) { + int ret; + if (msg->flags & I2C_M_RD) { - lpi2c_imx_read_init(lpi2c_imx, msg); + ret = lpi2c_imx_read_init(lpi2c_imx, msg); + if (ret) + return ret; return lpi2c_imx_read_atomic(lpi2c_imx, msg); }