* [PATCH v4 0/2] Add Qualcomm I2C target controller driver
@ 2026-09-20 12:04 Viken Dadhaniya
2026-09-20 12:04 ` [PATCH v4 1/2] dt-bindings: i2c: Add Qualcomm I2C target controller Viken Dadhaniya
2026-09-20 12:04 ` [PATCH v4 2/2] i2c: qcom-target: Add driver for " Viken Dadhaniya
0 siblings, 2 replies; 6+ messages in thread
From: Viken Dadhaniya @ 2026-09-20 12:04 UTC (permalink / raw)
To: Mukesh Kumar Savaliya, Andi Shyti, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-i2c, devicetree, linux-kernel,
Viken Dadhaniya, Krzysztof Kozlowski
QDU1000 and related Qualcomm SoCs include a dedicated I2C target
controller that operates exclusively in target mode and is not supported
by the existing Qualcomm I2C master controller drivers (GENI, QUP).
This series adds DT binding and driver support for this IP. The driver
uses the standard Linux I2C slave framework (reg_target/unreg_target,
i2c_slave_event) so any slave backend (e.g. slave-24c02) can be
attached via i2c_slave_register().
The series is structured as follows:
Patch 1: DT binding document
Patch 2: Driver implementation including Kconfig, Makefile and
MAINTAINERS entries
The driver has been tested on QDU1000 hardware.
Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
---
Changes in v4:
- Replaced usleep_range() with udelay(20) in the reset path, since
reset is invoked from the hardirq error handler.
- Avoided releasing clock stretch on RESTART_DETECTED when STRCH_RD is
co-asserted; TX FIFO is now loaded before ACK_RESUME is written.
- Added cleanup for both clocks on all probe failures after they are
enabled.
- Explicitly freed the IRQ before clock teardown in remove(), preventing
IRQ-handler register accesses after clocks are disabled.
- Replaced noirq PM callbacks with regular system suspend/resume callbacks
and quiesced the IRQ while clocks are disabled.
- Link to v3: https://patch.msgid.link/20260813-i2c-qcom-slave-v3-0-1d3742e2ad47@oss.qualcomm.com
Changes in v3:
- Rename file to match compatible (qcom,qdu1000-i2c-target.yaml)
- Remove interconnect-names property
- Remove allOf reference to i2c-controller.yaml
- Remove #address-cells and #size-cells from example
- Add minItems: 1 to pinctrl-names
- Move I2C_S_CORE_EN out of hw_init; enable core only in reg_slave
and hw_reset
- Add usleep_range(10, 20) after SW_RESET before reconfiguring hardware
- Fix RESTART_DETECTED dispatch order; handle before STRCH_RD and
Rx data phases
- Use test_and_set_bit in write_requested and handle_strch_rd
- Loop drain_rx_fifo until FIFO is empty to close post-STOP data race
- Disable core before clearing slave pointer in unreg_slave
- Clear target->status on resume
- Switch from devm_clk_get_enabled to devm_clk_get with manual
clk_prepare_enable
- Remove unbalanced disable_irq from remove
- Update struct kdoc to note I2C_LOCK_ROOT_ADAPTER serialises
reg/unreg_target
- Link to v2: https://patch.msgid.link/20260802-i2c-qcom-slave-v2-0-27653118fa75@oss.qualcomm.com
Changes in v2:
- Rename driver and binding from "slave" to "target" terminology
- Use SoC-specific compatible string (qcom,qdu1000-i2c-target) instead of
generic qcom,i2c-slave; add allOf/$ref to i2c-controller.yaml
- Replace SMBus layer (smbus_xfer, I2C_FUNC_SMBUS_*) with native Linux I2C
slave framework (reg_target/unreg_target, I2C_SLAVE_* events)
- Remove qcom,slave-addr DT property; slave address taken from the
registered i2c_client at reg_target() time
- Remove staging buffers and spinlock; bytes delivered directly to backend
via i2c_slave_event() per STRCH_RD/RX/STOP event
- Use SET_NOIRQ_SYSTEM_SLEEP_PM_OPS; PM core quiesces IRQs at noirq stage,
removing need for manual disable_irq/enable_irq in suspend
- Simplify clock-names (xo/ahb) and interconnect-names (i2c)
- Replace icc_enable/icc_disable with icc_set_bw() vote/unvote
- Merge MAINTAINERS entry into the dt-bindings patch
- Link to v1: https://patch.msgid.link/20260628-i2c-qcom-slave-v1-0-8b0a5c01f9f6@oss.qualcomm.com
---
Viken Dadhaniya (2):
dt-bindings: i2c: Add Qualcomm I2C target controller
i2c: qcom-target: Add driver for Qualcomm I2C target controller
.../bindings/i2c/qcom,qdu1000-i2c-target.yaml | 75 +++
MAINTAINERS | 9 +
drivers/i2c/busses/Kconfig | 15 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-qcom-target.c | 628 +++++++++++++++++++++
5 files changed, 728 insertions(+)
---
base-commit: 3f2425f5b5bbbdd991ca9cdfd5502e68d8895998
change-id: 20260628-i2c-qcom-slave-c382ff4e8691
Best regards,
--
Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v4 1/2] dt-bindings: i2c: Add Qualcomm I2C target controller
2026-09-20 12:04 [PATCH v4 0/2] Add Qualcomm I2C target controller driver Viken Dadhaniya
@ 2026-09-20 12:04 ` Viken Dadhaniya
2026-09-20 12:12 ` sashiko-bot
2026-09-20 12:04 ` [PATCH v4 2/2] i2c: qcom-target: Add driver for " Viken Dadhaniya
1 sibling, 1 reply; 6+ messages in thread
From: Viken Dadhaniya @ 2026-09-20 12:04 UTC (permalink / raw)
To: Mukesh Kumar Savaliya, Andi Shyti, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-i2c, devicetree, linux-kernel,
Viken Dadhaniya, Krzysztof Kozlowski
QDU1000 and related Qualcomm SoCs include a dedicated I2C target
controller that operates exclusively in target mode. It is a distinct
IP from the Qualcomm I2C master controllers (GENI, QUP) with a
different register interface, so it requires its own binding.
Document the MMIO region, interrupt, XO and AHB clocks, interconnect
path, and optional pinctrl states for the controller.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
---
.../bindings/i2c/qcom,qdu1000-i2c-target.yaml | 75 ++++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/Documentation/devicetree/bindings/i2c/qcom,qdu1000-i2c-target.yaml b/Documentation/devicetree/bindings/i2c/qcom,qdu1000-i2c-target.yaml
new file mode 100644
index 000000000000..9a6b08f0ab0e
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/qcom,qdu1000-i2c-target.yaml
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/i2c/qcom,qdu1000-i2c-target.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm I2C Target Controller
+
+maintainers:
+ - Mukesh Kumar Savaliya <mukesh.savaliya@oss.qualcomm.com>
+ - Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
+
+description:
+ Dedicated hardware IP found on Qualcomm SoCs that operates exclusively
+ as an I2C target (slave) device on the bus. Supports FIFO (PIO) mode
+ for data transfer.
+
+properties:
+ compatible:
+ enum:
+ - qcom,qdu1000-i2c-target
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: XO reference clock
+ - description: AHB bus clock
+
+ clock-names:
+ items:
+ - const: xo
+ - const: ahb
+
+ interconnects:
+ maxItems: 1
+
+ pinctrl-0: true
+ pinctrl-1: true
+
+ pinctrl-names:
+ minItems: 1
+ items:
+ - const: default
+ - const: sleep
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+ - interconnects
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/clock/qcom,qdu1000-gcc.h>
+ #include <dt-bindings/interconnect/qcom,qdu1000-rpmh.h>
+
+ i2c@88ca000 {
+ compatible = "qcom,qdu1000-i2c-target";
+ reg = <0x88ca000 0x64>;
+ clocks = <&gcc GCC_SM_BUS_XO_CLK>, <&gcc GCC_SM_BUS_AHB_CLK>;
+ clock-names = "xo", "ahb";
+ interrupts = <GIC_SPI 358 IRQ_TYPE_LEVEL_HIGH>;
+ interconnects = <&gem_noc MASTER_APPSS_PROC 0 &config_noc SLAVE_SMBUS_CFG 0>;
+ };
+...
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v4 2/2] i2c: qcom-target: Add driver for Qualcomm I2C target controller
2026-09-20 12:04 [PATCH v4 0/2] Add Qualcomm I2C target controller driver Viken Dadhaniya
2026-09-20 12:04 ` [PATCH v4 1/2] dt-bindings: i2c: Add Qualcomm I2C target controller Viken Dadhaniya
@ 2026-09-20 12:04 ` Viken Dadhaniya
2026-09-20 12:17 ` sashiko-bot
2026-09-29 6:51 ` Andi Shyti
1 sibling, 2 replies; 6+ messages in thread
From: Viken Dadhaniya @ 2026-09-20 12:04 UTC (permalink / raw)
To: Mukesh Kumar Savaliya, Andi Shyti, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-i2c, devicetree, linux-kernel,
Viken Dadhaniya
QDU1000 and related Qualcomm SoCs include a dedicated I2C target
controller that operates exclusively in target mode. The existing
Qualcomm I2C controller drivers, GENI and QUP, are master-only and
cannot serve systems where the SoC must respond as an I2C target on
the bus.
Register the controller with the Linux I2C slave framework through
i2c_algorithm.reg_slave and i2c_algorithm.unreg_slave. This allows
standard slave backends, such as slave-24c02, to be attached at
runtime through i2c_slave_register().
Handle IRQ events for RX FIFO service, clock stretching during read
and write phases, STOP and repeated-start conditions, and error
recovery through a software reset.
Enable the required AHB and XO clocks and vote for interconnect
bandwidth. Restore the hardware state across system suspend and
resume using regular system-sleep PM callbacks.
Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
---
MAINTAINERS | 9 +
drivers/i2c/busses/Kconfig | 15 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-qcom-target.c | 628 +++++++++++++++++++++++++++++++++++
4 files changed, 653 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 06760c45c582..07ade6c1dfdf 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22670,6 +22670,15 @@ S: Maintained
F: Documentation/devicetree/bindings/i2c/qcom,i2c-geni-qcom.yaml
F: drivers/i2c/busses/i2c-qcom-geni.c
+QUALCOMM I2C TARGET CONTROLLER DRIVER
+M: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
+M: Mukesh Kumar Savaliya <mukesh.savaliya@oss.qualcomm.com>
+L: linux-i2c@vger.kernel.org
+L: linux-arm-msm@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/i2c/qcom,qdu1000-i2c-target.yaml
+F: drivers/i2c/busses/i2c-qcom-target.c
+
QUALCOMM I2C CCI DRIVER
M: Loic Poulain <loic.poulain@oss.qualcomm.com>
M: Robert Foss <rfoss@kernel.org>
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index d7b89508311f..2e18238e0ae6 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1070,6 +1070,21 @@ config I2C_QCOM_GENI
This driver can also be built as a module. If so, the module
will be called i2c-qcom-geni.
+config I2C_QCOM_TARGET
+ tristate "Qualcomm I2C target controller"
+ depends on ARCH_QCOM || COMPILE_TEST
+ depends on COMMON_CLK
+ depends on INTERCONNECT
+ select I2C_SLAVE
+ help
+ This driver supports I2C target mode on Qualcomm Technologies
+ SoCs. If you say yes to this option, support will be included
+ for the built-in I2C target controller on QDU1000 and other
+ compatible Qualcomm SoCs.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-qcom-target.
+
config I2C_QUP
tristate "Qualcomm QUP based I2C controller"
depends on ARCH_QCOM || COMPILE_TEST
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 3755c54b3d82..ab3070cdb144 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -101,6 +101,7 @@ obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
obj-$(CONFIG_I2C_QCOM_CCI) += i2c-qcom-cci.o
obj-$(CONFIG_I2C_QCOM_GENI) += i2c-qcom-geni.o
+obj-$(CONFIG_I2C_QCOM_TARGET) += i2c-qcom-target.o
obj-$(CONFIG_I2C_QUP) += i2c-qup.o
obj-$(CONFIG_I2C_RIIC) += i2c-riic.o
obj-$(CONFIG_I2C_RK3X) += i2c-rk3x.o
diff --git a/drivers/i2c/busses/i2c-qcom-target.c b/drivers/i2c/busses/i2c-qcom-target.c
new file mode 100644
index 000000000000..5cf4bad1b601
--- /dev/null
+++ b/drivers/i2c/busses/i2c-qcom-target.c
@@ -0,0 +1,628 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interconnect.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+/* Register offsets */
+#define I2C_S_DEVICE_ADDR 0x00
+#define I2C_S_IRQ_STATUS 0x08
+#define I2C_S_IRQ_CLR 0x0C
+#define I2C_S_IRQ_EN 0x10
+#define I2C_S_CONFIG 0x18
+#define I2C_S_CONTROL 0x1C
+#define I2C_S_FIFOS_STATUS 0x20
+#define I2C_S_TX_FIFO 0x24
+#define I2C_S_RX_FIFO 0x28
+#define I2C_S_DEBUG_REG1 0x3C
+#define I2C_S_DEBUG_REG2 0x40
+#define I2C_S_SW_RESET_REG 0x4C
+#define I2C_S_CLK_LOW_TIMEOUT 0x50
+#define I2C_S_CLK_RELEASE_DELAY_CNT_VAL 0x54
+#define I2C_S_SDA_HOLD_CNT_VAL 0x58
+
+/* I2C_S_CONFIG register fields */
+#define I2C_S_CORE_EN BIT(0)
+
+/* I2C_S_CONTROL register fields */
+#define CLEAR_RX_FIFO BIT(0)
+#define CLEAR_TX_FIFO BIT(1)
+#define NACK BIT(2)
+#define ACK_RESUME BIT(3)
+
+/* I2C_S_SW_RESET_REG register fields */
+#define SW_RESET BIT(0)
+
+/* I2C_S_FIFOS_STATUS register fields */
+#define RX_FIFO_COUNT_MASK GENMASK(31, 16)
+
+/* Interconnect bandwidth vote in bytes per second */
+#define APPS_PROC_TO_I2C_TARGET_VOTE 1190000
+
+/**
+ * enum qcom_i2c_target_irq - IRQ bit positions in I2C_S_IRQ_STATUS
+ * @STOP_DETECTED: I2C stop condition detected on the bus
+ * @RX_FIFO_FULL: receive FIFO has reached capacity
+ * @TX_FIFO_EMPTY: transmit FIFO is empty
+ * @RX_DATA_AVAIL: receive data is available in the RX FIFO
+ * @CLOCK_LOW_TIMEOUT: SCL held low longer than the configured timeout
+ * @STRCH_WR: clock stretching during a write (Rx) phase
+ * @STRCH_RD: clock stretching during a read (Tx) phase
+ * @GCA_DETECTED: general call address detected (not used)
+ * @ERR_CONDITION: unexpected start or stop bit detected (error)
+ * @RESTART_DETECTED: repeated start condition detected
+ */
+enum qcom_i2c_target_irq {
+ STOP_DETECTED,
+ RX_FIFO_FULL,
+ TX_FIFO_EMPTY,
+ RX_DATA_AVAIL,
+ CLOCK_LOW_TIMEOUT,
+ STRCH_WR,
+ STRCH_RD,
+ GCA_DETECTED,
+ ERR_CONDITION,
+ RESTART_DETECTED,
+};
+
+/*
+ * TX_FIFO_EMPTY is excluded: this driver fills the TX FIFO one byte at a
+ * time in response to STRCH_RD (clock-stretch during read phase), so
+ * TX_FIFO_EMPTY adds no value and would double the interrupt rate.
+ * GCA (general call address) is unsupported.
+ */
+#define QCOM_I2C_TARGET_ALL_IRQ (GENMASK(RESTART_DETECTED, STOP_DETECTED) \
+ & ~(BIT(GCA_DETECTED) | BIT(TX_FIFO_EMPTY)))
+
+/* Bit indices for the status bitmask, used with set_bit/test_bit/clear_bit */
+enum qcom_i2c_target_status {
+ READ_IN_PROGRESS,
+ WRITE_IN_PROGRESS,
+};
+
+/**
+ * struct qcom_i2c_target - Qualcomm I2C target controller private data
+ * @dev: driver model device node
+ * @base: base address of HW registers
+ * @adap: I2C adapter (slave mode)
+ * @ahb_clk: AHB bus clock
+ * @xo_clk: XO reference clock
+ * @icc_path: interconnect bandwidth path
+ * @slave: currently registered slave backend client; written only
+ * under disable_irq() in reg_slave()/unreg_slave() so the
+ * ISR sees a stable pointer for its entire execution
+ * @status: bitmask of enum qcom_i2c_target_status flags; must be
+ * unsigned long for set_bit/test_bit/clear_bit; accessed
+ * only from the ISR and from reg_slave()/unreg_slave()
+ * under disable_irq(), so no additional lock is needed
+ * @irq: interrupt line number
+ */
+struct qcom_i2c_target {
+ struct device *dev;
+ void __iomem *base;
+ struct i2c_adapter adap;
+ struct clk *ahb_clk;
+ struct clk *xo_clk;
+ struct icc_path *icc_path;
+ struct i2c_client *slave;
+ unsigned long status;
+ int irq;
+};
+
+static void qcom_i2c_target_dump_regs(struct qcom_i2c_target *target)
+{
+ dev_dbg(target->dev, "I2C_S_DEVICE_ADDR: 0x%x\n",
+ readl_relaxed(target->base + I2C_S_DEVICE_ADDR));
+ dev_dbg(target->dev, "I2C_S_IRQ_STATUS: 0x%x\n",
+ readl_relaxed(target->base + I2C_S_IRQ_STATUS));
+ dev_dbg(target->dev, "I2C_S_CONFIG: 0x%x\n",
+ readl_relaxed(target->base + I2C_S_CONFIG));
+ dev_dbg(target->dev, "I2C_S_IRQ_EN: 0x%x\n",
+ readl_relaxed(target->base + I2C_S_IRQ_EN));
+ dev_dbg(target->dev, "I2C_S_FIFOS_STATUS: 0x%x\n",
+ readl_relaxed(target->base + I2C_S_FIFOS_STATUS));
+ dev_dbg(target->dev, "I2C_S_DEBUG_REG1: 0x%x\n",
+ readl_relaxed(target->base + I2C_S_DEBUG_REG1));
+ dev_dbg(target->dev, "I2C_S_DEBUG_REG2: 0x%x\n",
+ readl_relaxed(target->base + I2C_S_DEBUG_REG2));
+ dev_dbg(target->dev, "I2C_S_CLK_LOW_TIMEOUT: 0x%x\n",
+ readl_relaxed(target->base + I2C_S_CLK_LOW_TIMEOUT));
+ dev_dbg(target->dev, "I2C_S_CLK_RELEASE_DELAY_CNT_VAL: 0x%x\n",
+ readl_relaxed(target->base + I2C_S_CLK_RELEASE_DELAY_CNT_VAL));
+ dev_dbg(target->dev, "I2C_S_SDA_HOLD_CNT_VAL: 0x%x\n",
+ readl_relaxed(target->base + I2C_S_SDA_HOLD_CNT_VAL));
+}
+
+static void qcom_i2c_target_hw_init(struct qcom_i2c_target *target)
+{
+ dev_dbg(target->dev, "HW init: resetting FIFOs, enabling IRQs\n");
+ writel(CLEAR_TX_FIFO | CLEAR_RX_FIFO, target->base + I2C_S_CONTROL);
+ writel(QCOM_I2C_TARGET_ALL_IRQ, target->base + I2C_S_IRQ_EN);
+}
+
+static void qcom_i2c_target_write_requested(struct qcom_i2c_target *target)
+{
+ u8 val = 0;
+
+ if (!test_and_set_bit(WRITE_IN_PROGRESS, &target->status)) {
+ dev_dbg(target->dev, "Write phase started\n");
+ i2c_slave_event(target->slave, I2C_SLAVE_WRITE_REQUESTED, &val);
+ }
+}
+
+static int qcom_i2c_target_drain_rx_fifo(struct qcom_i2c_target *target)
+{
+ unsigned int rx_count;
+ int ret = 0;
+ u8 val;
+
+ while (!ret) {
+ rx_count = FIELD_GET(RX_FIFO_COUNT_MASK,
+ readl_relaxed(target->base + I2C_S_FIFOS_STATUS));
+ if (!rx_count)
+ break;
+
+ while (rx_count--) {
+ val = (u8)readl_relaxed(target->base + I2C_S_RX_FIFO);
+ dev_dbg(target->dev, "Data from RX FIFO: 0x%x\n", val);
+ ret = i2c_slave_event(target->slave, I2C_SLAVE_WRITE_RECEIVED, &val);
+ if (ret)
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static void qcom_i2c_target_hw_reset(struct qcom_i2c_target *target)
+{
+ /* Clear error bits before SW_RESET; the reset may not be instantaneous */
+ writel(BIT(ERR_CONDITION) | BIT(CLOCK_LOW_TIMEOUT),
+ target->base + I2C_S_IRQ_CLR);
+ writel(SW_RESET, target->base + I2C_S_SW_RESET_REG);
+
+ /*
+ * I2C_S_SW_RESET_REG is write-only so completion cannot be polled.
+ * This helper is called from the hard IRQ handler, so the reset delay
+ * must not sleep. Use a short busy wait before reconfiguring the
+ * controller.
+ */
+ udelay(20);
+ qcom_i2c_target_hw_init(target);
+ writel(target->slave->addr, target->base + I2C_S_DEVICE_ADDR);
+ writel(I2C_S_CORE_EN, target->base + I2C_S_CONFIG);
+}
+
+static irqreturn_t qcom_i2c_target_handle_error(struct qcom_i2c_target *target,
+ u32 irq_stat)
+{
+ u8 val = 0;
+
+ if (irq_stat & BIT(ERR_CONDITION))
+ dev_err(target->dev, "Error condition: unexpected Start/Stop bits\n");
+ else
+ dev_err(target->dev, "Clock low timeout\n");
+ qcom_i2c_target_dump_regs(target);
+ qcom_i2c_target_hw_reset(target);
+ i2c_slave_event(target->slave, I2C_SLAVE_STOP, &val);
+ target->status = 0;
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qcom_i2c_target_handle_stop(struct qcom_i2c_target *target,
+ u32 irq_stat)
+{
+ u8 val = 0;
+
+ dev_dbg(target->dev, "Stop bit detected\n");
+ /*
+ * Short-write corner case: WRITE_IN_PROGRESS was never set because
+ * no RX_DATA_AVAIL or STRCH_WR fired before STOP. Do not call
+ * qcom_i2c_target_write_requested() here — STOP ends the transaction
+ * so setting WRITE_IN_PROGRESS now would leave a stale bit after
+ * target->status is cleared below.
+ */
+ if (!test_bit(WRITE_IN_PROGRESS, &target->status)) {
+ unsigned int rx_count;
+
+ rx_count = FIELD_GET(RX_FIFO_COUNT_MASK,
+ readl_relaxed(target->base + I2C_S_FIFOS_STATUS));
+ if (rx_count)
+ i2c_slave_event(target->slave, I2C_SLAVE_WRITE_REQUESTED,
+ &val);
+ }
+
+ qcom_i2c_target_drain_rx_fifo(target);
+ i2c_slave_event(target->slave, I2C_SLAVE_STOP, &val);
+ target->status = 0;
+ writel(CLEAR_RX_FIFO, target->base + I2C_S_CONTROL);
+ /*
+ * STOP terminates the transaction, so any other Rx or clock
+ * stretch bits latched in this same status read have already
+ * been serviced by the drain above or are now stale. Ack the
+ * whole word rather than just STOP_DETECTED; clearing only STOP
+ * would leave those bits set and immediately re-enter the
+ * handler, which for a co-asserted STRCH_RD would push a stale
+ * byte into the TX FIFO.
+ */
+ writel(irq_stat, target->base + I2C_S_IRQ_CLR);
+
+ return IRQ_HANDLED;
+}
+
+static void qcom_i2c_target_handle_rx_data(struct qcom_i2c_target *target,
+ u32 rx_irq_bits)
+{
+ int ret;
+
+ dev_dbg(target->dev, "Rx data event (rx_irq_bits=0x%x)\n", rx_irq_bits);
+ qcom_i2c_target_write_requested(target);
+ ret = qcom_i2c_target_drain_rx_fifo(target);
+ if (ret)
+ dev_dbg(target->dev, "Backend requested NACK\n");
+
+ writel(ret ? NACK | CLEAR_RX_FIFO : ACK_RESUME,
+ target->base + I2C_S_CONTROL);
+ writel(rx_irq_bits, target->base + I2C_S_IRQ_CLR);
+}
+
+static void qcom_i2c_target_handle_strch_rd(struct qcom_i2c_target *target)
+{
+ enum i2c_slave_event event = I2C_SLAVE_READ_PROCESSED;
+ u8 val = 0;
+
+ dev_dbg(target->dev, "Clock stretching during read (Tx) phase\n");
+ if (!test_and_set_bit(READ_IN_PROGRESS, &target->status)) {
+ /* Repeated-start: master switched direction from write to read */
+ clear_bit(WRITE_IN_PROGRESS, &target->status);
+ event = I2C_SLAVE_READ_REQUESTED;
+ }
+
+ i2c_slave_event(target->slave, event, &val);
+ dev_dbg(target->dev, "Data to TX FIFO: 0x%x\n", val);
+ writel(val, target->base + I2C_S_TX_FIFO);
+ writel(ACK_RESUME, target->base + I2C_S_CONTROL);
+ writel(BIT(STRCH_RD), target->base + I2C_S_IRQ_CLR);
+}
+
+static irqreturn_t qcom_i2c_target_irq(int irq, void *dev)
+{
+ struct qcom_i2c_target *target = dev;
+ u32 irq_stat, rx_bits;
+
+ /*
+ * Dispatch priority (highest first):
+ * ERR_CONDITION / CLOCK_LOW_TIMEOUT — hardware error, triggers SW reset
+ * STOP_DETECTED — end of transaction, clears all state
+ * RESTART_DETECTED — repeated start, resets state before
+ * any data phase in the same snapshot
+ * STRCH_RD — read-phase data supply
+ * RX_FIFO_FULL / RX_DATA_AVAIL /
+ * STRCH_WR — write-phase Rx, coalesced into one drain
+ */
+ irq_stat = readl_relaxed(target->base + I2C_S_IRQ_STATUS);
+ if (!irq_stat)
+ return IRQ_NONE;
+
+ dev_dbg(target->dev, "IRQ status: 0x%x\n", irq_stat);
+
+ /*
+ * Load target->slave once. Both reg_slave() and unreg_slave() disable
+ * the IRQ before writing the pointer, so it cannot change while this
+ * handler runs. Sub-handlers may dereference target->slave directly.
+ *
+ * The core is enabled only in reg_slave() and disabled in unreg_slave(),
+ * so no bus activity is expected here. Clear and discard any stale IRQ.
+ */
+ if (!READ_ONCE(target->slave)) {
+ writel(irq_stat, target->base + I2C_S_IRQ_CLR);
+ return IRQ_HANDLED;
+ }
+
+ if (irq_stat & (BIT(ERR_CONDITION) | BIT(CLOCK_LOW_TIMEOUT)))
+ return qcom_i2c_target_handle_error(target, irq_stat);
+
+ if (irq_stat & BIT(STOP_DETECTED))
+ return qcom_i2c_target_handle_stop(target, irq_stat);
+
+ if (irq_stat & BIT(RESTART_DETECTED)) {
+ dev_dbg(target->dev, "Repeated start bit detected\n");
+ target->status = 0;
+ /*
+ * Keep clock stretch asserted when STRCH_RD is co-asserted so
+ * qcom_i2c_target_handle_strch_rd() can fill TX FIFO first.
+ */
+ if (!(irq_stat & BIT(STRCH_RD)))
+ writel(ACK_RESUME, target->base + I2C_S_CONTROL);
+ writel(BIT(RESTART_DETECTED), target->base + I2C_S_IRQ_CLR);
+ }
+
+ if (irq_stat & BIT(STRCH_RD))
+ qcom_i2c_target_handle_strch_rd(target);
+
+ /*
+ * Coalesce all write-phase Rx bits into a single drain+ACK. When the
+ * RX FIFO fills at threshold, RX_FIFO_FULL, RX_DATA_AVAIL and STRCH_WR
+ * can all assert in the same irq_stat snapshot. Pass the combined mask
+ * so ACK_RESUME is written exactly once and all bits are cleared together.
+ */
+ rx_bits = irq_stat & (BIT(RX_FIFO_FULL) | BIT(RX_DATA_AVAIL) |
+ BIT(STRCH_WR));
+ if (rx_bits)
+ qcom_i2c_target_handle_rx_data(target, rx_bits);
+
+ return IRQ_HANDLED;
+}
+
+static int qcom_i2c_target_reg_slave(struct i2c_client *slave)
+{
+ struct qcom_i2c_target *target = i2c_get_adapdata(slave->adapter);
+
+ if (target->slave)
+ return -EBUSY;
+
+ if (slave->flags & I2C_CLIENT_TEN)
+ return -EAFNOSUPPORT;
+
+ disable_irq(target->irq);
+ WRITE_ONCE(target->slave, slave);
+ writel(slave->addr, target->base + I2C_S_DEVICE_ADDR);
+ writel(I2C_S_CORE_EN, target->base + I2C_S_CONFIG);
+ enable_irq(target->irq);
+
+ return 0;
+}
+
+static int qcom_i2c_target_unreg_slave(struct i2c_client *slave)
+{
+ struct qcom_i2c_target *target = i2c_get_adapdata(slave->adapter);
+
+ if (!target->slave)
+ return -EINVAL;
+
+ disable_irq(target->irq);
+ writel(0, target->base + I2C_S_CONFIG);
+ WRITE_ONCE(target->slave, NULL);
+ target->status = 0;
+ writel(0, target->base + I2C_S_DEVICE_ADDR);
+ enable_irq(target->irq);
+
+ return 0;
+}
+
+static int qcom_i2c_target_icc_init(struct qcom_i2c_target *target)
+{
+ int ret;
+
+ target->icc_path = devm_of_icc_get(target->dev, NULL);
+ if (IS_ERR(target->icc_path))
+ return dev_err_probe(target->dev, PTR_ERR(target->icc_path),
+ "failed to get ICC path\n");
+
+ /*
+ * The controller only needs interconnect bandwidth while it is
+ * powered. The vote is placed here and across resume with
+ * icc_set_bw(), and dropped with icc_set_bw(path, 0, 0) on suspend
+ * and remove, so the vote and unvote are always symmetric.
+ */
+ ret = icc_set_bw(target->icc_path, APPS_PROC_TO_I2C_TARGET_VOTE,
+ APPS_PROC_TO_I2C_TARGET_VOTE);
+ if (ret)
+ return dev_err_probe(target->dev, ret, "icc_set_bw failed\n");
+
+ return 0;
+}
+
+static u32 qcom_i2c_target_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_SLAVE;
+}
+
+static const struct i2c_algorithm qcom_i2c_target_algo = {
+ .reg_target = qcom_i2c_target_reg_slave,
+ .unreg_target = qcom_i2c_target_unreg_slave,
+ .functionality = qcom_i2c_target_func,
+};
+
+static int qcom_i2c_target_adap_init(struct qcom_i2c_target *target)
+{
+ target->adap.algo = &qcom_i2c_target_algo;
+ target->adap.dev.parent = target->dev;
+ target->adap.dev.of_node = target->dev->of_node;
+ strscpy(target->adap.name, "qcom-i2c-target",
+ sizeof(target->adap.name));
+ i2c_set_adapdata(&target->adap, target);
+
+ return i2c_add_adapter(&target->adap);
+}
+
+static int qcom_i2c_target_probe(struct platform_device *pdev)
+{
+ struct qcom_i2c_target *target;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ target = devm_kzalloc(dev, sizeof(*target), GFP_KERNEL);
+ if (!target)
+ return -ENOMEM;
+
+ target->dev = dev;
+
+ target->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(target->base))
+ return dev_err_probe(dev, PTR_ERR(target->base),
+ "failed to map registers\n");
+
+ target->xo_clk = devm_clk_get(dev, "xo");
+ if (IS_ERR(target->xo_clk))
+ return dev_err_probe(dev, PTR_ERR(target->xo_clk),
+ "failed to get XO clock\n");
+
+ target->ahb_clk = devm_clk_get(dev, "ahb");
+ if (IS_ERR(target->ahb_clk))
+ return dev_err_probe(dev, PTR_ERR(target->ahb_clk),
+ "failed to get AHB clock\n");
+
+ ret = clk_prepare_enable(target->xo_clk);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable XO clock\n");
+
+ ret = clk_prepare_enable(target->ahb_clk);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to enable AHB clock\n");
+ goto err_disable_xo;
+ }
+
+ target->irq = platform_get_irq(pdev, 0);
+ if (target->irq < 0) {
+ ret = target->irq;
+ goto err_disable_ahb;
+ }
+
+ ret = qcom_i2c_target_icc_init(target);
+ if (ret)
+ goto err_disable_ahb;
+
+ ret = devm_request_irq(dev, target->irq, qcom_i2c_target_irq, 0,
+ dev_name(dev), target);
+ if (ret) {
+ dev_err_probe(dev, ret, "request_irq failed for IRQ %d\n",
+ target->irq);
+ goto err_disable_icc;
+ }
+
+ qcom_i2c_target_hw_init(target);
+
+ platform_set_drvdata(pdev, target);
+
+ ret = qcom_i2c_target_adap_init(target);
+ if (ret) {
+ dev_err_probe(dev, ret, "i2c_add_adapter failed\n");
+ goto err_free_irq;
+ }
+
+ return 0;
+
+err_free_irq:
+ devm_free_irq(dev, target->irq, target);
+err_disable_icc:
+ icc_set_bw(target->icc_path, 0, 0);
+err_disable_ahb:
+ clk_disable_unprepare(target->ahb_clk);
+err_disable_xo:
+ clk_disable_unprepare(target->xo_clk);
+
+ return ret;
+}
+
+static void qcom_i2c_target_remove(struct platform_device *pdev)
+{
+ struct qcom_i2c_target *target = platform_get_drvdata(pdev);
+
+ writel(0, target->base + I2C_S_CONFIG);
+ i2c_del_adapter(&target->adap);
+ devm_free_irq(&pdev->dev, target->irq, target);
+ icc_set_bw(target->icc_path, 0, 0);
+ clk_disable_unprepare(target->xo_clk);
+ clk_disable_unprepare(target->ahb_clk);
+}
+
+static int qcom_i2c_target_suspend(struct device *dev)
+{
+ struct qcom_i2c_target *target = dev_get_drvdata(dev);
+ int ret;
+
+ disable_irq(target->irq);
+ writel(0, target->base + I2C_S_IRQ_EN);
+ writel(0, target->base + I2C_S_CONFIG);
+
+ ret = icc_set_bw(target->icc_path, 0, 0);
+ if (ret)
+ dev_err(dev, "icc_set_bw failed on suspend: %d\n", ret);
+
+ clk_disable_unprepare(target->xo_clk);
+ clk_disable_unprepare(target->ahb_clk);
+
+ return 0;
+}
+
+static int qcom_i2c_target_resume(struct device *dev)
+{
+ struct qcom_i2c_target *target = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(target->ahb_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable AHB clock\n");
+ enable_irq(target->irq);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(target->xo_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable XO clock\n");
+ goto err_disable_ahb;
+ }
+
+ ret = icc_set_bw(target->icc_path, APPS_PROC_TO_I2C_TARGET_VOTE,
+ APPS_PROC_TO_I2C_TARGET_VOTE);
+ if (ret) {
+ dev_err(dev, "icc_set_bw failed\n");
+ goto err_disable_xo;
+ }
+
+ qcom_i2c_target_hw_init(target);
+ target->status = 0;
+ if (target->slave) {
+ writel(target->slave->addr, target->base + I2C_S_DEVICE_ADDR);
+ writel(I2C_S_CORE_EN, target->base + I2C_S_CONFIG);
+ }
+
+ enable_irq(target->irq);
+
+ return 0;
+
+err_disable_xo:
+ clk_disable_unprepare(target->xo_clk);
+err_disable_ahb:
+ clk_disable_unprepare(target->ahb_clk);
+ enable_irq(target->irq);
+
+ return ret;
+}
+
+static const struct dev_pm_ops qcom_i2c_target_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(qcom_i2c_target_suspend,
+ qcom_i2c_target_resume)
+};
+
+static const struct of_device_id qcom_i2c_target_dt_match[] = {
+ { .compatible = "qcom,qdu1000-i2c-target" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, qcom_i2c_target_dt_match);
+
+static struct platform_driver qcom_i2c_target_driver = {
+ .driver = {
+ .name = "qcom-i2c-target",
+ .pm = &qcom_i2c_target_pm_ops,
+ .of_match_table = qcom_i2c_target_dt_match,
+ },
+ .probe = qcom_i2c_target_probe,
+ .remove = qcom_i2c_target_remove,
+};
+module_platform_driver(qcom_i2c_target_driver);
+
+MODULE_DESCRIPTION("Qualcomm I2C target controller driver");
+MODULE_AUTHOR("Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v4 1/2] dt-bindings: i2c: Add Qualcomm I2C target controller
2026-09-20 12:04 ` [PATCH v4 1/2] dt-bindings: i2c: Add Qualcomm I2C target controller Viken Dadhaniya
@ 2026-09-20 12:12 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-09-20 12:12 UTC (permalink / raw)
To: Viken Dadhaniya; +Cc: devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] dt-bindings: i2c: qcom,qdu1000-i2c-target lacks i2c-controller schema ref
--
commit 04d5fcf83b820d8d116604eccea697fd3f7a8e35
Author: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
dt-bindings: i2c: Add Qualcomm I2C target controller
This commit adds a new devicetree binding for the dedicated Qualcomm I2C
target controller used in QDU1000 and related SoCs.
> diff --git a/Documentation/devicetree/bindings/i2c/qcom,qdu1000-i2c-target.yaml b/Documentation/devicetree/bindings/i2c/qcom,qdu1000-i2c-target.yaml
> new file mode 100644
> index 0000000000000..9a6b08f0ab0e7
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/qcom,qdu1000-i2c-target.yaml
[ ... ]
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - clocks
> + - clock-names
> + - interconnects
> +
> +additionalProperties: false
[Severity: Medium]
Does this strict additionalProperties rule block the addition of valid I2C
target backend child nodes?
In the Linux I2C subsystem, target (slave) backends like EEPROM emulators or
MCTP endpoints are described as child nodes of the I2C controller. The driver
calls i2c_add_adapter() with the controller's of_node, which naturally
parses these child nodes.
Because this binding omits a reference to /schemas/i2c/i2c-controller.yaml#,
it does not define #address-cells or #size-cells. When a devicetree developer
attempts to add a child node with a reg property for the slave address, schema
validation will reject it.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260920-i2c-qcom-slave-v4-0-f7e1020f3bb4@oss.qualcomm.com?part=1
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v4 2/2] i2c: qcom-target: Add driver for Qualcomm I2C target controller
2026-09-20 12:04 ` [PATCH v4 2/2] i2c: qcom-target: Add driver for " Viken Dadhaniya
@ 2026-09-20 12:17 ` sashiko-bot
2026-09-29 6:51 ` Andi Shyti
1 sibling, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-09-20 12:17 UTC (permalink / raw)
To: Viken Dadhaniya; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] i2c: missing adapter owner initialization allows use-after-free
- [High] i2c: incorrect IRQ dispatch order corrupts state on Repeated Start
- [High] i2c: inconsistent clock teardown and resume ordering causes bus hangs
- [High] i2c: fatal unclocked MMIO access hazard in resume error paths
- [Low] i2c: unused PM sleep callbacks trigger warnings without CONFIG_PM_SLEEP
--
commit c5134b7c0f90bc099584b542a4a86588eb8e6053
Author: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
i2c: qcom-target: Add driver for Qualcomm I2C target controller
This commit introduces a new driver for the Qualcomm I2C target controller
found on QDU1000 and related SoCs. It registers with the Linux I2C slave
framework to allow standard slave backends to attach.
> diff --git a/drivers/i2c/busses/i2c-qcom-target.c b/drivers/i2c/busses/i2c-qcom-target.c
> index 0000000000000..5cf4bad1b601a
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-qcom-target.c
[ ... ]
> +static irqreturn_t qcom_i2c_target_irq(int irq, void *dev)
> +{
> + struct qcom_i2c_target *target = dev;
> + u32 irq_stat, rx_bits;
[ ... ]
> + if (irq_stat & BIT(RESTART_DETECTED)) {
> + dev_dbg(target->dev, "Repeated start bit detected\n");
> + target->status = 0;
> + /*
> + * Keep clock stretch asserted when STRCH_RD is co-asserted so
> + * qcom_i2c_target_handle_strch_rd() can fill TX FIFO first.
> + */
> + if (!(irq_stat & BIT(STRCH_RD)))
> + writel(ACK_RESUME, target->base + I2C_S_CONTROL);
> + writel(BIT(RESTART_DETECTED), target->base + I2C_S_IRQ_CLR);
> + }
> +
> + if (irq_stat & BIT(STRCH_RD))
> + qcom_i2c_target_handle_strch_rd(target);
> +
> + /*
> + * Coalesce all write-phase Rx bits into a single drain+ACK. When the
> + * RX FIFO fills at threshold, RX_FIFO_FULL, RX_DATA_AVAIL and STRCH_WR
> + * can all assert in the same irq_stat snapshot. Pass the combined mask
> + * so ACK_RESUME is written exactly once and all bits are cleared together.
> + */
> + rx_bits = irq_stat & (BIT(RX_FIFO_FULL) | BIT(RX_DATA_AVAIL) |
> + BIT(STRCH_WR));
> + if (rx_bits)
> + qcom_i2c_target_handle_rx_data(target, rx_bits);
[Severity: High]
Is there a state corruption issue here if a Repeated Start occurs immediately
following a write phase?
If the hardware asserts both RESTART_DETECTED and RX_DATA_AVAIL in the same
interrupt, target->status is cleared to 0 first, which wipes the
WRITE_IN_PROGRESS flag. When qcom_i2c_target_handle_rx_data() is called right
after, could this cause the backend to receive an erroneous
I2C_SLAVE_WRITE_REQUESTED event and interpret the trailing data from the
previous write as the start of a brand new transaction?
[ ... ]
> +static int qcom_i2c_target_adap_init(struct qcom_i2c_target *target)
> +{
> + target->adap.algo = &qcom_i2c_target_algo;
> + target->adap.dev.parent = target->dev;
> + target->adap.dev.of_node = target->dev->of_node;
> + strscpy(target->adap.name, "qcom-i2c-target",
> + sizeof(target->adap.name));
> + i2c_set_adapdata(&target->adap, target);
> +
> + return i2c_add_adapter(&target->adap);
> +}
[Severity: High]
Is the adapter missing the .owner initialization?
Without setting target->adap.owner = THIS_MODULE, the I2C core won't be able
to properly manage module refcounts. Can this lead to a use-after-free if the
module is removed while an unprivileged userspace process holds the character
device open?
[ ... ]
> +static void qcom_i2c_target_remove(struct platform_device *pdev)
> +{
> + struct qcom_i2c_target *target = platform_get_drvdata(pdev);
> +
> + writel(0, target->base + I2C_S_CONFIG);
> + i2c_del_adapter(&target->adap);
> + devm_free_irq(&pdev->dev, target->irq, target);
> + icc_set_bw(target->icc_path, 0, 0);
> + clk_disable_unprepare(target->xo_clk);
> + clk_disable_unprepare(target->ahb_clk);
> +}
[Severity: High]
Does this code tear down the clocks in the correct order?
In qcom_i2c_target_probe(), xo_clk is enabled before ahb_clk. Here in remove
(and similarly in suspend below), xo_clk is disabled before ahb_clk. Does
this sequence risk a bus lockup or controller hang?
> +static int qcom_i2c_target_suspend(struct device *dev)
> +{
> + struct qcom_i2c_target *target = dev_get_drvdata(dev);
> + int ret;
> +
> + disable_irq(target->irq);
> + writel(0, target->base + I2C_S_IRQ_EN);
> + writel(0, target->base + I2C_S_CONFIG);
> +
> + ret = icc_set_bw(target->icc_path, 0, 0);
> + if (ret)
> + dev_err(dev, "icc_set_bw failed on suspend: %d\n", ret);
> +
> + clk_disable_unprepare(target->xo_clk);
> + clk_disable_unprepare(target->ahb_clk);
> +
> + return 0;
> +}
[Severity: Low]
Could these PM callbacks cause unused function warnings?
When CONFIG_PM_SLEEP is disabled, SET_SYSTEM_SLEEP_PM_OPS becomes empty,
rendering qcom_i2c_target_suspend and qcom_i2c_target_resume unreferenced in
the source. This might trigger compiler warnings that break builds using
CONFIG_WERROR.
> +static int qcom_i2c_target_resume(struct device *dev)
> +{
> + struct qcom_i2c_target *target = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = clk_prepare_enable(target->ahb_clk);
> + if (ret) {
> + dev_err(dev, "failed to enable AHB clock\n");
> + enable_irq(target->irq);
> + return ret;
> + }
[Severity: High]
There appear to be two issues in this error path.
First, does resume enable the clocks in the opposite order compared to the
probe sequence? It enables ahb_clk before xo_clk, which might contradict
standard hardware initialization sequences.
Second, does enable_irq() unmask the interrupt while the bus clocks are
disabled? If the IRQ line is asserted, the CPU will jump to
qcom_i2c_target_irq(), which immediately executes a readl_relaxed() on
I2C_S_IRQ_STATUS. Could this cause a fatal bus error (synchronous external
abort) due to an unclocked MMIO access?
[ ... ]
> +err_disable_ahb:
> + clk_disable_unprepare(target->ahb_clk);
> + enable_irq(target->irq);
> +
> + return ret;
> +}
[Severity: High]
Similar to the observation above, does enabling the IRQ here while clocks are
completely disabled risk an unclocked MMIO access and a resulting kernel panic
if the interrupt fires?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260920-i2c-qcom-slave-v4-0-f7e1020f3bb4@oss.qualcomm.com?part=2
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v4 2/2] i2c: qcom-target: Add driver for Qualcomm I2C target controller
2026-09-20 12:04 ` [PATCH v4 2/2] i2c: qcom-target: Add driver for " Viken Dadhaniya
2026-09-20 12:17 ` sashiko-bot
@ 2026-09-29 6:51 ` Andi Shyti
1 sibling, 0 replies; 6+ messages in thread
From: Andi Shyti @ 2026-09-29 6:51 UTC (permalink / raw)
To: Viken Dadhaniya
Cc: Mukesh Kumar Savaliya, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, linux-arm-msm, linux-i2c, devicetree, linux-kernel
Hi Viken,
...
> +static void qcom_i2c_target_write_requested(struct qcom_i2c_target *target)
> +{
> + u8 val = 0;
> +
> + if (!test_and_set_bit(WRITE_IN_PROGRESS, &target->status)) {
> + dev_dbg(target->dev, "Write phase started\n");
> + i2c_slave_event(target->slave, I2C_SLAVE_WRITE_REQUESTED, &val);
It's the second time in two days that I'm reviewing this:
slave_event can fail and when it fails we need to nack the next
write requests (check slave-interface.rst).
> + }
> +}
...
> +static int qcom_i2c_target_reg_slave(struct i2c_client *slave)
Please, use the inclusive name:
master -> adapter
slave -> target
> +{
> + struct qcom_i2c_target *target = i2c_get_adapdata(slave->adapter);
> +
> + if (target->slave)
> + return -EBUSY;
...
> +static int qcom_i2c_target_icc_init(struct qcom_i2c_target *target)
> +{
> + int ret;
> +
> + target->icc_path = devm_of_icc_get(target->dev, NULL);
> + if (IS_ERR(target->icc_path))
> + return dev_err_probe(target->dev, PTR_ERR(target->icc_path),
> + "failed to get ICC path\n");
> +
> + /*
> + * The controller only needs interconnect bandwidth while it is
> + * powered. The vote is placed here and across resume with
> + * icc_set_bw(), and dropped with icc_set_bw(path, 0, 0) on suspend
> + * and remove, so the vote and unvote are always symmetric.
> + */
> + ret = icc_set_bw(target->icc_path, APPS_PROC_TO_I2C_TARGET_VOTE,
> + APPS_PROC_TO_I2C_TARGET_VOTE);
TARGET_VOTE is in bytes per second, while icc_set_bw expects
kilobytes per second.
Thanks,
Andi
> + if (ret)
> + return dev_err_probe(target->dev, ret, "icc_set_bw failed\n");
> +
> + return 0;
> +}
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-29 6:51 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-20 12:04 [PATCH v4 0/2] Add Qualcomm I2C target controller driver Viken Dadhaniya
2026-09-20 12:04 ` [PATCH v4 1/2] dt-bindings: i2c: Add Qualcomm I2C target controller Viken Dadhaniya
2026-09-20 12:12 ` sashiko-bot
2026-09-20 12:04 ` [PATCH v4 2/2] i2c: qcom-target: Add driver for " Viken Dadhaniya
2026-09-20 12:17 ` sashiko-bot
2026-09-29 6:51 ` Andi Shyti
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox