* [PATCH v4 0/3] i2c: Add Novatek NT72676 SoC I2C controller
@ 2026-07-30 7:25 nina_kuo
2026-07-30 7:25 ` [PATCH v4 1/3] dt-bindings: i2c: Add Novatek NT72676 " nina_kuo
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: nina_kuo @ 2026-07-30 7:25 UTC (permalink / raw)
To: andi.shyti, robh, krzk+dt, conor+dt, linux-i2c, devicetree,
linux-kernel
Cc: ben_huang, toby_chui, shihpei_hsu
From: Ben Huang <Ben_Huang@novatek.com.tw>
This patch series adds support for the I2C bus controllers on
Novatek NT72676 SoC.
NT72676 SoC provides two I2C controller variants:
- Generic I2C controller
- Accessible only by the host CPU
- Driven by a 96 MHz AHB clock
- STBC I2C controller
- Accessible by either the host CPU or STBC (Standby Controller)
- Driven by a 12 MHz STBC clock
- Requires STBC authentication before CPU access
The controller driver implements the fundamental I2C read/write
operations and supports Standard-mode and Fast-mode.
The input clock rates of the two controller variants are fixed by
hardware integration and are not configurable. There is currently
no clock provider driver available for NT72676 SoC, therefore the
driver uses compatible-specific clock rate data instead of the common
clock framework.
This series was tested with concurrent I2C read/write operations on
two I2C buses for 1,000,000 iterations on NT72676 SoC.
No errors or data corruption were observed.
---
v4:
From Krzysztof's review:
- Replace all occurrences of "726xx" with "72676"
- Reorganize the patch series
- In novatek,nt72676-i2c.yaml:
- Correct the order of entries
- Change the compatible string of generic I2C to
"novatek,nt72676-i2c"
- Add the compatible string of STBC I2C as
"novatek,nt72676-stbc-i2c"
- Remove `novatek,hwmods` custom property
- In i2c-nt72676.c:
- Use dev_err() and dev_info() for driver messages
- Remove unnecessary logs
- Add different compatible data to "novatek,nt72676-i2c" and
"novatek,nt72676-stbc-i2c"
- Replace platform_get_resource() and devm_ioremap_resource() with
devm_platform_ioremap_resource()
- Remove definition of owner under nvt_i2c_driver
- Register this driver with module_platform_driver()
From internal review:
- Modify commit messages
- In novatek,nt72676-i2c.yaml:
- Add description about the I2C controllers on Novatek NT72676 SoC
- Add `novatek,stbc-syscon` custom property for STBC authentication
- Modify the examples for generic I2C and STBC I2C
- In i2c-nt72676.c:
- Add nt72676_i2c_writel() and nt72676_i2c_readl() wrappers for
register access
- Modify definitions of the I2C registers for both generic I2C and
STBC I2C use
- Use BIT() and GENMASK() for register bit definitions
- Use regmap to access STBC syscon registers for authentication
before CPU access
v3:
https://lore.kernel.org/lkml/20260715073443.147490-1-Nina_Kuo@novatek.com.tw/T/#t
- Only remove attached HTML messages, no code is modified.
v2:
https://lore.kernel.org/lkml/20260714094145.84387-1-Nina_Kuo@novatek.com.tw/T/#t
From Krzysztof's review of novatek,nt726xx-i2c.yaml:
- Explicitly specify the unique compatibles "novatek,nt72600-i2c" for
Novatek NT726xx SoCs
- Modify `maxItems` as 1 for both `reg` and `interrupts` properties
- Modify description and allowed values for `clock-frequency`
property
- Remove `minItems` for `novatek,hwmods` custom property
- Rename, fix wrong type definition and add more description for
`novatek,stbc-controllable` custom property
- Remove `bus-enable` custom property
- Add an example for dtsi declaration
From Sashiko AI's review of i2c-nt726xx.c:
- Remove flows related to `bus-enable` custom property
- Add default value 100000 (100 kHz) of `clock-frequency` property
to prevent from divide-by-zero panic
- Add flows for handling ioremap() in nvt_i2c_use_case_feature()
- Remove I2C_FUNC_SMBUS_QUICK functionality due to unsupported handling
of 0-byte messages
- Add flows of disabling IRQs in nvt_i2c_suspend() and nvt_i2c_remove()
- Add of_node_get() to release the reference to device tree node in
nvt_i2c_remove()
v1:
https://lore.kernel.org/lkml/20260604060411.355675-1-SP_ISW1_AT@novatek.com.tw/T/#t
Signed-off-by: Ben Huang <Ben_Huang@novatek.com.tw>
Signed-off-by: Nina Kuo <Nina_Kuo@novatek.com.tw>
Ben Huang (3):
dt-bindings: i2c: Add Novatek NT72676 I2C controller
i2c: nt72676: Add Novatek NT72676 I2C controller driver
MAINTAINERS: Add entry for Novatek NT72676 I2C controller driver
.../bindings/i2c/novatek,nt72676-i2c.yaml | 87 +++
MAINTAINERS | 7 +
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-nt72676.c | 683 ++++++++++++++++++
5 files changed, 788 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/novatek,nt72676-i2c.yaml
create mode 100644 drivers/i2c/busses/i2c-nt72676.c
--
2.40.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v4 1/3] dt-bindings: i2c: Add Novatek NT72676 I2C controller
2026-07-30 7:25 [PATCH v4 0/3] i2c: Add Novatek NT72676 SoC I2C controller nina_kuo
@ 2026-07-30 7:25 ` nina_kuo
2026-07-30 7:43 ` sashiko-bot
2026-07-30 7:25 ` [PATCH v4 2/3] i2c: nt72676: Add Novatek NT72676 I2C controller driver nina_kuo
2026-07-30 7:25 ` [PATCH v4 3/3] MAINTAINERS: Add entry for " nina_kuo
2 siblings, 1 reply; 6+ messages in thread
From: nina_kuo @ 2026-07-30 7:25 UTC (permalink / raw)
To: andi.shyti, robh, krzk+dt, conor+dt, linux-i2c, devicetree,
linux-kernel
Cc: ben_huang, toby_chui, shihpei_hsu
From: Ben Huang <Ben_Huang@novatek.com.tw>
Add Devicetree binding documentation for Novatek NT72676 I2C
controller.
Signed-off-by: Ben Huang <Ben_Huang@novatek.com.tw>
Signed-off-by: Nina Kuo <Nina_Kuo@novatek.com.tw>
---
.../bindings/i2c/novatek,nt72676-i2c.yaml | 87 +++++++++++++++++++
1 file changed, 87 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/novatek,nt72676-i2c.yaml
diff --git a/Documentation/devicetree/bindings/i2c/novatek,nt72676-i2c.yaml b/Documentation/devicetree/bindings/i2c/novatek,nt72676-i2c.yaml
new file mode 100644
index 000000000000..8e2595f882c4
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/novatek,nt72676-i2c.yaml
@@ -0,0 +1,87 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/i2c/novatek,nt72676-i2c.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Novatek NT72676 I2C Controller
+
+maintainers:
+ - Ben Huang <ben_huang@novatek.com.tw>
+ - Jason JJ Wu <jason_jj_wu@novatek.com.tw>
+
+description: |
+ Novatek NT72676 SoC provides two I2C controller variants: generic I2C and
+ STBC I2C.
+ The generic I2C controllers are accessible only by the host CPU and driven
+ by a 96 MHz AHB clock.
+ On the other hand, the STBC I2C controllers can be controlled by either the
+ host CPU or STBC (Standby Controller, an 8051-based microcontroller) and
+ driven by a 12 MHz STBC clock. Authentication with STBC is required before
+ the host CPU can access the STBC I2C controller registers.
+
+properties:
+ compatible:
+ enum:
+ - novatek,nt72676-i2c
+ - novatek,nt72676-stbc-i2c
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clock-frequency:
+ default: 100000
+ enum: [ 100000, 400000 ]
+
+ novatek,stbc-syscon:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle to the STBC system controller registers used to authenticate
+ host access and switch ownership of the STBC I2C controller.
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+allOf:
+ - $ref: /schemas/i2c/i2c-controller.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: novatek,nt72676-stbc-i2c
+ then:
+ required:
+ - novatek,stbc-syscon
+ else:
+ properties:
+ novatek,stbc-syscon: false
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c@fd000000 {
+ compatible = "novatek,nt72676-i2c";
+ reg = <0xfd000000 0x100>;
+ interrupts = <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
+ clock-frequency = <400000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ i2c@fc040a00 {
+ compatible = "novatek,nt72676-stbc-i2c";
+ reg = <0xfc040a00 0x40>;
+ interrupts = <GIC_SPI 77 IRQ_TYPE_LEVEL_HIGH>;
+ novatek,stbc-syscon = <&sysreg_system_controller>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
--
2.40.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v4 2/3] i2c: nt72676: Add Novatek NT72676 I2C controller driver
2026-07-30 7:25 [PATCH v4 0/3] i2c: Add Novatek NT72676 SoC I2C controller nina_kuo
2026-07-30 7:25 ` [PATCH v4 1/3] dt-bindings: i2c: Add Novatek NT72676 " nina_kuo
@ 2026-07-30 7:25 ` nina_kuo
2026-07-30 7:42 ` sashiko-bot
2026-07-30 7:25 ` [PATCH v4 3/3] MAINTAINERS: Add entry for " nina_kuo
2 siblings, 1 reply; 6+ messages in thread
From: nina_kuo @ 2026-07-30 7:25 UTC (permalink / raw)
To: andi.shyti, robh, krzk+dt, conor+dt, linux-i2c, devicetree,
linux-kernel
Cc: ben_huang, toby_chui, shihpei_hsu
From: Ben Huang <Ben_Huang@novatek.com.tw>
This driver performs the fundamental read/write functions as an I2C
controller and supports Standard-mode and Fast-mode.
Default operation is Standard-mode.
Signed-off-by: Ben Huang <Ben_Huang@novatek.com.tw>
Signed-off-by: Nina Kuo <Nina_Kuo@novatek.com.tw>
---
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-nt72676.c | 683 +++++++++++++++++++++++++++++++
3 files changed, 694 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-nt72676.c
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index d35456994280..a94b8746d883 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -962,6 +962,16 @@ config I2C_NPCM
controllers.
Driver can also support slave mode (select I2C_SLAVE).
+config I2C_NT72676
+ tristate "Novatek NT72676 I2C controller"
+ depends on OF || COMPILE_TEST
+ help
+ Say Y here if you want to enable I2C bus controller on
+ Novatek NT72676 SoC.
+ This driver performs fundamental read/write functions
+ as an I2C bus controller and supports Standard-mode and
+ Fast-mode. Default operation is Standard-mode.
+
config I2C_OCORES
tristate "OpenCores I2C Controller"
help
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 3755c54b3d82..e5440de0fe14 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -90,6 +90,7 @@ obj-$(CONFIG_I2C_MV64XXX) += i2c-mv64xxx.o
obj-$(CONFIG_I2C_MXS) += i2c-mxs.o
obj-$(CONFIG_I2C_NOMADIK) += i2c-nomadik.o
obj-$(CONFIG_I2C_NPCM) += i2c-npcm7xx.o
+obj-$(CONFIG_I2C_NT72676) += i2c-nt72676.o
obj-$(CONFIG_I2C_OCORES) += i2c-ocores.o
obj-$(CONFIG_I2C_OMAP) += i2c-omap.o
obj-$(CONFIG_I2C_OWL) += i2c-owl.o
diff --git a/drivers/i2c/busses/i2c-nt72676.c b/drivers/i2c/busses/i2c-nt72676.c
new file mode 100644
index 000000000000..138b01dd98a7
--- /dev/null
+++ b/drivers/i2c/busses/i2c-nt72676.c
@@ -0,0 +1,683 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Novatek Microelectronics Corp.
+ * Author: Ben Huang <ben_huang@novatek.com.tw>
+ */
+
+#include <linux/completion.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define I2C_REG_CTRL 0x00
+#define I2C_REG_CLK 0x04
+#define I2C_REG_ACK 0x08
+#define I2C_REG_SIZE 0x0C
+#define I2C_REG_FIFO1 0x10
+#define I2C_REG_SUBADDR 0x20
+#define I2C_REG_PINGPONG 0x24
+#define I2C_REG_INTR 0x28
+#define I2C_REG_FIFO2 0x2C
+#define I2C_REG_DUTY 0x3C
+#define I2C_CLR_FIFO1 BIT(7)
+#define I2C_CLR_FIFO2 BIT(23)
+#define I2C_BUF_LITTLE_ENDIAN BIT(13)
+#define I2C_BUSY BIT(1)
+#define I2C_ENABLE BIT(2)
+#define I2C_REPEAT_ENABLE BIT(7)
+#define I2C_READ_OPERATION BIT(8)
+#define I2C_NACK BIT(24)
+#define I2C_CLOCK_DUTY_ENABLE BIT(21)
+#define I2C_CLOCK_STRETCH_ENABLE BIT(27)
+#define I2C_MASTER_CLK_STRETCH_ENABLE BIT(28)
+#define I2C_TRIGGER BIT(0)
+#define I2C_IRQ_FLAG GENMASK(15, 8)
+#define I2C_IRQ_ARBI_LOSS BIT(15)
+#define I2C_IRQ_SUS BIT(14)
+#define I2C_IRQ_ALERT BIT(13)
+#define I2C_IRQ_CLK_STR_TIMEOUT BIT(12)
+#define I2C_IRQ_NACK BIT(11)
+#define I2C_IRQ_RX_FULL BIT(10)
+#define I2C_IRQ_TX_EMPTY BIT(9)
+#define I2C_IRQ_FINISH BIT(8)
+#define I2C_IRQ_ENABLE_SETTING (GENMASK(20, 16) | GENMASK(4, 0))
+#define I2C_IRQ_DISABLE_SETTING 0x00000000
+#define I2C_SUBADDR_ENABLE BIT(6)
+#define I2C_16BITSUBADDR_ENABLE BIT(16)
+#define I2C_24BITSUBADDR_ENABLE BIT(17)
+#define I2C_32BITSUBADDR_ENABLE BIT(18)
+#define I2C_ACK_CTRL_COUNTER 0x000001E0
+#define I2C_TX_EMPTY_FIFO1 BIT(6)
+#define I2C_TX_EMPTY_FIFO2 BIT(22)
+#define I2C_RX_FULL_FIFO1 BIT(5)
+#define I2C_RX_FULL_FIFO2 BIT(21)
+
+#define STBC_REG_PSWD 0x0204
+#define STBC_REG_KEYPASS 0x0208
+#define STBC_REG_I2C_SWITCH 0x0220
+#define STBC_PSWD_DATA1 0x72682
+#define STBC_PSWD_DATA2 0x28627
+#define STBC_KEYPASS_ENABLE BIT(0)
+#define STBC_AGPIO_SWITCH_TO_CPU BIT(14)
+
+#define FIFO_CHUNK_SIZE 16
+#define FIFO_WORD_BYTES 4
+#define MAX_MSG_SIZE 4096
+
+enum {
+ SUBADDR_DISABLE,
+ SUBADDR_8BITS,
+ SUBADDR_16BITS,
+ SUBADDR_24BITS,
+ SUBADDR_32BITS,
+ SUBADDR_40BITS,
+ SUBADDR_48BITS,
+ SUBADDR_56BITS,
+ SUBADDR_64BITS
+};
+
+enum {
+ FIFO_1,
+ FIFO_2,
+ FIFO_ALL
+};
+
+struct nvt_i2c_compatible_data {
+ bool stbc_i2c;
+ unsigned int source_clock; /* Unit: Hz */
+ unsigned long reg_offset;
+};
+
+struct nvt_i2c_bus {
+ void __iomem *base;
+ struct i2c_adapter adapter;
+ struct device *dev;
+ struct completion msg_complete;
+ struct i2c_msg *msg;
+ unsigned int bus_clk_rate;
+ const struct nvt_i2c_compatible_data *comp_data;
+ struct regmap *stbc_regmap;
+ int irq;
+ /* used for xfer */
+ struct i2c_msg *current_msg;
+ int remaining;
+ int write_ptr;
+ int read_ptr;
+ int fifo_idx;
+ int error_code;
+};
+
+static void nt72676_i2c_writel(u32 val, struct nvt_i2c_bus *i2c, unsigned int reg)
+{
+ writel(val, i2c->base + i2c->comp_data->reg_offset + reg);
+}
+
+static u32 nt72676_i2c_readl(struct nvt_i2c_bus *i2c, unsigned int reg)
+{
+ return readl(i2c->base + i2c->comp_data->reg_offset + reg);
+}
+
+static int nvt_i2c_stbc_auth(struct nvt_i2c_bus *i2c)
+{
+ int ret = 0;
+ unsigned int val;
+
+ if (!i2c->comp_data->stbc_i2c)
+ return 0;
+
+ ret = regmap_write(i2c->stbc_regmap, STBC_REG_PSWD, STBC_PSWD_DATA1);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(i2c->stbc_regmap, STBC_REG_PSWD, STBC_PSWD_DATA2);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(i2c->stbc_regmap, STBC_REG_KEYPASS, STBC_KEYPASS_ENABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(i2c->stbc_regmap, STBC_REG_I2C_SWITCH, &val);
+ if (ret)
+ return ret;
+
+ val |= STBC_AGPIO_SWITCH_TO_CPU;
+ return regmap_write(i2c->stbc_regmap, STBC_REG_I2C_SWITCH, val);
+}
+
+static void nvt_i2c_reset(struct nvt_i2c_bus *i2c)
+{
+ nt72676_i2c_writel(nt72676_i2c_readl(i2c, I2C_REG_CTRL) & ~I2C_ENABLE,
+ i2c, I2C_REG_CTRL);
+ nt72676_i2c_writel(nt72676_i2c_readl(i2c, I2C_REG_CTRL) | I2C_ENABLE,
+ i2c, I2C_REG_CTRL);
+}
+
+static void nvt_i2c_set_clk(struct nvt_i2c_bus *i2c)
+{
+ unsigned int duty = 0;
+ unsigned int clk_div = 0;
+ unsigned int source_speed = i2c->comp_data->source_clock;
+
+ clk_div = source_speed / i2c->bus_clk_rate;
+ nt72676_i2c_writel(clk_div << 1, i2c, I2C_REG_CLK);
+
+ duty = (clk_div * 9 + 10) / 20;
+ nt72676_i2c_writel(duty << 16, i2c, I2C_REG_DUTY);
+
+ nt72676_i2c_writel(I2C_ACK_CTRL_COUNTER, i2c, I2C_REG_ACK);
+}
+
+static void nvt_i2c_set_subaddr(struct nvt_i2c_bus *i2c, struct i2c_msg *msg)
+{
+ unsigned int reg_ctrl;
+ unsigned int subaddr = 0;
+ int i = 0;
+
+ reg_ctrl = nt72676_i2c_readl(i2c, I2C_REG_CTRL);
+ reg_ctrl &= ~(I2C_16BITSUBADDR_ENABLE |
+ I2C_24BITSUBADDR_ENABLE |
+ I2C_32BITSUBADDR_ENABLE);
+
+ if (msg && msg->len > 0 && msg->len <= 4 && msg->buf) {
+ reg_ctrl |= I2C_SUBADDR_ENABLE;
+
+ switch (msg->len) {
+ case SUBADDR_8BITS:
+ break;
+ case SUBADDR_16BITS:
+ reg_ctrl |= I2C_16BITSUBADDR_ENABLE;
+ break;
+ case SUBADDR_24BITS:
+ reg_ctrl |= I2C_24BITSUBADDR_ENABLE;
+ break;
+ case SUBADDR_32BITS:
+ reg_ctrl |= I2C_32BITSUBADDR_ENABLE;
+ break;
+ default:
+ return;
+ }
+
+ for (i = 0; i < msg->len; i++)
+ subaddr |= msg->buf[i] << (8 * (msg->len - 1 - i));
+ nt72676_i2c_writel(subaddr, i2c, I2C_REG_SUBADDR);
+ } else {
+ reg_ctrl &= ~I2C_SUBADDR_ENABLE;
+ }
+
+ nt72676_i2c_writel(reg_ctrl, i2c, I2C_REG_CTRL);
+}
+
+static int nvt_i2c_init(struct nvt_i2c_bus *i2c)
+{
+ if (i2c->comp_data->stbc_i2c) {
+ int ret = nvt_i2c_stbc_auth(i2c);
+
+ if (ret) {
+ dev_err(i2c->dev, "[%s] STBC authentication failed, ret = %d\n",
+ i2c->adapter.name, ret);
+ return ret;
+ }
+ }
+ nvt_i2c_set_clk(i2c);
+ nt72676_i2c_writel(I2C_BUF_LITTLE_ENDIAN, i2c, I2C_REG_PINGPONG);
+ nt72676_i2c_writel(I2C_IRQ_ENABLE_SETTING, i2c, I2C_REG_INTR);
+
+ return 0;
+}
+
+static int nvt_i2c_suspend(struct device *dev)
+{
+ struct nvt_i2c_bus *i2c = dev_get_drvdata(dev);
+
+ if (i2c) {
+ i2c_mark_adapter_suspended(&i2c->adapter);
+ i2c->current_msg = NULL;
+ nt72676_i2c_writel(I2C_IRQ_DISABLE_SETTING, i2c, I2C_REG_INTR);
+ }
+
+ return 0;
+}
+
+static int nvt_i2c_resume(struct device *dev)
+{
+ struct nvt_i2c_bus *i2c = dev_get_drvdata(dev);
+
+ if (i2c) {
+ int ret = nvt_i2c_init(i2c);
+
+ if (ret)
+ return ret;
+ i2c_mark_adapter_resumed(&i2c->adapter);
+ }
+
+ return 0;
+}
+
+static void nvt_i2c_clear_fifo(struct nvt_i2c_bus *i2c, unsigned int which)
+{
+ unsigned int regval = nt72676_i2c_readl(i2c, I2C_REG_PINGPONG);
+
+ switch (which) {
+ case FIFO_1:
+ regval |= I2C_CLR_FIFO1;
+ break;
+ case FIFO_2:
+ regval |= I2C_CLR_FIFO2;
+ break;
+ case FIFO_ALL:
+ regval |= I2C_CLR_FIFO1 | I2C_CLR_FIFO2;
+ break;
+ default:
+ break;
+ }
+ nt72676_i2c_writel(regval, i2c, I2C_REG_PINGPONG);
+}
+
+static void nvt_i2c_write_fifo(struct nvt_i2c_bus *i2c,
+ unsigned int fifo_reg,
+ const unsigned char *buf,
+ unsigned int buf_offset,
+ unsigned int length)
+{
+ unsigned int reg_idx = 0, copy_bytes = 0, j = 0, value = 0;
+
+ while (length > 0) {
+ value = 0;
+ copy_bytes = length >= FIFO_WORD_BYTES ? FIFO_WORD_BYTES : length;
+ for (j = 0; j < copy_bytes; j++)
+ value |= ((unsigned int)buf[buf_offset + j]) << (j * 8);
+
+ nt72676_i2c_writel(value, i2c, fifo_reg + reg_idx * 4);
+ buf_offset += copy_bytes;
+ length -= copy_bytes;
+ reg_idx++;
+ }
+}
+
+static void nvt_i2c_read_fifo(struct nvt_i2c_bus *i2c,
+ unsigned int fifo_reg,
+ unsigned char *buf,
+ unsigned int buf_offset,
+ unsigned int length)
+{
+ unsigned int reg_idx = 0, copy_bytes = 0, j = 0, value = 0;
+
+ while (length > 0) {
+ value = nt72676_i2c_readl(i2c, fifo_reg + reg_idx * 4);
+ copy_bytes = length >= FIFO_WORD_BYTES ? FIFO_WORD_BYTES : length;
+ for (j = 0; j < copy_bytes; j++)
+ buf[buf_offset + j] = (unsigned char)(value >> (j * 8));
+
+ buf_offset += copy_bytes;
+ length -= copy_bytes;
+ reg_idx++;
+ }
+}
+
+static void nvt_i2c_handle(struct nvt_i2c_bus *i2c, struct i2c_msg *msg, bool is_read)
+{
+ unsigned int bytes, fiforeg;
+
+ if (!i2c || !msg || !msg->buf || msg->len == 0 || msg->len > MAX_MSG_SIZE) {
+ dev_err(i2c->dev, "I2C invalid msg: i2c=%p, msg=%p, buf=%p, len=%d\n",
+ i2c, msg, msg ? msg->buf : NULL, msg ? msg->len : 0);
+ if (i2c) {
+ dev_err(i2c->dev, "[%s]: i2c_handle\n", i2c->adapter.name);
+ i2c->error_code = -EINVAL;
+ }
+
+ return;
+ }
+
+ bytes = i2c->remaining > FIFO_CHUNK_SIZE ? FIFO_CHUNK_SIZE : i2c->remaining;
+ fiforeg = i2c->fifo_idx == 0 ? I2C_REG_FIFO1 : I2C_REG_FIFO2;
+
+ if (is_read) {
+ nvt_i2c_read_fifo(i2c, fiforeg, msg->buf, i2c->read_ptr, bytes);
+ i2c->read_ptr += bytes;
+ } else {
+ nvt_i2c_write_fifo(i2c, fiforeg, msg->buf, i2c->write_ptr, bytes);
+ i2c->write_ptr += bytes;
+ }
+ nvt_i2c_clear_fifo(i2c, i2c->fifo_idx);
+ i2c->remaining -= bytes;
+ i2c->fifo_idx ^= 1;
+}
+
+static irqreturn_t nvt_i2c_isr(int irq, void *dev_id)
+{
+ struct nvt_i2c_bus *i2c = dev_id;
+ struct i2c_msg *msg = i2c->current_msg;
+ unsigned int status = nt72676_i2c_readl(i2c, I2C_REG_INTR);
+ unsigned int clr = 0;
+ int do_complete = 0;
+
+ if (!(status & I2C_IRQ_FLAG) || !i2c->current_msg)
+ return IRQ_NONE;
+
+ if (status & I2C_IRQ_NACK) {
+ i2c->error_code = -ENXIO;
+ clr |= I2C_IRQ_NACK << 8;
+ } else if (status & I2C_IRQ_RX_FULL) {
+ if (i2c->remaining > 0)
+ nvt_i2c_handle(i2c, msg, true);
+ clr |= I2C_IRQ_RX_FULL << 8;
+ } else if (status & I2C_IRQ_TX_EMPTY) {
+ if (i2c->remaining > 0)
+ nvt_i2c_handle(i2c, msg, false);
+ clr |= I2C_IRQ_TX_EMPTY << 8;
+ } else if (status & I2C_IRQ_FINISH) {
+ if (i2c->remaining > 0 && (msg->flags & I2C_M_RD))
+ nvt_i2c_handle(i2c, msg, true);
+ clr |= I2C_IRQ_FINISH << 8;
+ do_complete = 1;
+ }
+ if (i2c->error_code)
+ do_complete = 1;
+
+ nt72676_i2c_writel(status | clr, i2c, I2C_REG_INTR);
+ if (do_complete)
+ complete(&i2c->msg_complete);
+
+ return IRQ_HANDLED;
+}
+
+static void nvt_i2c_ctrl_init(struct nvt_i2c_bus *i2c)
+{
+ int i = 0;
+
+ nt72676_i2c_writel(0, i2c, I2C_REG_CTRL);
+ for (i = 0; i < 4; i++) {
+ nt72676_i2c_writel(0, i2c, I2C_REG_FIFO1 + i * 4);
+ nt72676_i2c_writel(0, i2c, I2C_REG_FIFO2 + i * 4);
+ }
+ nvt_i2c_clear_fifo(i2c, FIFO_ALL);
+ reinit_completion(&i2c->msg_complete);
+}
+
+static int nvt_i2c_check_msg(const struct i2c_msg *msg)
+{
+ if (!msg || !msg->buf || msg->len == 0 || msg->len > MAX_MSG_SIZE)
+ return -EINVAL;
+
+ return 0;
+}
+
+static void nvt_i2c_prepare_xfer(struct nvt_i2c_bus *i2c, struct i2c_msg *msg)
+{
+ i2c->remaining = msg->len;
+ i2c->current_msg = msg;
+ i2c->write_ptr = 0;
+ i2c->read_ptr = 0;
+ i2c->error_code = 0;
+ i2c->fifo_idx = 0;
+}
+
+static int nvt_i2c_write(struct nvt_i2c_bus *i2c, struct i2c_msg *msg)
+{
+ const unsigned char *buf = msg->buf;
+ int ret, offset = 0, write_bytes, fifo_num;
+ unsigned int ctrl_mask;
+
+ ret = nvt_i2c_check_msg(msg);
+ if (ret)
+ return ret;
+
+ nvt_i2c_prepare_xfer(i2c, msg);
+
+ nt72676_i2c_writel((msg->len * 8) << 8, i2c, I2C_REG_SIZE);
+
+ /* Write FIFO data first */
+ for (fifo_num = 0; fifo_num < FIFO_ALL && offset < msg->len; fifo_num++) {
+ write_bytes = msg->len - offset > FIFO_CHUNK_SIZE ?
+ FIFO_CHUNK_SIZE : msg->len - offset;
+ nvt_i2c_write_fifo(i2c, fifo_num == 0 ? I2C_REG_FIFO1 : I2C_REG_FIFO2,
+ buf, offset, write_bytes);
+ offset += write_bytes;
+ }
+ i2c->write_ptr = offset;
+ i2c->remaining = msg->len - offset;
+ i2c->fifo_idx = 0;
+
+ ctrl_mask = nt72676_i2c_readl(i2c, I2C_REG_CTRL);
+ ctrl_mask |= (((msg->addr << 1) << 8) | I2C_ENABLE |
+ I2C_CLOCK_DUTY_ENABLE | I2C_CLOCK_STRETCH_ENABLE |
+ I2C_MASTER_CLK_STRETCH_ENABLE | I2C_TRIGGER);
+ nt72676_i2c_writel(ctrl_mask, i2c, I2C_REG_CTRL);
+
+ ret = wait_for_completion_timeout(&i2c->msg_complete, i2c->adapter.timeout);
+ if (ret == 0) {
+ i2c->error_code = -ETIMEDOUT;
+ nvt_i2c_reset(i2c);
+ }
+ if (i2c->error_code)
+ dev_err(i2c->dev, "[%s]: write failed (err:%d); SA[0x%X]\n",
+ i2c->adapter.name, i2c->error_code, msg->addr);
+
+ i2c->current_msg = NULL;
+
+ return i2c->error_code;
+}
+
+static int nvt_i2c_read(struct nvt_i2c_bus *i2c, struct i2c_msg *msg)
+{
+ unsigned int ctrl_mask;
+ int ret;
+
+ ret = nvt_i2c_check_msg(msg);
+ if (ret)
+ return ret;
+
+ nvt_i2c_prepare_xfer(i2c, msg);
+
+ nt72676_i2c_writel((msg->len * 8) << 8, i2c, I2C_REG_SIZE);
+
+ ctrl_mask = nt72676_i2c_readl(i2c, I2C_REG_CTRL);
+ ctrl_mask |= (((msg->addr << 1) << 8) | I2C_ENABLE |
+ I2C_REPEAT_ENABLE | I2C_READ_OPERATION |
+ I2C_CLOCK_DUTY_ENABLE | I2C_CLOCK_STRETCH_ENABLE |
+ I2C_MASTER_CLK_STRETCH_ENABLE | I2C_TRIGGER);
+ nt72676_i2c_writel(ctrl_mask, i2c, I2C_REG_CTRL);
+
+ ret = wait_for_completion_timeout(&i2c->msg_complete, i2c->adapter.timeout);
+ if (ret == 0) {
+ i2c->error_code = -ETIMEDOUT;
+ nvt_i2c_reset(i2c);
+ }
+ if (i2c->error_code)
+ dev_err(i2c->dev, "[%s]: read failed (err:%d); SA[0x%X]\n",
+ i2c->adapter.name, i2c->error_code, msg->addr);
+
+ i2c->current_msg = NULL;
+
+ return i2c->error_code;
+}
+
+static int nvt_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg msgs[],
+ int num)
+{
+ struct nvt_i2c_bus *i2c = i2c_get_adapdata(adap);
+ int ret = 0, i = 0;
+ struct i2c_msg *msg = NULL;
+
+ nvt_i2c_ctrl_init(i2c);
+
+ if (num == 2) {
+ nvt_i2c_set_subaddr(i2c, &msgs[0]);
+ msg = &msgs[1];
+
+ if (msg->flags & I2C_M_RD)
+ ret = nvt_i2c_read(i2c, msg);
+ else
+ ret = nvt_i2c_write(i2c, msg);
+ } else {
+ nvt_i2c_set_subaddr(i2c, NULL);
+
+ for (i = 0; i < num; i++) {
+ msg = &msgs[i];
+ if (msg->flags & I2C_M_RD)
+ ret = nvt_i2c_read(i2c, msg);
+ else
+ ret = nvt_i2c_write(i2c, msg);
+ if (ret < 0)
+ break;
+ }
+ }
+
+ if (ret < 0)
+ return ret;
+ return num;
+}
+
+static u32 nvt_i2c_func(struct i2c_adapter *adap)
+{
+ return (I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL) & ~I2C_FUNC_SMBUS_QUICK;
+}
+
+static const struct i2c_algorithm nvt_i2c_algo = {
+ .master_xfer = nvt_i2c_xfer,
+ .functionality = nvt_i2c_func,
+};
+
+static int nvt_i2c_parse_dts(struct nvt_i2c_bus *i2c)
+{
+ int ret;
+ struct device *dev = i2c->dev;
+ struct device_node *np = dev->of_node;
+
+ i2c->comp_data = of_device_get_match_data(dev);
+
+ /* read DTS(novatek,stbc-syscon) for STBC I2C */
+ if (i2c->comp_data->stbc_i2c) {
+ i2c->stbc_regmap = syscon_regmap_lookup_by_phandle(i2c->dev->of_node,
+ "novatek,stbc-syscon");
+ if (IS_ERR(i2c->stbc_regmap)) {
+ dev_err(i2c->dev, "Failed to get STBC syscon\n");
+ return PTR_ERR(i2c->stbc_regmap);
+ }
+ }
+
+ /* read DTS(clock-frequency) */
+ ret = of_property_read_u32(np, "clock-frequency", &i2c->bus_clk_rate);
+ if (ret || !i2c->bus_clk_rate) {
+ dev_info(i2c->dev, "Not set dtb clock-frequency, set default 100kHz\n");
+ i2c->bus_clk_rate = I2C_MAX_STANDARD_MODE_FREQ;
+ }
+
+ return 0;
+}
+
+static const struct nvt_i2c_compatible_data nt72676_i2c_data = {
+ .stbc_i2c = false,
+ .source_clock = 96000000,
+ .reg_offset = 0xC0,
+};
+
+static const struct nvt_i2c_compatible_data nt72676_stbc_i2c_data = {
+ .stbc_i2c = true,
+ .source_clock = 12000000,
+ .reg_offset = 0,
+};
+
+static const struct of_device_id nvt_i2c_of_match[] = {
+ { .compatible = "novatek,nt72676-i2c", .data = &nt72676_i2c_data },
+ { .compatible = "novatek,nt72676-stbc-i2c", .data = &nt72676_stbc_i2c_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, nvt_i2c_of_match);
+
+static int nvt_i2c_probe(struct platform_device *pdev)
+{
+ struct nvt_i2c_bus *i2c;
+ int ret;
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
+ if (!i2c)
+ return -ENOMEM;
+
+ i2c->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(i2c->base))
+ return PTR_ERR(i2c->base);
+
+ init_completion(&i2c->msg_complete);
+ i2c->dev = &pdev->dev;
+
+ ret = nvt_i2c_parse_dts(i2c);
+ if (ret)
+ return ret;
+
+ i2c->irq = platform_get_irq(pdev, 0);
+ if (i2c->irq < 0)
+ return i2c->irq;
+
+ ret = devm_request_irq(&pdev->dev, i2c->irq, nvt_i2c_isr,
+ IRQF_SHARED | IRQF_TRIGGER_HIGH, dev_name(&pdev->dev), i2c);
+ if (ret) {
+ dev_err(&pdev->dev, "[%s] devm_request_irq fail\n", dev_name(&pdev->dev));
+ return ret;
+ }
+
+ ret = nvt_i2c_init(i2c);
+ if (ret) {
+ dev_err(&pdev->dev, "[%s] nvt_i2c_init fail, ret = %d\n",
+ dev_name(&pdev->dev), ret);
+ return ret;
+ }
+
+ /* Setup I2C adapter */
+ i2c->adapter.owner = THIS_MODULE;
+ i2c->adapter.algo = &nvt_i2c_algo;
+ i2c->adapter.dev.of_node = pdev->dev.of_node;
+ i2c->adapter.dev.parent = &pdev->dev;
+ i2c->adapter.timeout = 3 * HZ;
+ strscpy(i2c->adapter.name, dev_name(&pdev->dev), sizeof(i2c->adapter.name));
+ i2c_set_adapdata(&i2c->adapter, i2c);
+
+ ret = i2c_add_adapter(&i2c->adapter);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to add adapter\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, i2c);
+
+ return 0;
+}
+
+static void nvt_i2c_remove(struct platform_device *pdev)
+{
+ struct nvt_i2c_bus *i2c = platform_get_drvdata(pdev);
+
+ nt72676_i2c_writel(I2C_IRQ_DISABLE_SETTING, i2c, I2C_REG_INTR);
+ nt72676_i2c_writel(nt72676_i2c_readl(i2c, I2C_REG_CTRL) & ~I2C_ENABLE,
+ i2c, I2C_REG_CTRL);
+ i2c_del_adapter(&i2c->adapter);
+}
+
+static const struct dev_pm_ops nvt_i2c_pm_ops = {
+ .resume_early = nvt_i2c_resume,
+ .suspend_late = nvt_i2c_suspend,
+};
+
+static struct platform_driver nvt_i2c_driver = {
+ .probe = nvt_i2c_probe,
+ .remove = nvt_i2c_remove,
+ .driver = {
+ .name = "nt72676_i2c",
+ .pm = &nvt_i2c_pm_ops,
+ .of_match_table = of_match_ptr(nvt_i2c_of_match),
+ },
+};
+module_platform_driver(nvt_i2c_driver);
+
+MODULE_DESCRIPTION("Novatek NT72676 SoC I2C Bus Driver");
+MODULE_AUTHOR("Ben Huang <ben_huang@novatek.com.tw>");
+MODULE_LICENSE("GPL");
--
2.40.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v4 3/3] MAINTAINERS: Add entry for Novatek NT72676 I2C controller driver
2026-07-30 7:25 [PATCH v4 0/3] i2c: Add Novatek NT72676 SoC I2C controller nina_kuo
2026-07-30 7:25 ` [PATCH v4 1/3] dt-bindings: i2c: Add Novatek NT72676 " nina_kuo
2026-07-30 7:25 ` [PATCH v4 2/3] i2c: nt72676: Add Novatek NT72676 I2C controller driver nina_kuo
@ 2026-07-30 7:25 ` nina_kuo
2 siblings, 0 replies; 6+ messages in thread
From: nina_kuo @ 2026-07-30 7:25 UTC (permalink / raw)
To: andi.shyti, robh, krzk+dt, conor+dt, linux-i2c, devicetree,
linux-kernel
Cc: ben_huang, toby_chui, shihpei_hsu
From: Ben Huang <Ben_Huang@novatek.com.tw>
Add a maintainer entry for Novatek NT72676 I2C controller driver.
Signed-off-by: Ben Huang <Ben_Huang@novatek.com.tw>
Signed-off-by: Nina Kuo <Nina_Kuo@novatek.com.tw>
---
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 2f9472c1a090..f12020622675 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19239,6 +19239,13 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/nolibc/linux-nolibc.git
F: tools/include/nolibc/
F: tools/testing/selftests/nolibc/
+NOVATEK NT72676 I2C CONTROLLER DRIVER
+M: Ben Huang <ben_huang@novatek.com.tw>
+L: linux-i2c@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/i2c/novatek,nt72676-i2c.yaml
+F: drivers/i2c/busses/i2c-nt72676.c
+
NOVATEK NVT-TS I2C TOUCHSCREEN DRIVER
M: Hans de Goede <hansg@kernel.org>
L: linux-input@vger.kernel.org
--
2.40.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v4 2/3] i2c: nt72676: Add Novatek NT72676 I2C controller driver
2026-07-30 7:25 ` [PATCH v4 2/3] i2c: nt72676: Add Novatek NT72676 I2C controller driver nina_kuo
@ 2026-07-30 7:42 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-07-30 7:42 UTC (permalink / raw)
To: nina_kuo; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] The driver unconditionally treats any 2-message transfer as a subaddress operation, breaking I2C protocol semantics and leaking memory.
- [High] The interrupt handler improperly handles flags and active transfers, causing unacknowledged hardware interrupts and infinite interrupt storms.
- [High] Incorrect teardown order in `nvt_i2c_remove()` disables the I2C controller hardware before unregistering the adapter.
- [High] The driver fails to clear previous address and operation bits in `I2C_REG_CTRL` across multiple messages, corrupting I2C target addresses and read/write commands.
- [High] A race condition between the transfer timeout path and the interrupt handler leads to a Use-After-Free (UAF) of the I2C message buffer.
- [Medium] The driver fails to disable hardware interrupts in the probe error path, risking unhandled interrupts on a shared line.
- [Medium] The driver unconditionally dereferences the result of `of_device_get_match_data()` without checking for `NULL`, leading to a kernel crash on non-Device Tree platforms.
--
--- Patch [2]: [PATCH v4 2/3] i2c: nt72676: Add Novatek NT72676 I2C controller driver ---
commit 24e2f090fd92d0b0a276a1df0dc4a6e78c1ccda9
Author: Ben Huang <Ben_Huang@novatek.com.tw>
i2c: nt72676: Add Novatek NT72676 I2C controller driver
This commit adds the I2C controller driver for the Novatek NT72676 SoC.
It supports Standard-mode and Fast-mode I2C operations as a bus master.
> diff --git a/drivers/i2c/busses/i2c-nt72676.c b/drivers/i2c/busses/i2c-nt72676.c
> new file mode 100644
> index 0000000000000..138b01dd98a7f
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-nt72676.c
> @@ -0,0 +1,683 @@
[ ... ]
> +static irqreturn_t nvt_i2c_isr(int irq, void *dev_id)
> +{
> + struct nvt_i2c_bus *i2c = dev_id;
> + struct i2c_msg *msg = i2c->current_msg;
> + unsigned int status = nt72676_i2c_readl(i2c, I2C_REG_INTR);
> + unsigned int clr = 0;
> + int do_complete = 0;
> +
> + if (!(status & I2C_IRQ_FLAG) || !i2c->current_msg)
> + return IRQ_NONE;
[Severity: High]
Will this cause an interrupt storm?
If a spurious interrupt occurs or an interrupt fires after a timeout when
current_msg is NULL, the handler returns IRQ_NONE without clearing the
interrupt flags.
Since the interrupt is requested with IRQF_TRIGGER_HIGH, failing to clear the
hardware state will leave the interrupt line continuously asserted.
[ ... ]
> +static int nvt_i2c_write(struct nvt_i2c_bus *i2c, struct i2c_msg *msg)
> +{
[ ... ]
> + ctrl_mask = nt72676_i2c_readl(i2c, I2C_REG_CTRL);
> + ctrl_mask |= (((msg->addr << 1) << 8) | I2C_ENABLE |
> + I2C_CLOCK_DUTY_ENABLE | I2C_CLOCK_STRETCH_ENABLE |
> + I2C_MASTER_CLK_STRETCH_ENABLE | I2C_TRIGGER);
> + nt72676_i2c_writel(ctrl_mask, i2c, I2C_REG_CTRL);
[Severity: High]
Does this construct a valid control mask for subsequent messages?
The new address and flags are bitwise-ORed onto the existing I2C_REG_CTRL
value without clearing the previous bits.
In a multi-message transfer where subsequent messages have different addresses
or read/write directions, the accumulated bits might corrupt the target
address and operation commands.
> +
> + ret = wait_for_completion_timeout(&i2c->msg_complete, i2c->adapter.timeout);
> + if (ret == 0) {
> + i2c->error_code = -ETIMEDOUT;
> + nvt_i2c_reset(i2c);
> + }
[Severity: High]
Is there a race condition between this timeout path and the interrupt handler?
If a transfer times out exactly as the hardware interrupt fires, the timeout
path resets the hardware and returns to the caller, which may then free the
message buffer.
Because this does not synchronize with the ISR (for instance, by disabling the
interrupt or taking a lock), the ISR can concurrently access i2c->current_msg
and dereference a freed buffer when handling the late interrupt.
[ ... ]
> +static int nvt_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg msgs[],
> + int num)
> +{
> + struct nvt_i2c_bus *i2c = i2c_get_adapdata(adap);
> + int ret = 0, i = 0;
> + struct i2c_msg *msg = NULL;
> +
> + nvt_i2c_ctrl_init(i2c);
> +
> + if (num == 2) {
> + nvt_i2c_set_subaddr(i2c, &msgs[0]);
> + msg = &msgs[1];
[Severity: High]
Does this correctly handle all two-message transfers?
The driver unconditionally treats any 2-message transfer as a subaddress
write operation. It reads from msgs[0].buf to populate the subaddress
register and then drops msgs[0].
If a client initiates a standard two-message sequence (like a sequential
read), the first message buffer might be uninitialized, and the first message
will be dropped, breaking standard I2C protocol semantics.
[ ... ]
> +static int nvt_i2c_parse_dts(struct nvt_i2c_bus *i2c)
> +{
> + int ret;
> + struct device *dev = i2c->dev;
> + struct device_node *np = dev->of_node;
> +
> + i2c->comp_data = of_device_get_match_data(dev);
> +
> + /* read DTS(novatek,stbc-syscon) for STBC I2C */
> + if (i2c->comp_data->stbc_i2c) {
[Severity: Medium]
Can of_device_get_match_data() return NULL here?
If the driver is instantiated via sysfs bind or legacy platform matching
on a non-Device-Tree platform, this will return NULL.
Dereferencing i2c->comp_data immediately after will cause a NULL pointer
dereference.
[ ... ]
> +static int nvt_i2c_probe(struct platform_device *pdev)
> +{
[ ... ]
> + ret = i2c_add_adapter(&i2c->adapter);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to add adapter\n");
> + return ret;
> + }
[Severity: Medium]
Should the hardware interrupts be disabled before returning here?
If i2c_add_adapter() fails, the probe function returns an error without
disabling the I2C interrupts in the hardware.
The devres cleanup will free the interrupt handler, but the hardware might
still be enabled and assert the shared IRQ line, resulting in an unhandled
interrupt storm.
[ ... ]
> +static void nvt_i2c_remove(struct platform_device *pdev)
> +{
> + struct nvt_i2c_bus *i2c = platform_get_drvdata(pdev);
> +
> + nt72676_i2c_writel(I2C_IRQ_DISABLE_SETTING, i2c, I2C_REG_INTR);
> + nt72676_i2c_writel(nt72676_i2c_readl(i2c, I2C_REG_CTRL) & ~I2C_ENABLE,
> + i2c, I2C_REG_CTRL);
> + i2c_del_adapter(&i2c->adapter);
> +}
[Severity: High]
Is this teardown order correct?
The hardware is disabled before i2c_del_adapter() unregisters the clients.
Many client drivers need to perform I2C transfers in their remove() functions
to gracefully power down devices. Disabling the controller hardware first
guarantees that these shutdown transfers will fail.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260730072529.492398-1-nina_kuo@novatek.com.tw?part=2
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v4 1/3] dt-bindings: i2c: Add Novatek NT72676 I2C controller
2026-07-30 7:25 ` [PATCH v4 1/3] dt-bindings: i2c: Add Novatek NT72676 " nina_kuo
@ 2026-07-30 7:43 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-07-30 7:43 UTC (permalink / raw)
To: nina_kuo; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The dt_binding_check target will fail to compile the example due to an unresolved phandle reference.
--
--- Patch [1]: [PATCH v4 1/3] dt-bindings: i2c: Add Novatek NT72676 I2C controller ---
commit 232c18d6988d7363aafdb8a90285eb1bffed9745
Author: Ben Huang <Ben_Huang@novatek.com.tw>
dt-bindings: i2c: Add Novatek NT72676 I2C controller
Add Devicetree binding documentation for Novatek NT72676 I2C
controller.
> diff --git a/Documentation/devicetree/bindings/i2c/novatek,nt72676-i2c.yaml b/Documentation/devicetree/bindings/i2c/novatek,nt72676-i2c.yaml
> new file mode 100644
> index 0000000000000..8e2595f882c44
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/novatek,nt72676-i2c.yaml
[ ... ]
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + i2c@fd000000 {
> + compatible = "novatek,nt72676-i2c";
> + reg = <0xfd000000 0x100>;
> + interrupts = <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
> + clock-frequency = <400000>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> + };
> +
> + i2c@fc040a00 {
> + compatible = "novatek,nt72676-stbc-i2c";
> + reg = <0xfc040a00 0x40>;
> + interrupts = <GIC_SPI 77 IRQ_TYPE_LEVEL_HIGH>;
> + novatek,stbc-syscon = <&sysreg_system_controller>;
[Severity: Low]
Will dt_binding_check fail to compile the example here? It looks like the
label sysreg_system_controller is not defined anywhere in the example node
tree.
Should a dummy node with the sysreg_system_controller label be added to the
example so the compiler can resolve this phandle?
> + #address-cells = <1>;
> + #size-cells = <0>;
> + };
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260730072529.492398-1-nina_kuo@novatek.com.tw?part=1
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-07-30 7:43 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-30 7:25 [PATCH v4 0/3] i2c: Add Novatek NT72676 SoC I2C controller nina_kuo
2026-07-30 7:25 ` [PATCH v4 1/3] dt-bindings: i2c: Add Novatek NT72676 " nina_kuo
2026-07-30 7:43 ` sashiko-bot
2026-07-30 7:25 ` [PATCH v4 2/3] i2c: nt72676: Add Novatek NT72676 I2C controller driver nina_kuo
2026-07-30 7:42 ` sashiko-bot
2026-07-30 7:25 ` [PATCH v4 3/3] MAINTAINERS: Add entry for " nina_kuo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox