* [PATCH v5 0/2] i2c: Add driver support Amlogic A9 I2C
@ 2026-11-03 2:52 Xianwei Zhao via B4 Relay
2026-11-03 2:52 ` [PATCH v5 1/2] dt-bindings: i2c: add Amlogic A9 I2C controller Xianwei Zhao via B4 Relay
2026-11-03 2:52 ` [PATCH v5 2/2] i2c: amlogic: Add Amlogic A9 I2C controller driver Xianwei Zhao via B4 Relay
0 siblings, 2 replies; 4+ messages in thread
From: Xianwei Zhao via B4 Relay @ 2026-11-03 2:52 UTC (permalink / raw)
To: Junyi Zhao, Andi Shyti, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Xianwei Zhao
Cc: Junyi Zhao, linux-i2c, linux-amlogic, devicetree, linux-kernel,
Xianwei Zhao, Conor Dooley
Add a Device Tree binding and driver for the I2C controller found on the
Amlogic A9 SoC.
Signed-off-by: Xianwei Zhao <xianwei.zhao@amlogic.com>
---
Changes in v5:
- Mask out I2C_FUNC_SMBUS_QUICK and return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK).
- Link to v4: https://lore.kernel.org/r/20261102-a9-i2c-v4-0-5a169215b8e9@amlogic.com
Changes in v4:
- Repocess xfer len equal 0 use I2C_AQ_NO_ZERO_LEN, and Optimized the function.
- Disable A9_RX_THRESH_READ/A9_TX_THRESH_WRITE enables of irq when xfer timeout.
- Use guard(spinlock_irqsave) instead for direct call spinlock in xfer timeout process.
- Link to v3: https://lore.kernel.org/r/20261029-a9-i2c-v3-0-7b30ccc00533@amlogic.com
Changes in v3:
- Based on the questions raised by the robot, the data flow was reorganized.
- Some processing was optimized according to Andi's and Markus's suggestions.
- Link to v2: https://lore.kernel.org/r/20261022-a9-i2c-v2-0-45e3cef175bf@amlogic.com
Changes in v2:
- dtbinding: make some minor modifications, suggestted by Conor and robot.
- driver: Make some adjustments, including macro definitions, format adjustments,
adding quirks, process optimization, and so on. According to Andi and robot's suggestion
- Give up MAINTAINERS file the separate submission, make it together with the driver.
- Link to v1: https://lore.kernel.org/r/20260924-a9-i2c-v1-0-b8ad9b46f4a8@amlogic.com
---
Junyi Zhao (1):
i2c: amlogic: Add Amlogic A9 I2C controller driver
Xianwei Zhao (1):
dt-bindings: i2c: add Amlogic A9 I2C controller
.../devicetree/bindings/i2c/amlogic,a9-i2c.yaml | 56 +++
MAINTAINERS | 9 +
drivers/i2c/busses/Kconfig | 8 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-amlogic-a9.c | 431 +++++++++++++++++++++
5 files changed, 505 insertions(+)
---
base-commit: 4ec2365c08de306f52650d8736aef5862baedb8d
change-id: 20260911-a9-i2c-24478e22e683
Best regards,
--
Xianwei Zhao <xianwei.zhao@amlogic.com>
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH v5 1/2] dt-bindings: i2c: add Amlogic A9 I2C controller 2026-11-03 2:52 [PATCH v5 0/2] i2c: Add driver support Amlogic A9 I2C Xianwei Zhao via B4 Relay @ 2026-11-03 2:52 ` Xianwei Zhao via B4 Relay 2026-11-03 2:52 ` [PATCH v5 2/2] i2c: amlogic: Add Amlogic A9 I2C controller driver Xianwei Zhao via B4 Relay 1 sibling, 0 replies; 4+ messages in thread From: Xianwei Zhao via B4 Relay @ 2026-11-03 2:52 UTC (permalink / raw) To: Junyi Zhao, Andi Shyti, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Xianwei Zhao Cc: Junyi Zhao, linux-i2c, linux-amlogic, devicetree, linux-kernel, Xianwei Zhao, Conor Dooley From: Xianwei Zhao <xianwei.zhao@amlogic.com> Add a Device Tree binding for the I2C controller found on the Amlogic A9 SoC. Acked-by: Conor Dooley <conor.dooley@microchip.com> Signed-off-by: Xianwei Zhao <xianwei.zhao@amlogic.com> --- .../devicetree/bindings/i2c/amlogic,a9-i2c.yaml | 56 ++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/Documentation/devicetree/bindings/i2c/amlogic,a9-i2c.yaml b/Documentation/devicetree/bindings/i2c/amlogic,a9-i2c.yaml new file mode 100644 index 000000000000..31ddc6d23732 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/amlogic,a9-i2c.yaml @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/i2c/amlogic,a9-i2c.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Amlogic A9 I2C Controller + +maintainers: + - Xianwei Zhao <xianwei.zhao@amlogic.com> + - Junyi Zhao <junyi.zhao@amlogic.com> + +allOf: + - $ref: /schemas/i2c/i2c-controller.yaml# + +properties: + compatible: + const: amlogic,a9-i2c + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + dmas: + items: + - description: DMA controller phandle and request line for RX + - description: DMA controller phandle and request line for TX + + dma-names: + items: + - const: rx + - const: tx + +required: + - compatible + - reg + - interrupts + - clocks + +unevaluatedProperties: false + +examples: + - | + i2c@3a4400 { + compatible = "amlogic,a9-i2c"; + reg = <0x3a4400 0x3c>; + interrupts = <0 164 1>; + clocks = <&clkc_periphs 11>; + #address-cells = <1>; + #size-cells = <0>; + }; -- 2.52.0 ^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH v5 2/2] i2c: amlogic: Add Amlogic A9 I2C controller driver 2026-11-03 2:52 [PATCH v5 0/2] i2c: Add driver support Amlogic A9 I2C Xianwei Zhao via B4 Relay 2026-11-03 2:52 ` [PATCH v5 1/2] dt-bindings: i2c: add Amlogic A9 I2C controller Xianwei Zhao via B4 Relay @ 2026-11-03 2:52 ` Xianwei Zhao via B4 Relay 2026-11-03 3:11 ` sashiko-bot 1 sibling, 1 reply; 4+ messages in thread From: Xianwei Zhao via B4 Relay @ 2026-11-03 2:52 UTC (permalink / raw) To: Junyi Zhao, Andi Shyti, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Xianwei Zhao Cc: Junyi Zhao, linux-i2c, linux-amlogic, devicetree, linux-kernel, Xianwei Zhao From: Junyi Zhao <junyi.zhao@amlogic.com> Add support for the new I2C controller used by Amlogic A9 SoCs. Add a dedicated Kconfig option and Makefile entry for the A9 I2C controller driver. And add Amlogic I2C controller entry to MAINTAINERS to clarify the maintainers. Signed-off-by: Junyi Zhao <junyi.zhao@amlogic.com> Signed-off-by: Xianwei Zhao <xianwei.zhao@amlogic.com> --- MAINTAINERS | 9 + drivers/i2c/busses/Kconfig | 8 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-amlogic-a9.c | 431 ++++++++++++++++++++++++++++++++++++ 4 files changed, 449 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index a2d702919ef3..a56bdfefabad 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1371,6 +1371,15 @@ F: Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml F: drivers/perf/amlogic/ F: include/soc/amlogic/ +AMLOGIC I2C DRIVER +M: Junyi Zhao <junyi.zhao@amlogic.com> +M: Xianwei Zhao <xianwei.zhao@amlogic.com> +L: linux-i2c@vger.kernel.org +L: linux-amlogic@lists.infradead.org +S: Maintained +F: Documentation/devicetree/bindings/i2c/amlogic,a9-i2c.yaml +F: drivers/i2c/busses/i2c-amlogic-a9.c + AMLOGIC ISP DRIVER M: Keke Li <keke.li@amlogic.com> L: linux-media@vger.kernel.org diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 275aaa7f3583..50e031d97ed3 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -885,6 +885,14 @@ config I2C_MESON If you say yes to this option, support will be included for the I2C interface on the Amlogic Meson family of SoCs. +config I2C_AMLOGIC_A9 + tristate "Amlogic new I2C controller" + depends on ARCH_MESON || COMPILE_TEST + depends on COMMON_CLK + help + If you say yes to this option, support will be included for the + I2C interface on the new Amlogic family of SoCs. + config I2C_MICROCHIP_CORE tristate "Microchip FPGA I2C controller" depends on ARCH_MICROCHIP_POLARFIRE || COMPILE_TEST diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index e5440de0fe14..343eec4b0889 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -82,6 +82,7 @@ obj-$(CONFIG_I2C_LPC2K) += i2c-lpc2k.o obj-$(CONFIG_I2C_LS2X) += i2c-ls2x.o obj-$(CONFIG_I2C_LS2X_V2) += i2c-ls2x-v2.o obj-$(CONFIG_I2C_MESON) += i2c-meson.o +obj-$(CONFIG_I2C_AMLOGIC_A9) += i2c-amlogic-a9.o obj-$(CONFIG_I2C_MICROCHIP_CORE) += i2c-microchip-corei2c.o obj-$(CONFIG_I2C_MPC) += i2c-mpc.o obj-$(CONFIG_I2C_MT65XX) += i2c-mt65xx.o diff --git a/drivers/i2c/busses/i2c-amlogic-a9.c b/drivers/i2c/busses/i2c-amlogic-a9.c new file mode 100644 index 000000000000..d7f88d6feb05 --- /dev/null +++ b/drivers/i2c/busses/i2c-amlogic-a9.c @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2026 Amlogic, Inc. All rights reserved + */ + +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +/* Amlogic I2C register map */ +#define REG_CFG_RDY 0x00 +#define REG_CFG_I2C 0x04 +#define REG_CFG_START 0x08 +#define REG_CFG_BUS 0x0c +#define REG_TX_RD_ADDR 0x10 +#define REG_TX_WR_ADDR 0x14 +#define REG_RX_RD_ADDR 0x18 +#define REG_RX_WR_ADDR 0x1c +#define REG_CFG_TX 0x20 +#define REG_CFG_RX 0x24 +#define REG_CFG_IRQ_STATE 0x30 +#define REG_CFG_IRQ_ENABLE 0x34 +#define REG_SHAKE_BLK_CNT 0x38 + +/* CFG RDY fields */ +#define RDY_TEE_ONLY BIT(1) +#define RDY_IF BIT(0) + +/* CFG I2C fields */ +#define I2C_RX_THR GENMASK(23, 16) +#define I2C_TX_THR GENMASK(15, 8) + +/* CFG BUS fields */ +#define BUS_NO_STOP BIT(18) + +/* SCL = clk/b_ratio if b_ratio<=8, SCL = clk/8 */ +#define BUS_RATIO_MASK GENMASK(11, 0) + +/* CFG START fields */ +#define START_START BIT(31) +#define START_LEN GENMASK(23, 12) +#define START_LEN_SHIFT 12 +#define START_SLAVE_ADDR GENMASK(10, 1) +#define START_SADDR_SHIFT 1 +#define START_READ BIT(0) + +/* CFG TX_RX fields */ +#define TX_RX_DATA GENMASK(7, 0) + +/* CFG IRQ fields */ +#define IRQ_ALL_MASK GENMASK(15, 0) +#define A9_NCK_ERROR BIT(0) +#define A9_RX_THRESH_READ BIT(5) +#define A9_TX_THRESH_WRITE BIT(6) +#define A9_PHY_DONE BIT(7) +#define A9_TASK_DONE BIT(8) +#define A9_ALL_DONE BIT(9) + +#define A9_TRANS_DONE (A9_PHY_DONE | A9_NCK_ERROR) +#define A9_TRANS_ERROR (A9_NCK_ERROR) +#define A9_ENABLE_IRQ_BIT (A9_NCK_ERROR | A9_PHY_DONE) +#define THRESH_MODE (A9_RX_THRESH_READ | A9_TX_THRESH_WRITE) + +#define A9_I2C_FIFO_DEPTH 32 +#define A9_I2C_HALF_FIFO (A9_I2C_FIFO_DEPTH >> 1) + +#define A9_I2C_MAX_XFER_SIZE 0xfff + +#define I2C_TIMEOUT_MS 500 + +enum { + A9_STATE_IDLE, + A9_STATE_READ, + A9_STATE_WRITE, +}; + +enum fifo_fill_mode { + A9_FIFO_FILL_FULL, + A9_FIFO_FILL_HALF, +}; + +/** + * struct aml_i2c - Amlogic I2C device private data + * + * @adap: I2C adapter instance + * @dev: Pointer to device structure + * @regs: Base address of the device memory mapped registers + * @clk: Pointer to clock structure + * @msg: Pointer to the current I2C message + * @state: Current state in the driver state machine + * @pos: Current position in the send/receive buffer + * @error: Flag set when an error is received + * @lock: To avoid race conditions between irq handler and xfer code + * @done: Completion used to wait for transfer termination + * @clk_rate: Clock source rate of I2C module + * @frequency: Operating frequency of I2C bus clock + */ +struct aml_i2c { + struct i2c_adapter adap; + struct device *dev; + void __iomem *regs; + struct clk *clk; + + struct i2c_msg *msg; + int state; + int pos; + int error; + /* protect reg access */ + spinlock_t lock; + struct completion done; + unsigned long clk_rate; + unsigned int frequency; +}; + +static void aml_i2c_set_mask(struct aml_i2c *i2c, int reg, u32 mask, u32 val) +{ + u32 data; + + data = readl(i2c->regs + reg); + data &= ~mask; + data |= val & mask; + writel(data, i2c->regs + reg); +} + +/* + * SCL = clk/b_ratio. if b_ratio<=8, SCL = clk/8 + */ +static int aml_i2c_set_clk_div(struct aml_i2c *i2c) +{ + unsigned long clk_rate = i2c->clk_rate; + unsigned int div; + + div = DIV_ROUND_UP(clk_rate, i2c->frequency); + + /* clock divider has 12 bits */ + if (div >= (1 << 12)) { + dev_err(i2c->dev, "requested bus frequency too low\n"); + return -EINVAL; + } else if (div <= 8) { + dev_err(i2c->dev, "requested bus frequency too high\n"); + return -EINVAL; + } + /* CFG_BUS reg:11 - 0 bits */ + aml_i2c_set_mask(i2c, REG_CFG_BUS, BUS_RATIO_MASK, div); + + return 0; +} + +static void reset_fifo_pos(struct aml_i2c *i2c) +{ + writel(0x00, i2c->regs + REG_TX_RD_ADDR); + writel(0x00, i2c->regs + REG_TX_WR_ADDR); + writel(0x00, i2c->regs + REG_RX_WR_ADDR); + writel(0x00, i2c->regs + REG_RX_RD_ADDR); +} + +static void clear_irq_status(struct aml_i2c *i2c, u32 irq_bits) +{ + writel(irq_bits, i2c->regs + REG_CFG_IRQ_STATE); +} + +static int aml_i2c_init(struct aml_i2c *i2c) +{ + int ret; + + clear_irq_status(i2c, IRQ_ALL_MASK); + aml_i2c_set_mask(i2c, REG_CFG_BUS, BUS_NO_STOP, BUS_NO_STOP); + aml_i2c_set_mask(i2c, REG_CFG_I2C, I2C_RX_THR | I2C_TX_THR, + (A9_I2C_FIFO_DEPTH << 16) | (A9_I2C_HALF_FIFO << 8)); + reset_fifo_pos(i2c); + ret = aml_i2c_set_clk_div(i2c); + + return ret; +} + +static void aml_i2c_prepare_xfer(struct aml_i2c *i2c, enum fifo_fill_mode mode) +{ + bool write = !(i2c->msg->flags & I2C_M_RD); + int i, count; + + if (!write) + return; + + count = min(i2c->msg->len - i2c->pos, + mode == A9_FIFO_FILL_FULL ? A9_I2C_FIFO_DEPTH : A9_I2C_HALF_FIFO); + + for (i = 0; i < count; i++) + writel(i2c->msg->buf[i2c->pos++], i2c->regs + REG_CFG_TX); +} + +static irqreturn_t aml_i2c_irq(int irqno, void *dev_id) +{ + struct aml_i2c *i2c = dev_id; + u8 *buf = NULL; + u32 rd_len, i; + u32 rd_val; + u32 irq_state; + + spin_lock(&i2c->lock); + + irq_state = readl(i2c->regs + REG_CFG_IRQ_STATE); + if (i2c->state == A9_STATE_IDLE) { + dev_warn(i2c->dev, "i2c irq triggered abnormally, irq_state:0x%x,irq_en:0x%x\n", + irq_state, readl(i2c->regs + REG_CFG_IRQ_ENABLE)); + goto complete_irq_out; + } + + if (irq_state & A9_TRANS_ERROR) { + i2c->error = -ENXIO; + goto complete_irq_out; + } + + switch (i2c->state) { + case A9_STATE_READ: + buf = i2c->msg->buf; + rd_len = min(i2c->msg->len - i2c->pos, A9_I2C_FIFO_DEPTH); + for (i = 0; i < rd_len; i++) { + rd_val = readl(i2c->regs + REG_CFG_RX); + buf[i2c->pos++] = rd_val & TX_RX_DATA; + } + /* Rx must clear thresh after read fifo */ + clear_irq_status(i2c, A9_RX_THRESH_READ); + break; + case A9_STATE_WRITE: + aml_i2c_prepare_xfer(i2c, A9_FIFO_FILL_HALF); + if (i2c->pos == i2c->msg->len) + aml_i2c_set_mask(i2c, REG_CFG_IRQ_ENABLE, A9_TX_THRESH_WRITE, 0); + /* Tx must clear thresh after write fifo */ + clear_irq_status(i2c, A9_TX_THRESH_WRITE); + break; + default: + break; + } + + if (!(irq_state & A9_TRANS_DONE) || + (i2c->state == A9_STATE_READ && i2c->pos < i2c->msg->len)) + goto irq_trans_out; + +complete_irq_out: + i2c->state = A9_STATE_IDLE; + reset_fifo_pos(i2c); + complete(&i2c->done); + aml_i2c_set_mask(i2c, REG_CFG_IRQ_ENABLE, A9_RX_THRESH_READ | A9_TX_THRESH_WRITE, 0); + clear_irq_status(i2c, IRQ_ALL_MASK); +irq_trans_out: + spin_unlock(&i2c->lock); + + return IRQ_HANDLED; +} + +static void aml_i2c_do_start(struct aml_i2c *i2c, struct i2c_msg *msg) +{ + unsigned int reg_val; + unsigned int trans_len; + + trans_len = msg->len; + reg_val = (unsigned int)(((msg->addr & 0x7f) << START_SADDR_SHIFT) | + ((msg->flags & I2C_M_RD) ? START_READ : 0) | + ((trans_len & 0xfff) << START_LEN_SHIFT) | START_START); + aml_i2c_set_mask(i2c, REG_CFG_START, + START_START | START_SLAVE_ADDR | START_READ | START_LEN, reg_val); + clear_irq_status(i2c, IRQ_ALL_MASK); + /* enable thresh mode */ + if (trans_len > A9_I2C_FIFO_DEPTH) { + reg_val = (msg->flags & I2C_M_RD) ? A9_RX_THRESH_READ : A9_TX_THRESH_WRITE; + aml_i2c_set_mask(i2c, REG_CFG_IRQ_ENABLE, + A9_RX_THRESH_READ | A9_TX_THRESH_WRITE, reg_val); + } +} + +static int aml_i2c_xfer_msg(struct aml_i2c *i2c, struct i2c_msg *msg, bool last) +{ + unsigned long time_left, flags; + + i2c->msg = msg; + i2c->pos = 0; + i2c->error = 0; + + spin_lock_irqsave(&i2c->lock, flags); + i2c->state = (msg->flags & I2C_M_RD) ? A9_STATE_READ : A9_STATE_WRITE; + aml_i2c_prepare_xfer(i2c, A9_FIFO_FILL_FULL); + reinit_completion(&i2c->done); + + aml_i2c_set_mask(i2c, REG_CFG_BUS, BUS_NO_STOP, (last ? 0 : BUS_NO_STOP)); + + /* Start the transfer */ + aml_i2c_do_start(i2c, msg); + spin_unlock_irqrestore(&i2c->lock, flags); + + time_left = msecs_to_jiffies(I2C_TIMEOUT_MS); + time_left = wait_for_completion_timeout(&i2c->done, time_left); + + /* + * Protect access to i2c struct and registers from interrupt + * handlers triggered by a transfer terminated after the + * timeout period + */ + + if (!time_left) { + guard(spinlock_irqsave)(&i2c->lock); + i2c->state = A9_STATE_IDLE; + aml_i2c_set_mask(i2c, REG_CFG_IRQ_ENABLE, + A9_RX_THRESH_READ | A9_TX_THRESH_WRITE, 0); + clear_irq_status(i2c, IRQ_ALL_MASK); + reset_fifo_pos(i2c); + return -ETIMEDOUT; + } + + return i2c->error; +} + +static int aml_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct aml_i2c *i2c = adap->algo_data; + int i, ret = 0; + + for (i = 0; i < num; i++) { + ret = aml_i2c_xfer_msg(i2c, msgs + i, i == num - 1); + if (ret) + return ret; + } + + return i; +} + +static u32 aml_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); +} + +static const struct i2c_adapter_quirks aml_i2c_quirks = { + .max_read_len = A9_I2C_MAX_XFER_SIZE, + .max_write_len = A9_I2C_MAX_XFER_SIZE, + .flags = I2C_AQ_NO_ZERO_LEN, +}; + +static const struct i2c_algorithm aml_i2c_algorithm = { + .master_xfer = aml_i2c_xfer, + .functionality = aml_i2c_func, +}; + +static int aml_i2c_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct aml_i2c *i2c; + struct i2c_timings timings; + int irq, ret; + + i2c = devm_kzalloc(&pdev->dev, sizeof(struct aml_i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + i2c_parse_fw_timings(&pdev->dev, &timings, true); + i2c->frequency = timings.bus_freq_hz; + + i2c->dev = &pdev->dev; + platform_set_drvdata(pdev, i2c); + + spin_lock_init(&i2c->lock); + init_completion(&i2c->done); + + i2c->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(i2c->regs)) + return PTR_ERR(i2c->regs); + + i2c->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(i2c->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2c->clk), "can't get enable clock\n"); + + i2c->clk_rate = clk_get_rate(i2c->clk); + if (i2c->clk_rate == 0) + return dev_err_probe(&pdev->dev, -EINVAL, "failed to get clk rate\n"); + + ret = aml_i2c_init(i2c); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "fail init i2c\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return dev_err_probe(&pdev->dev, irq, "can't find IRQ\n"); + + ret = devm_request_irq(&pdev->dev, irq, aml_i2c_irq, 0, dev_name(&pdev->dev), i2c); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "can't request IRQ\n"); + /* Enable error & done irq */ + writel(A9_ENABLE_IRQ_BIT, i2c->regs + REG_CFG_IRQ_ENABLE); + strscpy(i2c->adap.name, "Amlogic I2C adapter", + sizeof(i2c->adap.name)); + i2c->adap.owner = THIS_MODULE; + i2c->adap.algo = &aml_i2c_algorithm; + i2c->adap.quirks = &aml_i2c_quirks; + i2c->adap.dev.parent = &pdev->dev; + i2c->adap.dev.of_node = np; + i2c->adap.algo_data = i2c; + + return i2c_add_adapter(&i2c->adap); +} + +static void aml_i2c_remove(struct platform_device *pdev) +{ + struct aml_i2c *i2c = platform_get_drvdata(pdev); + + i2c_del_adapter(&i2c->adap); +} + +static const struct of_device_id aml_i2c_match[] = { + { .compatible = "amlogic,a9-i2c",}, + { } +}; + +MODULE_DEVICE_TABLE(of, aml_i2c_match); + +static struct platform_driver aml_i2c_driver = { + .probe = aml_i2c_probe, + .remove = aml_i2c_remove, + .driver = { + .name = "amlogic-i2c", + .of_match_table = aml_i2c_match, + }, +}; + +module_platform_driver(aml_i2c_driver); + +MODULE_DESCRIPTION("Amlogic I2C Bus driver"); +MODULE_AUTHOR("Junyi Zhao <junyi.zhao@amlogic.com>"); +MODULE_LICENSE("GPL"); -- 2.52.0 ^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v5 2/2] i2c: amlogic: Add Amlogic A9 I2C controller driver 2026-11-03 2:52 ` [PATCH v5 2/2] i2c: amlogic: Add Amlogic A9 I2C controller driver Xianwei Zhao via B4 Relay @ 2026-11-03 3:11 ` sashiko-bot 0 siblings, 0 replies; 4+ messages in thread From: sashiko-bot @ 2026-11-03 3:11 UTC (permalink / raw) To: Xianwei Zhao; +Cc: linux-amlogic, devicetree, robh, conor+dt, neil.armstrong Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: - [High] i2c: amlogic: unstopped hw transfer on timeout in aml_i2c_xfer_msg() - [Medium] i2c: amlogic-a9: transfer started before IRQ setup in aml_i2c_do_start() - [Medium] i2c: amlogic-a9: missed completion in aml_i2c_irq() on multi-chunk reads - [Low] i2c: amlogic: use of deprecated master_xfer in aml_i2c_algorithm - [Low] i2c: amlogic-a9: off-by-one minimum divider check in aml_i2c_set_clk_div() -- commit 0ce6cfd604c34b299eddac3e8d6a46653816e69c Author: Junyi Zhao <junyi.zhao@amlogic.com> i2c: amlogic: Add Amlogic A9 I2C controller driver This commit adds a driver for the I2C controller found on Amlogic A9 SoCs, along with the corresponding Kconfig, Makefile, and MAINTAINERS entries. > diff --git a/drivers/i2c/busses/i2c-amlogic-a9.c b/drivers/i2c/busses/i2c-amlogic-a9.c > new file mode 100644 > index 0000000000000..d7f88d6feb05b > --- /dev/null > +++ b/drivers/i2c/busses/i2c-amlogic-a9.c > @@ -0,0 +1,431 @@ [ ... ] > +/* > + * SCL = clk/b_ratio. if b_ratio<=8, SCL = clk/8 > + */ > +static int aml_i2c_set_clk_div(struct aml_i2c *i2c) > +{ > + unsigned long clk_rate = i2c->clk_rate; > + unsigned int div; > + > + div = DIV_ROUND_UP(clk_rate, i2c->frequency); > + > + /* clock divider has 12 bits */ > + if (div >= (1 << 12)) { > + dev_err(i2c->dev, "requested bus frequency too low\n"); > + return -EINVAL; > + } else if (div <= 8) { > + dev_err(i2c->dev, "requested bus frequency too high\n"); > + return -EINVAL; > + } [Severity: Low] In aml_i2c_set_clk_div(), the comment notes that b_ratio <= 8 yields SCL = clk / 8. Since div == 8 is the minimum divider that produces the exact clk / 8 frequency without clamping, should this check be div < 8 rather than div <= 8 so that a valid divider of 8 is not rejected with -EINVAL? [ ... ] > +static irqreturn_t aml_i2c_irq(int irqno, void *dev_id) > +{ > + struct aml_i2c *i2c = dev_id; > + u8 *buf = NULL; > + u32 rd_len, i; > + u32 rd_val; > + u32 irq_state; > + > + spin_lock(&i2c->lock); > + > + irq_state = readl(i2c->regs + REG_CFG_IRQ_STATE); > + if (i2c->state == A9_STATE_IDLE) { > + dev_warn(i2c->dev, "i2c irq triggered abnormally, irq_state:0x%x,irq_en:0x%x\n", > + irq_state, readl(i2c->regs + REG_CFG_IRQ_ENABLE)); > + goto complete_irq_out; > + } > + > + if (irq_state & A9_TRANS_ERROR) { > + i2c->error = -ENXIO; > + goto complete_irq_out; > + } > + > + switch (i2c->state) { > + case A9_STATE_READ: > + buf = i2c->msg->buf; > + rd_len = min(i2c->msg->len - i2c->pos, A9_I2C_FIFO_DEPTH); > + for (i = 0; i < rd_len; i++) { > + rd_val = readl(i2c->regs + REG_CFG_RX); > + buf[i2c->pos++] = rd_val & TX_RX_DATA; > + } > + /* Rx must clear thresh after read fifo */ > + clear_irq_status(i2c, A9_RX_THRESH_READ); > + break; > + case A9_STATE_WRITE: > + aml_i2c_prepare_xfer(i2c, A9_FIFO_FILL_HALF); > + if (i2c->pos == i2c->msg->len) > + aml_i2c_set_mask(i2c, REG_CFG_IRQ_ENABLE, A9_TX_THRESH_WRITE, 0); > + /* Tx must clear thresh after write fifo */ > + clear_irq_status(i2c, A9_TX_THRESH_WRITE); > + break; > + default: > + break; > + } > + > + if (!(irq_state & A9_TRANS_DONE) || > + (i2c->state == A9_STATE_READ && i2c->pos < i2c->msg->len)) > + goto irq_trans_out; > + > +complete_irq_out: > + i2c->state = A9_STATE_IDLE; > + reset_fifo_pos(i2c); > + complete(&i2c->done); > + aml_i2c_set_mask(i2c, REG_CFG_IRQ_ENABLE, A9_RX_THRESH_READ | A9_TX_THRESH_WRITE, 0); > + clear_irq_status(i2c, IRQ_ALL_MASK); > +irq_trans_out: > + spin_unlock(&i2c->lock); > + > + return IRQ_HANDLED; > +} [Severity: Medium] As reported by Sashiko on v2 [1], can multi-chunk I2C read transfers (msg->len > A9_I2C_FIFO_DEPTH) miss the transfer completion interrupt in aml_i2c_irq()? Unlike A9_STATE_WRITE, which disables A9_TX_THRESH_WRITE once the final bytes are queued, A9_STATE_READ leaves A9_RX_THRESH_READ enabled and samples REG_CFG_IRQ_STATE only once upon entry. When msg->len is a multiple of 32 (for example, 64), the 32nd byte of the final chunk triggers A9_RX_THRESH_READ before the I2C STOP condition finishes on the bus. If A9_PHY_DONE asserts while aml_i2c_irq() is draining those 32 bytes from REG_CFG_RX and before A9_RX_THRESH_READ is cleared, the interrupt line remains continuously asserted without generating a new rising edge, while aml_i2c_irq() takes goto irq_trans_out because the initial irq_state snapshot did not have A9_TRANS_DONE set. Similarly, if both A9_RX_THRESH_READ and A9_PHY_DONE are set when aml_i2c_irq() enters with more than 32 bytes remaining, aml_i2c_irq() drains only 32 bytes, clears only A9_RX_THRESH_READ, and exits via irq_trans_out with i2c->pos < i2c->msg->len. Because A9_PHY_DONE is already asserted and fewer than 32 bytes remain to re-trigger A9_RX_THRESH_READ, no further interrupt edge occurs, leaving wait_for_completion_timeout() in aml_i2c_xfer_msg() to time out with -ETIMEDOUT. > + > +static void aml_i2c_do_start(struct aml_i2c *i2c, struct i2c_msg *msg) > +{ > + unsigned int reg_val; > + unsigned int trans_len; > + > + trans_len = msg->len; > + reg_val = (unsigned int)(((msg->addr & 0x7f) << START_SADDR_SHIFT) | > + ((msg->flags & I2C_M_RD) ? START_READ : 0) | > + ((trans_len & 0xfff) << START_LEN_SHIFT) | START_START); > + aml_i2c_set_mask(i2c, REG_CFG_START, > + START_START | START_SLAVE_ADDR | START_READ | START_LEN, reg_val); > + clear_irq_status(i2c, IRQ_ALL_MASK); > + /* enable thresh mode */ > + if (trans_len > A9_I2C_FIFO_DEPTH) { > + reg_val = (msg->flags & I2C_M_RD) ? A9_RX_THRESH_READ : A9_TX_THRESH_WRITE; > + aml_i2c_set_mask(i2c, REG_CFG_IRQ_ENABLE, > + A9_RX_THRESH_READ | A9_TX_THRESH_WRITE, reg_val); > + } > +} [Severity: Medium] As reported by Sashiko on v2 [2], in aml_i2c_do_start(), setting START_START in REG_CFG_START begins the hardware transfer while A9_ENABLE_IRQ_BIT (A9_NCK_ERROR | A9_PHY_DONE) is already enabled in REG_CFG_IRQ_ENABLE, before clear_irq_status(i2c, IRQ_ALL_MASK) is called and before A9_RX_THRESH_READ or A9_TX_THRESH_WRITE is enabled in REG_CFG_IRQ_ENABLE. If execution is delayed between writing REG_CFG_START and writing REG_CFG_IRQ_STATE (for example, due to task preemption under CONFIG_PREEMPT_RT, vCPU preemption, or NMI/FIQ latency), the controller can assert A9_NCK_ERROR on an address NACK or A9_PHY_DONE on a short transfer, which is then wiped out by clear_irq_status(i2c, IRQ_ALL_MASK) before aml_i2c_irq() acquires i2c->lock and reads REG_CFG_IRQ_STATE. When aml_i2c_irq() runs, it sees !(irq_state & A9_TRANS_DONE) and exits via irq_trans_out without completing i2c->done, causing aml_i2c_xfer_msg() to time out. Should the interrupt status be cleared and the threshold interrupts enabled before asserting START_START in REG_CFG_START? > + > +static int aml_i2c_xfer_msg(struct aml_i2c *i2c, struct i2c_msg *msg, bool last) > +{ > + unsigned long time_left, flags; > + > + i2c->msg = msg; > + i2c->pos = 0; > + i2c->error = 0; > + > + spin_lock_irqsave(&i2c->lock, flags); > + i2c->state = (msg->flags & I2C_M_RD) ? A9_STATE_READ : A9_STATE_WRITE; > + aml_i2c_prepare_xfer(i2c, A9_FIFO_FILL_FULL); > + reinit_completion(&i2c->done); > + > + aml_i2c_set_mask(i2c, REG_CFG_BUS, BUS_NO_STOP, (last ? 0 : BUS_NO_STOP)); > + > + /* Start the transfer */ > + aml_i2c_do_start(i2c, msg); > + spin_unlock_irqrestore(&i2c->lock, flags); > + > + time_left = msecs_to_jiffies(I2C_TIMEOUT_MS); > + time_left = wait_for_completion_timeout(&i2c->done, time_left); > + > + /* > + * Protect access to i2c struct and registers from interrupt > + * handlers triggered by a transfer terminated after the > + * timeout period > + */ > + > + if (!time_left) { > + guard(spinlock_irqsave)(&i2c->lock); > + i2c->state = A9_STATE_IDLE; > + aml_i2c_set_mask(i2c, REG_CFG_IRQ_ENABLE, > + A9_RX_THRESH_READ | A9_TX_THRESH_WRITE, 0); > + clear_irq_status(i2c, IRQ_ALL_MASK); > + reset_fifo_pos(i2c); > + return -ETIMEDOUT; > + } > + > + return i2c->error; > +} [Severity: High] As reported by Sashiko on v2 [3], when wait_for_completion_timeout() expires (!time_left) in aml_i2c_xfer_msg(), this timeout path sets i2c->state to A9_STATE_IDLE, disables the threshold IRQs, clears REG_CFG_IRQ_STATE, and calls reset_fifo_pos(i2c), but never clears START_START in REG_CFG_START or otherwise aborts the active hardware transfer while A9_ENABLE_IRQ_BIT remains enabled in REG_CFG_IRQ_ENABLE. If the stalled transfer shifts additional bytes after reset_fifo_pos(i2c), the hardware FIFO pointers become non-zero for the next message. Furthermore, if the stalled transfer finishes or NACKs after a subsequent aml_i2c_xfer_msg() has set i2c->state to A9_STATE_READ or A9_STATE_WRITE, aml_i2c_irq() will process the stale A9_PHY_DONE or A9_NCK_ERROR interrupt against the new i2c->msg, corrupting i2c->msg->buf, i2c->pos, and i2c->error and prematurely completing i2c->done. Even if the late interrupt arrives while i2c->state is still A9_STATE_IDLE, aml_i2c_irq() jumps to complete_irq_out and calls complete(&i2c->done) when no transfer is waiting. Also, this function mixes manual spin_lock_irqsave() / spin_unlock_irqrestore() with guard(spinlock_irqsave)(&i2c->lock) without directly including linux/cleanup.h. Could the hardware transfer be explicitly halted on timeout, the spurious complete(&i2c->done) when A9_STATE_IDLE avoided, and the locking style made consistent across aml_i2c_xfer_msg()? [ ... ] > +static const struct i2c_algorithm aml_i2c_algorithm = { > + .master_xfer = aml_i2c_xfer, > + .functionality = aml_i2c_func, > +}; [Severity: Low] In struct i2c_algorithm, master_xfer is marked deprecated in include/linux/i2c.h in favor of xfer. Could aml_i2c_algorithm initialize .xfer = aml_i2c_xfer instead? [1] https://lore.kernel.org/r/sashiko-outbox-181284@kernel.org [2] https://lore.kernel.org/r/sashiko-outbox-181284@kernel.org [3] https://lore.kernel.org/r/sashiko-outbox-181284@kernel.org -- Sashiko AI review · https://sashiko.dev/#/patchset/20261103-a9-i2c-v5-0-86f2dcd179d8@amlogic.com?part=2 ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-03 3:11 UTC | newest] Thread overview: 4+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-11-03 2:52 [PATCH v5 0/2] i2c: Add driver support Amlogic A9 I2C Xianwei Zhao via B4 Relay 2026-11-03 2:52 ` [PATCH v5 1/2] dt-bindings: i2c: add Amlogic A9 I2C controller Xianwei Zhao via B4 Relay 2026-11-03 2:52 ` [PATCH v5 2/2] i2c: amlogic: Add Amlogic A9 I2C controller driver Xianwei Zhao via B4 Relay 2026-11-03 3:11 ` sashiko-bot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox