* [PATCH v4 0/2] i2c: Add Cortina Systems CS75xx I2C controller driver
@ 2026-11-15 16:55 Fil Dunsky via B4 Relay
2026-11-15 16:55 ` [PATCH v4 1/2] dt-bindings: i2c: Add Cortina Systems CS7542 I2C controller Fil Dunsky via B4 Relay
2026-11-15 16:55 ` [PATCH v4 2/2] i2c: Add Cortina Systems CS75xx I2C controller driver Fil Dunsky via B4 Relay
0 siblings, 2 replies; 4+ messages in thread
From: Fil Dunsky via B4 Relay @ 2026-11-15 16:55 UTC (permalink / raw)
To: Andi Shyti, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-i2c, devicetree, linux-kernel, linux-arm-kernel, Fil Dunsky
The I2C ("Bi-Wire") controller of the Cortina Systems CS75xx
("Goldengate G2") SoCs transfers one byte per command, with a command
register much like the OpenCores one, and raises an interrupt when a
command has completed. This series adds the binding and a driver for
plain I2C and emulated SMBus, without quick commands (every message
carries at least one data byte) and without 10-bit addresses.
The driver used to be part of a series with the interrupt controller,
GPIO and watchdog drivers and the device tree changes. That series is
now split per subsystem: the driver builds with COMPILE_TEST and does
not depend on the platform series [1], which is under review separately.
The device tree nodes will follow with the other CS75xx dts changes once
the platform is merged.
There is no public documentation; the register layout comes from the GPL
vendor kernels. Tested on a Securifi Almond+ (CS7542) with the SX8650
touchscreen controller at 0x48: reads are ACKed, an empty address returns
-ENXIO without timeouts. Repeated START between messages has not been
exercised on the board.
[1] https://lore.kernel.org/all/20261114-b4-cs75xx-v7-0-8f17f38821b4@gmail.com/
---
Changes in v4:
- rebase on v7.4-rc1
- split out of "Cortina Systems CS75xx: interrupt controller, GPIO, I2C
and TWD watchdog" so that each driver can go through its own tree; no
longer based on the platform series
- binding: limit clock-frequency to 400 kHz instead of listing 100 and
400 kHz, which is what the driver accepts; I kept Rob's Reviewed-by
from v3 for this one-line change
- collect Reviewed-by tags
- use fsleep() and device_set_node(); sort the Kconfig and Makefile
entries
- Link to v3: https://patch.msgid.link/20261013-b4-cs75xx-s2-v3-0-181bb0d23f4c@gmail.com
Changes in v3:
- rebase on v6 of the platform series
- reset the controller with its interrupt disabled, check the prescaler
against the field width exactly, wait for a bus held by another master
up to the adapter timeout (sashiko-bot)
- Link to v2: https://patch.msgid.link/20261013-b4-cs75xx-s2-v2-0-94f8c0a90cda@gmail.com
Changes in v2:
- rebase on v5 of the platform series
- do not include <linux/mod_devicetable.h> (Uwe)
- complete a failed transfer only once its stop condition is done, so
that the stop interrupt cannot race with the next transfer; take the
lock over the whole interrupt handler; report a NACK of the last byte
of a write; enable the interrupt again after a reset on timeout; fail
with -EBUSY when the bus stays busy (sashiko-bot)
- Link to v1: https://patch.msgid.link/20261012-b4-cs75xx-s2-v1-0-8a7641b7b79c@gmail.com
To: Andi Shyti <andi.shyti@kernel.org>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
Cc: linux-i2c@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
---
Fil Dunsky (2):
dt-bindings: i2c: Add Cortina Systems CS7542 I2C controller
i2c: Add Cortina Systems CS75xx I2C controller driver
.../bindings/i2c/cortina,cs7542-i2c.yaml | 56 +++
drivers/i2c/busses/Kconfig | 11 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-cs75xx.c | 392 +++++++++++++++++++++
4 files changed, 460 insertions(+)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261015-cs75xx-i2c-944d27231b4c
Best regards,
--
Fil Dunsky <filipp.dunsky@gmail.com>
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v4 1/2] dt-bindings: i2c: Add Cortina Systems CS7542 I2C controller
2026-11-15 16:55 [PATCH v4 0/2] i2c: Add Cortina Systems CS75xx I2C controller driver Fil Dunsky via B4 Relay
@ 2026-11-15 16:55 ` Fil Dunsky via B4 Relay
2026-11-15 16:55 ` [PATCH v4 2/2] i2c: Add Cortina Systems CS75xx I2C controller driver Fil Dunsky via B4 Relay
1 sibling, 0 replies; 4+ messages in thread
From: Fil Dunsky via B4 Relay @ 2026-11-15 16:55 UTC (permalink / raw)
To: Andi Shyti, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-i2c, devicetree, linux-kernel, linux-arm-kernel, Fil Dunsky
From: Fil Dunsky <filipp.dunsky@gmail.com>
Add a binding for the I2C ("Bi-Wire") controller of the Cortina Systems
CS75xx SoCs.
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Fil Dunsky <filipp.dunsky@gmail.com>
---
.../bindings/i2c/cortina,cs7542-i2c.yaml | 56 ++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/Documentation/devicetree/bindings/i2c/cortina,cs7542-i2c.yaml b/Documentation/devicetree/bindings/i2c/cortina,cs7542-i2c.yaml
new file mode 100644
index 000000000..095e24379
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/cortina,cs7542-i2c.yaml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/i2c/cortina,cs7542-i2c.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Cortina Systems CS7542 I2C controller
+
+maintainers:
+ - Fil Dunsky <filipp.dunsky@gmail.com>
+
+description:
+ The I2C ("Bi-Wire") controller of the Cortina Systems CS75xx (Goldengate
+ G2) SoCs. It transfers one byte per command and runs from the peripheral
+ (APB) clock through a 16-bit prescaler.
+
+allOf:
+ - $ref: /schemas/i2c/i2c-controller.yaml#
+
+properties:
+ compatible:
+ const: cortina,cs7542-i2c
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-frequency:
+ maximum: 400000
+ default: 100000
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i2c@f00700e8 {
+ compatible = "cortina,cs7542-i2c";
+ reg = <0xf00700e8 0x28>;
+ interrupt-parent = <&intc>;
+ interrupts = <11>;
+ clocks = <&apb_clk>;
+ clock-frequency = <400000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH v4 2/2] i2c: Add Cortina Systems CS75xx I2C controller driver
2026-11-15 16:55 [PATCH v4 0/2] i2c: Add Cortina Systems CS75xx I2C controller driver Fil Dunsky via B4 Relay
2026-11-15 16:55 ` [PATCH v4 1/2] dt-bindings: i2c: Add Cortina Systems CS7542 I2C controller Fil Dunsky via B4 Relay
@ 2026-11-15 16:55 ` Fil Dunsky via B4 Relay
2026-11-15 17:13 ` sashiko-bot
1 sibling, 1 reply; 4+ messages in thread
From: Fil Dunsky via B4 Relay @ 2026-11-15 16:55 UTC (permalink / raw)
To: Andi Shyti, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-i2c, devicetree, linux-kernel, linux-arm-kernel, Fil Dunsky
From: Fil Dunsky <filipp.dunsky@gmail.com>
The I2C ("Bi-Wire") controller of the Cortina Systems CS75xx SoCs
transfers one byte per command, with a command register much like the
OpenCores one (start, stop, read, write, acknowledge), and raises an
interrupt when a command has completed.
Messages are sent with a repeated start between them and a stop after
the last one. Every message needs at least one data byte, so SMBus quick
commands are not supported. Arbitration loss is reported as -EAGAIN and
retried by the core.
Signed-off-by: Fil Dunsky <filipp.dunsky@gmail.com>
---
drivers/i2c/busses/Kconfig | 11 ++
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-cs75xx.c | 392 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 404 insertions(+)
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 275aaa7f3..db1b218a6 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -550,6 +550,17 @@ config I2C_CPM
This driver can also be built as a module. If so, the module
will be called i2c-cpm.
+config I2C_CS75XX
+ tristate "Cortina Systems CS75xx I2C controller"
+ depends on ARCH_CORTINA_ACCESS || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ Say yes here to support the I2C ("Bi-Wire") controller of the
+ Cortina Systems CS75xx SoCs.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-cs75xx.
+
config I2C_DAVINCI
tristate "DaVinci I2C driver"
depends on ARCH_DAVINCI || ARCH_KEYSTONE || COMPILE_TEST
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index e5440de0f..f19df25eb 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_I2C_CADENCE) += i2c-cadence.o
obj-$(CONFIG_I2C_CBUS_GPIO) += i2c-cbus-gpio.o
obj-$(CONFIG_I2C_CGBC) += i2c-cgbc.o
obj-$(CONFIG_I2C_CPM) += i2c-cpm.o
+obj-$(CONFIG_I2C_CS75XX) += i2c-cs75xx.o
obj-$(CONFIG_I2C_DAVINCI) += i2c-davinci.o
obj-$(CONFIG_I2C_DESIGNWARE_CORE) += i2c-designware-core.o
i2c-designware-core-y := i2c-designware-common.o
diff --git a/drivers/i2c/busses/i2c-cs75xx.c b/drivers/i2c/busses/i2c-cs75xx.c
new file mode 100644
index 000000000..a7aec20ea
--- /dev/null
+++ b/drivers/i2c/busses/i2c-cs75xx.c
@@ -0,0 +1,392 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Cortina Systems CS75xx I2C ("Bi-Wire") controller
+ *
+ * The controller works one byte at a time: software writes the byte to
+ * transmit and a command (start, stop, read, write, acknowledge) and gets
+ * an interrupt when the command has completed.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/spinlock.h>
+
+#define CS75XX_I2C_CFG 0x00
+#define CS75XX_I2C_CTRL 0x04
+#define CS75XX_I2C_TXR 0x08
+#define CS75XX_I2C_RXR 0x0c
+#define CS75XX_I2C_ACK 0x10
+#define CS75XX_I2C_IE 0x14
+#define CS75XX_I2C_INT 0x18
+
+#define CS75XX_I2C_CFG_EN BIT(0)
+#define CS75XX_I2C_CFG_PRER GENMASK(31, 16)
+
+#define CS75XX_I2C_CTRL_DONE BIT(0)
+#define CS75XX_I2C_CTRL_NACK BIT(3)
+#define CS75XX_I2C_CTRL_WRITE BIT(4)
+#define CS75XX_I2C_CTRL_READ BIT(5)
+#define CS75XX_I2C_CTRL_STOP BIT(6)
+#define CS75XX_I2C_CTRL_START BIT(7)
+
+#define CS75XX_I2C_ACK_AL BIT(0)
+#define CS75XX_I2C_ACK_BUSY BIT(1)
+#define CS75XX_I2C_ACK_NACK BIT(2)
+
+#define CS75XX_I2C_INT_DONE BIT(0)
+
+enum cs75xx_i2c_state {
+ CS75XX_I2C_IDLE,
+ CS75XX_I2C_ADDR,
+ CS75XX_I2C_WRITE,
+ CS75XX_I2C_READ,
+ CS75XX_I2C_STOP,
+};
+
+struct cs75xx_i2c {
+ struct i2c_adapter adap;
+ struct device *dev;
+ void __iomem *base;
+ int irq;
+ u32 prer;
+
+ spinlock_t lock; /* protects the transfer state */
+ struct completion done;
+ enum cs75xx_i2c_state state;
+ struct i2c_msg *msg;
+ unsigned int pos;
+ bool last;
+ int err;
+};
+
+static void cs75xx_i2c_hw_init(struct cs75xx_i2c *i2c)
+{
+ if (readl(i2c->base + CS75XX_I2C_CFG) & CS75XX_I2C_CFG_EN) {
+ writel(0, i2c->base + CS75XX_I2C_CFG);
+ fsleep(5000);
+ }
+
+ writel(FIELD_PREP(CS75XX_I2C_CFG_PRER, i2c->prer) | CS75XX_I2C_CFG_EN,
+ i2c->base + CS75XX_I2C_CFG);
+ fsleep(5000);
+}
+
+/*
+ * Reset the controller and enable its interrupt again, with the handler
+ * kept away from the registers meanwhile
+ */
+static void cs75xx_i2c_reset(struct cs75xx_i2c *i2c)
+{
+ disable_irq(i2c->irq);
+ cs75xx_i2c_hw_init(i2c);
+ writel(CS75XX_I2C_INT_DONE, i2c->base + CS75XX_I2C_INT);
+ writel(CS75XX_I2C_INT_DONE, i2c->base + CS75XX_I2C_IE);
+ enable_irq(i2c->irq);
+}
+
+static void cs75xx_i2c_finish(struct cs75xx_i2c *i2c, int err)
+{
+ i2c->err = err;
+ i2c->state = CS75XX_I2C_IDLE;
+ complete(&i2c->done);
+}
+
+/*
+ * End the transfer with a stop condition and complete it only once the
+ * stop is done, so that its interrupt cannot be taken for the next one
+ */
+static void cs75xx_i2c_abort(struct cs75xx_i2c *i2c, int err)
+{
+ i2c->err = err;
+ i2c->state = CS75XX_I2C_STOP;
+ writel(CS75XX_I2C_CTRL_STOP, i2c->base + CS75XX_I2C_CTRL);
+}
+
+/* Start the transfer of the next data byte of the message */
+static void cs75xx_i2c_next_byte(struct cs75xx_i2c *i2c)
+{
+ struct i2c_msg *msg = i2c->msg;
+ bool last_byte = i2c->pos == msg->len - 1;
+ u32 cmd;
+
+ if (msg->flags & I2C_M_RD) {
+ i2c->state = CS75XX_I2C_READ;
+ cmd = CS75XX_I2C_CTRL_READ;
+ /* Do not acknowledge the last byte of a read */
+ if (last_byte)
+ cmd |= CS75XX_I2C_CTRL_NACK;
+ } else {
+ i2c->state = CS75XX_I2C_WRITE;
+ writel(msg->buf[i2c->pos++], i2c->base + CS75XX_I2C_TXR);
+ cmd = CS75XX_I2C_CTRL_WRITE;
+ }
+
+ if (last_byte && i2c->last)
+ cmd |= CS75XX_I2C_CTRL_STOP;
+
+ writel(cmd, i2c->base + CS75XX_I2C_CTRL);
+}
+
+static void cs75xx_i2c_process(struct cs75xx_i2c *i2c, u32 ack)
+{
+ struct i2c_msg *msg = i2c->msg;
+
+ if (i2c->state == CS75XX_I2C_STOP) {
+ cs75xx_i2c_finish(i2c, i2c->err);
+ return;
+ }
+
+ if (ack & CS75XX_I2C_ACK_AL) {
+ cs75xx_i2c_abort(i2c, -EAGAIN);
+ return;
+ }
+
+ switch (i2c->state) {
+ case CS75XX_I2C_ADDR:
+ if (ack & CS75XX_I2C_ACK_NACK) {
+ cs75xx_i2c_abort(i2c, -ENXIO);
+ return;
+ }
+ cs75xx_i2c_next_byte(i2c);
+ break;
+
+ case CS75XX_I2C_WRITE:
+ if (ack & CS75XX_I2C_ACK_NACK) {
+ /* The last byte of the last message carried the stop */
+ if (i2c->pos == msg->len && i2c->last)
+ cs75xx_i2c_finish(i2c, -EIO);
+ else
+ cs75xx_i2c_abort(i2c, -EIO);
+ return;
+ }
+ if (i2c->pos == msg->len) {
+ cs75xx_i2c_finish(i2c, 0);
+ return;
+ }
+ cs75xx_i2c_next_byte(i2c);
+ break;
+
+ case CS75XX_I2C_READ:
+ msg->buf[i2c->pos++] = readl(i2c->base + CS75XX_I2C_RXR);
+ if (i2c->pos == msg->len) {
+ cs75xx_i2c_finish(i2c, 0);
+ return;
+ }
+ cs75xx_i2c_next_byte(i2c);
+ break;
+
+ case CS75XX_I2C_IDLE:
+ case CS75XX_I2C_STOP:
+ /* Late interrupt of a transfer that timed out */
+ break;
+ }
+}
+
+static irqreturn_t cs75xx_i2c_isr(int irq, void *dev_id)
+{
+ struct cs75xx_i2c *i2c = dev_id;
+ u32 ctrl, ack;
+
+ guard(spinlock)(&i2c->lock);
+
+ if (!(readl(i2c->base + CS75XX_I2C_INT) & CS75XX_I2C_INT_DONE))
+ return IRQ_NONE;
+
+ writel(CS75XX_I2C_INT_DONE, i2c->base + CS75XX_I2C_INT);
+
+ /* The done flag is cleared by writing it back */
+ ctrl = readl(i2c->base + CS75XX_I2C_CTRL);
+ writel(ctrl, i2c->base + CS75XX_I2C_CTRL);
+
+ ack = readl(i2c->base + CS75XX_I2C_ACK);
+ cs75xx_i2c_process(i2c, ack);
+
+ return IRQ_HANDLED;
+}
+
+static int cs75xx_i2c_xfer_msg(struct cs75xx_i2c *i2c, struct i2c_msg *msg,
+ bool last)
+{
+ unsigned long time_left;
+
+ spin_lock_irq(&i2c->lock);
+ reinit_completion(&i2c->done);
+ i2c->msg = msg;
+ i2c->pos = 0;
+ i2c->last = last;
+ i2c->err = 0;
+ i2c->state = CS75XX_I2C_ADDR;
+ writel(i2c_8bit_addr_from_msg(msg), i2c->base + CS75XX_I2C_TXR);
+ writel(CS75XX_I2C_CTRL_START | CS75XX_I2C_CTRL_WRITE,
+ i2c->base + CS75XX_I2C_CTRL);
+ spin_unlock_irq(&i2c->lock);
+
+ time_left = wait_for_completion_timeout(&i2c->done, i2c->adap.timeout);
+ if (!time_left) {
+ spin_lock_irq(&i2c->lock);
+ i2c->state = CS75XX_I2C_IDLE;
+ spin_unlock_irq(&i2c->lock);
+ dev_err(i2c->dev, "transfer timed out, resetting the controller\n");
+ cs75xx_i2c_reset(i2c);
+ return -ETIMEDOUT;
+ }
+
+ return i2c->err;
+}
+
+static int cs75xx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+ int num)
+{
+ struct cs75xx_i2c *i2c = i2c_get_adapdata(adap);
+ u32 val;
+ int i, ret;
+
+ /* Another master may hold the bus for a whole transfer */
+ if (readl_poll_timeout(i2c->base + CS75XX_I2C_ACK, val,
+ !(val & CS75XX_I2C_ACK_BUSY), 100,
+ jiffies_to_usecs(adap->timeout))) {
+ cs75xx_i2c_reset(i2c);
+ if (readl(i2c->base + CS75XX_I2C_ACK) & CS75XX_I2C_ACK_BUSY)
+ return -EBUSY;
+ }
+
+ for (i = 0; i < num; i++) {
+ ret = cs75xx_i2c_xfer_msg(i2c, &msgs[i], i == num - 1);
+ if (ret)
+ return ret;
+ }
+
+ return num;
+}
+
+static u32 cs75xx_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+}
+
+static const struct i2c_algorithm cs75xx_i2c_algo = {
+ .xfer = cs75xx_i2c_xfer,
+ .functionality = cs75xx_i2c_func,
+};
+
+/* A message always carries at least one data byte after the address */
+static const struct i2c_adapter_quirks cs75xx_i2c_quirks = {
+ .flags = I2C_AQ_NO_ZERO_LEN,
+};
+
+static void cs75xx_i2c_disable(void *data)
+{
+ struct cs75xx_i2c *i2c = data;
+
+ writel(0, i2c->base + CS75XX_I2C_IE);
+ writel(0, i2c->base + CS75XX_I2C_CFG);
+}
+
+static int cs75xx_i2c_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cs75xx_i2c *i2c;
+ unsigned long ref_rate;
+ struct clk *clk;
+ u32 bus_rate;
+ u64 div;
+ int ret;
+
+ i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
+ if (!i2c)
+ return -ENOMEM;
+
+ i2c->dev = dev;
+ spin_lock_init(&i2c->lock);
+ init_completion(&i2c->done);
+
+ i2c->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(i2c->base))
+ return PTR_ERR(i2c->base);
+
+ i2c->irq = platform_get_irq(pdev, 0);
+ if (i2c->irq < 0)
+ return i2c->irq;
+
+ clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n");
+
+ ref_rate = clk_get_rate(clk);
+ if (device_property_read_u32(dev, "clock-frequency", &bus_rate))
+ bus_rate = I2C_MAX_STANDARD_MODE_FREQ;
+ if (!bus_rate || bus_rate > I2C_MAX_FAST_MODE_FREQ)
+ return dev_err_probe(dev, -EINVAL,
+ "unsupported bus frequency %u Hz\n",
+ bus_rate);
+
+ div = DIV_ROUND_UP_ULL(ref_rate, 5ULL * bus_rate);
+ if (!div || div - 1 > FIELD_MAX(CS75XX_I2C_CFG_PRER))
+ return dev_err_probe(dev, -EINVAL,
+ "cannot derive %u Hz from %lu Hz\n",
+ bus_rate, ref_rate);
+ i2c->prer = div - 1;
+
+ cs75xx_i2c_hw_init(i2c);
+
+ writel(0, i2c->base + CS75XX_I2C_IE);
+ writel(CS75XX_I2C_INT_DONE, i2c->base + CS75XX_I2C_INT);
+
+ ret = devm_add_action_or_reset(dev, cs75xx_i2c_disable, i2c);
+ if (ret)
+ return ret;
+
+ ret = devm_request_irq(dev, i2c->irq, cs75xx_i2c_isr, 0, dev_name(dev),
+ i2c);
+ if (ret)
+ return ret;
+
+ writel(CS75XX_I2C_INT_DONE, i2c->base + CS75XX_I2C_IE);
+
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &cs75xx_i2c_algo;
+ i2c->adap.quirks = &cs75xx_i2c_quirks;
+ i2c->adap.retries = 3;
+ i2c->adap.dev.parent = dev;
+ device_set_node(&i2c->adap.dev, dev_fwnode(dev));
+ strscpy(i2c->adap.name, dev_name(dev), sizeof(i2c->adap.name));
+ i2c_set_adapdata(&i2c->adap, i2c);
+
+ ret = devm_i2c_add_adapter(dev, &i2c->adap);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "bus %u Hz, reference %lu Hz\n", bus_rate,
+ ref_rate);
+
+ return 0;
+}
+
+static const struct of_device_id cs75xx_i2c_of_match[] = {
+ { .compatible = "cortina,cs7542-i2c" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, cs75xx_i2c_of_match);
+
+static struct platform_driver cs75xx_i2c_driver = {
+ .probe = cs75xx_i2c_probe,
+ .driver = {
+ .name = "cs75xx-i2c",
+ .of_match_table = cs75xx_i2c_of_match,
+ },
+};
+module_platform_driver(cs75xx_i2c_driver);
+
+MODULE_DESCRIPTION("Cortina Systems CS75xx I2C controller driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v4 2/2] i2c: Add Cortina Systems CS75xx I2C controller driver
2026-11-15 16:55 ` [PATCH v4 2/2] i2c: Add Cortina Systems CS75xx I2C controller driver Fil Dunsky via B4 Relay
@ 2026-11-15 17:13 ` sashiko-bot
0 siblings, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-11-15 17:13 UTC (permalink / raw)
To: Fil Dunsky; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [Medium] i2c: cs75xx: CS75XX_I2C_ACK_AL handled before CS75XX_I2C_IDLE
in cs75xx_i2c_process()
- [Medium] i2c: cs75xx: Kconfig depends on undefined symbol
ARCH_CORTINA_ACCESS
- [Low] i2c: cs75xx: unused CS75XX_I2C_CTRL_DONE macro in
cs75xx_i2c_isr()
--
commit a796a9d6a4def2db9ac47c7036d59a29ac7bf6d7
Author: Fil Dunsky <filipp.dunsky@gmail.com>
i2c: Add Cortina Systems CS75xx I2C controller driver
This commit adds an I2C controller driver for the Cortina Systems CS75xx
("Bi-Wire") controller.
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 275aaa7f35833..db1b218a66a23 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -550,6 +550,17 @@ config I2C_CPM
> This driver can also be built as a module. If so, the module
> will be called i2c-cpm.
>
> +config I2C_CS75XX
> + tristate "Cortina Systems CS75xx I2C controller"
> + depends on ARCH_CORTINA_ACCESS || COMPILE_TEST
[Severity: Medium]
Does ARCH_CORTINA_ACCESS exist in Kconfig? No Kconfig file in the tree
defines this symbol, so CONFIG_I2C_CS75XX cannot be enabled unless
CONFIG_COMPILE_TEST is set.
[ ... ]
> diff --git a/drivers/i2c/busses/i2c-cs75xx.c b/drivers/i2c/busses/i2c-cs75xx.c
> new file mode 100644
> index 0000000000000..a7aec20ea5cf1
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-cs75xx.c
[ ... ]
> +static void cs75xx_i2c_process(struct cs75xx_i2c *i2c, u32 ack)
> +{
> + struct i2c_msg *msg = i2c->msg;
> +
> + if (i2c->state == CS75XX_I2C_STOP) {
> + cs75xx_i2c_finish(i2c, i2c->err);
> + return;
> + }
> +
> + if (ack & CS75XX_I2C_ACK_AL) {
> + cs75xx_i2c_abort(i2c, -EAGAIN);
> + return;
> + }
> +
> + switch (i2c->state) {
> + case CS75XX_I2C_ADDR:
> + if (ack & CS75XX_I2C_ACK_NACK) {
> + cs75xx_i2c_abort(i2c, -ENXIO);
> + return;
> + }
> + cs75xx_i2c_next_byte(i2c);
> + break;
> +
> + case CS75XX_I2C_WRITE:
> + if (ack & CS75XX_I2C_ACK_NACK) {
> + /* The last byte of the last message carried the stop */
> + if (i2c->pos == msg->len && i2c->last)
> + cs75xx_i2c_finish(i2c, -EIO);
> + else
> + cs75xx_i2c_abort(i2c, -EIO);
> + return;
> + }
> + if (i2c->pos == msg->len) {
> + cs75xx_i2c_finish(i2c, 0);
> + return;
> + }
> + cs75xx_i2c_next_byte(i2c);
> + break;
> +
> + case CS75XX_I2C_READ:
> + msg->buf[i2c->pos++] = readl(i2c->base + CS75XX_I2C_RXR);
> + if (i2c->pos == msg->len) {
> + cs75xx_i2c_finish(i2c, 0);
> + return;
> + }
> + cs75xx_i2c_next_byte(i2c);
> + break;
> +
> + case CS75XX_I2C_IDLE:
> + case CS75XX_I2C_STOP:
> + /* Late interrupt of a transfer that timed out */
> + break;
> + }
> +}
[Severity: Medium]
Can a late interrupt with CS75XX_I2C_ACK_AL set trigger cs75xx_i2c_abort()
while i2c->state is CS75XX_I2C_IDLE?
When cs75xx_i2c_xfer_msg() times out, it sets i2c->state to CS75XX_I2C_IDLE
and drops i2c->lock before cs75xx_i2c_reset() calls disable_irq(). If a
late interrupt arrives in that window with CS75XX_I2C_ACK_AL set,
cs75xx_i2c_process() checks CS75XX_I2C_ACK_AL before checking for
CS75XX_I2C_IDLE, writes CS75XX_I2C_CTRL_STOP to the hardware and leaves
i2c->state in CS75XX_I2C_STOP across cs75xx_i2c_reset(). In addition, case
CS75XX_I2C_STOP in the switch statement is unreachable because
cs75xx_i2c_process() already returns early for CS75XX_I2C_STOP.
> +
> +static irqreturn_t cs75xx_i2c_isr(int irq, void *dev_id)
> +{
> + struct cs75xx_i2c *i2c = dev_id;
> + u32 ctrl, ack;
> +
> + guard(spinlock)(&i2c->lock);
> +
> + if (!(readl(i2c->base + CS75XX_I2C_INT) & CS75XX_I2C_INT_DONE))
> + return IRQ_NONE;
> +
> + writel(CS75XX_I2C_INT_DONE, i2c->base + CS75XX_I2C_INT);
> +
> + /* The done flag is cleared by writing it back */
> + ctrl = readl(i2c->base + CS75XX_I2C_CTRL);
> + writel(ctrl, i2c->base + CS75XX_I2C_CTRL);
[Severity: Low]
Should cs75xx_i2c_isr() write CS75XX_I2C_CTRL_DONE instead of writing back
the entire CS75XX_I2C_CTRL register? CS75XX_I2C_CTRL_DONE is defined at the
top of the file but never used, and writing back all of ctrl risks
re-asserting any non-self-clearing control bits in CS75XX_I2C_CTRL.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261115-cs75xx-i2c-v4-0-4b16eea98a78@gmail.com?part=2
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-15 17:13 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-15 16:55 [PATCH v4 0/2] i2c: Add Cortina Systems CS75xx I2C controller driver Fil Dunsky via B4 Relay
2026-11-15 16:55 ` [PATCH v4 1/2] dt-bindings: i2c: Add Cortina Systems CS7542 I2C controller Fil Dunsky via B4 Relay
2026-11-15 16:55 ` [PATCH v4 2/2] i2c: Add Cortina Systems CS75xx I2C controller driver Fil Dunsky via B4 Relay
2026-11-15 17:13 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox