From 0ed3fa23704d16ff476c55b6ec8fe5b99df75ec9 Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Fri, 8 May 2026 15:25:24 +0800 Subject: [PATCH 01/38] i2c: spacemit: configure ILCR/IWCR for accurate SCL frequency The SpacemiT I2C controller's SCL (Serial Clock Line) frequency for master mode operations is determined by the ILCR (I2C Load Count Register). Previously, the driver relied on the hardware's reset default values for this register. The hardware's default ILCR values (SLV=0x156, FLV=0x5d) yield SCL frequencies lower than intended. For example, with the default 31.5 MHz input clock, these default settings result in an SCL frequency of approximately 93 kHz (standard mode) when targeting 100 kHz, and approximately 338 kHz (fast mode) when targeting 400 kHz. These frequencies are below the 100 kHz/400 kHz nominal speeds. This patch integrates the SCL frequency management into the Common Clock Framework (CCF). Specifically, the ILCR register, which acts as a frequency divider for the SCL clock, is now registered as a managed clock (scl_clk) within the CCF. The actual hardware timing formulas are: - standard mode: SCL = FCLK / (2 * SLV + 8) - fast mode: SCL = FCLK / (2 * FLV + 10) These formulas are only valid when the IWCR (Wait Count Register) is programmed to 0x142A, a value specified by the I2C IP designer. The driver now initializes IWCR to this value during controller init. Reviewed-by: Yixun Lan Signed-off-by: Troy Mitchell Reviewed-by: Alex Elder Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260508-k1-i2c-ilcr-v7-1-8c2dde5c3ed5@linux.spacemit.com --- drivers/i2c/busses/Kconfig | 2 +- drivers/i2c/busses/i2c-k1.c | 174 ++++++++++++++++++++++++++++++++++-- 2 files changed, 167 insertions(+), 9 deletions(-) diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index d35456994280..d7b89508311f 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -793,7 +793,7 @@ config I2C_JZ4780 config I2C_K1 tristate "SpacemiT K1 I2C adapter" depends on ARCH_SPACEMIT || COMPILE_TEST - depends on OF + depends on OF && COMMON_CLK default ARCH_SPACEMIT help This option enables support for the I2C interface on the SpacemiT K1 diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c index 9152cf436bea..c6fe2052e479 100644 --- a/drivers/i2c/busses/i2c-k1.c +++ b/drivers/i2c/busses/i2c-k1.c @@ -4,7 +4,9 @@ */ #include +#include #include +#include #include #include #include @@ -17,6 +19,8 @@ #define SPACEMIT_ISR 0x4 /* Status register */ #define SPACEMIT_IDBR 0xc /* Data buffer register */ #define SPACEMIT_IRCR 0x18 /* Reset cycle counter */ +#define SPACEMIT_ILCR 0x10 /* Load Count Register */ +#define SPACEMIT_IWCR 0x14 /* Wait Count Register */ #define SPACEMIT_IBMR 0x1c /* Bus monitor register */ /* SPACEMIT_ICR register fields */ @@ -88,6 +92,19 @@ #define SPACEMIT_BMR_SDA BIT(0) /* SDA line level */ #define SPACEMIT_BMR_SCL BIT(1) /* SCL line level */ +#define SPACEMIT_LCR_LV_STANDARD_MASK GENMASK(8, 0) +#define SPACEMIT_LCR_LV_FAST_MASK GENMASK(17, 9) + +/* SPACEMIT_IWCR register fields */ +#define SPACEMIT_WCR_COUNT GENMASK(4, 0) +#define SPACEMIT_WCR_HS_COUNT1 GENMASK(9, 5) +#define SPACEMIT_WCR_HS_COUNT2 GENMASK(14, 10) + +/* Required by I2C IP for correct SCL timing */ +#define SPACEMIT_IWCR_INIT_VALUE (FIELD_PREP(SPACEMIT_WCR_COUNT, 10) | \ + FIELD_PREP(SPACEMIT_WCR_HS_COUNT1, 1) | \ + FIELD_PREP(SPACEMIT_WCR_HS_COUNT2, 5)) + /* i2c bus recover timeout: us */ #define SPACEMIT_I2C_BUS_BUSY_TIMEOUT 100000 @@ -109,11 +126,20 @@ enum spacemit_i2c_state { SPACEMIT_STATE_WRITE, }; +enum spacemit_i2c_mode { + SPACEMIT_MODE_STANDARD, + SPACEMIT_MODE_FAST +}; + /* i2c-spacemit driver's main struct */ struct spacemit_i2c_dev { struct device *dev; struct i2c_adapter adapt; + struct clk_hw scl_clk_hw; + struct clk *scl_clk; + enum spacemit_i2c_mode mode; + /* hardware resources */ void __iomem *base; int irq; @@ -135,6 +161,85 @@ struct spacemit_i2c_dev { u32 status; }; +static void spacemit_i2c_scl_clk_disable_unprepare(void *data) +{ + clk_disable_unprepare(data); +} + +/* + * Calculate the ILCR divider value (lv) from the target SCL rate. + * + * Hardware timing formulas: + * - standard mode: SCL = FCLK / (2 * SLV + 8) + * - fast mode: SCL = FCLK / (2 * FLV + 10) + */ +static u32 spacemit_i2c_calc_lv(struct spacemit_i2c_dev *i2c, + unsigned long parent_rate, + unsigned long target_rate) +{ + u32 offset, denom; + + offset = (i2c->mode == SPACEMIT_MODE_STANDARD) ? 8 : 10; + denom = DIV_ROUND_CLOSEST(parent_rate, target_rate); + + return (denom <= offset) ? 0 : DIV_ROUND_CLOSEST(denom - offset, 2); +} + +static int spacemit_i2c_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw); + u32 lv, lcr, mask; + + lv = spacemit_i2c_calc_lv(i2c, parent_rate, rate); + + mask = (i2c->mode == SPACEMIT_MODE_STANDARD) ? + SPACEMIT_LCR_LV_STANDARD_MASK : SPACEMIT_LCR_LV_FAST_MASK; + + lcr = readl(i2c->base + SPACEMIT_ILCR); + lcr &= ~mask; + lcr |= field_prep(mask, lv); + writel(lcr, i2c->base + SPACEMIT_ILCR); + + return 0; +} + +static int spacemit_i2c_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw); + u32 lv, offset; + + lv = spacemit_i2c_calc_lv(i2c, req->best_parent_rate, req->rate); + offset = (i2c->mode == SPACEMIT_MODE_STANDARD) ? 8 : 10; + req->rate = DIV_ROUND_CLOSEST(req->best_parent_rate, lv * 2 + offset); + + return 0; +} + +static unsigned long spacemit_i2c_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw); + u32 lcr, lv = 0; + + lcr = readl(i2c->base + SPACEMIT_ILCR); + + if (i2c->mode == SPACEMIT_MODE_STANDARD) { + lv = FIELD_GET(SPACEMIT_LCR_LV_STANDARD_MASK, lcr); + return DIV_ROUND_CLOSEST(parent_rate, lv * 2 + 8); + } + + lv = FIELD_GET(SPACEMIT_LCR_LV_FAST_MASK, lcr); + return DIV_ROUND_CLOSEST(parent_rate, lv * 2 + 10); +} + +static const struct clk_ops spacemit_i2c_clk_ops = { + .set_rate = spacemit_i2c_clk_set_rate, + .determine_rate = spacemit_i2c_clk_determine_rate, + .recalc_rate = spacemit_i2c_clk_recalc_rate, +}; + static void spacemit_i2c_enable(struct spacemit_i2c_dev *i2c) { u32 val; @@ -153,6 +258,28 @@ static void spacemit_i2c_disable(struct spacemit_i2c_dev *i2c) writel(val, i2c->base + SPACEMIT_ICR); } +static int spacemit_i2c_register_scl_clk(struct spacemit_i2c_dev *i2c) +{ + struct clk_init_data init = {}; + char name[64]; + int ret; + + ret = snprintf(name, sizeof(name), "%s_scl_clk", dev_name(i2c->dev)); + if (ret >= ARRAY_SIZE(name)) + dev_warn(i2c->dev, "scl clock name truncated"); + + init.name = name; + init.ops = &spacemit_i2c_clk_ops; + init.parent_data = (struct clk_parent_data[]) { + { .fw_name = "func" }, + }; + init.num_parents = 1; + + i2c->scl_clk_hw.init = &init; + + return devm_clk_hw_register(i2c->dev, &i2c->scl_clk_hw); +} + static void spacemit_i2c_reset(struct spacemit_i2c_dev *i2c) { writel(SPACEMIT_CR_UR, i2c->base + SPACEMIT_ICR); @@ -286,7 +413,7 @@ static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c) val |= SPACEMIT_CR_MSDIE; } - if (i2c->clock_freq == SPACEMIT_I2C_MAX_FAST_MODE_FREQ) + if (i2c->mode == SPACEMIT_MODE_FAST) val |= SPACEMIT_CR_MODE_FAST; /* disable response to general call */ @@ -309,6 +436,14 @@ static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c) writel(val, i2c->base + SPACEMIT_IRCR); spacemit_i2c_clear_int_status(i2c, SPACEMIT_I2C_INT_STATUS_MASK); + + /* + * Initialize IWCR to the value specified by the I2C IP designer. + * The SCL frequency formulas (SCL = FCLK / (2*SLV+8) for standard + * mode, SCL = FCLK / (2*FLV+10) for fast mode) are only valid when + * IWCR contains this specific value. + */ + writel(SPACEMIT_IWCR_INIT_VALUE, i2c->base + SPACEMIT_IWCR); } static void spacemit_i2c_start(struct spacemit_i2c_dev *i2c) @@ -703,14 +838,15 @@ static int spacemit_i2c_probe(struct platform_device *pdev) dev_warn(dev, "failed to read clock-frequency property: %d\n", ret); /* For now, this driver doesn't support high-speed. */ - if (!i2c->clock_freq || i2c->clock_freq > SPACEMIT_I2C_MAX_FAST_MODE_FREQ) { - dev_warn(dev, "unsupported clock frequency %u; using %u\n", - i2c->clock_freq, SPACEMIT_I2C_MAX_FAST_MODE_FREQ); + if (i2c->clock_freq > SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ && + i2c->clock_freq <= SPACEMIT_I2C_MAX_FAST_MODE_FREQ) { + i2c->mode = SPACEMIT_MODE_FAST; + } else if (i2c->clock_freq && i2c->clock_freq <= SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ) { + i2c->mode = SPACEMIT_MODE_STANDARD; + } else { + dev_warn(i2c->dev, "invalid clock-frequency, fallback to fast mode"); + i2c->mode = SPACEMIT_MODE_FAST; i2c->clock_freq = SPACEMIT_I2C_MAX_FAST_MODE_FREQ; - } else if (i2c->clock_freq < SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ) { - dev_warn(dev, "unsupported clock frequency %u; using %u\n", - i2c->clock_freq, SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ); - i2c->clock_freq = SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ; } i2c->dev = &pdev->dev; @@ -732,6 +868,15 @@ static int spacemit_i2c_probe(struct platform_device *pdev) if (IS_ERR(clk)) return dev_err_probe(dev, PTR_ERR(clk), "failed to enable func clock"); + ret = spacemit_i2c_register_scl_clk(i2c); + if (ret) + return dev_err_probe(dev, ret, "failed to register scl clock\n"); + + i2c->scl_clk = devm_clk_hw_get_clk(dev, &i2c->scl_clk_hw, "scl"); + if (IS_ERR(i2c->scl_clk)) + return dev_err_probe(dev, PTR_ERR(i2c->scl_clk), + "failed to get scl clock\n"); + clk = devm_clk_get_enabled(dev, "bus"); if (IS_ERR(clk)) return dev_err_probe(dev, PTR_ERR(clk), "failed to enable bus clock"); @@ -741,6 +886,19 @@ static int spacemit_i2c_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(rst), "failed to acquire deasserted reset\n"); + ret = clk_set_rate(i2c->scl_clk, i2c->clock_freq); + if (ret) + return dev_err_probe(dev, ret, "failed to set rate for SCL clock"); + + ret = clk_prepare_enable(i2c->scl_clk); + if (ret) + return dev_err_probe(dev, ret, "failed to prepare and enable clock"); + + ret = devm_add_action_or_reset(dev, spacemit_i2c_scl_clk_disable_unprepare, + i2c->scl_clk); + if (ret) + return ret; + spacemit_i2c_reset(i2c); i2c_set_adapdata(&i2c->adapt, i2c); From 73a2f91bc2f6bd329d9c55d351809a139393307a Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Fri, 8 May 2026 15:25:25 +0800 Subject: [PATCH 02/38] i2c: spacemit: drop warning when clock-frequency property is absent The clock-frequency property is optional according to the DT binding. Do not emit a warning when the property is missing and fall back to the default frequency instead. Reviewed-by: Alex Elder Signed-off-by: Troy Mitchell Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260508-k1-i2c-ilcr-v7-2-8c2dde5c3ed5@linux.spacemit.com --- drivers/i2c/busses/i2c-k1.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c index c6fe2052e479..7cf5c05a20d2 100644 --- a/drivers/i2c/busses/i2c-k1.c +++ b/drivers/i2c/busses/i2c-k1.c @@ -833,9 +833,7 @@ static int spacemit_i2c_probe(struct platform_device *pdev) if (!i2c) return -ENOMEM; - ret = of_property_read_u32(of_node, "clock-frequency", &i2c->clock_freq); - if (ret && ret != -EINVAL) - dev_warn(dev, "failed to read clock-frequency property: %d\n", ret); + of_property_read_u32(of_node, "clock-frequency", &i2c->clock_freq); /* For now, this driver doesn't support high-speed. */ if (i2c->clock_freq > SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ && @@ -844,7 +842,7 @@ static int spacemit_i2c_probe(struct platform_device *pdev) } else if (i2c->clock_freq && i2c->clock_freq <= SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ) { i2c->mode = SPACEMIT_MODE_STANDARD; } else { - dev_warn(i2c->dev, "invalid clock-frequency, fallback to fast mode"); + dev_info(dev, "clock-frequency not set or out of range, using fast mode\n"); i2c->mode = SPACEMIT_MODE_FAST; i2c->clock_freq = SPACEMIT_I2C_MAX_FAST_MODE_FREQ; } From 3ca8d1ec9a5374177b3f93313ac6f3e10cd3b113 Mon Sep 17 00:00:00 2001 From: Chaitanya Sabnis Date: Tue, 5 May 2026 15:38:54 +0530 Subject: [PATCH 03/38] dt-bindings: i2c: convert axxia i2c to dt-schema Convert the LSI Axxia I2C Controller bindings from legacy text format to modern dt-schema (YAML). During the conversion, the obsolete `device_type = "i2c"` property was dropped from the example, as it is deprecated and not utilized by the driver. The `clock-names` property is enforced as required matching the driver probe behavior. The example node address was also updated to a standard 32-bit address. Signed-off-by: Chaitanya Sabnis Reviewed-by: Rob Herring (Arm) Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260505100854.5258-1-chaitanya.msabnis@gmail.com --- .../devicetree/bindings/i2c/i2c-axxia.txt | 30 ----------- .../devicetree/bindings/i2c/lsi,api2c.yaml | 52 +++++++++++++++++++ 2 files changed, 52 insertions(+), 30 deletions(-) delete mode 100644 Documentation/devicetree/bindings/i2c/i2c-axxia.txt create mode 100644 Documentation/devicetree/bindings/i2c/lsi,api2c.yaml diff --git a/Documentation/devicetree/bindings/i2c/i2c-axxia.txt b/Documentation/devicetree/bindings/i2c/i2c-axxia.txt deleted file mode 100644 index 7d53a2b79553..000000000000 --- a/Documentation/devicetree/bindings/i2c/i2c-axxia.txt +++ /dev/null @@ -1,30 +0,0 @@ -LSI Axxia I2C - -Required properties : -- compatible : Must be "lsi,api2c" -- reg : Offset and length of the register set for the device -- interrupts : the interrupt specifier -- #address-cells : Must be <1>; -- #size-cells : Must be <0>; -- clock-names : Must contain "i2c". -- clocks: Must contain an entry for each name in clock-names. See the common - clock bindings. - -Optional properties : -- clock-frequency : Desired I2C bus clock frequency in Hz. If not specified, - the default 100 kHz frequency will be used. As only Normal and Fast modes - are supported, possible values are 100000 and 400000. - -Example : - -i2c@2010084000 { - compatible = "lsi,api2c"; - device_type = "i2c"; - #address-cells = <1>; - #size-cells = <0>; - reg = <0x20 0x10084000 0x00 0x1000>; - interrupts = <0 19 4>; - clocks = <&clk_per>; - clock-names = "i2c"; - clock-frequency = <400000>; -}; diff --git a/Documentation/devicetree/bindings/i2c/lsi,api2c.yaml b/Documentation/devicetree/bindings/i2c/lsi,api2c.yaml new file mode 100644 index 000000000000..2d1c3069c3a3 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/lsi,api2c.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/i2c/lsi,api2c.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LSI Axxia I2C Controller + +maintainers: + - Anders Berg + - Chaitanya Sabnis + +allOf: + - $ref: /schemas/i2c/i2c-controller.yaml# + +properties: + compatible: + const: lsi,api2c + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + const: i2c + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + +unevaluatedProperties: false + +examples: + - | + i2c@10084000 { + compatible = "lsi,api2c"; + reg = <0x10084000 0x1000>; + interrupts = <0 19 4>; + clocks = <&clk_per>; + clock-names = "i2c"; + clock-frequency = <400000>; + #address-cells = <1>; + #size-cells = <0>; + }; From b2f4816a378dc840b605a1235b80e57c5f85b46c Mon Sep 17 00:00:00 2001 From: Paritosh Potukuchi Date: Fri, 12 Jun 2026 10:18:15 +0000 Subject: [PATCH 04/38] i2c: amd-asf: Validate firmware-reported length The firmware may sometimes return a length greater than the allocated buffer size, which can lead to out-of-bounds access and a kernel panic. Currently, the driver does not validate the length read from firmware. Add a check to ensure the firmware-reported length stays within the bounds of the data buffer. Signed-off-by: Paritosh Potukuchi Acked-by: Shyam Sundar S K Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260612101815.4124804-1-paritosh.potukuchi@amd.com --- drivers/i2c/busses/i2c-amd-asf-plat.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/i2c/busses/i2c-amd-asf-plat.c b/drivers/i2c/busses/i2c-amd-asf-plat.c index ca45f0f23321..f5b73c834049 100644 --- a/drivers/i2c/busses/i2c-amd-asf-plat.c +++ b/drivers/i2c/busses/i2c-amd-asf-plat.c @@ -90,6 +90,10 @@ static void amd_asf_process_target(struct work_struct *work) outb_p(reg, ASFDATABNKSEL); cmd = inb_p(ASFINDEX); len = inb_p(ASFDATARWPTR); + + if (len > ASF_BLOCK_MAX_BYTES) + return; + for (idx = 0; idx < len; idx++) data[idx] = inb_p(ASFINDEX); From ff434ac49a52995c97d2e4a700b3875dd9910dcd Mon Sep 17 00:00:00 2001 From: Chaitanya Sabnis Date: Tue, 5 May 2026 11:02:01 +0530 Subject: [PATCH 05/38] dt-bindings: i2c: convert altera i2c to dt-schema Convert the Altera SoftIP I2C Controller bindings from legacy text format to modern dt-schema (YAML). The hardware constraints and properties remain identical. The example node was updated to use a standard 32-bit address space to clear compilation warnings. Signed-off-by: Chaitanya Sabnis Reviewed-by: Rob Herring (Arm) Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260505053201.5795-1-chaitanya.msabnis@gmail.com --- .../bindings/i2c/altr,softip-i2c-v1.0.yaml | 62 +++++++++++++++++++ .../devicetree/bindings/i2c/i2c-altera.txt | 39 ------------ 2 files changed, 62 insertions(+), 39 deletions(-) create mode 100644 Documentation/devicetree/bindings/i2c/altr,softip-i2c-v1.0.yaml delete mode 100644 Documentation/devicetree/bindings/i2c/i2c-altera.txt diff --git a/Documentation/devicetree/bindings/i2c/altr,softip-i2c-v1.0.yaml b/Documentation/devicetree/bindings/i2c/altr,softip-i2c-v1.0.yaml new file mode 100644 index 000000000000..d04570680c05 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/altr,softip-i2c-v1.0.yaml @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/i2c/altr,softip-i2c-v1.0.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Altera I2C Controller + +maintainers: + - Thor Thayer + - Chaitanya Sabnis + +description: + Altera's synthesizable logic block I2C Controller for use in Altera's FPGAs. + +allOf: + - $ref: /schemas/i2c/i2c-controller.yaml# + +properties: + compatible: + const: altr,softip-i2c-v1.0 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + fifo-size: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Size of the RX and TX FIFOs in bytes. + +required: + - compatible + - reg + - interrupts + - clocks + +unevaluatedProperties: false + +examples: + - | + i2c@80000 { + compatible = "altr,softip-i2c-v1.0"; + reg = <0x80000 0x40>; + interrupt-parent = <&intc>; + interrupts = <0 43 4>; + clocks = <&clk_0>; + clock-frequency = <100000>; + #address-cells = <1>; + #size-cells = <0>; + fifo-size = <4>; + + eeprom@51 { + compatible = "atmel,24c32"; + reg = <0x51>; + pagesize = <32>; + }; + }; diff --git a/Documentation/devicetree/bindings/i2c/i2c-altera.txt b/Documentation/devicetree/bindings/i2c/i2c-altera.txt deleted file mode 100644 index 767664f448ec..000000000000 --- a/Documentation/devicetree/bindings/i2c/i2c-altera.txt +++ /dev/null @@ -1,39 +0,0 @@ -* Altera I2C Controller -* This is Altera's synthesizable logic block I2C Controller for use -* in Altera's FPGAs. - -Required properties : - - compatible : should be "altr,softip-i2c-v1.0" - - reg : Offset and length of the register set for the device - - interrupts : where IRQ is the interrupt number. - - clocks : phandle to input clock. - - #address-cells = <1>; - - #size-cells = <0>; - -Recommended properties : - - clock-frequency : desired I2C bus clock frequency in Hz. - -Optional properties : - - fifo-size : Size of the RX and TX FIFOs in bytes. - - Child nodes conforming to i2c bus binding - -Example : - - i2c@100080000 { - compatible = "altr,softip-i2c-v1.0"; - reg = <0x00000001 0x00080000 0x00000040>; - interrupt-parent = <&intc>; - interrupts = <0 43 4>; - clocks = <&clk_0>; - clock-frequency = <100000>; - #address-cells = <1>; - #size-cells = <0>; - fifo-size = <4>; - - eeprom@51 { - compatible = "atmel,24c32"; - reg = <0x51>; - pagesize = <32>; - }; - }; - From fd359c0272bb4078f3a7ef34c1d53ee0deb59900 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Wed, 17 Jun 2026 10:20:15 +0530 Subject: [PATCH 06/38] dt-bindings: i2c: Describe SA8255p Add DT bindings for the QUP GENI I2C controller on sa8255p platforms. SA8255p platform abstracts resources such as clocks, interconnect and GPIO pins configuration in Firmware. SCMI power and perf protocol are utilized to request resource configurations. SA8255p platform does not require the Serial Engine (SE) common properties as the SE firmware is loaded and managed by the TrustZone (TZ) secure environment. Co-developed-by: Nikunj Kela Signed-off-by: Nikunj Kela Signed-off-by: Praveen Talari Reviewed-by: Krzysztof Kozlowski Tested-by: Mattijs Korpershoek Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260617-enable-i2c-on-sa8255p-v7-1-ad736dbeab57@oss.qualcomm.com --- .../bindings/i2c/qcom,sa8255p-geni-i2c.yaml | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml diff --git a/Documentation/devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml b/Documentation/devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml new file mode 100644 index 000000000000..a61e40b5cbc1 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/i2c/qcom,sa8255p-geni-i2c.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm SA8255p QUP GENI I2C Controller + +maintainers: + - Praveen Talari + +properties: + compatible: + const: qcom,sa8255p-geni-i2c + + reg: + maxItems: 1 + + dmas: + maxItems: 2 + + dma-names: + items: + - const: tx + - const: rx + + interrupts: + maxItems: 1 + + power-domains: + maxItems: 2 + + power-domain-names: + items: + - const: power + - const: perf + +required: + - compatible + - reg + - interrupts + - power-domains + +allOf: + - $ref: /schemas/i2c/i2c-controller.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + #include + + i2c@a90000 { + compatible = "qcom,sa8255p-geni-i2c"; + reg = <0xa90000 0x4000>; + interrupts = ; + dmas = <&gpi_dma0 0 0 QCOM_GPI_I2C>, + <&gpi_dma0 1 0 QCOM_GPI_I2C>; + dma-names = "tx", "rx"; + power-domains = <&scmi0_pd 0>, <&scmi0_dvfs 0>; + power-domain-names = "power", "perf"; + }; +... From d8d3bb127ad119853ddcf5da8f546cf37c3cc346 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Wed, 17 Jun 2026 10:20:16 +0530 Subject: [PATCH 07/38] i2c: qcom-geni: Isolate serial engine setup Moving the serial engine setup to geni_i2c_init() API for a cleaner probe function and utilizes the PM runtime API to control resources instead of direct clock-related APIs for better resource management. Enables reusability of the serial engine initialization like hibernation and deep sleep features where hardware context is lost. Signed-off-by: Praveen Talari Acked-by: Viken Dadhaniya Reviewed-by: Konrad Dybcio Reviewed-by: Mukesh Kumar Savaliya Tested-by: Mattijs Korpershoek Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260617-enable-i2c-on-sa8255p-v7-2-ad736dbeab57@oss.qualcomm.com --- drivers/i2c/busses/i2c-qcom-geni.c | 158 ++++++++++++++--------------- 1 file changed, 75 insertions(+), 83 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index d2f5055b0b10..3ee0be228d7c 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -990,10 +990,77 @@ err_tx: return ret; } +static int geni_i2c_init(struct geni_i2c_dev *gi2c) +{ + const struct geni_i2c_desc *desc = NULL; + u32 proto, tx_depth; + bool fifo_disable; + int ret; + + ret = pm_runtime_resume_and_get(gi2c->se.dev); + if (ret < 0) { + dev_err(gi2c->se.dev, "error turning on device :%d\n", ret); + return ret; + } + + proto = geni_se_read_proto(&gi2c->se); + if (proto == GENI_SE_INVALID_PROTO) { + ret = geni_load_se_firmware(&gi2c->se, GENI_SE_I2C); + if (ret) { + dev_err_probe(gi2c->se.dev, ret, "i2c firmware load failed ret: %d\n", ret); + goto err; + } + } else if (proto != GENI_SE_I2C) { + ret = dev_err_probe(gi2c->se.dev, -ENXIO, "Invalid proto %d\n", proto); + goto err; + } + + desc = device_get_match_data(gi2c->se.dev); + if (desc && desc->no_dma_support) { + fifo_disable = false; + gi2c->no_dma = true; + } else { + fifo_disable = readl_relaxed(gi2c->se.base + GENI_IF_DISABLE_RO) & FIFO_IF_DISABLE; + } + + if (fifo_disable) { + /* FIFO is disabled, so we can only use GPI DMA */ + gi2c->gpi_mode = true; + ret = setup_gpi_dma(gi2c); + if (ret) + goto err; + + dev_dbg(gi2c->se.dev, "Using GPI DMA mode for I2C\n"); + } else { + gi2c->gpi_mode = false; + tx_depth = geni_se_get_tx_fifo_depth(&gi2c->se); + + /* I2C Master Hub Serial Elements doesn't have the HW_PARAM_0 register */ + if (!tx_depth && desc) + tx_depth = desc->tx_fifo_depth; + + if (!tx_depth) { + ret = dev_err_probe(gi2c->se.dev, -EINVAL, + "Invalid TX FIFO depth\n"); + goto err; + } + + gi2c->tx_wm = tx_depth - 1; + geni_se_init(&gi2c->se, gi2c->tx_wm, tx_depth); + geni_se_config_packing(&gi2c->se, BITS_PER_BYTE, + PACKING_BYTES_PW, true, true, true); + + dev_dbg(gi2c->se.dev, "i2c fifo/se-dma mode. fifo depth:%d\n", tx_depth); + } + +err: + pm_runtime_put(gi2c->se.dev); + return ret; +} + static int geni_i2c_probe(struct platform_device *pdev) { struct geni_i2c_dev *gi2c; - u32 proto, tx_depth, fifo_disable; int ret; struct device *dev = &pdev->dev; const struct geni_i2c_desc *desc = NULL; @@ -1073,101 +1140,26 @@ static int geni_i2c_probe(struct platform_device *pdev) if (ret) return ret; - ret = clk_prepare_enable(gi2c->core_clk); - if (ret) - return ret; - - ret = geni_se_resources_on(&gi2c->se); - if (ret) { - dev_err_probe(dev, ret, "Error turning on resources\n"); - goto err_clk; - } - proto = geni_se_read_proto(&gi2c->se); - if (proto == GENI_SE_INVALID_PROTO) { - ret = geni_load_se_firmware(&gi2c->se, GENI_SE_I2C); - if (ret) { - dev_err_probe(dev, ret, "i2c firmware load failed ret: %d\n", ret); - goto err_resources; - } - } else if (proto != GENI_SE_I2C) { - ret = dev_err_probe(dev, -ENXIO, "Invalid proto %d\n", proto); - goto err_resources; - } - - if (desc && desc->no_dma_support) { - fifo_disable = false; - gi2c->no_dma = true; - } else { - fifo_disable = readl_relaxed(gi2c->se.base + GENI_IF_DISABLE_RO) & FIFO_IF_DISABLE; - } - - if (fifo_disable) { - /* FIFO is disabled, so we can only use GPI DMA */ - gi2c->gpi_mode = true; - ret = setup_gpi_dma(gi2c); - if (ret) - goto err_resources; - - dev_dbg(dev, "Using GPI DMA mode for I2C\n"); - } else { - gi2c->gpi_mode = false; - tx_depth = geni_se_get_tx_fifo_depth(&gi2c->se); - - /* I2C Master Hub Serial Elements doesn't have the HW_PARAM_0 register */ - if (!tx_depth && desc) - tx_depth = desc->tx_fifo_depth; - - if (!tx_depth) { - ret = dev_err_probe(dev, -EINVAL, - "Invalid TX FIFO depth\n"); - goto err_resources; - } - - gi2c->tx_wm = tx_depth - 1; - geni_se_init(&gi2c->se, gi2c->tx_wm, tx_depth); - geni_se_config_packing(&gi2c->se, BITS_PER_BYTE, - PACKING_BYTES_PW, true, true, true); - - dev_dbg(dev, "i2c fifo/se-dma mode. fifo depth:%d\n", tx_depth); - } - - clk_disable_unprepare(gi2c->core_clk); - ret = geni_se_resources_off(&gi2c->se); - if (ret) { - dev_err_probe(dev, ret, "Error turning off resources\n"); - goto err_dma; - } - - ret = geni_icc_disable(&gi2c->se); - if (ret) - goto err_dma; - pm_runtime_set_suspended(gi2c->se.dev); pm_runtime_set_autosuspend_delay(gi2c->se.dev, I2C_AUTO_SUSPEND_DELAY); pm_runtime_use_autosuspend(gi2c->se.dev); pm_runtime_enable(gi2c->se.dev); + ret = geni_i2c_init(gi2c); + if (ret < 0) { + pm_runtime_disable(gi2c->se.dev); + return ret; + } + ret = i2c_add_adapter(&gi2c->adap); if (ret) { dev_err_probe(dev, ret, "Error adding i2c adapter\n"); pm_runtime_disable(gi2c->se.dev); - goto err_dma; + return ret; } dev_dbg(dev, "Geni-I2C adaptor successfully added\n"); - return ret; - -err_resources: - geni_se_resources_off(&gi2c->se); -err_clk: - clk_disable_unprepare(gi2c->core_clk); - - return ret; - -err_dma: - release_gpi_dma(gi2c); - return ret; } From ed4b34033db25a0f35bb84289377e1916ffe2329 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Wed, 17 Jun 2026 10:20:17 +0530 Subject: [PATCH 08/38] i2c: qcom-geni: Move resource initialization to separate function Refactor the resource initialization in geni_i2c_probe() by introducing a new geni_i2c_resources_init() function and utilizing the common geni_se_resources_init() framework and clock frequency mapping, making the probe function cleaner. Signed-off-by: Praveen Talari Acked-by: Viken Dadhaniya Reviewed-by: Konrad Dybcio Tested-by: Mattijs Korpershoek Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260617-enable-i2c-on-sa8255p-v7-3-ad736dbeab57@oss.qualcomm.com --- drivers/i2c/busses/i2c-qcom-geni.c | 64 +++++++++++------------------- 1 file changed, 24 insertions(+), 40 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index 3ee0be228d7c..a7672b6c2bc0 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -107,7 +107,6 @@ struct geni_i2c_dev { int cur_wr; int cur_rd; spinlock_t lock; - struct clk *core_clk; u32 clk_freq_out; const struct geni_i2c_clk_fld *clk_fld; void *dma_buf; @@ -124,8 +123,6 @@ struct geni_i2c_dev { }; struct geni_i2c_desc { - bool has_core_clk; - char *icc_ddr; bool no_dma_support; unsigned int tx_fifo_depth; }; @@ -1058,6 +1055,23 @@ err: return ret; } +static int geni_i2c_resources_init(struct geni_i2c_dev *gi2c) +{ + int ret; + + ret = geni_se_resources_init(&gi2c->se); + if (ret) + return ret; + + ret = geni_i2c_clk_map_idx(gi2c); + if (ret) + return dev_err_probe(gi2c->se.dev, ret, "Invalid clk frequency %d Hz\n", + gi2c->clk_freq_out); + + return geni_icc_set_bw_ab(&gi2c->se, GENI_DEFAULT_BW, GENI_DEFAULT_BW, + Bps_to_icc(gi2c->clk_freq_out)); +} + static int geni_i2c_probe(struct platform_device *pdev) { struct geni_i2c_dev *gi2c; @@ -1077,16 +1091,6 @@ static int geni_i2c_probe(struct platform_device *pdev) desc = device_get_match_data(&pdev->dev); - if (desc && desc->has_core_clk) { - gi2c->core_clk = devm_clk_get(dev, "core"); - if (IS_ERR(gi2c->core_clk)) - return PTR_ERR(gi2c->core_clk); - } - - gi2c->se.clk = devm_clk_get(dev, "se"); - if (IS_ERR(gi2c->se.clk) && !has_acpi_companion(dev)) - return PTR_ERR(gi2c->se.clk); - ret = device_property_read_u32(dev, "clock-frequency", &gi2c->clk_freq_out); if (ret) { @@ -1101,16 +1105,15 @@ static int geni_i2c_probe(struct platform_device *pdev) if (gi2c->irq < 0) return gi2c->irq; - ret = geni_i2c_clk_map_idx(gi2c); - if (ret) - return dev_err_probe(dev, ret, "Invalid clk frequency %d Hz\n", - gi2c->clk_freq_out); - gi2c->adap.algo = &geni_i2c_algo; init_completion(&gi2c->done); spin_lock_init(&gi2c->lock); platform_set_drvdata(pdev, gi2c); + ret = geni_i2c_resources_init(gi2c); + if (ret) + return ret; + /* Keep interrupts disabled initially to allow for low-power modes */ ret = devm_request_irq(dev, gi2c->irq, geni_i2c_irq, IRQF_NO_AUTOEN, dev_name(dev), gi2c); @@ -1123,23 +1126,6 @@ static int geni_i2c_probe(struct platform_device *pdev) gi2c->adap.dev.of_node = dev->of_node; strscpy(gi2c->adap.name, "Geni-I2C", sizeof(gi2c->adap.name)); - ret = geni_icc_get(&gi2c->se, desc ? desc->icc_ddr : "qup-memory"); - if (ret) - return ret; - /* - * Set the bus quota for core and cpu to a reasonable value for - * register access. - * Set quota for DDR based on bus speed. - */ - gi2c->se.icc_paths[GENI_TO_CORE].avg_bw = GENI_DEFAULT_BW; - gi2c->se.icc_paths[CPU_TO_GENI].avg_bw = GENI_DEFAULT_BW; - if (!desc || desc->icc_ddr) - gi2c->se.icc_paths[GENI_TO_DDR].avg_bw = Bps_to_icc(gi2c->clk_freq_out); - - ret = geni_icc_set_bw(&gi2c->se); - if (ret) - return ret; - pm_runtime_set_suspended(gi2c->se.dev); pm_runtime_set_autosuspend_delay(gi2c->se.dev, I2C_AUTO_SUSPEND_DELAY); pm_runtime_use_autosuspend(gi2c->se.dev); @@ -1192,7 +1178,7 @@ static int __maybe_unused geni_i2c_runtime_suspend(struct device *dev) return ret; } - clk_disable_unprepare(gi2c->core_clk); + clk_disable_unprepare(gi2c->se.core_clk); return geni_icc_disable(&gi2c->se); } @@ -1206,7 +1192,7 @@ static int __maybe_unused geni_i2c_runtime_resume(struct device *dev) if (ret) return ret; - ret = clk_prepare_enable(gi2c->core_clk); + ret = clk_prepare_enable(gi2c->se.core_clk); if (ret) goto out_icc_disable; @@ -1219,7 +1205,7 @@ static int __maybe_unused geni_i2c_runtime_resume(struct device *dev) return 0; out_clk_disable: - clk_disable_unprepare(gi2c->core_clk); + clk_disable_unprepare(gi2c->se.core_clk); out_icc_disable: geni_icc_disable(&gi2c->se); @@ -1260,8 +1246,6 @@ static const struct dev_pm_ops geni_i2c_pm_ops = { }; static const struct geni_i2c_desc i2c_master_hub = { - .has_core_clk = true, - .icc_ddr = NULL, .no_dma_support = true, .tx_fifo_depth = 16, }; From e441365d09030377d5bbb219c4bf974ef9960851 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Wed, 17 Jun 2026 10:20:18 +0530 Subject: [PATCH 09/38] i2c: qcom-geni: Use resources helper APIs in runtime PM functions To manage GENI serial engine resources during runtime power management, drivers currently need to call functions for ICC, clock, and SE resource operations in both suspend and resume paths, resulting in code duplication across drivers. The new geni_se_resources_activate() and geni_se_resources_deactivate() helper APIs addresses this issue by providing a streamlined method to enable or disable all resources based, thereby eliminating redundancy across drivers. Signed-off-by: Praveen Talari Acked-by: Viken Dadhaniya Reviewed-by: Konrad Dybcio Tested-by: Mattijs Korpershoek Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260617-enable-i2c-on-sa8255p-v7-4-ad736dbeab57@oss.qualcomm.com --- drivers/i2c/busses/i2c-qcom-geni.c | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index a7672b6c2bc0..8f8b74c4e88a 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -1172,15 +1172,14 @@ static int __maybe_unused geni_i2c_runtime_suspend(struct device *dev) struct geni_i2c_dev *gi2c = dev_get_drvdata(dev); disable_irq(gi2c->irq); - ret = geni_se_resources_off(&gi2c->se); + + ret = geni_se_resources_deactivate(&gi2c->se); if (ret) { enable_irq(gi2c->irq); return ret; } - clk_disable_unprepare(gi2c->se.core_clk); - - return geni_icc_disable(&gi2c->se); + return 0; } static int __maybe_unused geni_i2c_runtime_resume(struct device *dev) @@ -1188,28 +1187,13 @@ static int __maybe_unused geni_i2c_runtime_resume(struct device *dev) int ret; struct geni_i2c_dev *gi2c = dev_get_drvdata(dev); - ret = geni_icc_enable(&gi2c->se); + ret = geni_se_resources_activate(&gi2c->se); if (ret) return ret; - ret = clk_prepare_enable(gi2c->se.core_clk); - if (ret) - goto out_icc_disable; - - ret = geni_se_resources_on(&gi2c->se); - if (ret) - goto out_clk_disable; - enable_irq(gi2c->irq); return 0; - -out_clk_disable: - clk_disable_unprepare(gi2c->se.core_clk); -out_icc_disable: - geni_icc_disable(&gi2c->se); - - return ret; } static int __maybe_unused geni_i2c_suspend_noirq(struct device *dev) From 692e0c84db5fdd88c242eadc873d498787c94e3e Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Wed, 17 Jun 2026 10:20:19 +0530 Subject: [PATCH 10/38] i2c: qcom-geni: Store of_device_id data in driver private struct To avoid repeatedly fetching and checking platform data across various functions, store the struct of_device_id data directly in the i2c private structure. This change enhances code maintainability and reduces redundancy. Signed-off-by: Praveen Talari Acked-by: Viken Dadhaniya Reviewed-by: Konrad Dybcio Tested-by: Mattijs Korpershoek Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260617-enable-i2c-on-sa8255p-v7-5-ad736dbeab57@oss.qualcomm.com --- drivers/i2c/busses/i2c-qcom-geni.c | 46 ++++++++++++++++-------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index 8f8b74c4e88a..92cccb1cce69 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -77,6 +77,11 @@ enum geni_i2c_err_code { #define XFER_TIMEOUT HZ #define RST_TIMEOUT HZ +struct geni_i2c_desc { + bool no_dma_support; + unsigned int tx_fifo_depth; +}; + #define QCOM_I2C_MIN_NUM_OF_MSGS_MULTI_DESC 2 /** @@ -120,11 +125,7 @@ struct geni_i2c_dev { bool is_tx_multi_desc_xfer; u32 num_msgs; struct geni_i2c_gpi_multi_desc_xfer i2c_multi_desc_config; -}; - -struct geni_i2c_desc { - bool no_dma_support; - unsigned int tx_fifo_depth; + const struct geni_i2c_desc *dev_data; }; struct geni_i2c_err_log { @@ -941,15 +942,6 @@ static const struct i2c_algorithm geni_i2c_algo = { .functionality = geni_i2c_func, }; -#ifdef CONFIG_ACPI -static const struct acpi_device_id geni_i2c_acpi_match[] = { - { "QCOM0220"}, - { "QCOM0411" }, - { } -}; -MODULE_DEVICE_TABLE(acpi, geni_i2c_acpi_match); -#endif - static void release_gpi_dma(struct geni_i2c_dev *gi2c) { if (gi2c->rx_c) @@ -989,7 +981,6 @@ err_tx: static int geni_i2c_init(struct geni_i2c_dev *gi2c) { - const struct geni_i2c_desc *desc = NULL; u32 proto, tx_depth; bool fifo_disable; int ret; @@ -1012,8 +1003,7 @@ static int geni_i2c_init(struct geni_i2c_dev *gi2c) goto err; } - desc = device_get_match_data(gi2c->se.dev); - if (desc && desc->no_dma_support) { + if (gi2c->dev_data->no_dma_support) { fifo_disable = false; gi2c->no_dma = true; } else { @@ -1033,8 +1023,8 @@ static int geni_i2c_init(struct geni_i2c_dev *gi2c) tx_depth = geni_se_get_tx_fifo_depth(&gi2c->se); /* I2C Master Hub Serial Elements doesn't have the HW_PARAM_0 register */ - if (!tx_depth && desc) - tx_depth = desc->tx_fifo_depth; + if (!tx_depth && gi2c->se.core_clk) + tx_depth = gi2c->dev_data->tx_fifo_depth; if (!tx_depth) { ret = dev_err_probe(gi2c->se.dev, -EINVAL, @@ -1077,7 +1067,6 @@ static int geni_i2c_probe(struct platform_device *pdev) struct geni_i2c_dev *gi2c; int ret; struct device *dev = &pdev->dev; - const struct geni_i2c_desc *desc = NULL; gi2c = devm_kzalloc(dev, sizeof(*gi2c), GFP_KERNEL); if (!gi2c) @@ -1089,7 +1078,9 @@ static int geni_i2c_probe(struct platform_device *pdev) if (IS_ERR(gi2c->se.base)) return PTR_ERR(gi2c->se.base); - desc = device_get_match_data(&pdev->dev); + gi2c->dev_data = device_get_match_data(&pdev->dev); + if (!gi2c->dev_data) + return -EINVAL; ret = device_property_read_u32(dev, "clock-frequency", &gi2c->clk_freq_out); @@ -1229,13 +1220,24 @@ static const struct dev_pm_ops geni_i2c_pm_ops = { NULL) }; +static const struct geni_i2c_desc geni_i2c = {}; + static const struct geni_i2c_desc i2c_master_hub = { .no_dma_support = true, .tx_fifo_depth = 16, }; +#ifdef CONFIG_ACPI +static const struct acpi_device_id geni_i2c_acpi_match[] = { + { "QCOM0220", (kernel_ulong_t)&geni_i2c}, + { "QCOM0411", (kernel_ulong_t)&geni_i2c}, + { } +}; +MODULE_DEVICE_TABLE(acpi, geni_i2c_acpi_match); +#endif + static const struct of_device_id geni_i2c_dt_match[] = { - { .compatible = "qcom,geni-i2c" }, + { .compatible = "qcom,geni-i2c", .data = &geni_i2c }, { .compatible = "qcom,geni-i2c-master-hub", .data = &i2c_master_hub }, {} }; From 10e74f4c5046dfc9e90e97730bac4de8afd9e954 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Wed, 17 Jun 2026 10:20:20 +0530 Subject: [PATCH 11/38] i2c: qcom-geni: Enable I2C on SA8255p Qualcomm platforms The Qualcomm automotive SA8255p SoC relies on firmware to configure platform resources, including clocks, interconnects and TLMM. The driver requests resources operations over SCMI using power and performance protocols. The SCMI power protocol enables or disables resources like clocks, interconnect paths, and TLMM (GPIOs) using runtime PM framework APIs, such as resume/suspend, to control power on/off. The SCMI performance protocol manages I2C frequency, with each frequency rate represented by a performance level. The driver uses geni_se_set_perf_opp() API to request the desired frequency rate.. As part of geni_se_set_perf_opp(), the OPP for the requested frequency is obtained using dev_pm_opp_find_freq_floor() and the performance level is set using dev_pm_opp_set_opp(). Signed-off-by: Praveen Talari Acked-by: Viken Dadhaniya Reviewed-by: Konrad Dybcio Tested-by: Mattijs Korpershoek Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260617-enable-i2c-on-sa8255p-v7-6-ad736dbeab57@oss.qualcomm.com --- drivers/i2c/busses/i2c-qcom-geni.c | 56 ++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index 92cccb1cce69..96dbf04138be 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -80,6 +80,10 @@ enum geni_i2c_err_code { struct geni_i2c_desc { bool no_dma_support; unsigned int tx_fifo_depth; + int (*resources_init)(struct geni_se *se); + int (*set_rate)(struct geni_se *se, unsigned long freq); + int (*power_on)(struct geni_se *se); + int (*power_off)(struct geni_se *se); }; #define QCOM_I2C_MIN_NUM_OF_MSGS_MULTI_DESC 2 @@ -200,8 +204,9 @@ static int geni_i2c_clk_map_idx(struct geni_i2c_dev *gi2c) return -EINVAL; } -static void qcom_geni_i2c_conf(struct geni_i2c_dev *gi2c) +static int qcom_geni_i2c_conf(struct geni_se *se, unsigned long freq) { + struct geni_i2c_dev *gi2c = dev_get_drvdata(se->dev); const struct geni_i2c_clk_fld *itr = gi2c->clk_fld; u32 val; @@ -214,6 +219,7 @@ static void qcom_geni_i2c_conf(struct geni_i2c_dev *gi2c) val |= itr->t_low_cnt << LOW_COUNTER_SHFT; val |= itr->t_cycle_cnt; writel_relaxed(val, gi2c->se.base + SE_I2C_SCL_COUNTERS); + return 0; } static void geni_i2c_err_misc(struct geni_i2c_dev *gi2c) @@ -919,7 +925,9 @@ static int geni_i2c_xfer(struct i2c_adapter *adap, return ret; } - qcom_geni_i2c_conf(gi2c); + ret = gi2c->dev_data->set_rate(&gi2c->se, gi2c->clk_freq_out); + if (ret) + return ret; if (gi2c->gpi_mode) ret = geni_i2c_gpi_xfer(gi2c, msgs, num); @@ -1045,8 +1053,9 @@ err: return ret; } -static int geni_i2c_resources_init(struct geni_i2c_dev *gi2c) +static int geni_i2c_resources_init(struct geni_se *se) { + struct geni_i2c_dev *gi2c = dev_get_drvdata(se->dev); int ret; ret = geni_se_resources_init(&gi2c->se); @@ -1101,7 +1110,7 @@ static int geni_i2c_probe(struct platform_device *pdev) spin_lock_init(&gi2c->lock); platform_set_drvdata(pdev, gi2c); - ret = geni_i2c_resources_init(gi2c); + ret = gi2c->dev_data->resources_init(&gi2c->se); if (ret) return ret; @@ -1159,15 +1168,17 @@ static void geni_i2c_shutdown(struct platform_device *pdev) static int __maybe_unused geni_i2c_runtime_suspend(struct device *dev) { - int ret; + int ret = 0; struct geni_i2c_dev *gi2c = dev_get_drvdata(dev); disable_irq(gi2c->irq); - ret = geni_se_resources_deactivate(&gi2c->se); - if (ret) { - enable_irq(gi2c->irq); - return ret; + if (gi2c->dev_data->power_off) { + ret = gi2c->dev_data->power_off(&gi2c->se); + if (ret) { + enable_irq(gi2c->irq); + return ret; + } } return 0; @@ -1175,12 +1186,14 @@ static int __maybe_unused geni_i2c_runtime_suspend(struct device *dev) static int __maybe_unused geni_i2c_runtime_resume(struct device *dev) { - int ret; + int ret = 0; struct geni_i2c_dev *gi2c = dev_get_drvdata(dev); - ret = geni_se_resources_activate(&gi2c->se); - if (ret) - return ret; + if (gi2c->dev_data->power_on) { + ret = gi2c->dev_data->power_on(&gi2c->se); + if (ret) + return ret; + } enable_irq(gi2c->irq); @@ -1220,11 +1233,25 @@ static const struct dev_pm_ops geni_i2c_pm_ops = { NULL) }; -static const struct geni_i2c_desc geni_i2c = {}; +static const struct geni_i2c_desc geni_i2c = { + .resources_init = geni_i2c_resources_init, + .set_rate = qcom_geni_i2c_conf, + .power_on = geni_se_resources_activate, + .power_off = geni_se_resources_deactivate, +}; static const struct geni_i2c_desc i2c_master_hub = { .no_dma_support = true, .tx_fifo_depth = 16, + .resources_init = geni_i2c_resources_init, + .set_rate = qcom_geni_i2c_conf, + .power_on = geni_se_resources_activate, + .power_off = geni_se_resources_deactivate, +}; + +static const struct geni_i2c_desc sa8255p_geni_i2c = { + .resources_init = geni_se_domain_attach, + .set_rate = geni_se_set_perf_opp, }; #ifdef CONFIG_ACPI @@ -1239,6 +1266,7 @@ MODULE_DEVICE_TABLE(acpi, geni_i2c_acpi_match); static const struct of_device_id geni_i2c_dt_match[] = { { .compatible = "qcom,geni-i2c", .data = &geni_i2c }, { .compatible = "qcom,geni-i2c-master-hub", .data = &i2c_master_hub }, + { .compatible = "qcom,sa8255p-geni-i2c", .data = &sa8255p_geni_i2c }, {} }; MODULE_DEVICE_TABLE(of, geni_i2c_dt_match); From 9e9762e578db7556bea644bb5a8a2435f63544d1 Mon Sep 17 00:00:00 2001 From: Abhijeeth Babu Date: Thu, 18 Jun 2026 00:56:11 +0530 Subject: [PATCH 12/38] i2c: acpi: Force ELAN1300 touchpads to 100 kHz The ELAN1300 touchpad with HID ID 04F3:3104 on the ASUS VivoBook X513UA_KM513UA exhibits severe pointer jumps and erratic movement when the I2C bus operates at 400 kHz. The system DSDT describes the touchpad using an I2cSerialBusV2 resource with a speed of 0x00061A80, corresponding to 400 kHz, on the AMDI0010:03 controller. At this speed, libinput repeatedly reports: kernel bug: Touch jump detected and discarded Add ELAN1300 to i2c_acpi_force_100khz_device_ids so that it operates at 100 kHz. Before the change, nine touch-jump events were recorded, with additional messages suppressed by libinput's rate limiter. With the bus forced to 100 kHz, no touch-jump events have been observed and the touchpad operates normally. The affected system uses BIOS X513UA.319 dated 2023-05-09. Signed-off-by: Abhijeeth Babu Acked-by: Mika Westerberg Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260617192611.125513-1-babu.jeeth@gmail.com --- drivers/i2c/i2c-core-acpi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c index e5fddacae9a4..763743d5dbd6 100644 --- a/drivers/i2c/i2c-core-acpi.c +++ b/drivers/i2c/i2c-core-acpi.c @@ -376,6 +376,7 @@ static const struct acpi_device_id i2c_acpi_force_100khz_device_ids[] = { { "DLL0945", 0 }, { "ELAN0678", 0 }, { "ELAN06FA", 0 }, + { "ELAN1300", 0 }, {} }; From 20c3dcb1f473aea0810bb6c3b46149188fe3be0b Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 17 Jun 2026 11:20:46 +0200 Subject: [PATCH 13/38] i2c: acpi: Free resource list at appropriate time We do unneeded "double free" (emptying an empty list) in one case. At the same time we shadow a real error code. Address them by refactoring the code in question. Both are not critical issues at all, they just make code robust against any possible future changes in the flow. Signed-off-by: Andy Shevchenko Acked-by: Mika Westerberg Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260617092046.2649219-1-andriy.shevchenko@linux.intel.com --- drivers/i2c/i2c-core-acpi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c index 763743d5dbd6..10cdceaba475 100644 --- a/drivers/i2c/i2c-core-acpi.c +++ b/drivers/i2c/i2c-core-acpi.c @@ -168,9 +168,12 @@ static int i2c_acpi_do_lookup(struct acpi_device *adev, INIT_LIST_HEAD(&resource_list); ret = acpi_dev_get_resources(adev, &resource_list, i2c_acpi_fill_info, lookup); + if (ret < 0) + return ret; + acpi_dev_free_resource_list(&resource_list); - if (ret < 0 || !info->addr) + if (!info->addr) return -EINVAL; return 0; From 566eba52236f154c0fc6517d1db8fcca630fd400 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 18 Jun 2026 15:09:48 +0200 Subject: [PATCH 14/38] i2c: pnx: Use generic definitions for bus frequencies Since we have generic definitions for bus frequencies, let's use them. Signed-off-by: Andy Shevchenko Reviewed-by: Vladimir Zapolskiy Link: https://lore.kernel.org/r/20260618130948.3199768-1-andriy.shevchenko@linux.intel.com Signed-off-by: Andi Shyti --- drivers/i2c/busses/i2c-pnx.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-pnx.c b/drivers/i2c/busses/i2c-pnx.c index 8daa0008bd05..e1cc2b2bd628 100644 --- a/drivers/i2c/busses/i2c-pnx.c +++ b/drivers/i2c/busses/i2c-pnx.c @@ -24,7 +24,6 @@ #include #define I2C_PNX_TIMEOUT_DEFAULT 10 /* msec */ -#define I2C_PNX_SPEED_KHZ_DEFAULT 100 #define I2C_PNX_REGION_SIZE 0x100 struct i2c_pnx_mif { @@ -606,12 +605,12 @@ static DEFINE_SIMPLE_DEV_PM_OPS(i2c_pnx_pm, static int i2c_pnx_probe(struct platform_device *pdev) { + u32 speed = I2C_MAX_STANDARD_MODE_FREQ; unsigned long tmp; int ret = 0; struct i2c_pnx_algo_data *alg_data; unsigned long freq; struct resource *res; - u32 speed = I2C_PNX_SPEED_KHZ_DEFAULT * 1000; alg_data = devm_kzalloc(&pdev->dev, sizeof(*alg_data), GFP_KERNEL); if (!alg_data) From 20ec09394d5dff5af8379d2d50fc79f51e9af742 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 18 Jun 2026 15:34:29 +0200 Subject: [PATCH 15/38] i2c: davinci: Use generic definitions for bus frequencies Since we have generic definitions for bus frequencies, let's use them. Signed-off-by: Andy Shevchenko Reviewed-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20260618133429.3214475-1-andriy.shevchenko@linux.intel.com Signed-off-by: Andi Shyti --- drivers/i2c/busses/i2c-davinci.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/i2c/busses/i2c-davinci.c b/drivers/i2c/busses/i2c-davinci.c index 0617f416cb0b..e15eef163a8f 100644 --- a/drivers/i2c/busses/i2c-davinci.c +++ b/drivers/i2c/busses/i2c-davinci.c @@ -117,8 +117,6 @@ /* timeout for pm runtime autosuspend */ #define DAVINCI_I2C_PM_TIMEOUT 1000 /* ms */ -#define DAVINCI_I2C_DEFAULT_BUS_FREQ 100000 - struct davinci_i2c_dev { struct device *dev; void __iomem *base; @@ -760,7 +758,7 @@ static int davinci_i2c_probe(struct platform_device *pdev) r = device_property_read_u32(&pdev->dev, "clock-frequency", &prop); if (r) - prop = DAVINCI_I2C_DEFAULT_BUS_FREQ; + prop = I2C_MAX_STANDARD_MODE_FREQ; dev->bus_freq = prop / 1000; From 08d50f151747c12c41589e850c86232370fb427c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 18 Jun 2026 16:11:25 +0200 Subject: [PATCH 16/38] i2c: k1: Use generic definitions for bus frequencies Since we have generic definitions for bus frequencies, let's use them. Signed-off-by: Andy Shevchenko Reviewed-by: Troy Mitchell Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260618141125.3241048-1-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/i2c-k1.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c index 7cf5c05a20d2..c2d090f6ba80 100644 --- a/drivers/i2c/busses/i2c-k1.c +++ b/drivers/i2c/busses/i2c-k1.c @@ -108,9 +108,6 @@ /* i2c bus recover timeout: us */ #define SPACEMIT_I2C_BUS_BUSY_TIMEOUT 100000 -#define SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ 100000 /* Hz */ -#define SPACEMIT_I2C_MAX_FAST_MODE_FREQ 400000 /* Hz */ - #define SPACEMIT_SR_ERR (SPACEMIT_SR_BED | SPACEMIT_SR_RXOV | SPACEMIT_SR_ALD) #define SPACEMIT_BUS_RESET_CLK_CNT_MAX 9 @@ -836,15 +833,15 @@ static int spacemit_i2c_probe(struct platform_device *pdev) of_property_read_u32(of_node, "clock-frequency", &i2c->clock_freq); /* For now, this driver doesn't support high-speed. */ - if (i2c->clock_freq > SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ && - i2c->clock_freq <= SPACEMIT_I2C_MAX_FAST_MODE_FREQ) { + if (i2c->clock_freq > I2C_MAX_STANDARD_MODE_FREQ && + i2c->clock_freq <= I2C_MAX_FAST_MODE_FREQ) { i2c->mode = SPACEMIT_MODE_FAST; - } else if (i2c->clock_freq && i2c->clock_freq <= SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ) { + } else if (i2c->clock_freq && i2c->clock_freq <= I2C_MAX_STANDARD_MODE_FREQ) { i2c->mode = SPACEMIT_MODE_STANDARD; } else { dev_info(dev, "clock-frequency not set or out of range, using fast mode\n"); i2c->mode = SPACEMIT_MODE_FAST; - i2c->clock_freq = SPACEMIT_I2C_MAX_FAST_MODE_FREQ; + i2c->clock_freq = I2C_MAX_FAST_MODE_FREQ; } i2c->dev = &pdev->dev; From dfdca0b1eb8c1aace81dff0813f44e5f3411cd2f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 18 Jun 2026 16:13:46 +0200 Subject: [PATCH 17/38] i2c: microchip-corei2c: Use generic definitions for bus frequencies Since we have generic definitions for bus frequencies, let's use them. Signed-off-by: Andy Shevchenko Acked-by: Conor Dooley Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260618141346.3241286-1-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/i2c-microchip-corei2c.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-microchip-corei2c.c b/drivers/i2c/busses/i2c-microchip-corei2c.c index c8599733633e..330e150ef6d5 100644 --- a/drivers/i2c/busses/i2c-microchip-corei2c.c +++ b/drivers/i2c/busses/i2c-microchip-corei2c.c @@ -565,10 +565,10 @@ static int mchp_corei2c_probe(struct platform_device *pdev) &idev->bus_clk_rate); if (ret || !idev->bus_clk_rate) { dev_info(&pdev->dev, "default to 100kHz\n"); - idev->bus_clk_rate = 100000; + idev->bus_clk_rate = I2C_MAX_STANDARD_MODE_FREQ; } - if (idev->bus_clk_rate > 400000) + if (idev->bus_clk_rate > I2C_MAX_FAST_MODE_FREQ) return dev_err_probe(&pdev->dev, -EINVAL, "clock-frequency too high: %d\n", idev->bus_clk_rate); From a660c2264f3bf11f954321baede4f8f8eb987d84 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 18 Jun 2026 16:15:46 +0200 Subject: [PATCH 18/38] i2c: octeon-core: Use generic definitions for bus frequencies Since we have generic definitions for bus frequencies, let's use them. Signed-off-by: Andy Shevchenko Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260618141546.3241531-1-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/i2c-octeon-core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-octeon-core.h b/drivers/i2c/busses/i2c-octeon-core.h index 32a44f2d6274..aba81477d7d4 100644 --- a/drivers/i2c/busses/i2c-octeon-core.h +++ b/drivers/i2c/busses/i2c-octeon-core.h @@ -235,7 +235,7 @@ static inline void octeon_i2c_write_int(struct octeon_i2c *i2c, u64 data) octeon_i2c_writeq_flush(data, i2c->twsi_base + OCTEON_REG_TWSI_INT(i2c)); } -#define IS_LS_FREQ(twsi_freq) ((twsi_freq) <= 400000) +#define IS_LS_FREQ(twsi_freq) ((twsi_freq) <= I2C_MAX_FAST_MODE_FREQ) #define PCI_SUBSYS_DEVID_9XXX 0xB #define PCI_SUBSYS_MASK GENMASK(15, 12) /** From 04e9bf1648f846976b543e91c1838a712433772a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 18 Jun 2026 16:17:30 +0200 Subject: [PATCH 19/38] i2c: nomadik: Use generic definitions for bus frequencies Since we have generic definitions for bus frequencies, let's use them. Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260618141730.3243303-1-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/i2c-nomadik.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index b63ee51c1652..404709179d73 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -1050,9 +1050,9 @@ static int nmk_i2c_eyeq5_probe(struct nmk_i2c_dev *priv) if (id >= ARRAY_SIZE(nmk_i2c_eyeq5_masks)) return -ENOENT; - if (priv->clk_freq <= 400000) + if (priv->clk_freq <= I2C_MAX_FAST_MODE_FREQ) speed_mode = I2C_EYEQ5_SPEED_FAST; - else if (priv->clk_freq <= 1000000) + else if (priv->clk_freq <= I2C_MAX_FAST_MODE_PLUS_FREQ) speed_mode = I2C_EYEQ5_SPEED_FAST_PLUS; else speed_mode = I2C_EYEQ5_SPEED_HIGH_SPEED; From e26e82b04ffdd60d44f2e64db1495855b78e3083 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 23 Jul 2026 10:11:40 +0800 Subject: [PATCH 20/38] i2c: spacemit: request IRQ after controller initialization spacemit_i2c_probe() requests the IRQ before it enables the clocks, resets the controller and runs init_completion(). If an interrupt is already pending, the handler runs too early: it reads registers while the clocks are still off and calls complete() on an uninitialized completion. Request the IRQ after the controller and completion are initialized, but still before the adapter is registered. Fixes: 5ea558473fa3 ("i2c: spacemit: add support for SpacemiT K1 SoC") Signed-off-by: Linmao Li Cc: # v6.15+ Reviewed-by: Troy Mitchell Reviewed-by: Alex Elder Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260723021140.2293844-1-lilinmao@kylinos.cn --- drivers/i2c/busses/i2c-k1.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c index 51a0c3d80fc9..3fe716cc153d 100644 --- a/drivers/i2c/busses/i2c-k1.c +++ b/drivers/i2c/busses/i2c-k1.c @@ -723,11 +723,6 @@ static int spacemit_i2c_probe(struct platform_device *pdev) if (i2c->irq < 0) return dev_err_probe(dev, i2c->irq, "failed to get irq resource"); - ret = devm_request_irq(i2c->dev, i2c->irq, spacemit_i2c_irq_handler, - IRQF_NO_SUSPEND, dev_name(i2c->dev), i2c); - if (ret) - return dev_err_probe(dev, ret, "failed to request irq"); - clk = devm_clk_get_enabled(dev, "func"); if (IS_ERR(clk)) return dev_err_probe(dev, PTR_ERR(clk), "failed to enable func clock"); @@ -755,6 +750,11 @@ static int spacemit_i2c_probe(struct platform_device *pdev) init_completion(&i2c->complete); + ret = devm_request_irq(i2c->dev, i2c->irq, spacemit_i2c_irq_handler, + IRQF_NO_SUSPEND, dev_name(i2c->dev), i2c); + if (ret) + return dev_err_probe(dev, ret, "failed to request irq"); + platform_set_drvdata(pdev, i2c); ret = i2c_add_numbered_adapter(&i2c->adapt); From 877ed2eb9c14e07e19041e88470f98df684537ea Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Tue, 21 Jul 2026 23:41:47 +0900 Subject: [PATCH 21/38] i2c: amd-mp2: Unregister callback on adapter add failure amd_mp2_register_cb() stores the platform I2C context in the MP2 PCI driver's callback table before the adapter is registered. If i2c_add_adapter() fails, probe returns and devres frees the context, but the PCI driver can still dereference the stale pointer from its IRQ and system-sleep callbacks. Unregister the callback before returning the adapter registration error. Fixes: 529766e0a011 ("i2c: Add drivers for the AMD PCIe MP2 I2C controller") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Cc: # v5.2+ Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260721144147.31150-1-mhun512@gmail.com --- drivers/i2c/busses/i2c-amd-mp2-plat.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-amd-mp2-plat.c b/drivers/i2c/busses/i2c-amd-mp2-plat.c index 188e24cc4d35..9fdd6a5fb8b6 100644 --- a/drivers/i2c/busses/i2c-amd-mp2-plat.c +++ b/drivers/i2c/busses/i2c-amd-mp2-plat.c @@ -316,8 +316,10 @@ static int i2c_amd_probe(struct platform_device *pdev) amd_mp2_pm_runtime_put(mp2_dev); - if (ret < 0) + if (ret < 0) { dev_err(&pdev->dev, "i2c add adapter failed = %d\n", ret); + amd_mp2_unregister_cb(&i2c_dev->common); + } return ret; } From d3ac4939fb795be89fe483f57455963f825d0c61 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 7 Jul 2026 17:57:08 +0800 Subject: [PATCH 22/38] dt-bindings: i2c: qcom,sa8255p-geni-i2c: Add compatible for Nord SA8797P Add compatible for Nord SA8797P QUP GENI I2C controller, which is compatible with SA8255P controller. Signed-off-by: Shawn Guo Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260707095708.3801043-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Andi Shyti --- .../devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml b/Documentation/devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml index a61e40b5cbc1..04caf622b3ba 100644 --- a/Documentation/devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml @@ -11,7 +11,11 @@ maintainers: properties: compatible: - const: qcom,sa8255p-geni-i2c + oneOf: + - const: qcom,sa8255p-geni-i2c + - items: + - const: qcom,sa8797p-geni-i2c + - const: qcom,sa8255p-geni-i2c reg: maxItems: 1 From 5272a6ab6bea418d90150aea30ac386a538cfff8 Mon Sep 17 00:00:00 2001 From: Mukesh Kumar Savaliya Date: Tue, 14 Jul 2026 10:45:12 +0530 Subject: [PATCH 23/38] i2c: qcom-geni: Use devm_pm_runtime_enable() for PM management The current implementation manually calls pm_runtime_enable() in probe() and pm_runtime_disable() in remove() and error paths. This pattern is error-prone and requires careful cleanup in all failure paths. Using the devres-managed variant eliminates this complexity. Migrate from manual pm_runtime_enable()/pm_runtime_disable() calls to the devres-managed devm_pm_runtime_enable() API. This simplifies the driver by automatically handling runtime PM cleanup when the device is removed or probe fails. This helps with Simplified error handling and Automatic cleanup. Signed-off-by: Mukesh Kumar Savaliya Reviewed-by: Konrad Dybcio Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260714051512.3732551-1-mukesh.savaliya@oss.qualcomm.com --- drivers/i2c/busses/i2c-qcom-geni.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index 96dbf04138be..d5c977e34994 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -1126,27 +1126,25 @@ static int geni_i2c_probe(struct platform_device *pdev) gi2c->adap.dev.of_node = dev->of_node; strscpy(gi2c->adap.name, "Geni-I2C", sizeof(gi2c->adap.name)); - pm_runtime_set_suspended(gi2c->se.dev); - pm_runtime_set_autosuspend_delay(gi2c->se.dev, I2C_AUTO_SUSPEND_DELAY); - pm_runtime_use_autosuspend(gi2c->se.dev); - pm_runtime_enable(gi2c->se.dev); + pm_runtime_set_suspended(dev); + pm_runtime_set_autosuspend_delay(dev, I2C_AUTO_SUSPEND_DELAY); + pm_runtime_use_autosuspend(dev); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; ret = geni_i2c_init(gi2c); - if (ret < 0) { - pm_runtime_disable(gi2c->se.dev); + if (ret < 0) return ret; - } ret = i2c_add_adapter(&gi2c->adap); - if (ret) { - dev_err_probe(dev, ret, "Error adding i2c adapter\n"); - pm_runtime_disable(gi2c->se.dev); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "Error adding i2c adapter\n"); dev_dbg(dev, "Geni-I2C adaptor successfully added\n"); - return ret; + return 0; } static void geni_i2c_remove(struct platform_device *pdev) @@ -1155,7 +1153,6 @@ static void geni_i2c_remove(struct platform_device *pdev) i2c_del_adapter(&gi2c->adap); release_gpi_dma(gi2c); - pm_runtime_disable(gi2c->se.dev); } static void geni_i2c_shutdown(struct platform_device *pdev) From d2b83deba536de9dd068860c223c5c1704b8a4a7 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Mon, 20 Jul 2026 16:53:26 +0800 Subject: [PATCH 24/38] i2c: busses: drop redundant dev_err_probe() around irq helpers platform_get_irq(), devm_request_irq() and devm_request_threaded_irq() already print an error message via dev_err_probe() on failure, so wrapping their return value with another dev_err_probe() results in duplicate error output. Drop these redundant dev_err_probe() calls across i2c bus drivers and return the error code directly instead. Signed-off-by: Pei Xiao Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/aea09858af6be39497325e2560267771ed62e6b0.1784537126.git.xiaopei01@kylinos.cn --- drivers/i2c/busses/i2c-amd-asf-plat.c | 4 ++-- drivers/i2c/busses/i2c-bcm-iproc.c | 3 +-- drivers/i2c/busses/i2c-gxp.c | 2 +- drivers/i2c/busses/i2c-hisi.c | 2 +- drivers/i2c/busses/i2c-imx-lpi2c.c | 2 +- drivers/i2c/busses/i2c-imx.c | 2 +- drivers/i2c/busses/i2c-k1.c | 4 ++-- drivers/i2c/busses/i2c-ls2x.c | 2 +- drivers/i2c/busses/i2c-microchip-corei2c.c | 3 +-- drivers/i2c/busses/i2c-mlxbf.c | 2 +- drivers/i2c/busses/i2c-nomadik.c | 3 +-- drivers/i2c/busses/i2c-qcom-geni.c | 3 +-- drivers/i2c/busses/i2c-riic.c | 3 +-- drivers/i2c/busses/i2c-rzv2m.c | 2 +- drivers/i2c/busses/i2c-sprd.c | 4 +--- drivers/i2c/busses/i2c-st.c | 3 +-- drivers/i2c/busses/i2c-stm32f4.c | 6 ++---- drivers/i2c/busses/i2c-stm32f7.c | 4 ++-- drivers/i2c/busses/i2c-sun6i-p2wi.c | 4 +--- drivers/i2c/busses/i2c-synquacer.c | 2 +- drivers/i2c/busses/i2c-uniphier-f.c | 2 +- drivers/i2c/busses/i2c-uniphier.c | 2 +- drivers/i2c/busses/i2c-viai2c-wmt.c | 3 +-- drivers/i2c/busses/i2c-viai2c-zhaoxin.c | 3 +-- drivers/i2c/busses/i2c-xlp9xx.c | 2 +- 25 files changed, 29 insertions(+), 43 deletions(-) diff --git a/drivers/i2c/busses/i2c-amd-asf-plat.c b/drivers/i2c/busses/i2c-amd-asf-plat.c index 5968953e483a..b0b40fe1d79b 100644 --- a/drivers/i2c/busses/i2c-amd-asf-plat.c +++ b/drivers/i2c/busses/i2c-amd-asf-plat.c @@ -337,11 +337,11 @@ static int amd_asf_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); if (irq < 0) - return dev_err_probe(dev, irq, "missing IRQ resources\n"); + return irq; ret = devm_request_irq(dev, irq, amd_asf_irq_handler, IRQF_SHARED, "amd_asf", asf_dev); if (ret) - return dev_err_probe(dev, ret, "Unable to request irq: %d for use\n", irq); + return ret; asf_dev->adap.owner = THIS_MODULE; asf_dev->adap.algo = &amd_asf_smbus_algorithm; diff --git a/drivers/i2c/busses/i2c-bcm-iproc.c b/drivers/i2c/busses/i2c-bcm-iproc.c index b5629cffe99b..0107621db792 100644 --- a/drivers/i2c/busses/i2c-bcm-iproc.c +++ b/drivers/i2c/busses/i2c-bcm-iproc.c @@ -1136,8 +1136,7 @@ static int bcm_iproc_i2c_probe(struct platform_device *pdev) bcm_iproc_i2c_isr, 0, pdev->name, iproc_i2c); if (ret < 0) - return dev_err_probe(iproc_i2c->device, ret, - "unable to request irq %i\n", irq); + return ret; iproc_i2c->irq = irq; } else { diff --git a/drivers/i2c/busses/i2c-gxp.c b/drivers/i2c/busses/i2c-gxp.c index f9a5465f52da..6a5cef7e4383 100644 --- a/drivers/i2c/busses/i2c-gxp.c +++ b/drivers/i2c/busses/i2c-gxp.c @@ -551,7 +551,7 @@ static int gxp_i2c_probe(struct platform_device *pdev) rc = devm_request_irq(&pdev->dev, drvdata->irq, gxp_i2c_irq_handler, IRQF_SHARED, gxp_i2c_name[drvdata->engine], drvdata); if (rc < 0) - return dev_err_probe(&pdev->dev, rc, "irq request failed\n"); + return rc; i2c_parse_fw_timings(&pdev->dev, &drvdata->t, true); diff --git a/drivers/i2c/busses/i2c-hisi.c b/drivers/i2c/busses/i2c-hisi.c index 04d7978cae04..ba5c9579ae19 100644 --- a/drivers/i2c/busses/i2c-hisi.c +++ b/drivers/i2c/busses/i2c-hisi.c @@ -481,7 +481,7 @@ static int hisi_i2c_probe(struct platform_device *pdev) ret = devm_request_irq(dev, ctlr->irq, hisi_i2c_irq, 0, "hisi-i2c", ctlr); if (ret) - return dev_err_probe(dev, ret, "failed to request irq handler\n"); + return ret; ctlr->clk = devm_clk_get_optional_enabled(&pdev->dev, NULL); if (IS_ERR_OR_NULL(ctlr->clk)) { diff --git a/drivers/i2c/busses/i2c-imx-lpi2c.c b/drivers/i2c/busses/i2c-imx-lpi2c.c index e6c24a9d934d..1ad7a645ca9a 100644 --- a/drivers/i2c/busses/i2c-imx-lpi2c.c +++ b/drivers/i2c/busses/i2c-imx-lpi2c.c @@ -1513,7 +1513,7 @@ static int lpi2c_imx_probe(struct platform_device *pdev) 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", lpi2c_imx->irq); + return ret; i2c_set_adapdata(&lpi2c_imx->adapter, lpi2c_imx); platform_set_drvdata(pdev, lpi2c_imx); diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index d5e6e2eca3b3..fae7986812e3 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -1750,7 +1750,7 @@ static int i2c_imx_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); if (irq < 0) - return dev_err_probe(&pdev->dev, irq, "can't get IRQ\n"); + return irq; base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(base)) diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c index ee75eee15c10..4e1d67541132 100644 --- a/drivers/i2c/busses/i2c-k1.c +++ b/drivers/i2c/busses/i2c-k1.c @@ -852,7 +852,7 @@ static int spacemit_i2c_probe(struct platform_device *pdev) i2c->irq = platform_get_irq(pdev, 0); if (i2c->irq < 0) - return dev_err_probe(dev, i2c->irq, "failed to get irq resource"); + return i2c->irq; clk = devm_clk_get_enabled(dev, "func"); if (IS_ERR(clk)) @@ -906,7 +906,7 @@ static int spacemit_i2c_probe(struct platform_device *pdev) ret = devm_request_irq(i2c->dev, i2c->irq, spacemit_i2c_irq_handler, IRQF_NO_SUSPEND, dev_name(i2c->dev), i2c); if (ret) - return dev_err_probe(dev, ret, "failed to request irq"); + return ret; platform_set_drvdata(pdev, i2c); diff --git a/drivers/i2c/busses/i2c-ls2x.c b/drivers/i2c/busses/i2c-ls2x.c index b475dd27b7af..af7bf61b0f4d 100644 --- a/drivers/i2c/busses/i2c-ls2x.c +++ b/drivers/i2c/busses/i2c-ls2x.c @@ -323,7 +323,7 @@ static int ls2x_i2c_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, ls2x_i2c_isr, IRQF_SHARED, "ls2x-i2c", priv); if (ret < 0) - return dev_err_probe(dev, ret, "Unable to request irq %d\n", irq); + return ret; return devm_i2c_add_adapter(dev, adap); } diff --git a/drivers/i2c/busses/i2c-microchip-corei2c.c b/drivers/i2c/busses/i2c-microchip-corei2c.c index 330e150ef6d5..5a429f17359f 100644 --- a/drivers/i2c/busses/i2c-microchip-corei2c.c +++ b/drivers/i2c/busses/i2c-microchip-corei2c.c @@ -581,8 +581,7 @@ static int mchp_corei2c_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq, mchp_corei2c_isr, IRQF_SHARED, pdev->name, idev); if (ret) - return dev_err_probe(&pdev->dev, ret, - "failed to claim irq %d\n", irq); + return ret; ret = clk_prepare_enable(idev->i2c_clk); if (ret) diff --git a/drivers/i2c/busses/i2c-mlxbf.c b/drivers/i2c/busses/i2c-mlxbf.c index e33512b25353..24ccc4546ab8 100644 --- a/drivers/i2c/busses/i2c-mlxbf.c +++ b/drivers/i2c/busses/i2c-mlxbf.c @@ -2350,7 +2350,7 @@ static int mlxbf_i2c_probe(struct platform_device *pdev) IRQF_SHARED | IRQF_PROBE_SHARED, dev_name(dev), priv); if (ret < 0) - return dev_err_probe(dev, ret, "Cannot get irq %d\n", irq); + return ret; priv->irq = irq; diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index 404709179d73..c3878670da9c 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -1137,8 +1137,7 @@ static int nmk_i2c_probe(struct amba_device *adev, const struct amba_id *id) ret = devm_request_irq(dev, priv->irq, i2c_irq_handler, 0, DRIVER_NAME, priv); if (ret) - return dev_err_probe(dev, ret, - "cannot claim the irq %d\n", priv->irq); + return ret; priv->clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(priv->clk)) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index d5c977e34994..52ca934fb658 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -1118,8 +1118,7 @@ static int geni_i2c_probe(struct platform_device *pdev) ret = devm_request_irq(dev, gi2c->irq, geni_i2c_irq, IRQF_NO_AUTOEN, dev_name(dev), gi2c); if (ret) - return dev_err_probe(dev, ret, - "Request_irq failed: %d\n", gi2c->irq); + return ret; i2c_set_adapdata(&gi2c->adap, gi2c); gi2c->adap.dev.parent = dev; diff --git a/drivers/i2c/busses/i2c-riic.c b/drivers/i2c/busses/i2c-riic.c index 6d2ebf67dd62..2fa48219b66b 100644 --- a/drivers/i2c/busses/i2c-riic.c +++ b/drivers/i2c/busses/i2c-riic.c @@ -554,8 +554,7 @@ static int riic_i2c_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, irq_desc->isr, 0, irq_desc->name, riic); if (ret) - return dev_err_probe(dev, ret, "failed to request irq %s\n", - irq_desc->name); + return ret; } diff --git a/drivers/i2c/busses/i2c-rzv2m.c b/drivers/i2c/busses/i2c-rzv2m.c index 4ba8eaa322e5..206a99e722a3 100644 --- a/drivers/i2c/busses/i2c-rzv2m.c +++ b/drivers/i2c/busses/i2c-rzv2m.c @@ -442,7 +442,7 @@ static int rzv2m_i2c_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, rzv2m_i2c_tia_irq_handler, 0, dev_name(dev), priv); if (ret < 0) - return dev_err_probe(dev, ret, "Unable to request irq %d\n", irq); + return ret; adap = &priv->adap; adap->nr = pdev->id; diff --git a/drivers/i2c/busses/i2c-sprd.c b/drivers/i2c/busses/i2c-sprd.c index 7b321a956fca..c42925f9a330 100644 --- a/drivers/i2c/busses/i2c-sprd.c +++ b/drivers/i2c/busses/i2c-sprd.c @@ -546,10 +546,8 @@ static int sprd_i2c_probe(struct platform_device *pdev) sprd_i2c_isr, sprd_i2c_isr_thread, IRQF_NO_SUSPEND | IRQF_ONESHOT, pdev->name, i2c_dev); - if (ret) { - dev_err_probe(&pdev->dev, ret, "failed to request irq %d\n", i2c_dev->irq); + if (ret) goto err_rpm_put; - } ret = i2c_add_numbered_adapter(&i2c_dev->adap); if (ret) { diff --git a/drivers/i2c/busses/i2c-st.c b/drivers/i2c/busses/i2c-st.c index 3f89c2145741..0a2b43b5577b 100644 --- a/drivers/i2c/busses/i2c-st.c +++ b/drivers/i2c/busses/i2c-st.c @@ -825,8 +825,7 @@ static int st_i2c_probe(struct platform_device *pdev) NULL, st_i2c_isr_thread, IRQF_ONESHOT, pdev->name, i2c_dev); if (ret) - return dev_err_probe(&pdev->dev, ret, - "Failed to request irq %i\n", i2c_dev->irq); + return ret; pinctrl_pm_select_default_state(i2c_dev->dev); /* In case idle state available, select it */ diff --git a/drivers/i2c/busses/i2c-stm32f4.c b/drivers/i2c/busses/i2c-stm32f4.c index 44e8b04962bb..579ab29ef554 100644 --- a/drivers/i2c/busses/i2c-stm32f4.c +++ b/drivers/i2c/busses/i2c-stm32f4.c @@ -801,14 +801,12 @@ static int stm32f4_i2c_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq_event, stm32f4_i2c_isr_event, 0, pdev->name, i2c_dev); if (ret) - return dev_err_probe(&pdev->dev, ret, - "Failed to request irq event %i\n", irq_event); + return ret; ret = devm_request_irq(&pdev->dev, irq_error, stm32f4_i2c_isr_error, 0, pdev->name, i2c_dev); if (ret) - return dev_err_probe(&pdev->dev, ret, - "Failed to request irq error %i\n", irq_error); + return ret; ret = stm32f4_i2c_hw_config(i2c_dev); if (ret) diff --git a/drivers/i2c/busses/i2c-stm32f7.c b/drivers/i2c/busses/i2c-stm32f7.c index d6d993b436cb..6bd281a9ef72 100644 --- a/drivers/i2c/busses/i2c-stm32f7.c +++ b/drivers/i2c/busses/i2c-stm32f7.c @@ -2204,7 +2204,7 @@ static int stm32f7_i2c_probe(struct platform_device *pdev) IRQF_ONESHOT, pdev->name, i2c_dev); if (ret) - return dev_err_probe(&pdev->dev, ret, "Failed to request irq event\n"); + return ret; if (!i2c_dev->setup.single_it_line) { irq_error = platform_get_irq(pdev, 1); @@ -2217,7 +2217,7 @@ static int stm32f7_i2c_probe(struct platform_device *pdev) IRQF_ONESHOT, pdev->name, i2c_dev); if (ret) - return dev_err_probe(&pdev->dev, ret, "Failed to request irq error\n"); + return ret; } ret = stm32f7_i2c_setup_timing(i2c_dev, &i2c_dev->setup); diff --git a/drivers/i2c/busses/i2c-sun6i-p2wi.c b/drivers/i2c/busses/i2c-sun6i-p2wi.c index dffbe776a195..4f70b12a9559 100644 --- a/drivers/i2c/busses/i2c-sun6i-p2wi.c +++ b/drivers/i2c/busses/i2c-sun6i-p2wi.c @@ -261,10 +261,8 @@ static int p2wi_probe(struct platform_device *pdev) i2c_set_adapdata(&p2wi->adapter, p2wi); ret = devm_request_irq(dev, irq, p2wi_interrupt, 0, pdev->name, p2wi); - if (ret) { - dev_err_probe(dev, ret, "can't register interrupt handler irq%d\n", irq); + if (ret) goto err_reset_assert; - } writel(P2WI_CTRL_SOFT_RST, p2wi->regs + P2WI_CTRL); diff --git a/drivers/i2c/busses/i2c-synquacer.c b/drivers/i2c/busses/i2c-synquacer.c index 4891d68bf0ee..0216e398f274 100644 --- a/drivers/i2c/busses/i2c-synquacer.c +++ b/drivers/i2c/busses/i2c-synquacer.c @@ -576,7 +576,7 @@ static int synquacer_i2c_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, i2c->irq, synquacer_i2c_isr, 0, dev_name(&pdev->dev), i2c); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, "cannot claim IRQ %d\n", i2c->irq); + return ret; i2c->state = STATE_IDLE; i2c->dev = &pdev->dev; diff --git a/drivers/i2c/busses/i2c-uniphier-f.c b/drivers/i2c/busses/i2c-uniphier-f.c index ca0358e8f928..7190802a493c 100644 --- a/drivers/i2c/busses/i2c-uniphier-f.c +++ b/drivers/i2c/busses/i2c-uniphier-f.c @@ -560,7 +560,7 @@ static int uniphier_fi2c_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, uniphier_fi2c_interrupt, 0, pdev->name, priv); if (ret) - return dev_err_probe(dev, ret, "failed to request irq %d\n", irq); + return ret; return i2c_add_adapter(&priv->adap); } diff --git a/drivers/i2c/busses/i2c-uniphier.c b/drivers/i2c/busses/i2c-uniphier.c index 9d49a3d5d612..ad7bbf5e2466 100644 --- a/drivers/i2c/busses/i2c-uniphier.c +++ b/drivers/i2c/busses/i2c-uniphier.c @@ -354,7 +354,7 @@ static int uniphier_i2c_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, uniphier_i2c_interrupt, 0, pdev->name, priv); if (ret) - return dev_err_probe(dev, ret, "failed to request irq %d\n", irq); + return ret; return i2c_add_adapter(&priv->adap); } diff --git a/drivers/i2c/busses/i2c-viai2c-wmt.c b/drivers/i2c/busses/i2c-viai2c-wmt.c index 2cf3cc0165fb..d3dda7534b0a 100644 --- a/drivers/i2c/busses/i2c-viai2c-wmt.c +++ b/drivers/i2c/busses/i2c-viai2c-wmt.c @@ -114,8 +114,7 @@ static int wmt_i2c_probe(struct platform_device *pdev) err = devm_request_irq(&pdev->dev, i2c->irq, wmt_i2c_isr, 0, pdev->name, i2c); if (err) - return dev_err_probe(&pdev->dev, err, - "failed to request irq %i\n", i2c->irq); + return err; i2c->clk = of_clk_get(np, 0); if (IS_ERR(i2c->clk)) diff --git a/drivers/i2c/busses/i2c-viai2c-zhaoxin.c b/drivers/i2c/busses/i2c-viai2c-zhaoxin.c index 95dc64902b7c..764a2dbc957a 100644 --- a/drivers/i2c/busses/i2c-viai2c-zhaoxin.c +++ b/drivers/i2c/busses/i2c-viai2c-zhaoxin.c @@ -305,8 +305,7 @@ static int zxi2c_probe(struct platform_device *pdev) error = devm_request_irq(&pdev->dev, i2c->irq, zxi2c_isr, IRQF_SHARED, pdev->name, i2c); if (error) - return dev_err_probe(&pdev->dev, error, - "failed to request irq %i\n", i2c->irq); + return error; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) diff --git a/drivers/i2c/busses/i2c-xlp9xx.c b/drivers/i2c/busses/i2c-xlp9xx.c index ddb1c3e8bc9d..ad0e5cd218f0 100644 --- a/drivers/i2c/busses/i2c-xlp9xx.c +++ b/drivers/i2c/busses/i2c-xlp9xx.c @@ -530,7 +530,7 @@ static int xlp9xx_i2c_probe(struct platform_device *pdev) err = devm_request_irq(&pdev->dev, priv->irq, xlp9xx_i2c_isr, 0, pdev->name, priv); if (err) - return dev_err_probe(&pdev->dev, err, "IRQ request failed!\n"); + return err; init_completion(&priv->msg_complete); priv->adapter.dev.parent = &pdev->dev; From edf133cdeeb3cf8deb8166c07592061c6aedd385 Mon Sep 17 00:00:00 2001 From: Swark Yang Date: Thu, 16 Jul 2026 20:51:10 -0700 Subject: [PATCH 25/38] dt-bindings: i2c: cdns: add Axiado AX3005 I2C variant Add binding for Axiado AX3005 I2C controller. So far, no changes are known, so it can fall back to the cdns,i2c-r1p14 compatible. Signed-off-by: Swark Yang Reviewed-by: Krzysztof Kozlowski Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260716-upstream-axiado-ax3005-upstream-v3-3-c429095143ec@axiado.com --- .../devicetree/bindings/i2c/cdns,i2c-r1p10.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/i2c/cdns,i2c-r1p10.yaml b/Documentation/devicetree/bindings/i2c/cdns,i2c-r1p10.yaml index 9f1d35ce1fe8..4dc69d54f75d 100644 --- a/Documentation/devicetree/bindings/i2c/cdns,i2c-r1p10.yaml +++ b/Documentation/devicetree/bindings/i2c/cdns,i2c-r1p10.yaml @@ -14,9 +14,13 @@ allOf: properties: compatible: - enum: - - cdns,i2c-r1p10 # cadence i2c controller version 1.0 - - cdns,i2c-r1p14 # cadence i2c controller version 1.4 + oneOf: + - enum: + - cdns,i2c-r1p10 # cadence i2c controller version 1.0 + - cdns,i2c-r1p14 # cadence i2c controller version 1.4 + - items: + - const: axiado,ax3005-i2c + - const: cdns,i2c-r1p14 reg: maxItems: 1 From c19f6f452664a2a967a2be3ba3fa714bb99ebe28 Mon Sep 17 00:00:00 2001 From: Carlos Song Date: Thu, 16 Jul 2026 15:19:56 +0800 Subject: [PATCH 26/38] i2c: imx-lpi2c: properly unwind resources on probe failure When probe fails at devm_clk_rate_exclusive_get() or clk_get_rate(), which occur before runtime PM is initialized, the clocks enabled by clk_bulk_prepare_enable() are never disabled. When probe fails after runtime PM is initialized, the previous error path called pm_runtime_put_sync(), which triggers the runtime suspend callback. However, due to different clock management strategies on different SoCs[1] (to avoid deadlocks between the global prepare_lock and runtime PM), the callback may only disable clocks without unpreparing them, causing an incomplete unwind. Introduce a new error label 'clk_disable' to explicitly invoke clk_bulk_disable_unprepare(). Replace pm_runtime_put_sync() with the sequence of pm_runtime_disable(), pm_runtime_set_suspended() and pm_runtime_put_noidle() to bypass the runtime suspend callback during error recovery. During the LPI2C driver probe phase, clock APIs are used exclusively to manage clocks. Once probing succeeds, clock management is handed over to the runtime PM core. [1] https://lore.kernel.org/all/20251125084718.2156168-1-carlos.song@nxp.com/ Signed-off-by: Carlos Song Reviewed-by: Frank Li Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260716071957.2670263-2-carlos.song@oss.nxp.com --- drivers/i2c/busses/i2c-imx-lpi2c.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/drivers/i2c/busses/i2c-imx-lpi2c.c b/drivers/i2c/busses/i2c-imx-lpi2c.c index 1ad7a645ca9a..0081354d91e1 100644 --- a/drivers/i2c/busses/i2c-imx-lpi2c.c +++ b/drivers/i2c/busses/i2c-imx-lpi2c.c @@ -1527,14 +1527,18 @@ static int lpi2c_imx_probe(struct platform_device *pdev) * each transfer */ ret = devm_clk_rate_exclusive_get(&pdev->dev, lpi2c_imx->clks[0].clk); - if (ret) - return dev_err_probe(&pdev->dev, ret, - "can't lock I2C peripheral clock rate\n"); + if (ret) { + ret = dev_err_probe(&pdev->dev, ret, + "can't lock I2C peripheral clock rate\n"); + goto clk_disable; + } lpi2c_imx->rate_per = clk_get_rate(lpi2c_imx->clks[0].clk); - if (!lpi2c_imx->rate_per) - return dev_err_probe(&pdev->dev, -EINVAL, - "can't get I2C peripheral clock rate\n"); + if (!lpi2c_imx->rate_per) { + ret = dev_err_probe(&pdev->dev, -EINVAL, + "can't get I2C peripheral clock rate\n"); + goto clk_disable; + } if (lpi2c_imx->hwdata->need_prepare_unprepare_clk) pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_LONG_TIMEOUT_MS); @@ -1576,8 +1580,11 @@ static int lpi2c_imx_probe(struct platform_device *pdev) rpm_disable: pm_runtime_dont_use_autosuspend(&pdev->dev); - pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); +clk_disable: + clk_bulk_disable_unprepare(lpi2c_imx->num_clks, lpi2c_imx->clks); return ret; } From 90311787f483a12541baddb9f57c26c2878e73b3 Mon Sep 17 00:00:00 2001 From: Carlos Song Date: Thu, 16 Jul 2026 15:19:57 +0800 Subject: [PATCH 27/38] i2c: imx-lpi2c: reset controller in probe stage Reset I2C controller in probe stage to avoid unexpected LPI2C controller state left from previous stages and hang system boot. Per the LPI2C reference manual, section 7.1.4 "Controller Control (MCR)" and 7.1.20 Target Control (SCR), the RST bit (bit 1) description states: "The reset takes effect immediately and remains asserted until negated by software. There is no minimum delay required before clearing the software reset." Therefore, it is safe to write 0 to MCR and SCR immediately after asserting the RST bit without any additional delay. Signed-off-by: Carlos Song Reviewed-by: Frank Li Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260716071957.2670263-3-carlos.song@oss.nxp.com --- drivers/i2c/busses/i2c-imx-lpi2c.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/i2c/busses/i2c-imx-lpi2c.c b/drivers/i2c/busses/i2c-imx-lpi2c.c index 0081354d91e1..e1a4338bc51e 100644 --- a/drivers/i2c/busses/i2c-imx-lpi2c.c +++ b/drivers/i2c/busses/i2c-imx-lpi2c.c @@ -1510,11 +1510,6 @@ 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, lpi2c_imx->irq, lpi2c_imx_isr, IRQF_NO_SUSPEND, - pdev->name, lpi2c_imx); - if (ret) - return ret; - i2c_set_adapdata(&lpi2c_imx->adapter, lpi2c_imx); platform_set_drvdata(pdev, lpi2c_imx); @@ -1550,6 +1545,20 @@ static int lpi2c_imx_probe(struct platform_device *pdev) pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); + /* + * Reset all internal controller registers of both Master and Target + * to avoid effects of previous status. + */ + writel(MCR_RST, lpi2c_imx->base + LPI2C_MCR); + writel(SCR_RST, lpi2c_imx->base + LPI2C_SCR); + writel(0, lpi2c_imx->base + LPI2C_MCR); + writel(0, lpi2c_imx->base + LPI2C_SCR); + + ret = devm_request_irq(&pdev->dev, lpi2c_imx->irq, lpi2c_imx_isr, IRQF_NO_SUSPEND, + pdev->name, lpi2c_imx); + if (ret) + goto rpm_disable; + temp = readl(lpi2c_imx->base + LPI2C_PARAM); lpi2c_imx->txfifosize = 1 << (temp & 0x0f); lpi2c_imx->rxfifosize = 1 << ((temp >> 8) & 0x0f); From 3275b99ecb5d371df00581cac92ad0d890736b6b Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Thu, 16 Jul 2026 12:08:43 +0530 Subject: [PATCH 28/38] i2c: qcom-geni: use cancel command before abort on transfer timeout The GENI I2C driver currently invokes geni_se_abort_m_cmd() directly when a transfer times out. However, the GENI hardware command cancellation flow requires a cancel command to be issued first. An abort should only be used as a fallback when the cancel operation itself fails to complete. Introduce a dedicated cancel_done completion that is signaled when M_CMD_CANCEL_EN is received. The timeout recovery path waits for cancel completion and escalates to geni_i2c_abort_xfer() only if the cancel command does not complete within the expected time. Co-developed-by: Naresh Maramaina Signed-off-by: Naresh Maramaina Signed-off-by: Praveen Talari Reviewed-by: Mukesh Kumar Savaliya Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260716-fix_cancel_sequence_on_failure_for_i2c-v3-1-9091315a33a0@oss.qualcomm.com --- drivers/i2c/busses/i2c-qcom-geni.c | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index d5c977e34994..b29b110c3e03 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -74,6 +74,7 @@ enum geni_i2c_err_code { #define PACKING_BYTES_PW 4 #define ABORT_TIMEOUT HZ +#define CANCEL_TIMEOUT HZ #define XFER_TIMEOUT HZ #define RST_TIMEOUT HZ @@ -112,6 +113,7 @@ struct geni_i2c_dev { int err; struct i2c_adapter adap; struct completion done; + struct completion cancel_done; struct i2c_msg *cur; int cur_wr; int cur_rd; @@ -361,6 +363,8 @@ static irqreturn_t geni_i2c_irq(int irq, void *dev) dm_tx_st & TX_DMA_DONE || dm_tx_st & TX_RESET_DONE || dm_rx_st & RX_DMA_DONE || dm_rx_st & RX_RESET_DONE) complete(&gi2c->done); + if (m_stat & M_CMD_CANCEL_EN) + complete(&gi2c->cancel_done); spin_unlock(&gi2c->lock); @@ -387,6 +391,27 @@ static void geni_i2c_abort_xfer(struct geni_i2c_dev *gi2c) dev_err(gi2c->se.dev, "Timeout abort_m_cmd\n"); } +static void geni_i2c_cancel_xfer(struct geni_i2c_dev *gi2c) +{ + unsigned long time_left = msecs_to_jiffies(CANCEL_TIMEOUT); + unsigned long flags; + + reinit_completion(&gi2c->cancel_done); + + spin_lock_irqsave(&gi2c->lock, flags); + if (!gi2c->err) + geni_i2c_err(gi2c, GENI_TIMEOUT); + gi2c->cur = NULL; + geni_se_cancel_m_cmd(&gi2c->se); + spin_unlock_irqrestore(&gi2c->lock, flags); + + time_left = wait_for_completion_timeout(&gi2c->cancel_done, time_left); + if (!time_left) { + dev_err(gi2c->se.dev, "Timeout cancel_m_cmd\n"); + geni_i2c_abort_xfer(gi2c); + } +} + static void geni_i2c_rx_fsm_rst(struct geni_i2c_dev *gi2c) { u32 val; @@ -473,7 +498,7 @@ static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg, cur = gi2c->cur; time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT); if (!time_left) - geni_i2c_abort_xfer(gi2c); + geni_i2c_cancel_xfer(gi2c); geni_i2c_rx_msg_cleanup(gi2c, cur); @@ -515,7 +540,7 @@ static int geni_i2c_tx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg, cur = gi2c->cur; time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT); if (!time_left) - geni_i2c_abort_xfer(gi2c); + geni_i2c_cancel_xfer(gi2c); geni_i2c_tx_msg_cleanup(gi2c, cur); @@ -1107,6 +1132,7 @@ static int geni_i2c_probe(struct platform_device *pdev) gi2c->adap.algo = &geni_i2c_algo; init_completion(&gi2c->done); + init_completion(&gi2c->cancel_done); spin_lock_init(&gi2c->lock); platform_set_drvdata(pdev, gi2c); From b64f1a070904b46c741f22e963e4ff29347ef220 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Thu, 16 Jul 2026 12:08:44 +0530 Subject: [PATCH 29/38] i2c: qcom-geni: use dedicated completions for abort and reset events The driver uses a common completion event for transfer, abort and DMA reset operations. This allows unrelated completion events to prematurely wake abort and reset waiters, leading to incorrect synchronization. Introduce dedicated completions for abort, TX reset, and RX reset operations, and signal them only from their respective interrupt events. This removes the dependency on shared completion state and eliminates the abort_done flag-based synchronization. Co-developed-by: Naresh Maramaina Signed-off-by: Naresh Maramaina Signed-off-by: Praveen Talari Reviewed-by: Mukesh Kumar Savaliya Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260716-fix_cancel_sequence_on_failure_for_i2c-v3-2-9091315a33a0@oss.qualcomm.com --- drivers/i2c/busses/i2c-qcom-geni.c | 54 +++++++++++++++--------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index b29b110c3e03..d62c3d3b0d3e 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -113,7 +113,10 @@ struct geni_i2c_dev { int err; struct i2c_adapter adap; struct completion done; + struct completion abort_done; struct completion cancel_done; + struct completion tx_reset_done; + struct completion rx_reset_done; struct i2c_msg *cur; int cur_wr; int cur_rd; @@ -127,7 +130,6 @@ struct geni_i2c_dev { struct dma_chan *rx_c; bool no_dma; bool gpi_mode; - bool abort_done; bool is_tx_multi_desc_xfer; u32 num_msgs; struct geni_i2c_gpi_multi_desc_xfer i2c_multi_desc_config; @@ -256,8 +258,6 @@ static void geni_i2c_err(struct geni_i2c_dev *gi2c, int err) switch (err) { case GENI_ABORT_DONE: - gi2c->abort_done = true; - break; case NACK: case GENI_TIMEOUT: dev_dbg(gi2c->se.dev, "%s\n", gi2c_log[err].msg); @@ -359,12 +359,18 @@ static irqreturn_t geni_i2c_irq(int irq, void *dev) writel_relaxed(dm_rx_st, base + SE_DMA_RX_IRQ_CLR); /* if this is err with done-bit not set, handle that through timeout. */ - if (m_stat & M_CMD_DONE_EN || m_stat & M_CMD_ABORT_EN || - dm_tx_st & TX_DMA_DONE || dm_tx_st & TX_RESET_DONE || - dm_rx_st & RX_DMA_DONE || dm_rx_st & RX_RESET_DONE) + if (m_stat & M_CMD_DONE_EN || + dm_tx_st & TX_DMA_DONE || + dm_rx_st & RX_DMA_DONE) complete(&gi2c->done); if (m_stat & M_CMD_CANCEL_EN) complete(&gi2c->cancel_done); + if (m_stat & M_CMD_ABORT_EN) + complete(&gi2c->abort_done); + if (dm_tx_st & TX_RESET_DONE) + complete(&gi2c->tx_reset_done); + if (dm_rx_st & RX_RESET_DONE) + complete(&gi2c->rx_reset_done); spin_unlock(&gi2c->lock); @@ -376,17 +382,13 @@ static void geni_i2c_abort_xfer(struct geni_i2c_dev *gi2c) unsigned long time_left = ABORT_TIMEOUT; unsigned long flags; + reinit_completion(&gi2c->abort_done); + spin_lock_irqsave(&gi2c->lock, flags); - geni_i2c_err(gi2c, GENI_TIMEOUT); - gi2c->cur = NULL; - gi2c->abort_done = false; geni_se_abort_m_cmd(&gi2c->se); spin_unlock_irqrestore(&gi2c->lock, flags); - do { - time_left = wait_for_completion_timeout(&gi2c->done, time_left); - } while (!gi2c->abort_done && time_left); - + time_left = wait_for_completion_timeout(&gi2c->abort_done, time_left); if (!time_left) dev_err(gi2c->se.dev, "Timeout abort_m_cmd\n"); } @@ -414,31 +416,25 @@ static void geni_i2c_cancel_xfer(struct geni_i2c_dev *gi2c) static void geni_i2c_rx_fsm_rst(struct geni_i2c_dev *gi2c) { - u32 val; unsigned long time_left = RST_TIMEOUT; + reinit_completion(&gi2c->rx_reset_done); writel_relaxed(1, gi2c->se.base + SE_DMA_RX_FSM_RST); - do { - time_left = wait_for_completion_timeout(&gi2c->done, time_left); - val = readl_relaxed(gi2c->se.base + SE_DMA_RX_IRQ_STAT); - } while (!(val & RX_RESET_DONE) && time_left); - if (!(val & RX_RESET_DONE)) + time_left = wait_for_completion_timeout(&gi2c->rx_reset_done, time_left); + if (!time_left) dev_err(gi2c->se.dev, "Timeout resetting RX_FSM\n"); } static void geni_i2c_tx_fsm_rst(struct geni_i2c_dev *gi2c) { - u32 val; unsigned long time_left = RST_TIMEOUT; + reinit_completion(&gi2c->tx_reset_done); writel_relaxed(1, gi2c->se.base + SE_DMA_TX_FSM_RST); - do { - time_left = wait_for_completion_timeout(&gi2c->done, time_left); - val = readl_relaxed(gi2c->se.base + SE_DMA_TX_IRQ_STAT); - } while (!(val & TX_RESET_DONE) && time_left); - if (!(val & TX_RESET_DONE)) + time_left = wait_for_completion_timeout(&gi2c->tx_reset_done, time_left); + if (!time_left) dev_err(gi2c->se.dev, "Timeout resetting TX_FSM\n"); } @@ -851,6 +847,7 @@ static int geni_i2c_gpi_xfer(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], i for (i = 0; i < num; i++) { gi2c->cur = &msgs[i]; gi2c->err = 0; + reinit_completion(&gi2c->done); dev_dbg(gi2c->se.dev, "msg[%d].len:%d\n", i, gi2c->cur->len); peripheral.stretch = 0; @@ -920,6 +917,8 @@ static int geni_i2c_fifo_xfer(struct geni_i2c_dev *gi2c, m_param |= ((msgs[i].addr << SLV_ADDR_SHFT) & SLV_ADDR_MSK); gi2c->cur = &msgs[i]; + gi2c->err = 0; + reinit_completion(&gi2c->done); if (msgs[i].flags & I2C_M_RD) ret = geni_i2c_rx_one_msg(gi2c, &msgs[i], m_param); else @@ -939,8 +938,6 @@ static int geni_i2c_xfer(struct i2c_adapter *adap, struct geni_i2c_dev *gi2c = i2c_get_adapdata(adap); int ret; - gi2c->err = 0; - reinit_completion(&gi2c->done); ret = pm_runtime_get_sync(gi2c->se.dev); if (ret < 0) { dev_err(gi2c->se.dev, "error turning SE resources:%d\n", ret); @@ -1132,7 +1129,10 @@ static int geni_i2c_probe(struct platform_device *pdev) gi2c->adap.algo = &geni_i2c_algo; init_completion(&gi2c->done); + init_completion(&gi2c->abort_done); init_completion(&gi2c->cancel_done); + init_completion(&gi2c->tx_reset_done); + init_completion(&gi2c->rx_reset_done); spin_lock_init(&gi2c->lock); platform_set_drvdata(pdev, gi2c); From 1275cbed80a740e4d56b6aff2745de9fce4818fa Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Thu, 16 Jul 2026 12:08:45 +0530 Subject: [PATCH 30/38] i2c: qcom-geni: distinguish address-phase and data-phase NACK The M_GP_IRQ_1 interrupt signals a NACK condition, but does not distinguish whether it occurred during the address phase or the data phase. The driver always attempted cancel and DMA FSM reset on any NACK, which is incorrect for an address NACK since the DMA engine was never armed and the hardware requires no recovery. Add geni_i2c_check_addr_data_nack() to distinguish the two cases by reading SE_GENI_M_GP_LENGTH after the NACK event. A zero value means the address phase was NACKed (ADDR_NACK) and no DMA recovery is needed. A non-zero value on a write transfer means a data byte was NACKed (DATA_NACK) and the normal cancel and DMA FSM reset path must run. Co-developed-by: Naresh Maramaina Signed-off-by: Naresh Maramaina Signed-off-by: Praveen Talari Reviewed-by: Mukesh Kumar Savaliya Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260716-fix_cancel_sequence_on_failure_for_i2c-v3-3-9091315a33a0@oss.qualcomm.com --- drivers/i2c/busses/i2c-qcom-geni.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index d62c3d3b0d3e..ec0bd10e6774 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -56,7 +56,8 @@ enum geni_i2c_err_code { GP_IRQ0, - NACK, + ADDR_NACK, + DATA_NACK, GP_IRQ2, BUS_PROTO, ARB_LOST, @@ -67,7 +68,7 @@ enum geni_i2c_err_code { GENI_TIMEOUT, }; -#define DM_I2C_CB_ERR ((BIT(NACK) | BIT(BUS_PROTO) | BIT(ARB_LOST)) \ +#define DM_I2C_CB_ERR ((BIT(ADDR_NACK) | BIT(BUS_PROTO) | BIT(ARB_LOST)) \ << 5) #define I2C_AUTO_SUSPEND_DELAY 250 @@ -143,7 +144,8 @@ struct geni_i2c_err_log { static const struct geni_i2c_err_log gi2c_log[] = { [GP_IRQ0] = {-EIO, "Unknown I2C err GP_IRQ0"}, - [NACK] = {-ENXIO, "NACK: slv unresponsive, check its power/reset-ln"}, + [ADDR_NACK] = {-ENXIO, "NACK: target device unresponsive, check its power/reset-ln"}, + [DATA_NACK] = {-EIO, "Data NACK: TX transfer NACK"}, [GP_IRQ2] = {-EIO, "Unknown I2C err GP IRQ2"}, [BUS_PROTO] = {-EPROTO, "Bus proto err, noisy/unexpected start/stop"}, [ARB_LOST] = {-EAGAIN, "Bus arbitration lost, clock line undriveable"}, @@ -258,7 +260,8 @@ static void geni_i2c_err(struct geni_i2c_dev *gi2c, int err) switch (err) { case GENI_ABORT_DONE: - case NACK: + case ADDR_NACK: + case DATA_NACK: case GENI_TIMEOUT: dev_dbg(gi2c->se.dev, "%s\n", gi2c_log[err].msg); break; @@ -269,6 +272,14 @@ static void geni_i2c_err(struct geni_i2c_dev *gi2c, int err) } } +static void geni_i2c_check_addr_data_nack(struct geni_i2c_dev *gi2c) +{ + if (!readl_relaxed(gi2c->se.base + SE_GENI_M_GP_LENGTH)) + geni_i2c_err(gi2c, ADDR_NACK); + else if (!(gi2c->cur->flags & I2C_M_RD)) + geni_i2c_err(gi2c, DATA_NACK); +} + static irqreturn_t geni_i2c_irq(int irq, void *dev) { struct geni_i2c_dev *gi2c = dev; @@ -294,7 +305,7 @@ static irqreturn_t geni_i2c_irq(int irq, void *dev) m_stat & (M_CMD_FAILURE_EN | M_CMD_ABORT_EN) || dm_rx_st & (DM_I2C_CB_ERR)) { if (m_stat & M_GP_IRQ_1_EN) - geni_i2c_err(gi2c, NACK); + geni_i2c_check_addr_data_nack(gi2c); if (m_stat & M_GP_IRQ_3_EN) geni_i2c_err(gi2c, BUS_PROTO); if (m_stat & M_GP_IRQ_4_EN) @@ -443,7 +454,7 @@ static void geni_i2c_rx_msg_cleanup(struct geni_i2c_dev *gi2c, { gi2c->cur_rd = 0; if (gi2c->dma_buf) { - if (gi2c->err) + if (gi2c->err && gi2c->err != gi2c_log[ADDR_NACK].err) geni_i2c_rx_fsm_rst(gi2c); geni_se_rx_dma_unprep(&gi2c->se, gi2c->dma_addr, gi2c->xfer_len); i2c_put_dma_safe_msg_buf(gi2c->dma_buf, cur, !gi2c->err); @@ -455,7 +466,7 @@ static void geni_i2c_tx_msg_cleanup(struct geni_i2c_dev *gi2c, { gi2c->cur_wr = 0; if (gi2c->dma_buf) { - if (gi2c->err) + if (gi2c->err && gi2c->err != gi2c_log[ADDR_NACK].err) geni_i2c_tx_fsm_rst(gi2c); geni_se_tx_dma_unprep(&gi2c->se, gi2c->dma_addr, gi2c->xfer_len); i2c_put_dma_safe_msg_buf(gi2c->dma_buf, cur, !gi2c->err); @@ -493,7 +504,7 @@ static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg, cur = gi2c->cur; time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT); - if (!time_left) + if (!time_left || (gi2c->err && gi2c->err != gi2c_log[ADDR_NACK].err)) geni_i2c_cancel_xfer(gi2c); geni_i2c_rx_msg_cleanup(gi2c, cur); @@ -535,7 +546,7 @@ static int geni_i2c_tx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg, cur = gi2c->cur; time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT); - if (!time_left) + if (!time_left || (gi2c->err && gi2c->err != gi2c_log[ADDR_NACK].err)) geni_i2c_cancel_xfer(gi2c); geni_i2c_tx_msg_cleanup(gi2c, cur); From a477626332800942f463d76aa48db0c47f1dfd18 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 24 Jun 2026 13:52:37 +0800 Subject: [PATCH 31/38] i2c: qup: Propagate clock enable failures The QUP I2C driver treats the core and iface clocks as required resources, but qup_i2c_enable_clocks() ignores clk_prepare_enable() failures. Probe can then continue to register the I2C adapter, and runtime/system resume can return success, even when a required clock transition failed. Make the helper return an error, unwind a partially enabled clock, and propagate failures from probe and resume paths. Signed-off-by: Pengpeng Hou Reviewed-by: Konrad Dybcio Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260624055237.29977-1-pengpeng@iscas.ac.cn --- drivers/i2c/busses/i2c-qup.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/drivers/i2c/busses/i2c-qup.c b/drivers/i2c/busses/i2c-qup.c index a0e076fc5f36..ee7915ee2ba2 100644 --- a/drivers/i2c/busses/i2c-qup.c +++ b/drivers/i2c/busses/i2c-qup.c @@ -1657,10 +1657,21 @@ static const struct i2c_adapter_quirks qup_i2c_quirks_v2 = { .flags = I2C_AQ_NO_ZERO_LEN, }; -static void qup_i2c_enable_clocks(struct qup_i2c_dev *qup) +static int qup_i2c_enable_clocks(struct qup_i2c_dev *qup) { - clk_prepare_enable(qup->clk); - clk_prepare_enable(qup->pclk); + int ret; + + ret = clk_prepare_enable(qup->clk); + if (ret) + return ret; + + ret = clk_prepare_enable(qup->pclk); + if (ret) { + clk_disable_unprepare(qup->clk); + return ret; + } + + return 0; } static void qup_i2c_disable_clocks(struct qup_i2c_dev *qup) @@ -1823,7 +1834,9 @@ nodma: ret = PTR_ERR(qup->pclk); goto fail_dma; } - qup_i2c_enable_clocks(qup); + ret = qup_i2c_enable_clocks(qup); + if (ret) + goto fail_dma; src_clk_freq = clk_get_rate(qup->clk); } qup->src_clk_freq = src_clk_freq; @@ -1975,8 +1988,7 @@ static int qup_i2c_pm_resume_runtime(struct device *device) struct qup_i2c_dev *qup = dev_get_drvdata(device); dev_dbg(device, "pm_runtime: resuming...\n"); - qup_i2c_enable_clocks(qup); - return 0; + return qup_i2c_enable_clocks(qup); } static int qup_i2c_suspend(struct device *device) @@ -1988,7 +2000,12 @@ static int qup_i2c_suspend(struct device *device) static int qup_i2c_resume(struct device *device) { - qup_i2c_pm_resume_runtime(device); + int ret; + + ret = qup_i2c_pm_resume_runtime(device); + if (ret) + return ret; + pm_request_autosuspend(device); return 0; } From d3de2edd75ee2646bac8b00f6aadc10a3fbe6e80 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 3 Jul 2026 20:38:09 +0530 Subject: [PATCH 32/38] i2c: qcom-geni: trace: Add trace events for Qualcomm GENI I2C Add trace event support to the Qualcomm GENI I2C driver to enable detailed runtime debugging and analysis. The trace events capture I2C clock configuration, interrupt status and error code and message. Signed-off-by: Praveen Talari Reviewed-by: Steven Rostedt Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260703-add-tracepoints-for-qcom-geni-i2c-v2-1-e8bf8b178290@oss.qualcomm.com --- include/trace/events/qcom_geni_i2c.h | 82 ++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 include/trace/events/qcom_geni_i2c.h diff --git a/include/trace/events/qcom_geni_i2c.h b/include/trace/events/qcom_geni_i2c.h new file mode 100644 index 000000000000..c7e7984f3620 --- /dev/null +++ b/include/trace/events/qcom_geni_i2c.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM qcom_geni_i2c + +#if !defined(_TRACE_QCOM_GENI_I2C_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_QCOM_GENI_I2C_H + +#include + +TRACE_EVENT(geni_i2c_bus_setup, + TP_PROTO(struct device *dev, u32 clk_freq, u8 clk_div, + u8 t_high_cnt, u8 t_low_cnt, u8 t_cycle_cnt), + TP_ARGS(dev, clk_freq, clk_div, t_high_cnt, t_low_cnt, t_cycle_cnt), + + TP_STRUCT__entry(__string(name, dev_name(dev)) + __field(u32, clk_freq) + __field(u8, clk_div) + __field(u8, t_high_cnt) + __field(u8, t_low_cnt) + __field(u8, t_cycle_cnt) + ), + + TP_fast_assign(__assign_str(name); + __entry->clk_freq = clk_freq; + __entry->clk_div = clk_div; + __entry->t_high_cnt = t_high_cnt; + __entry->t_low_cnt = t_low_cnt; + __entry->t_cycle_cnt = t_cycle_cnt; + ), + + TP_printk("%s: clk_freq=%u clk_div=%u t_high=%u t_low=%u t_cycle=%u", + __get_str(name), __entry->clk_freq, __entry->clk_div, + __entry->t_high_cnt, __entry->t_low_cnt, + __entry->t_cycle_cnt) +); + +TRACE_EVENT(geni_i2c_irq, + TP_PROTO(struct device *dev, u32 m_stat, u32 rx_st, + u32 dm_tx_st, u32 dm_rx_st), + TP_ARGS(dev, m_stat, rx_st, dm_tx_st, dm_rx_st), + + TP_STRUCT__entry(__string(name, dev_name(dev)) + __field(u32, m_stat) + __field(u32, rx_st) + __field(u32, dm_tx_st) + __field(u32, dm_rx_st) + ), + + TP_fast_assign(__assign_str(name); + __entry->m_stat = m_stat; + __entry->rx_st = rx_st; + __entry->dm_tx_st = dm_tx_st; + __entry->dm_rx_st = dm_rx_st; + ), + + TP_printk("%s: m_stat=0x%08x rx_st=0x%08x dm_tx=0x%08x dm_rx=0x%08x", + __get_str(name), __entry->m_stat, __entry->rx_st, + __entry->dm_tx_st, __entry->dm_rx_st) +); + +TRACE_EVENT(geni_i2c_err, + TP_PROTO(struct device *dev, int err, const char *msg), + TP_ARGS(dev, err, msg), + + TP_STRUCT__entry(__string(name, dev_name(dev)) + __field(int, err) + __string(msg, msg) + ), + + TP_fast_assign(__assign_str(name); + __entry->err = err; + __assign_str(msg); + ), + + TP_printk("%s: err=%d msg=%s", + __get_str(name), __entry->err, __get_str(msg)) +); + +#endif /* _TRACE_QCOM_GENI_I2C_H */ + +/* This part must be outside protection */ +#include From f957616d55a829b4695e56d179569f38971b35de Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 3 Jul 2026 20:38:10 +0530 Subject: [PATCH 33/38] i2c: qcom-geni: Add trace events for Qualcomm GENI I2C driver Add trace event definitions for the Qualcomm GENI (Generic Interface) I2C driver. These trace events enable runtime debugging and performance analysis of I2C operations. The trace events capture I2C clock configuration, interrupt status and error code and message. Reviewed-by: Konrad Dybcio Signed-off-by: Praveen Talari Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260703-add-tracepoints-for-qcom-geni-i2c-v2-2-e8bf8b178290@oss.qualcomm.com --- drivers/i2c/busses/i2c-qcom-geni.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index ec0bd10e6774..dd488df45c4c 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. +#define CREATE_TRACE_POINTS +#include + #include #include #include @@ -225,6 +228,9 @@ static int qcom_geni_i2c_conf(struct geni_se *se, unsigned long freq) val |= itr->t_low_cnt << LOW_COUNTER_SHFT; val |= itr->t_cycle_cnt; writel_relaxed(val, gi2c->se.base + SE_I2C_SCL_COUNTERS); + trace_geni_i2c_bus_setup(gi2c->se.dev, gi2c->clk_freq_out, + itr->clk_div, itr->t_high_cnt, + itr->t_low_cnt, itr->t_cycle_cnt); return 0; } @@ -258,6 +264,8 @@ static void geni_i2c_err(struct geni_i2c_dev *gi2c, int err) dev_dbg(gi2c->se.dev, "len:%d, slv-addr:0x%x, RD/WR:%d\n", gi2c->cur->len, gi2c->cur->addr, gi2c->cur->flags); + trace_geni_i2c_err(gi2c->se.dev, gi2c_log[err].err, gi2c_log[err].msg); + switch (err) { case GENI_ABORT_DONE: case ADDR_NACK: @@ -301,6 +309,8 @@ static irqreturn_t geni_i2c_irq(int irq, void *dev) dma = readl_relaxed(base + SE_GENI_DMA_MODE_EN); cur = gi2c->cur; + trace_geni_i2c_irq(gi2c->se.dev, m_stat, rx_st, dm_tx_st, dm_rx_st); + if (!cur || m_stat & (M_CMD_FAILURE_EN | M_CMD_ABORT_EN) || dm_rx_st & (DM_I2C_CB_ERR)) { @@ -820,6 +830,10 @@ static int geni_i2c_gpi_xfer(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], i peripheral.set_config = 1; peripheral.multi_msg = false; + trace_geni_i2c_bus_setup(gi2c->se.dev, gi2c->clk_freq_out, + itr->clk_div, itr->t_high_cnt, + itr->t_low_cnt, itr->t_cycle_cnt); + gi2c->num_msgs = num; gi2c->is_tx_multi_desc_xfer = false; From a40498ebe1b456a2f5e9e1c4182c636786963505 Mon Sep 17 00:00:00 2001 From: Markus Stockhausen Date: Tue, 14 Jul 2026 18:29:14 +0200 Subject: [PATCH 34/38] i2c: algo: bit: Allow to skip bit test An I2C bus can make use of shared resources. E.g. two GPIO based buses that share a single SCL line. To synchronize access to the bus the driver might use locking with the help of i2c_lock_operations(). While this works for normal transfers it is not available during initialization. Especially if i2c-algo-bit module is loaded with parameter bit_test it will issue some basic sanity checks that will access the bus without locking. This might interfere badly with concurrent transfers. Even if these are well synchronized via locks. Allow the consumer of an algorithm to override if the bit_test is allowed or not. For this add a new boolean attribute named skip_bit_test to i2c_algo_bit_data. If set the test is not run. Signed-off-by: Markus Stockhausen Reviewed-by: Bartosz Golaszewski Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260714162915.3018703-2-markus.stockhausen@gmx.de --- drivers/i2c/algos/i2c-algo-bit.c | 2 +- include/linux/i2c-algo-bit.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/algos/i2c-algo-bit.c b/drivers/i2c/algos/i2c-algo-bit.c index d1d9a6c1a1e2..7ca565cbff5b 100644 --- a/drivers/i2c/algos/i2c-algo-bit.c +++ b/drivers/i2c/algos/i2c-algo-bit.c @@ -639,7 +639,7 @@ static int __i2c_bit_add_bus(struct i2c_adapter *adap, struct i2c_algo_bit_data *bit_adap = adap->algo_data; int ret; - if (bit_test) { + if (bit_test && !bit_adap->skip_bit_test) { ret = test_bus(adap); if (bit_test >= 2 && ret < 0) return -ENODEV; diff --git a/include/linux/i2c-algo-bit.h b/include/linux/i2c-algo-bit.h index 7fd5575a368f..c4c43aec1e67 100644 --- a/include/linux/i2c-algo-bit.h +++ b/include/linux/i2c-algo-bit.h @@ -34,6 +34,7 @@ struct i2c_algo_bit_data { maximum 50 us for SMBus */ int timeout; /* in jiffies */ bool can_do_atomic; /* callbacks don't sleep, we can be atomic */ + bool skip_bit_test; /* override bit_test module parameter */ }; int i2c_bit_add_bus(struct i2c_adapter *); From 645c3d7d07fa03810f2263c21f69b57659d11fd8 Mon Sep 17 00:00:00 2001 From: Markus Stockhausen Date: Tue, 14 Jul 2026 18:29:15 +0200 Subject: [PATCH 35/38] i2c: i2c-gpio: Enhance driver for buses with shared SCL Some lower end hardware (especially Realtek based switches) are designed with multiple I2C buses that share a single clock line. E.g. the D-Link DGS-1250-28X realizes 4 I2C SFP busses with 5 GPIOs. Enhance the i2c-gpio driver so it can handle such hardware designs. - Detect shared SCL GPIOs that are used by multiple I2C buses in the devicetree by using a "unique identifier". This is basically the phandle and all additional cells. - The first probing instance allocates and requests the shared SCL GPIO with an associated rt_mutex. Subsequent instances detect the existing entry via the identifier and increment a reference count to reuse the descriptor. - All data transfers are serialized via custom lock_ops that handle both the standard adapter bus lock and the shared SCL mutex. This ensures mutual exclusion across adapters sharing the clock line. - This shared SCL detection works only for dts based systems where the GPIO node has at least one cell (usually the pin). GPIOs in legacy systems without devicetree will be handled individudally as before. This patch was successfully tested on Linksys LGS310C that has two SFP slots with two GPIO based I2C buses that share a single SCL. Test environment: OpenWrt snapshot ported to kernel 6.19.14 including CONFIG_GPIO_SHARED=y and CONFIG_GPIO_SHARED_PROXY=y. Signed-off-by: Markus Stockhausen Tested-by: Sander Vanheule Reviewed-by: Bartosz Golaszewski Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260714162915.3018703-3-markus.stockhausen@gmx.de --- drivers/i2c/busses/i2c-gpio.c | 223 +++++++++++++++++++++++++++++++--- 1 file changed, 209 insertions(+), 14 deletions(-) diff --git a/drivers/i2c/busses/i2c-gpio.c b/drivers/i2c/busses/i2c-gpio.c index f4355b17bfbf..b7521b7ece37 100644 --- a/drivers/i2c/busses/i2c-gpio.c +++ b/drivers/i2c/busses/i2c-gpio.c @@ -18,9 +18,21 @@ #include #include +static LIST_HEAD(i2c_gpio_scl_list); +static DEFINE_MUTEX(i2c_gpio_scl_list_lock); +static struct lock_class_key i2c_gpio_scl_lock_key; + +struct i2c_gpio_scl_data { + struct fwnode_reference_args args; + struct gpio_desc *gpio; + struct rt_mutex lock; + struct list_head list; + refcount_t ref; +}; + struct i2c_gpio_private_data { struct gpio_desc *sda; - struct gpio_desc *scl; + struct i2c_gpio_scl_data *scl; struct i2c_adapter adap; struct i2c_algo_bit_data bit_data; struct i2c_gpio_platform_data pdata; @@ -31,6 +43,11 @@ struct i2c_gpio_private_data { #endif }; +static inline struct i2c_gpio_private_data *adap_to_priv(struct i2c_adapter *adap) +{ + return container_of(adap, struct i2c_gpio_private_data, adap); +} + /* * Toggle SDA by changing the output value of the pin. This is only * valid for pins configured as open drain (i.e. setting the value @@ -53,7 +70,7 @@ static void i2c_gpio_setscl_val(void *data, int state) { struct i2c_gpio_private_data *priv = data; - gpiod_set_value_cansleep(priv->scl, state); + gpiod_set_value_cansleep(priv->scl->gpio, state); } static int i2c_gpio_getsda(void *data) @@ -67,9 +84,41 @@ static int i2c_gpio_getscl(void *data) { struct i2c_gpio_private_data *priv = data; - return gpiod_get_value_cansleep(priv->scl); + return gpiod_get_value_cansleep(priv->scl->gpio); } +static void i2c_gpio_lock_bus(struct i2c_adapter *adap, unsigned int flags) +{ + /* Take care about adapter lock. See i2c_adapter_lock_bus() and others. */ + rt_mutex_lock_nested(&adap->bus_lock, i2c_adapter_depth(adap)); + rt_mutex_lock(&adap_to_priv(adap)->scl->lock); +} + +static int i2c_gpio_trylock_bus(struct i2c_adapter *adap, unsigned int flags) +{ + if (!rt_mutex_trylock(&adap->bus_lock)) + return 0; + + if (!rt_mutex_trylock(&adap_to_priv(adap)->scl->lock)) { + rt_mutex_unlock(&adap->bus_lock); + return 0; + } + + return 1; +} + +static void i2c_gpio_unlock_bus(struct i2c_adapter *adap, unsigned int flags) +{ + rt_mutex_unlock(&adap_to_priv(adap)->scl->lock); + rt_mutex_unlock(&adap->bus_lock); +} + +static const struct i2c_lock_operations i2c_gpio_lock_ops = { + .lock_bus = i2c_gpio_lock_bus, + .trylock_bus = i2c_gpio_trylock_bus, + .unlock_bus = i2c_gpio_unlock_bus, +}; + #ifdef CONFIG_I2C_GPIO_FAULT_INJECTOR #define setsda(bd, val) ((bd)->setsda((bd)->data, val)) @@ -165,14 +214,14 @@ DEFINE_DEBUGFS_ATTRIBUTE(fops_incomplete_write_byte, NULL, fops_incomplete_write static int i2c_gpio_fi_act_on_scl_irq(struct i2c_gpio_private_data *priv, irqreturn_t handler(int, void*)) { - int ret, irq = gpiod_to_irq(priv->scl); + int ret, irq = gpiod_to_irq(priv->scl->gpio); if (irq < 0) return irq; i2c_lock_bus(&priv->adap, I2C_LOCK_ROOT_ADAPTER); - ret = gpiod_direction_input(priv->scl); + ret = gpiod_direction_input(priv->scl->gpio); if (ret) goto unlock; @@ -187,7 +236,7 @@ static int i2c_gpio_fi_act_on_scl_irq(struct i2c_gpio_private_data *priv, free_irq(irq, priv); output: - ret = gpiod_direction_output(priv->scl, 1) ?: ret; + ret = gpiod_direction_output(priv->scl->gpio, 1) ?: ret; unlock: i2c_unlock_bus(&priv->adap, I2C_LOCK_ROOT_ADAPTER); @@ -308,13 +357,17 @@ static struct gpio_desc *i2c_gpio_get_desc(struct device *dev, struct gpio_desc *retdesc; int ret; - retdesc = devm_gpiod_get(dev, con_id, gflags); + /* + * Don't use resource-managed functions. SCL may be shared across adapters and has + * its own lifetime management. SDA uses the same path for consistency. + */ + retdesc = gpiod_get(dev, con_id, gflags); if (!IS_ERR(retdesc)) { dev_dbg(dev, "got GPIO from name %s\n", con_id); return retdesc; } - retdesc = devm_gpiod_get_index(dev, NULL, index, gflags); + retdesc = gpiod_get_index(dev, NULL, index, gflags); if (!IS_ERR(retdesc)) { dev_dbg(dev, "got GPIO from index %u\n", index); return retdesc; @@ -336,6 +389,134 @@ static struct gpio_desc *i2c_gpio_get_desc(struct device *dev, return retdesc; } +static struct i2c_gpio_scl_data *i2c_gpio_create_scl(struct device *dev) +{ + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct fwnode_reference_args args; + struct i2c_gpio_scl_data *scl; + bool sharable = false; + int ret; + + /* + * SCL gpios can be shared if they are defined in the devicetree and have at least one + * cell in addition to the phandle. This is usually the pin. To support complex designs + * with arbitrary of_xlate() mappings handle the node cells opaque. + */ + if (fwnode) { + ret = fwnode_property_get_reference_args(fwnode, "scl-gpios", + "#gpio-cells", 0, 0, &args); + if (ret) + /* try the ancient way */ + ret = fwnode_property_get_reference_args(fwnode, "gpios", + "#gpio-cells", 0, 1, &args); + + if (!ret) { + if (args.nargs >= 1) + sharable = true; + else + fwnode_handle_put(args.fwnode); + } + } + + scl = kzalloc(sizeof(*scl), GFP_KERNEL); + if (!scl) { + if (sharable) + fwnode_handle_put(args.fwnode); + return ERR_PTR(-ENOMEM); + } + + if (sharable) { + scl->args.fwnode = args.fwnode; + scl->args.nargs = args.nargs; + memcpy(scl->args.args, args.args, sizeof(args.args[0]) * args.nargs); + } + + rt_mutex_init(&scl->lock); + lockdep_set_class(&scl->lock, &i2c_gpio_scl_lock_key); + refcount_set(&scl->ref, 1); + + return scl; +} + +static void i2c_gpio_free_scl(struct i2c_gpio_scl_data *scl) +{ + if (scl->args.fwnode) + fwnode_handle_put(scl->args.fwnode); + kfree(scl); +} + +static bool i2c_gpio_scl_matches(struct i2c_gpio_scl_data *a, struct i2c_gpio_scl_data *b) +{ + if (!a->args.fwnode || !b->args.fwnode || + a->args.fwnode != b->args.fwnode || a->args.nargs != b->args.nargs) + return false; + + return memcmp(a->args.args, b->args.args, sizeof(a->args.args[0]) * a->args.nargs) == 0; +} + +/* + * Look up an existing or create a new shared SCL structure. Optimistic setup sequence always + * creates and tries to add a new entry to the list. This uses minimum locking and afterwards + * requests the GPIO without a lock held. Concurrent probes for the same SCL pin see the entry + * and do not race into a second gpiod_get(). Until everything is setup they terminate with + * -EPROBE_DEFER. + */ +static struct i2c_gpio_scl_data *i2c_gpio_lookup_scl(struct device *dev, enum gpiod_flags gflags) +{ + struct i2c_gpio_scl_data *scl, *new_scl; + struct gpio_desc *gpio; + + new_scl = i2c_gpio_create_scl(dev); + if (IS_ERR(new_scl)) + return new_scl; + + scoped_guard(mutex, &i2c_gpio_scl_list_lock) { + list_for_each_entry(scl, &i2c_gpio_scl_list, list) { + if (!i2c_gpio_scl_matches(scl, new_scl)) + continue; + + i2c_gpio_free_scl(new_scl); + if (!scl->gpio) + return ERR_PTR(-EPROBE_DEFER); + + refcount_inc(&scl->ref); + if (scl->args.fwnode) + dev_dbg(dev, "reusing shared SCL (%pfwP)\n", scl->args.fwnode); + + return scl; + } + list_add(&new_scl->list, &i2c_gpio_scl_list); + } + + gpio = i2c_gpio_get_desc(dev, "scl", 1, gflags); + if (IS_ERR(gpio)) { + scoped_guard(mutex, &i2c_gpio_scl_list_lock) + list_del(&new_scl->list); + i2c_gpio_free_scl(new_scl); + + return ERR_CAST(gpio); + } + + scoped_guard(mutex, &i2c_gpio_scl_list_lock) + new_scl->gpio = gpio; + + if (new_scl->args.fwnode) + dev_dbg(dev, "registered shared SCL (%pfwP)\n", new_scl->args.fwnode); + + return new_scl; +} + +static void i2c_gpio_cleanup_scl(struct i2c_gpio_scl_data *scl) +{ + if (!refcount_dec_and_mutex_lock(&scl->ref, &i2c_gpio_scl_list_lock)) + return; + + list_del(&scl->list); + mutex_unlock(&i2c_gpio_scl_list_lock); + gpiod_put(scl->gpio); + i2c_gpio_free_scl(scl); +} + static int i2c_gpio_probe(struct platform_device *pdev) { struct i2c_gpio_private_data *priv; @@ -386,15 +567,18 @@ static int i2c_gpio_probe(struct platform_device *pdev) gflags = GPIOD_OUT_HIGH; else gflags = GPIOD_OUT_HIGH_OPEN_DRAIN; - priv->scl = i2c_gpio_get_desc(dev, "scl", 1, gflags); - if (IS_ERR(priv->scl)) - return PTR_ERR(priv->scl); + priv->scl = i2c_gpio_lookup_scl(dev, gflags); + if (IS_ERR(priv->scl)) { + ret = PTR_ERR(priv->scl); + goto err_cleanup_sda; + } - if (gpiod_cansleep(priv->sda) || gpiod_cansleep(priv->scl)) + if (gpiod_cansleep(priv->sda) || gpiod_cansleep(priv->scl->gpio)) dev_warn(dev, "Slow GPIO pins might wreak havoc into I2C/SMBus bus timing"); else bit_data->can_do_atomic = true; + bit_data->skip_bit_test = !!priv->scl->args.fwnode; bit_data->setsda = i2c_gpio_setsda_val; bit_data->setscl = i2c_gpio_setscl_val; @@ -423,6 +607,8 @@ static int i2c_gpio_probe(struct platform_device *pdev) else snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id); + /* Always use shared SCL aware locking */ + adap->lock_ops = &i2c_gpio_lock_ops; adap->algo_data = bit_data; adap->class = I2C_CLASS_HWMON; adap->dev.parent = dev; @@ -431,7 +617,7 @@ static int i2c_gpio_probe(struct platform_device *pdev) adap->nr = pdev->id; ret = i2c_bit_add_numbered_bus(adap); if (ret) - return ret; + goto err_cleanup_scl; platform_set_drvdata(pdev, priv); @@ -441,13 +627,20 @@ static int i2c_gpio_probe(struct platform_device *pdev) * from the descriptor, then provide that instead. */ dev_info(dev, "using lines %u (SDA) and %u (SCL%s)\n", - desc_to_gpio(priv->sda), desc_to_gpio(priv->scl), + desc_to_gpio(priv->sda), desc_to_gpio(priv->scl->gpio), pdata->scl_is_output_only ? ", no clock stretching" : ""); i2c_gpio_fault_injector_init(pdev); return 0; + +err_cleanup_scl: + i2c_gpio_cleanup_scl(priv->scl); +err_cleanup_sda: + gpiod_put(priv->sda); + + return ret; } static void i2c_gpio_remove(struct platform_device *pdev) @@ -459,6 +652,8 @@ static void i2c_gpio_remove(struct platform_device *pdev) adap = &priv->adap; i2c_del_adapter(adap); + i2c_gpio_cleanup_scl(priv->scl); + gpiod_put(priv->sda); } static const struct of_device_id i2c_gpio_dt_ids[] = { From a5c811aa0a1550c302855176b309e6ed46ce01fe Mon Sep 17 00:00:00 2001 From: Minh Le Date: Mon, 27 Jul 2026 14:25:47 +0200 Subject: [PATCH 36/38] dt-bindings: i2c: rcar-i2c: Document R-Car X5H support Document support for rcar-i2c on R8A78000 (X5H) SoCs. Signed-off-by: Minh Le [wsa: fixed white space issues, added reset requirement] Signed-off-by: Wolfram Sang Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260727122546.4232-5-wsa+renesas@sang-engineering.com --- Documentation/devicetree/bindings/i2c/renesas,rcar-i2c.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/i2c/renesas,rcar-i2c.yaml b/Documentation/devicetree/bindings/i2c/renesas,rcar-i2c.yaml index 6cc60c3f61cd..f9ede03c3287 100644 --- a/Documentation/devicetree/bindings/i2c/renesas,rcar-i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/renesas,rcar-i2c.yaml @@ -56,6 +56,11 @@ properties: - renesas,i2c-r8a779h0 # R-Car V4M - const: renesas,rcar-gen4-i2c # R-Car Gen4 + - items: + - enum: + - renesas,i2c-r8a78000 # R-Car X5H + - const: renesas,rcar-gen5-i2c # R-Car Gen5 + reg: maxItems: 1 @@ -140,6 +145,7 @@ allOf: - renesas,rcar-gen2-i2c - renesas,rcar-gen3-i2c - renesas,rcar-gen4-i2c + - renesas,rcar-gen5-i2c then: required: - resets From 87e713f2004811062e5283a5a4efb52c883bb355 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Mon, 27 Jul 2026 14:25:48 +0200 Subject: [PATCH 37/38] i2c: rcar: add R-Car Gen5 support To support the next generation of R-Car SoCs, we need to skip polling the reset status. SCMI doesn't support it and the firmware must take care of this anyhow. Other than that, the driver works fine as-is. Signed-off-by: Wolfram Sang Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260727122546.4232-6-wsa+renesas@sang-engineering.com --- drivers/i2c/busses/i2c-rcar.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/drivers/i2c/busses/i2c-rcar.c b/drivers/i2c/busses/i2c-rcar.c index 5ce8f8e4856f..46508176712c 100644 --- a/drivers/i2c/busses/i2c-rcar.c +++ b/drivers/i2c/busses/i2c-rcar.c @@ -137,6 +137,7 @@ enum rcar_i2c_type { I2C_RCAR_GEN2, I2C_RCAR_GEN3, I2C_RCAR_GEN4, + I2C_RCAR_GEN5, }; struct rcar_i2c_priv { @@ -900,8 +901,12 @@ static int rcar_i2c_do_reset(struct rcar_i2c_priv *priv) if (ret) return ret; - return read_poll_timeout_atomic(reset_control_status, ret, ret == 0, 1, - 100, false, priv->rstc); + /* SCMI based resets don't need to poll for success */ + if (priv->devtype < I2C_RCAR_GEN5) + return read_poll_timeout_atomic(reset_control_status, ret, ret == 0, + 1, 100, false, priv->rstc); + + return 0; } static int rcar_i2c_master_xfer(struct i2c_adapter *adap, @@ -1111,6 +1116,7 @@ static const struct of_device_id rcar_i2c_dt_ids[] = { { .compatible = "renesas,rcar-gen2-i2c", .data = (void *)I2C_RCAR_GEN2 }, { .compatible = "renesas,rcar-gen3-i2c", .data = (void *)I2C_RCAR_GEN3 }, { .compatible = "renesas,rcar-gen4-i2c", .data = (void *)I2C_RCAR_GEN4 }, + { .compatible = "renesas,rcar-gen5-i2c", .data = (void *)I2C_RCAR_GEN5 }, {}, }; MODULE_DEVICE_TABLE(of, rcar_i2c_dt_ids); @@ -1194,9 +1200,15 @@ static int rcar_i2c_probe(struct platform_device *pdev) goto out_pm_put; } - ret = reset_control_status(priv->rstc); - if (ret < 0) - goto out_pm_put; + /* + * Gen5+ uses SCMI based reset which cannot report status. + * Firmware has to ensure proper reset + */ + if (priv->devtype < I2C_RCAR_GEN5) { + ret = reset_control_status(priv->rstc); + if (ret < 0) + goto out_pm_put; + } /* hard reset disturbs HostNotify local target, so disable it */ priv->flags &= ~ID_P_HOST_NOTIFY; From 18ca2ce160ffff977f28456039f2475a3b500bc0 Mon Sep 17 00:00:00 2001 From: Jie Li Date: Mon, 11 May 2026 13:37:26 +0200 Subject: [PATCH 38/38] i2c: core: support recovery for single-ended GPIOs Currently, i2c_init_recovery() only assigns the set_sda/set_scl hooks if gpiod_get_direction() returns GPIO_LINE_DIRECTION_OUT. This logic fails on certain SoC controllers where open-drain lines in a high-impedance state are physically reported as inputs. This leads to a "deadlock" where the I2C core refuses to assign the recovery hooks because it incorrectly assumes the pins are input-only, even though they are fully capable of driving the bus low for recovery. Update the recovery initialization to use the new gpiod_is_single_ended() helper. If a GPIO is configured as open-drain or open-source in the firmware, it is safe to assume it can be used for bus recovery, even if the current hardware direction is reported as input. Signed-off-by: Jie Li Reviewed-by: Linus Walleij Acked-by: Wolfram Sang Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260511113726.49041-3-jie.i.li@nokia.com --- drivers/i2c/i2c-core-base.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index 3ec04787a737..fb25704219c7 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -446,7 +446,8 @@ static int i2c_init_recovery(struct i2c_adapter *adap) bri->set_scl = set_scl_gpio_value; if (bri->sda_gpiod) { bri->get_sda = get_sda_gpio_value; - if (gpiod_get_direction(bri->sda_gpiod) == GPIO_LINE_DIRECTION_OUT) + if (gpiod_get_direction(bri->sda_gpiod) == GPIO_LINE_DIRECTION_OUT || + gpiod_is_single_ended(bri->sda_gpiod)) bri->set_sda = set_sda_gpio_value; } } else if (bri->recover_bus == i2c_generic_scl_recovery) {