* [PATCH v3 0/3] i2c: ma35d1: Add support for MA35D1 I2C controller
@ 2026-05-12 7:39 Zi-Yu Chen
2026-05-12 7:39 ` [PATCH v3 1/3] dt-bindings: i2c: nuvoton,ma35d1-i2c: Add " Zi-Yu Chen
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Zi-Yu Chen @ 2026-05-12 7:39 UTC (permalink / raw)
To: Jacky Huang, Andi Shyti
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-arm-kernel,
linux-i2c, devicetree, linux-kernel, Zi-Yu Chen
This series adds support for the I2C controller found in the Nuvoton
MA35D1 SoC. The driver supports controller and optional target mode
and runtime power management.
The implementation has been tested on the Nuvoton MA35D1 SOM board.
Changes in v3:
- Fix minor DTS formatting issues (whitespace, missing newline)
v2: https://lore.kernel.org/r/20260316063726.41048-1-zychennvt@gmail.com
Changes in v2:
- Overall:
- Rebase on linux-i2c/i2c-next
- Switched terminology from "master/slave" to "controller/target".
- Patch 1 (dt-bindings):
- Simplified description and fixed 'reg' size in example.
- Patch 2 (driver):
- Modernized using devm_*, generic device properties, and FIELD_PREP/GENMASK.
- Optimized power management by moving clock control to runtime PM.
- Simplified code by removing redundant .remove(), .owner, and inlines.
- Added dev_err_probe() and default bus frequency handling.
- Patch 3 (dts):
- Moved i2c aliases to board dts and reordered nodes alphabetically.
v1: https://lore.kernel.org/r/20260302020822.13936-1-zychennvt@gmail.com
Zi-Yu Chen (3):
dt-bindings: i2c: nuvoton,ma35d1-i2c: Add MA35D1 I2C controller
i2c: ma35d1: Add Nuvoton MA35D1 I2C driver support
arm64: dts: nuvoton: Add I2C nodes for MA35D1 SoC
.../bindings/i2c/nuvoton,ma35d1-i2c.yaml | 63 ++
.../boot/dts/nuvoton/ma35d1-som-256m.dts | 15 +
arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 60 ++
drivers/i2c/busses/Kconfig | 13 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-ma35d1.c | 792 ++++++++++++++++++
6 files changed, 944 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/nuvoton,ma35d1-i2c.yaml
create mode 100644 drivers/i2c/busses/i2c-ma35d1.c
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v3 1/3] dt-bindings: i2c: nuvoton,ma35d1-i2c: Add MA35D1 I2C controller
2026-05-12 7:39 [PATCH v3 0/3] i2c: ma35d1: Add support for MA35D1 I2C controller Zi-Yu Chen
@ 2026-05-12 7:39 ` Zi-Yu Chen
2026-05-12 7:39 ` [PATCH v3 2/3] i2c: ma35d1: Add Nuvoton MA35D1 I2C driver support Zi-Yu Chen
2026-05-12 7:39 ` [PATCH v3 3/3] arm64: dts: nuvoton: Add I2C nodes for MA35D1 SoC Zi-Yu Chen
2 siblings, 0 replies; 6+ messages in thread
From: Zi-Yu Chen @ 2026-05-12 7:39 UTC (permalink / raw)
To: Jacky Huang, Andi Shyti
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-arm-kernel,
linux-i2c, devicetree, linux-kernel, Zi-Yu Chen,
Krzysztof Kozlowski
Add device tree binding documentation for the I2C controller
found in the Nuvoton MA35D1 SoC.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Zi-Yu Chen <zychennvt@gmail.com>
---
.../bindings/i2c/nuvoton,ma35d1-i2c.yaml | 63 +++++++++++++++++++
1 file changed, 63 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/nuvoton,ma35d1-i2c.yaml
diff --git a/Documentation/devicetree/bindings/i2c/nuvoton,ma35d1-i2c.yaml b/Documentation/devicetree/bindings/i2c/nuvoton,ma35d1-i2c.yaml
new file mode 100644
index 000000000000..f2c004049d86
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/nuvoton,ma35d1-i2c.yaml
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/i2c/nuvoton,ma35d1-i2c.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Nuvoton MA35D1 I2C Controller
+
+maintainers:
+ - Zi-Yu Chen <zychennvt@gmail.com>
+
+description:
+ The Nuvoton MA35D1 I2C controller supports controller and optional target mode.
+
+allOf:
+ - $ref: /schemas/i2c/i2c-controller.yaml#
+
+properties:
+ compatible:
+ const: nuvoton,ma35d1-i2c
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-frequency:
+ description:
+ Desired I2C bus clock frequency in Hz. The absence of this property
+ indicates the default frequency 100 kHz.
+
+ resets:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - resets
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
+ #include <dt-bindings/reset/nuvoton,ma35d1-reset.h>
+
+ i2c0: i2c@40800000 {
+ compatible = "nuvoton,ma35d1-i2c";
+ reg = <0x40800000 0x1000>;
+ interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk I2C0_GATE>;
+ clock-frequency = <100000>;
+ resets = <&sys MA35D1_RESET_I2C0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v3 2/3] i2c: ma35d1: Add Nuvoton MA35D1 I2C driver support
2026-05-12 7:39 [PATCH v3 0/3] i2c: ma35d1: Add support for MA35D1 I2C controller Zi-Yu Chen
2026-05-12 7:39 ` [PATCH v3 1/3] dt-bindings: i2c: nuvoton,ma35d1-i2c: Add " Zi-Yu Chen
@ 2026-05-12 7:39 ` Zi-Yu Chen
2026-05-13 18:11 ` sashiko-bot
2026-05-12 7:39 ` [PATCH v3 3/3] arm64: dts: nuvoton: Add I2C nodes for MA35D1 SoC Zi-Yu Chen
2 siblings, 1 reply; 6+ messages in thread
From: Zi-Yu Chen @ 2026-05-12 7:39 UTC (permalink / raw)
To: Jacky Huang, Andi Shyti
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-arm-kernel,
linux-i2c, devicetree, linux-kernel, Zi-Yu Chen
Add I2C support for Nuvoton MA35D1 SoC.
The controller supports standard, fast and fast-plus modes,
and provides controller/target functionality.
Signed-off-by: Zi-Yu Chen <zychennvt@gmail.com>
---
drivers/i2c/busses/Kconfig | 13 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-ma35d1.c | 792 ++++++++++++++++++++++++++++++++
3 files changed, 806 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-ma35d1.c
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index ea3e7e92465d..401dcc9be46b 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1601,4 +1601,17 @@ config I2C_VIRTIO
This driver can also be built as a module. If so, the module
will be called i2c-virtio.
+config I2C_MA35D1
+ tristate "Nuvoton MA35D1 I2C driver"
+ depends on ARCH_MA35 || COMPILE_TEST
+ select I2C_SLAVE
+ help
+ If you say yes to this option, support will be included for the
+ I2C controller in the Nuvoton MA35D1 SoC. This driver
+ supports the standard I2C bus protocols, including master and
+ slave modes.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-ma35d1.
+
endmenu
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 3755c54b3d82..ca75dae4955c 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -131,6 +131,7 @@ obj-$(CONFIG_I2C_XILINX) += i2c-xiic.o
obj-$(CONFIG_I2C_XLP9XX) += i2c-xlp9xx.o
obj-$(CONFIG_I2C_RCAR) += i2c-rcar.o
obj-$(CONFIG_I2C_GXP) += i2c-gxp.o
+obj-$(CONFIG_I2C_MA35D1) += i2c-ma35d1.o
# External I2C/SMBus adapter drivers
obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o
diff --git a/drivers/i2c/busses/i2c-ma35d1.c b/drivers/i2c/busses/i2c-ma35d1.c
new file mode 100644
index 000000000000..c643f406e000
--- /dev/null
+++ b/drivers/i2c/busses/i2c-ma35d1.c
@@ -0,0 +1,792 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026 Nuvoton technology corporation.
+ *
+ * Author: Zi-Yu Chen <zychennvt@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+
+/* MA35D1 I2C registers offset */
+#define MA35_CTL0 0x00
+#define MA35_ADDR0 0x04
+#define MA35_DAT 0x08
+#define MA35_STATUS0 0x0c
+#define MA35_CLKDIV 0x10
+#define MA35_TOCTL 0x14
+#define MA35_ADDR1 0x18
+#define MA35_ADDR2 0x1c
+#define MA35_ADDR3 0x20
+#define MA35_ADDRMSK0 0x24
+#define MA35_ADDRMSK1 0x28
+#define MA35_ADDRMSK2 0x2c
+#define MA35_ADDRMSK3 0x30
+#define MA35_WKCTL 0x3c
+#define MA35_WKSTS 0x40
+#define MA35_CTL1 0x44
+#define MA35_STATUS1 0x48
+#define MA35_TMCTL 0x4c
+#define MA35_BUSCTL 0x50
+#define MA35_BUSTCTL 0x54
+#define MA35_BUSSTS 0x58
+#define MA35_PKTSIZE 0x5c
+#define MA35_PKTCRC 0x60
+#define MA35_BUSTOUT 0x64
+#define MA35_CLKTOUT 0x68
+#define MA35_AUTOCNT 0x78
+
+/* MA35D1 I2C Status */
+/* Controller */
+#define MA35_M_START 0x08 /* Start */
+#define MA35_M_REPEAT_START 0x10 /* Controller Repeat Start */
+#define MA35_M_TRAN_ADDR_ACK 0x18 /* Controller Transmit Address ACK */
+#define MA35_M_TRAN_ADDR_NACK 0x20 /* Controller Transmit Address NACK */
+#define MA35_M_TRAN_DATA_ACK 0x28 /* Controller Transmit Data ACK */
+#define MA35_M_TRAN_DATA_NACK 0x30 /* Controller Transmit Data NACK */
+#define MA35_M_ARB_LOST 0x38 /* Controller Arbitration Lost */
+#define MA35_M_RECE_ADDR_ACK 0x40 /* Controller Receive Address ACK */
+#define MA35_M_RECE_ADDR_NACK 0x48 /* Controller Receive Address NACK */
+#define MA35_M_RECE_DATA_ACK 0x50 /* Controller Receive Data ACK */
+#define MA35_M_RECE_DATA_NACK 0x58 /* Controller Receive Data NACK */
+#define MA35_BUS_ERROR 0x00 /* Bus error */
+
+/* Target */
+#define MA35_S_REPEAT_START_STOP 0xa0 /* Target Transmit Repeat Start or Stop */
+#define MA35_S_TRAN_ADDR_ACK 0xa8 /* Target Transmit Address ACK */
+#define MA35_S_TRAN_DATA_ACK 0xb8 /* Target Transmit Data ACK */
+#define MA35_S_TRAN_DATA_NACK 0xc0 /* Target Transmit Data NACK */
+#define MA35_S_TRAN_LAST_DATA_ACK 0xc8 /* Target Transmit Last Data ACK */
+#define MA35_S_RECE_ADDR_ACK 0x60 /* Target Receive Address ACK */
+#define MA35_S_RECE_ARB_LOST 0x68 /* Target Receive Arbitration Lost */
+#define MA35_S_RECE_DATA_ACK 0x80 /* Target Receive Data ACK */
+#define MA35_S_RECE_DATA_NACK 0x88 /* Target Receive Data NACK */
+
+/* GC Mode */
+#define MA35_GC_ADDR_ACK 0x70 /* GC mode Address ACK */
+#define MA35_GC_ARB_LOST 0x78 /* GC mode Arbitration Lost */
+#define MA35_GC_DATA_ACK 0x90 /* GC mode Data ACK */
+#define MA35_GC_DATA_NACK 0x98 /* GC mode Data NACK */
+
+/* Other */
+#define MA35_ADDR_TRAN_ARB_LOST 0xb0 /* Address Transmit Arbitration Lost */
+#define MA35_BUS_RELEASED 0xf8 /* Bus Released */
+
+/* I2C_CTL constant definitions. */
+#define MA35_CTL_AA BIT(2)
+#define MA35_CTL_SI BIT(3)
+#define MA35_CTL_STO BIT(4)
+#define MA35_CTL_STA BIT(5)
+#define MA35_CTL_I2CEN BIT(6)
+#define MA35_CTL_INTEN BIT(7)
+#define MA35_CTL_SI_AA (MA35_CTL_SI | MA35_CTL_AA)
+#define MA35_CTL_STO_SI (MA35_CTL_STO | MA35_CTL_SI)
+#define MA35_CTL_STA_SI (MA35_CTL_STA | MA35_CTL_SI)
+#define MA35_CTL_STA_SI_AA (MA35_CTL_STA | MA35_CTL_SI | MA35_CTL_AA)
+#define MA35_CTL_STO_SI_AA (MA35_CTL_STO | MA35_CTL_SI | MA35_CTL_AA)
+
+/* Constants */
+#define MA35_CLKDIV_MSK GENMASK(15, 0)
+#define MA35_I2C_ADDR_MASK (0x7f << 1)
+#define I2C_PM_TIMEOUT_MS 5000
+#define STOP_TIMEOUT_MS 50
+#define MA35_I2C_GC_EN 1
+#define MA35_I2C_GC_DIS 0
+
+struct ma35d1_i2c {
+ spinlock_t lock; /* Protects I2C register access and state */
+ wait_queue_head_t wait;
+ struct i2c_msg *msg;
+ unsigned int msg_num;
+ unsigned int msg_idx;
+ unsigned int msg_ptr;
+ unsigned int irq;
+ unsigned int arblost;
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ struct i2c_adapter adap;
+ struct i2c_client *target;
+ struct reset_control *rst;
+};
+
+static inline bool ma35d1_is_controller_status(unsigned int status)
+{
+ return status >= MA35_M_START && status <= MA35_M_RECE_DATA_NACK;
+}
+
+/*
+ * ma35d1_i2c_write_CTL - Update the I2C control register
+ * @i2c: Pointer to the ma35d1 i2c instance
+ * @ctl: Control bits to set (e.g., MA35_CTL_STA, SI, AA)
+ *
+ * This helper reads CTL0, clears the sticky state-change bits (STA, STO, SI, AA),
+ * and then applies the new control bits provided by @ctl.
+ */
+static void ma35d1_i2c_write_CTL(struct ma35d1_i2c *i2c, unsigned int ctl)
+{
+ unsigned int val;
+
+ val = readl(i2c->regs + MA35_CTL0);
+ val &= ~(MA35_CTL_STA_SI_AA | MA35_CTL_STO);
+ val |= ctl;
+ writel(val, i2c->regs + MA35_CTL0);
+}
+
+static void ma35d1_i2c_set_addr(struct ma35d1_i2c *i2c)
+{
+ unsigned int rw = i2c->msg->flags & I2C_M_RD;
+
+ writel(((i2c->msg->addr & 0x7f) << 1) | rw, i2c->regs + MA35_DAT);
+}
+
+static void ma35d1_i2c_controller_complete(struct ma35d1_i2c *i2c, int ret)
+{
+ dev_dbg(i2c->dev, "controller_complete %d\n", ret);
+
+ i2c->msg_ptr = 0;
+ i2c->msg = NULL;
+ i2c->msg_idx++;
+ i2c->msg_num = 0;
+ if (ret)
+ i2c->msg_idx = ret;
+
+ wake_up(&i2c->wait);
+}
+
+static void ma35d1_i2c_disable_irq(struct ma35d1_i2c *i2c)
+{
+ unsigned long tmp;
+
+ tmp = readl(i2c->regs + MA35_CTL0);
+ writel(tmp & ~MA35_CTL_INTEN, i2c->regs + MA35_CTL0);
+}
+
+static void ma35d1_i2c_enable_irq(struct ma35d1_i2c *i2c)
+{
+ unsigned long tmp;
+
+ tmp = readl(i2c->regs + MA35_CTL0);
+ writel(tmp | MA35_CTL_INTEN, i2c->regs + MA35_CTL0);
+}
+
+static void ma35d1_i2c_reset(struct ma35d1_i2c *i2c)
+{
+ unsigned int tmp;
+
+ tmp = readl(i2c->regs + MA35_CLKDIV);
+
+ reset_control_assert(i2c->rst);
+ usleep_range(10, 20);
+ reset_control_deassert(i2c->rst);
+
+ writel(tmp, (i2c->regs + MA35_CLKDIV));
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_I2CEN);
+
+ if (i2c->target)
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_SI_AA);
+}
+
+static void ma35d1_i2c_stop(struct ma35d1_i2c *i2c, int ret)
+{
+ unsigned int val;
+ int err;
+
+ /* Ensure AA is cleared to prevent the controller
+ * from re-claiming the bus unnecessarily
+ */
+ if (readl(i2c->regs + MA35_CTL0) & MA35_CTL_AA) {
+ val = readl(i2c->regs + MA35_CTL0);
+ val &= ~MA35_CTL_AA;
+ writel(val, (i2c->regs + MA35_CTL0));
+
+ err = readl_poll_timeout_atomic(i2c->regs + MA35_CTL0, val,
+ !(val & MA35_CTL_AA), 1, 1000);
+ if (err)
+ dev_warn(i2c->dev,
+ "AA bit could not be cleared in time\n");
+ }
+
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_STO_SI);
+
+ err = readl_poll_timeout_atomic(i2c->regs + MA35_CTL0, val,
+ !(val & MA35_CTL_STO), 1, 1 * 1000);
+ if (err)
+ dev_warn(i2c->dev, "I2C Stop Timeout\n");
+
+ if (i2c->target)
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_SI_AA);
+ else
+ ma35d1_i2c_disable_irq(i2c);
+
+ ma35d1_i2c_controller_complete(i2c, ret);
+}
+
+/* Check if this is the last message in the set */
+static inline bool is_last_msg(struct ma35d1_i2c *i2c)
+{
+ return i2c->msg_idx >= (i2c->msg_num - 1);
+}
+
+/* Check if this is the last byte in the current message */
+static inline bool is_last_byte(struct ma35d1_i2c *i2c)
+{
+ return i2c->msg_ptr == i2c->msg->len - 1;
+}
+
+/* Check if reached the end of the current message */
+static inline bool is_msgend(struct ma35d1_i2c *i2c)
+{
+ return i2c->msg_ptr >= i2c->msg->len;
+}
+
+/*
+ * i2c_ma35d1_irq_target_trx - I2C Target state machine handler
+ * @i2c: ma35d1 i2c instance
+ * @i2c_status: hardware status code from MA35_STATUS0
+ */
+static void i2c_ma35d1_irq_target_trx(struct ma35d1_i2c *i2c,
+ unsigned long i2c_status)
+{
+ unsigned char byte;
+
+ switch (i2c_status) {
+ case MA35_S_RECE_ADDR_ACK:
+ /* Own SLA+W has been receive; ACK has been return */
+ i2c_slave_event(i2c->target, I2C_SLAVE_WRITE_REQUESTED, &byte);
+ break;
+ case MA35_S_TRAN_DATA_NACK:
+ /* Data byte or last data in I2CDAT has been transmitted.
+ * Not ACK has been received
+ */
+ case MA35_S_RECE_DATA_NACK:
+ /* Previously addressed with own SLA address;
+ * NOT ACK has been returned
+ */
+ break;
+
+ case MA35_S_RECE_DATA_ACK:
+ /* Previously address with own SLA address Data has been received;
+ * ACK has been returned
+ */
+ byte = readb(i2c->regs + MA35_DAT);
+ i2c_slave_event(i2c->target, I2C_SLAVE_WRITE_RECEIVED, &byte);
+ break;
+
+ case MA35_S_TRAN_ADDR_ACK:
+ /* Own SLA+R has been receive; ACK has been return */
+ i2c_slave_event(i2c->target, I2C_SLAVE_READ_REQUESTED, &byte);
+
+ writel(byte, i2c->regs + MA35_DAT);
+ break;
+
+ case MA35_S_TRAN_DATA_ACK:
+ i2c_slave_event(i2c->target, I2C_SLAVE_READ_PROCESSED, &byte);
+ writel(byte, i2c->regs + MA35_DAT);
+ break;
+
+ case MA35_S_REPEAT_START_STOP:
+ /* A STOP or repeated START has been received
+ * while still addressed as Target/Receiver
+ */
+ i2c_slave_event(i2c->target, I2C_SLAVE_STOP, &byte);
+ break;
+
+ default:
+ dev_err(i2c->dev, "Status 0x%02lx is NOT processed\n",
+ i2c_status);
+ break;
+ }
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_SI_AA);
+}
+
+/*
+ * i2c_ma35d1_irq_controller_trx - I2C Controller state machine handler
+ * @i2c: ma35d1 i2c instance
+ * @i2c_status: hardware status code from MA35_STATUS0
+ */
+static void i2c_ma35d1_irq_controller_trx(struct ma35d1_i2c *i2c,
+ unsigned long i2c_status)
+{
+ unsigned char byte;
+
+ switch (i2c_status) {
+ case MA35_M_START:
+ case MA35_M_REPEAT_START:
+ ma35d1_i2c_set_addr(i2c);
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_SI);
+ break;
+
+ case MA35_M_TRAN_ADDR_ACK:
+ case MA35_M_TRAN_DATA_ACK:
+ /* SLA+W has been transmitted and ACK has been received */
+ if (i2c_status == MA35_M_TRAN_ADDR_ACK) {
+ if (is_last_msg(i2c) && i2c->msg->len == 0) {
+ ma35d1_i2c_stop(i2c, 0);
+ return;
+ }
+ }
+
+ if (!is_msgend(i2c)) {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ writel(byte, i2c->regs + MA35_DAT);
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_SI);
+ } else if (!is_last_msg(i2c)) {
+ dev_dbg(i2c->dev, "WRITE: Next Message\n");
+
+ i2c->msg_ptr = 0;
+ i2c->msg_idx++;
+ i2c->msg++;
+
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_STA | MA35_CTL_SI);
+ } else {
+ ma35d1_i2c_stop(i2c, 0);
+ }
+ break;
+
+ case MA35_M_TRAN_DATA_NACK:
+ ma35d1_i2c_stop(i2c, 0);
+ break;
+
+ case MA35_M_TRAN_ADDR_NACK:
+ case MA35_M_RECE_ADDR_NACK:
+ /* Controller Transmit Address NACK */
+ /* 0x20: SLA+W has been transmitted and NACK has been received */
+ /* 0x48: SLA+R has been transmitted and NACK has been received */
+ if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
+ dev_dbg(i2c->dev, "\n i2c: ack was not received\n");
+ ma35d1_i2c_stop(i2c, -ENXIO);
+ }
+ break;
+
+ case MA35_M_RECE_ADDR_ACK:
+ if (is_last_msg(i2c) && i2c->msg->len == 0)
+ ma35d1_i2c_stop(i2c, 0);
+ else if (is_last_msg(i2c) && (i2c->msg->len == 1))
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_SI);
+ else
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_SI_AA);
+ break;
+
+ case MA35_M_RECE_DATA_ACK:
+ case MA35_M_RECE_DATA_NACK:
+ /* DATA has been transmitted and ACK has been received */
+ byte = readb(i2c->regs + MA35_DAT);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+
+ if (is_last_byte(i2c)) {
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_SI);
+ } else if (is_msgend(i2c)) {
+ if (is_last_msg(i2c)) {
+ dev_dbg(i2c->dev, "READ: Send Stop\n");
+
+ ma35d1_i2c_stop(i2c, 0);
+ } else {
+ dev_dbg(i2c->dev, "READ: Next Transfer\n");
+
+ i2c->msg_ptr = 0;
+ i2c->msg_idx++;
+ i2c->msg++;
+
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_STA_SI);
+ }
+ } else {
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_SI_AA);
+ }
+ break;
+
+ default:
+ dev_err(i2c->dev, "Status 0x%02lx is NOT processed\n",
+ i2c_status);
+ ma35d1_i2c_disable_irq(i2c);
+ ma35d1_i2c_stop(i2c, 0);
+ break;
+ }
+}
+
+static irqreturn_t ma35d1_i2c_irq(int irqno, void *dev_id)
+{
+ struct ma35d1_i2c *i2c = dev_id;
+ unsigned long status, flags;
+
+ status = readl(i2c->regs + MA35_STATUS0);
+
+ spin_lock_irqsave(&i2c->lock, flags);
+
+ if (status == MA35_M_ARB_LOST) {
+ dev_err(i2c->dev, "Arbitration lost\n");
+ i2c->arblost = 1;
+ ma35d1_i2c_disable_irq(i2c);
+ ma35d1_i2c_stop(i2c, -EAGAIN);
+ goto out;
+ }
+
+ else if (status == MA35_BUS_ERROR) {
+ dev_err(i2c->dev, "Bus error during transfer\n");
+ ma35d1_i2c_disable_irq(i2c);
+ ma35d1_i2c_stop(i2c, 0);
+ goto out;
+ }
+
+ if (ma35d1_is_controller_status(status))
+ i2c_ma35d1_irq_controller_trx(i2c, status);
+ else
+ i2c_ma35d1_irq_target_trx(i2c, status);
+
+out:
+ spin_unlock_irqrestore(&i2c->lock, flags);
+ return IRQ_HANDLED;
+}
+
+static int ma35d1_i2c_doxfer(struct ma35d1_i2c *i2c, struct i2c_msg *msgs,
+ int num)
+{
+ unsigned long timeout;
+ unsigned int val;
+ int ret, err;
+
+ spin_lock_irq(&i2c->lock);
+
+ ma35d1_i2c_enable_irq(i2c);
+
+ i2c->msg = msgs;
+ i2c->msg_num = num;
+ i2c->msg_ptr = 0;
+ i2c->msg_idx = 0;
+
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_SI | MA35_CTL_STA);
+ spin_unlock_irq(&i2c->lock);
+
+ timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
+ ret = i2c->msg_idx;
+
+ if (timeout == 0)
+ dev_dbg(i2c->dev, "timeout\n");
+ else if (ret != num)
+ dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);
+
+ err = readl_poll_timeout(i2c->regs + MA35_CTL0, val,
+ !(val & MA35_CTL_STO), 100,
+ STOP_TIMEOUT_MS * 1000);
+
+ if (err) {
+ dev_err(i2c->dev, "Bus stuck! Resetting controller...\n");
+ ma35d1_i2c_reset(i2c);
+ }
+
+ if (i2c->arblost) {
+ dev_dbg(i2c->dev, "arb lost, stop\n");
+ i2c->arblost = 0;
+ }
+
+ return ret;
+}
+
+static int ma35d1_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+ int num)
+{
+ struct ma35d1_i2c *i2c = i2c_get_adapdata(adap);
+ int retry, ret;
+
+ ret = pm_runtime_resume_and_get(i2c->dev);
+ if (ret)
+ return ret;
+
+ for (retry = 0; retry < adap->retries; retry++) {
+ ret = ma35d1_i2c_doxfer(i2c, msgs, num);
+
+ if (ret != -EAGAIN)
+ break;
+
+ dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);
+ fsleep(100);
+ }
+
+ if (ret == -EAGAIN)
+ ret = -EREMOTEIO;
+
+ pm_runtime_put_autosuspend(i2c->dev);
+
+ return ret;
+}
+
+static int ma35d1_reg_target(struct i2c_client *target)
+{
+ struct ma35d1_i2c *i2c = i2c_get_adapdata(target->adapter);
+ unsigned int val, slvaddr;
+ int ret;
+
+ if (i2c->target)
+ return -EBUSY;
+
+ if (target->flags & I2C_CLIENT_TEN)
+ return -EAFNOSUPPORT;
+
+ ma35d1_i2c_enable_irq(i2c);
+
+ ret = pm_runtime_resume_and_get(i2c->dev);
+ if (ret) {
+ dev_err(i2c->dev, "failed to resume i2c controller\n");
+ return ret;
+ }
+
+ i2c->target = target;
+
+ val = readl(i2c->regs + MA35_CTL0);
+ val |= MA35_CTL_I2CEN;
+ writel(val, i2c->regs + MA35_CTL0);
+ slvaddr = target->addr << 1;
+ writel(slvaddr, i2c->regs + MA35_ADDR0);
+
+ /* I2C enter SLV mode */
+ ma35d1_i2c_write_CTL(i2c, MA35_CTL_SI_AA);
+
+ return 0;
+}
+
+static int ma35d1_unreg_target(struct i2c_client *target)
+{
+ struct ma35d1_i2c *i2c = i2c_get_adapdata(target->adapter);
+ unsigned int val;
+ int ret;
+
+ /* Disable I2C */
+ val = readl(i2c->regs + MA35_CTL0);
+ val &= ~MA35_CTL_I2CEN;
+ writel(val, i2c->regs + MA35_CTL0);
+
+ /* Disable I2C interrupt */
+ ma35d1_i2c_disable_irq(i2c);
+
+ i2c->target = NULL;
+
+ ret = pm_runtime_put_sync(i2c->dev);
+ if (ret)
+ dev_err(i2c->dev, "failed to suspend i2c controller");
+
+ return 0;
+}
+
+/* Declare Our I2C Functionality */
+static u32 ma35d1_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_PROTOCOL_MANGLING | I2C_FUNC_SMBUS_EMUL;
+}
+
+/* I2C Bus Registration Info */
+static const struct i2c_algorithm ma35d1_i2c_algorithm = {
+ .xfer = ma35d1_i2c_xfer,
+ .functionality = ma35d1_i2c_func,
+ .reg_target = ma35d1_reg_target,
+ .unreg_target = ma35d1_unreg_target,
+};
+
+static int ma35d1_i2c_probe(struct platform_device *pdev)
+{
+ struct ma35d1_i2c *i2c;
+ struct resource *res;
+ int ret, clkdiv;
+ unsigned int busfreq;
+ struct device *dev = &pdev->dev;
+
+ i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
+ if (!i2c)
+ return -ENOMEM;
+
+ spin_lock_init(&i2c->lock);
+ init_waitqueue_head(&i2c->wait);
+
+ i2c->dev = dev;
+
+ i2c->clk = devm_clk_get_prepared(dev, NULL);
+ if (IS_ERR(i2c->clk))
+ return dev_err_probe(dev, PTR_ERR(i2c->clk),
+ "failed to get core clk\n");
+
+ i2c->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(i2c->regs))
+ return PTR_ERR(i2c->regs);
+
+ i2c->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+ if (IS_ERR(i2c->rst))
+ return dev_err_probe(dev, PTR_ERR(i2c->rst),
+ "failed to get reset control\n");
+
+ /* Setup info block for the I2C core */
+ strscpy(i2c->adap.name, "ma35d1-i2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &ma35d1_i2c_algorithm;
+ i2c->adap.retries = 2;
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+ i2c->adap.dev.of_node = pdev->dev.of_node;
+ i2c_set_adapdata(&i2c->adap, i2c);
+
+ /* Default to 100kHz if not specified in DT */
+ busfreq = 100000;
+ device_property_read_u32(dev, "clock-frequency", &busfreq);
+
+ /* Calculate divider based on the current peripheral clock rate */
+ clkdiv = DIV_ROUND_CLOSEST(clk_get_rate(i2c->clk), busfreq * 4) - 1;
+ if (clkdiv < 0 || clkdiv > 0xffff)
+ return dev_err_probe(dev, -EINVAL, "invalid clkdiv value: %d\n",
+ clkdiv);
+
+ i2c->irq = platform_get_irq(pdev, 0);
+ if (i2c->irq < 0)
+ return dev_err_probe(dev, i2c->irq, "failed to get irq\n");
+
+ platform_set_drvdata(pdev, i2c);
+
+ pm_runtime_set_autosuspend_delay(dev, I2C_PM_TIMEOUT_MS);
+ pm_runtime_use_autosuspend(dev);
+ devm_pm_runtime_enable(dev);
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to resume device\n");
+
+ writel(FIELD_PREP(MA35_CLKDIV_MSK, clkdiv), i2c->regs + MA35_CLKDIV);
+
+ ret = devm_request_irq(dev, i2c->irq, ma35d1_i2c_irq, IRQF_SHARED,
+ dev_name(dev), i2c);
+ if (ret) {
+ dev_err_probe(dev, ret, "cannot claim IRQ %d\n", i2c->irq);
+ goto rpm_put;
+ }
+
+ ret = devm_i2c_add_adapter(dev, &i2c->adap);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to add bus to i2c core\n");
+ goto rpm_put;
+ }
+
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+
+rpm_put:
+ pm_runtime_put_noidle(dev);
+ return ret;
+}
+
+static int ma35d1_i2c_suspend(struct device *dev)
+{
+ struct ma35d1_i2c *i2c = dev_get_drvdata(dev);
+ unsigned int val;
+
+ spin_lock_irq(&i2c->lock);
+
+ /* Prepare for wake-up from I2C events if target mode is active */
+ if (i2c->target) {
+ val = readl(i2c->regs + MA35_CTL0);
+ val |= (MA35_CTL_SI | MA35_CTL_AA);
+ writel(val, i2c->regs + MA35_CTL0);
+ ma35d1_i2c_enable_irq(i2c);
+ }
+
+ spin_unlock_irq(&i2c->lock);
+
+ /* Setup wake-up control */
+ writel(0x1, i2c->regs + MA35_WKCTL);
+
+ /* Clear pending wake-up flags */
+ val = readl(i2c->regs + MA35_WKSTS);
+ writel(val, i2c->regs + MA35_WKSTS);
+
+ enable_irq_wake(i2c->irq);
+
+ return 0;
+}
+
+static int ma35d1_i2c_resume(struct device *dev)
+{
+ struct ma35d1_i2c *i2c = dev_get_drvdata(dev);
+ unsigned int val;
+
+ /* Disable wake-up */
+ writel(0x0, i2c->regs + MA35_WKCTL);
+
+ /* Clear pending wake-up flags */
+ val = readl(i2c->regs + MA35_WKSTS);
+ writel(val, i2c->regs + MA35_WKSTS);
+
+ disable_irq_wake(i2c->irq);
+
+ return 0;
+}
+
+static int ma35d1_i2c_runtime_suspend(struct device *dev)
+{
+ struct ma35d1_i2c *i2c = dev_get_drvdata(dev);
+ unsigned int val;
+
+ /* Disable I2C controller */
+ val = readl(i2c->regs + MA35_CTL0);
+ val &= ~MA35_CTL_I2CEN;
+ writel(val, i2c->regs + MA35_CTL0);
+
+ clk_disable(i2c->clk);
+
+ return 0;
+}
+
+static int ma35d1_i2c_runtime_resume(struct device *dev)
+{
+ struct ma35d1_i2c *i2c = dev_get_drvdata(dev);
+ unsigned int val;
+ int ret;
+
+ ret = clk_enable(i2c->clk);
+ if (ret) {
+ dev_err(dev, "failed to enable clock in resume\n");
+ return ret;
+ }
+
+ /* Enable I2C controller */
+ val = readl(i2c->regs + MA35_CTL0);
+ val |= MA35_CTL_I2CEN;
+ writel(val, i2c->regs + MA35_CTL0);
+
+ return 0;
+}
+
+static const struct dev_pm_ops ma35d1_i2c_pmops = {
+ SYSTEM_SLEEP_PM_OPS(ma35d1_i2c_suspend, ma35d1_i2c_resume)
+ RUNTIME_PM_OPS(ma35d1_i2c_runtime_suspend,
+ ma35d1_i2c_runtime_resume, NULL)
+};
+
+static const struct of_device_id ma35d1_i2c_of_match[] = {
+ { .compatible = "nuvoton,ma35d1-i2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ma35d1_i2c_of_match);
+
+static struct platform_driver ma35d1_i2c_driver = {
+ .probe = ma35d1_i2c_probe,
+ .driver = {
+ .name = "ma35d1-i2c",
+ .of_match_table = ma35d1_i2c_of_match,
+ .pm = pm_ptr(&ma35d1_i2c_pmops),
+ },
+};
+module_platform_driver(ma35d1_i2c_driver);
+
+MODULE_AUTHOR("Zi-Yu Chen <zychennvt@gmail.com>");
+MODULE_DESCRIPTION("MA35D1 I2C Bus Driver");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v3 3/3] arm64: dts: nuvoton: Add I2C nodes for MA35D1 SoC
2026-05-12 7:39 [PATCH v3 0/3] i2c: ma35d1: Add support for MA35D1 I2C controller Zi-Yu Chen
2026-05-12 7:39 ` [PATCH v3 1/3] dt-bindings: i2c: nuvoton,ma35d1-i2c: Add " Zi-Yu Chen
2026-05-12 7:39 ` [PATCH v3 2/3] i2c: ma35d1: Add Nuvoton MA35D1 I2C driver support Zi-Yu Chen
@ 2026-05-12 7:39 ` Zi-Yu Chen
2026-05-13 18:56 ` sashiko-bot
2 siblings, 1 reply; 6+ messages in thread
From: Zi-Yu Chen @ 2026-05-12 7:39 UTC (permalink / raw)
To: Jacky Huang, Andi Shyti
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-arm-kernel,
linux-i2c, devicetree, linux-kernel, Zi-Yu Chen
Add I2C controller nodes to the MA35D1 SoC dtsi.
Also enable the I2C interfaces on the MA35D1 SOM board
to allow communication with onboard peripherals.
Signed-off-by: Zi-Yu Chen <zychennvt@gmail.com>
---
.../boot/dts/nuvoton/ma35d1-som-256m.dts | 15 +++++
arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 60 +++++++++++++++++++
2 files changed, 75 insertions(+)
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
index f6f20a17e501..a0f1d76d288e 100644
--- a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
@@ -13,6 +13,7 @@ / {
compatible = "nuvoton,ma35d1-som", "nuvoton,ma35d1";
aliases {
+ i2c0 = &i2c2;
serial0 = &uart0;
serial11 = &uart11;
serial12 = &uart12;
@@ -55,6 +56,12 @@ &clk {
"integer";
};
+&i2c1 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c1>;
+ status = "okay";
+};
+
&pinctrl {
uart-grp {
pinctrl_uart0: uart0-pins {
@@ -98,6 +105,14 @@ pinctrl_uart16: uart16-pins {
power-source = <1>;
};
};
+
+ i2c-grp {
+ pinctrl_i2c1: i2c1-pins {
+ nuvoton,pins = <1 10 12>,
+ <1 11 12>;
+ bias-disable;
+ };
+ };
};
&uart0 {
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
index e51b98f5bdce..a88d1c2938e7 100644
--- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
@@ -372,6 +372,66 @@ uart15: serial@407f0000 {
status = "disabled";
};
+ i2c1: i2c@40810000 {
+ compatible = "nuvoton,ma35d1-i2c";
+ reg = <0x0 0x40810000 0x0 0x1000>;
+ interrupts = <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk I2C1_GATE>;
+ clock-frequency = <100000>;
+ resets = <&sys MA35D1_RESET_I2C1>;
+ status = "disabled";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ i2c2: i2c@40820000 {
+ compatible = "nuvoton,ma35d1-i2c";
+ reg = <0x0 0x40820000 0x0 0x1000>;
+ interrupts = <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk I2C2_GATE>;
+ clock-frequency = <100000>;
+ resets = <&sys MA35D1_RESET_I2C2>;
+ status = "disabled";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ i2c3: i2c@40830000 {
+ compatible = "nuvoton,ma35d1-i2c";
+ reg = <0x0 0x40830000 0x0 0x1000>;
+ interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk I2C3_GATE>;
+ clock-frequency = <100000>;
+ resets = <&sys MA35D1_RESET_I2C3>;
+ status = "disabled";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ i2c4: i2c@40840000 {
+ compatible = "nuvoton,ma35d1-i2c";
+ reg = <0x0 0x40840000 0x0 0x1000>;
+ interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk I2C4_GATE>;
+ clock-frequency = <100000>;
+ resets = <&sys MA35D1_RESET_I2C4>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c5: i2c@40850000 {
+ compatible = "nuvoton,ma35d1-i2c";
+ reg = <0x0 0x40850000 0x0 0x1000>;
+ interrupts = <GIC_SPI 124 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk I2C5_GATE>;
+ clock-frequency = <100000>;
+ resets = <&sys MA35D1_RESET_I2C5>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
uart16: serial@40880000 {
compatible = "nuvoton,ma35d1-uart";
reg = <0x0 0x40880000 0x0 0x100>;
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v3 2/3] i2c: ma35d1: Add Nuvoton MA35D1 I2C driver support
2026-05-12 7:39 ` [PATCH v3 2/3] i2c: ma35d1: Add Nuvoton MA35D1 I2C driver support Zi-Yu Chen
@ 2026-05-13 18:11 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-05-13 18:11 UTC (permalink / raw)
To: Zi-Yu Chen; +Cc: devicetree, robh, krzk+dt, conor+dt
Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:
- [High] Missing runtime PM checks in shared IRQ handler and missing synchronize_irq()
- [High] Unsafe hardware accesses with disabled clock in system PM and target registration
- [High] Transfer timeouts lead to Use-After-Free and fail to reset hardware
- [High] Unhandled NACK with I2C_M_IGNORE_NAK causes infinite interrupt storm
- [High] Protocol violation and memory corruption on 0-length and 1-length read messages
- [High] Race conditions in target unregistration and hardware reset
- [Medium] Data NACK is incorrectly reported as a successful transfer
- [Medium] Unsigned integer comparison prevents probe error detection
- [Medium] Clock resource leak in probe error path
- [Medium] Hardware reset function fails to restore target address configuration
- [Low] Incorrect Runtime PM error check in target unregistration
--
commit 900661bb640e824e1459adee8e6a1180f0ab5bca
Author: Zi-Yu Chen <zychennvt@gmail.com>
i2c: ma35d1: Add Nuvoton MA35D1 I2C driver support
This commit adds I2C support for the Nuvoton MA35D1 SoC. It provides
controller and target functionality, and supports standard, fast, and
fast-plus modes.
diff --git a/drivers/i2c/busses/i2c-ma35d1.c b/drivers/i2c/busses/i2c-ma35d1.c
new file mode 100644
index 0000000000000..c643f406e0008
--- /dev/null
+++ b/drivers/i2c/busses/i2c-ma35d1.c
@@ -0,0 +1,792 @@
[ ... ]
> +static void ma35d1_i2c_reset(struct ma35d1_i2c *i2c)
> +{
> + unsigned int tmp;
> +
> + tmp = readl(i2c->regs + MA35_CLKDIV);
> +
> + reset_control_assert(i2c->rst);
> + usleep_range(10, 20);
> + reset_control_deassert(i2c->rst);
> +
> + writel(tmp, (i2c->regs + MA35_CLKDIV));
> + ma35d1_i2c_write_CTL(i2c, MA35_CTL_I2CEN);
> +
> + if (i2c->target)
> + ma35d1_i2c_write_CTL(i2c, MA35_CTL_SI_AA);
> +}
Does this fully restore the target mode configuration? The reset asserts the
hardware reset, which wipes the controller state, but it does not rewrite the
target address register MA35_ADDR0.
Could this leave the device with a cleared target address, breaking target
functionality until it is manually re-registered?
Also, could this sleep during an active interrupt? If this reset is called
and the device uses a shared IRQ, could an interrupt fire during the
usleep_range() while the device is in reset? Because the lock is not taken
here, the IRQ handler might read from MA35_STATUS0 and trigger a bus fault.
[ ... ]
> +static void i2c_ma35d1_irq_controller_trx(struct ma35d1_i2c *i2c,
> + unsigned long i2c_status)
> +{
> + unsigned char byte;
> +
> + switch (i2c_status) {
[ ... ]
> + case MA35_M_TRAN_DATA_NACK:
> + ma35d1_i2c_stop(i2c, 0);
> + break;
Does passing 0 to ma35d1_i2c_stop() mask the error? This increments
i2c->msg_idx and reports to the caller that the message was successfully
transferred, even though the target rejected the data byte.
Should this pass an error code like -EIO or -ENXIO instead?
> +
> + case MA35_M_TRAN_ADDR_NACK:
> + case MA35_M_RECE_ADDR_NACK:
> + /* Controller Transmit Address NACK */
> + /* 0x20: SLA+W has been transmitted and NACK has been received */
> + /* 0x48: SLA+R has been transmitted and NACK has been received */
> + if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
> + dev_dbg(i2c->dev, "\n i2c: ack was not received\n");
> + ma35d1_i2c_stop(i2c, -ENXIO);
> + }
> + break;
Could this cause an infinite interrupt loop? If I2C_M_IGNORE_NAK is set,
the switch breaks without clearing the interrupt flag MA35_CTL_SI or
advancing the state machine.
When the IRQ handler returns, the same unacknowledged hardware interrupt
will fire again immediately, potentially freezing the CPU.
> +
> + case MA35_M_RECE_ADDR_ACK:
> + if (is_last_msg(i2c) && i2c->msg->len == 0)
> + ma35d1_i2c_stop(i2c, 0);
> + else if (is_last_msg(i2c) && (i2c->msg->len == 1))
> + ma35d1_i2c_write_CTL(i2c, MA35_CTL_SI);
> + else
> + ma35d1_i2c_write_CTL(i2c, MA35_CTL_SI_AA);
> + break;
Could this corrupt memory on 0-length read messages? If a read message is
not the final message (!is_last_msg), it falls through to the else branch,
setting MA35_CTL_SI_AA to ACK an incoming byte.
For a 0-length message, this commands the hardware to receive a byte, which
triggers MA35_M_RECE_DATA_ACK. The received byte will be written to
i2c->msg->buf[0], but the I2C core leaves buf as NULL for 0-length reads,
leading to a NULL pointer dereference.
Additionally, for a 1-length intermediate message, it would send an ACK
instead of a NACK for the final byte, holding SDA low and violating the I2C
protocol, which prevents a Repeated Start.
[ ... ]
> +static irqreturn_t ma35d1_i2c_irq(int irqno, void *dev_id)
> +{
> + struct ma35d1_i2c *i2c = dev_id;
> + unsigned long status, flags;
> +
> + status = readl(i2c->regs + MA35_STATUS0);
Is this hardware access safe when the device is runtime suspended?
The device requests the interrupt with IRQF_SHARED in probe. If another
device on the same IRQ line triggers an interrupt while this I2C controller
is runtime suspended and its clock is gated, reading MA35_STATUS0 will cause
a bus fault.
Should this use pm_runtime_get_if_active() to verify the device is powered
before accessing registers?
[ ... ]
> +static int ma35d1_i2c_doxfer(struct ma35d1_i2c *i2c, struct i2c_msg *msgs,
> + int num)
> +{
[ ... ]
> + timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
> + ret = i2c->msg_idx;
> +
> + if (timeout == 0)
> + dev_dbg(i2c->dev, "timeout\n");
> + else if (ret != num)
> + dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);
> +
> + err = readl_poll_timeout(i2c->regs + MA35_CTL0, val,
> + !(val & MA35_CTL_STO), 100,
> + STOP_TIMEOUT_MS * 1000);
> +
> + if (err) {
> + dev_err(i2c->dev, "Bus stuck! Resetting controller...\n");
> + ma35d1_i2c_reset(i2c);
> + }
> +
> + if (i2c->arblost) {
> + dev_dbg(i2c->dev, "arb lost, stop\n");
> + i2c->arblost = 0;
> + }
> +
> + return ret;
> +}
Could a timeout lead to a use-after-free regression?
If wait_event_timeout() expires, no STOP condition was ever commanded, so
the MA35_CTL_STO bit is likely already 0. This causes readl_poll_timeout()
to succeed instantly.
Because err is 0, ma35d1_i2c_reset() is bypassed, leaving the controller
active and interrupts enabled. The function returns without an error code
like -ETIMEDOUT, and the caller frees the msgs buffer.
If the hardware later completes the operation or asserts a delayed interrupt,
i2c_ma35d1_irq_controller_trx() will dereference the freed i2c->msg pointer.
[ ... ]
> +static int ma35d1_reg_target(struct i2c_client *target)
> +{
[ ... ]
> + if (target->flags & I2C_CLIENT_TEN)
> + return -EAFNOSUPPORT;
> +
> + ma35d1_i2c_enable_irq(i2c);
> +
> + ret = pm_runtime_resume_and_get(i2c->dev);
Can this access the controller while the clock is disabled?
ma35d1_i2c_enable_irq() writes to the MA35_CTL0 register. By calling it
before pm_runtime_resume_and_get(), the register write happens while the
device may still be runtime suspended with its clock gated off, resulting
in a bus fault.
[ ... ]
> +static int ma35d1_unreg_target(struct i2c_client *target)
> +{
> + struct ma35d1_i2c *i2c = i2c_get_adapdata(target->adapter);
> + unsigned int val;
> + int ret;
> +
> + /* Disable I2C */
> + val = readl(i2c->regs + MA35_CTL0);
> + val &= ~MA35_CTL_I2CEN;
> + writel(val, i2c->regs + MA35_CTL0);
> +
> + /* Disable I2C interrupt */
> + ma35d1_i2c_disable_irq(i2c);
> +
> + i2c->target = NULL;
Could this race with an active target interrupt? The i2c->target pointer
is set to NULL without acquiring i2c->lock.
If a target interrupt is firing concurrently, i2c_slave_event() could
dereference the NULL pointer.
> +
> + ret = pm_runtime_put_sync(i2c->dev);
> + if (ret)
> + dev_err(i2c->dev, "failed to suspend i2c controller");
Does this incorrectly treat a successful put as an error?
According to PM subsystem rules, pm_runtime_put_sync() returns 1 when the
device is already suspended. This is a success case, but checking if (ret)
will spuriously log an error. Should this be if (ret < 0)?
[ ... ]
> +struct ma35d1_i2c {
> + spinlock_t lock; /* Protects I2C register access and state */
[ ... ]
> + unsigned int msg_ptr;
> + unsigned int irq;
[ ... ]
> +static int ma35d1_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\n");
Does this successfully detect probe errors? Since i2c->irq is declared
as an unsigned int, the check if (i2c->irq < 0) will always be false.
A negative error code from platform_get_irq() would be cast to a positive
unsigned value, masking the failure here and deferring it until
devm_request_irq() fails later.
[ ... ]
> +rpm_put:
> + pm_runtime_put_noidle(dev);
> + return ret;
> +}
Does this leave the clock permanently enabled on probe failure?
Because pm_runtime_resume_and_get() was called earlier, using
pm_runtime_put_noidle() drops the usage count without allowing the device
to enter the runtime suspended state. When devres cleans up the PM runtime
context, the peripheral clock will remain enabled.
[ ... ]
> +static int ma35d1_i2c_suspend(struct device *dev)
> +{
> + struct ma35d1_i2c *i2c = dev_get_drvdata(dev);
> + unsigned int val;
> +
> + spin_lock_irq(&i2c->lock);
> +
> + /* Prepare for wake-up from I2C events if target mode is active */
> + if (i2c->target) {
> + val = readl(i2c->regs + MA35_CTL0);
> + val |= (MA35_CTL_SI | MA35_CTL_AA);
> + writel(val, i2c->regs + MA35_CTL0);
> + ma35d1_i2c_enable_irq(i2c);
> + }
> +
> + spin_unlock_irq(&i2c->lock);
> +
> + /* Setup wake-up control */
> + writel(0x1, i2c->regs + MA35_WKCTL);
Are these register accesses safe if the device is already runtime suspended?
The ma35d1_i2c_suspend() and ma35d1_i2c_resume() functions unconditionally
access the wakeup registers (MA35_WKCTL, MA35_WKSTS). If the device was
already runtime suspended before system sleep, its clock is gated off,
and these register accesses will cause a synchronous bus fault.
[ ... ]
> +static int ma35d1_i2c_runtime_suspend(struct device *dev)
> +{
> + struct ma35d1_i2c *i2c = dev_get_drvdata(dev);
> + unsigned int val;
> +
> + /* Disable I2C controller */
> + val = readl(i2c->regs + MA35_CTL0);
> + val &= ~MA35_CTL_I2CEN;
> + writel(val, i2c->regs + MA35_CTL0);
> +
> + clk_disable(i2c->clk);
> +
> + return 0;
> +}
Does this safely handle mid-flight interrupts? Since the interrupt is
registered with IRQF_SHARED, disabling the clock without first calling
synchronize_irq() risks a crash if an IRQ handler is still executing or
about to execute on another CPU when the clock is gated off.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260512073953.564323-1-zychennvt@gmail.com?part=2
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v3 3/3] arm64: dts: nuvoton: Add I2C nodes for MA35D1 SoC
2026-05-12 7:39 ` [PATCH v3 3/3] arm64: dts: nuvoton: Add I2C nodes for MA35D1 SoC Zi-Yu Chen
@ 2026-05-13 18:56 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-05-13 18:56 UTC (permalink / raw)
To: Zi-Yu Chen; +Cc: robh, devicetree, krzk+dt, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] The I2C0 controller node is entirely missing from the SoC device tree.
- [Medium] Dangling device alias pointing to a disabled node while the active node is left unaliased.
- [Medium] Meaningful mismatch between commit message and implementation.
--
commit 872a73a723152a79752769d63796c54985979c16
Author: Zi-Yu Chen <zychennvt@gmail.com>
arm64: dts: nuvoton: Add I2C nodes for MA35D1 SoC
This commit adds I2C controller nodes to the MA35D1 SoC device tree. It
also enables an I2C interface on the MA35D1 SOM board and sets up the
corresponding pin control configurations.
> Add I2C controller nodes to the MA35D1 SoC dtsi.
> Also enable the I2C interfaces on the MA35D1 SOM board
> to allow communication with onboard peripherals.
The commit message mentions enabling multiple I2C interfaces and allowing
communication with onboard peripherals. Since only a single interface is
enabled in the device tree and no child nodes for peripherals are added,
is this description accurate, or were some parts of the patch omitted?
> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
> index f6f20a17e5018..a0f1d76d288e1 100644
> --- a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
> @@ -13,6 +13,7 @@ / {
> compatible = "nuvoton,ma35d1-som", "nuvoton,ma35d1";
>
> aliases {
> + i2c0 = &i2c2;
Does this alias point to the correct node? Since the i2c2 node remains
disabled, the subsystem will not create this interface. Should this be
pointing to the newly enabled i2c1 node instead?
[ ... ]
> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> index e51b98f5bdce4..a88d1c2938e78 100644
> --- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> @@ -372,6 +372,66 @@ uart15: serial@407f0000 {
> status = "disabled";
> };
>
> + i2c1: i2c@40810000 {
It looks like the i2c0 controller node is missing from the hardware
description. Should i2c0 be included here to fully describe the SoC, even
if it is marked as reserved for the secure world or a coprocessor?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260512073953.564323-1-zychennvt@gmail.com?part=3
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-05-13 18:56 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-12 7:39 [PATCH v3 0/3] i2c: ma35d1: Add support for MA35D1 I2C controller Zi-Yu Chen
2026-05-12 7:39 ` [PATCH v3 1/3] dt-bindings: i2c: nuvoton,ma35d1-i2c: Add " Zi-Yu Chen
2026-05-12 7:39 ` [PATCH v3 2/3] i2c: ma35d1: Add Nuvoton MA35D1 I2C driver support Zi-Yu Chen
2026-05-13 18:11 ` sashiko-bot
2026-05-12 7:39 ` [PATCH v3 3/3] arm64: dts: nuvoton: Add I2C nodes for MA35D1 SoC Zi-Yu Chen
2026-05-13 18:56 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox