linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/2] Add ADI I3C Controller
@ 2025-06-18  7:16 Jorge Marques
  2025-06-18  7:16 ` [PATCH v3 1/2] dt-bindings: i3c: Add adi-i3c-master Jorge Marques
  2025-06-18  7:16 ` [PATCH v3 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP Jorge Marques
  0 siblings, 2 replies; 14+ messages in thread
From: Jorge Marques @ 2025-06-18  7:16 UTC (permalink / raw)
  To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-i3c, devicetree, linux-kernel, gastmaier, Jorge Marques

I3C Controller is subset of the I3C-basic specification to interface
peripherals through I3C and I2C. The controller RTL is FPGA
synthesizable and documentation is provided at
https://analogdevicesinc.github.io/hdl/library/i3c_controller

The main target for the I3C Controller IP is low-cost FPGAs.
In this version the driver supports IBI (only the MDB), I3C and I2C
transfers.

Signed-off-by: Jorge Marques <jorge.marques@analog.com>
---
Changes in v3:
Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml:
- Small reworking of the description
- Add -1.00.a suffix to compatible

adi-i3c-master.c:
- Misspelling
- Remove REG_CMD_FIFO_0_LEN_MAX since it is a HDL parameter
- Use adapter timeout value for I2C transfers, as in
  https://lore.kernel.org/linux-i3c/aEBd%2FFIKADYr%2F631@lizhi-Precision-Tower-5810/T/#t

- Link to v2: https://lore.kernel.org/r/20250606-adi-i3c-master-v2-0-e68b9aad2630@analog.com

Changes in v2:
Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml:
- Move allof
- Rename clocks to axi, i3c

adi-i3c-master.c:
- Update license year
- Rework regmap to use FIELD_GET, FIELD_PREP
- Reformat regmap to have FIELDS after REG, prefixed by reg name.
- Add overflow safeguards to cmd, tx fifos
- Fix macro related macros (mostly erroneous `| ~BITMASK`
- Use guard macros, remove goto.
- Simplify daa logic
- Replace devm_clk_get with devm_clk_get_enabled
- Solve 64bit->32bit warnings on x86_64 systems by casting to u32
- Immediate clear irq request flags, then handle it.

- Link to v1: https://lore.kernel.org/r/20250604-adi-i3c-master-v1-0-0488e80dafcb@analog.com

---
Jorge Marques (2):
      dt-bindings: i3c: Add adi-i3c-master
      i3c: master: Add driver for Analog Devices I3C Controller IP

 .../devicetree/bindings/i3c/adi,i3c-master.yaml    |   63 ++
 MAINTAINERS                                        |    6 +
 drivers/i3c/master/Kconfig                         |   11 +
 drivers/i3c/master/Makefile                        |    1 +
 drivers/i3c/master/adi-i3c-master.c                | 1026 ++++++++++++++++++++
 5 files changed, 1107 insertions(+)
---
base-commit: 00286d7d643d3c98e48d9cc3a9f471b37154f462
change-id: 20250604-adi-i3c-master-2a5148c58c47

Best regards,
-- 
Jorge Marques <jorge.marques@analog.com>


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v3 1/2] dt-bindings: i3c: Add adi-i3c-master
  2025-06-18  7:16 [PATCH v3 0/2] Add ADI I3C Controller Jorge Marques
@ 2025-06-18  7:16 ` Jorge Marques
  2025-06-18  8:40   ` Rob Herring (Arm)
  2025-06-18  9:21   ` Krzysztof Kozlowski
  2025-06-18  7:16 ` [PATCH v3 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP Jorge Marques
  1 sibling, 2 replies; 14+ messages in thread
From: Jorge Marques @ 2025-06-18  7:16 UTC (permalink / raw)
  To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-i3c, devicetree, linux-kernel, gastmaier, Jorge Marques

Add bindings doc for ADI I3C Controller IP core, a FPGA synthesizable IP
core that implements the MIPI I3C Basic controller specification.

Signed-off-by: Jorge Marques <jorge.marques@analog.com>
---
 .../devicetree/bindings/i3c/adi,i3c-master.yaml    | 63 ++++++++++++++++++++++
 MAINTAINERS                                        |  5 ++
 2 files changed, 68 insertions(+)

diff --git a/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml b/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..718733bbb450c34c5d4924050cc6f85d8a80fe4b
--- /dev/null
+++ b/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/i3c/adi,i3c-master.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices I3C Controller
+
+description: |
+  FPGA-based I3C controller designed to interface with I3C and I2C peripherals,
+  implementing a subset of the I3C-basic specification.
+
+  https://analogdevicesinc.github.io/hdl/library/i3c_controller
+
+maintainers:
+  - Jorge Marques <jorge.marques@analog.com>
+
+properties:
+  compatible:
+    const: adi,i3c-master-1.00.a
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    minItems: 1
+    items:
+      - description: The AXI interconnect clock.
+      - description: The I3C controller clock.
+
+  clock-names:
+    items:
+      - const: axi
+      - const: i3c
+
+  interrupts:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - interrupts
+
+allOf:
+  - $ref: i3c.yaml#
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    i3c@44a00000 {
+        compatible = "adi,i3c-master";
+        reg = <0x44a00000 0x1000>;
+        interrupts = <0 56 4>;
+        clocks = <&clkc 15>, <&clkc 15>;
+        clock-names = "axi", "i3c";
+        #address-cells = <3>;
+        #size-cells = <0>;
+
+        /* I3C and I2C devices */
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 96b82704950184bd71623ff41fc4df31e4c7fe87..6f56e17dcecf902c6812827c1ec3e067c65e9894 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11243,6 +11243,11 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/i3c/aspeed,ast2600-i3c.yaml
 F:	drivers/i3c/master/ast2600-i3c-master.c
 
+I3C DRIVER FOR ANALOG DEVICES I3C CONTROLLER IP
+M:	Jorge Marques <jorge.marques@analog.com>
+S:	Maintained
+F:	Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
+
 I3C DRIVER FOR CADENCE I3C MASTER IP
 M:	Przemysław Gaj <pgaj@cadence.com>
 S:	Maintained

-- 
2.49.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP
  2025-06-18  7:16 [PATCH v3 0/2] Add ADI I3C Controller Jorge Marques
  2025-06-18  7:16 ` [PATCH v3 1/2] dt-bindings: i3c: Add adi-i3c-master Jorge Marques
@ 2025-06-18  7:16 ` Jorge Marques
  2025-06-18 19:40   ` Frank Li
  1 sibling, 1 reply; 14+ messages in thread
From: Jorge Marques @ 2025-06-18  7:16 UTC (permalink / raw)
  To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-i3c, devicetree, linux-kernel, gastmaier, Jorge Marques

Add support for Analog Devices I3C Controller IP, an AXI-interfaced IP
core that supports I3C and I2C devices, multiple speed-grades and
I3C IBIs.

Signed-off-by: Jorge Marques <jorge.marques@analog.com>
---
 MAINTAINERS                         |    1 +
 drivers/i3c/master/Kconfig          |   11 +
 drivers/i3c/master/Makefile         |    1 +
 drivers/i3c/master/adi-i3c-master.c | 1026 +++++++++++++++++++++++++++++++++++
 4 files changed, 1039 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 6f56e17dcecf902c6812827c1ec3e067c65e9894..9eb5b6c327590725d1641fd4b73e48fc1d1a3954 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11247,6 +11247,7 @@ I3C DRIVER FOR ANALOG DEVICES I3C CONTROLLER IP
 M:	Jorge Marques <jorge.marques@analog.com>
 S:	Maintained
 F:	Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
+F:	drivers/i3c/master/adi-i3c-master.c
 
 I3C DRIVER FOR CADENCE I3C MASTER IP
 M:	Przemysław Gaj <pgaj@cadence.com>
diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig
index 7b30db3253af9d5c6aee6544c060e491bfbeb643..328b7145cdefa20e708ebfa3383e849ce51c5a71 100644
--- a/drivers/i3c/master/Kconfig
+++ b/drivers/i3c/master/Kconfig
@@ -1,4 +1,15 @@
 # SPDX-License-Identifier: GPL-2.0-only
+config ADI_I3C_MASTER
+	tristate "Analog Devices I3C master driver"
+	depends on HAS_IOMEM
+	help
+	  Support for Analog Devices I3C Controller IP, an AXI-interfaced IP
+	  core that supports I3C and I2C devices, multiple speed-grades and
+	  I3C IBIs.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called adi-i3c-master.
+
 config CDNS_I3C_MASTER
 	tristate "Cadence I3C master driver"
 	depends on HAS_IOMEM
diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile
index 3e97960160bc85e5eaf2966ec0c3fae458c2711e..6cc4f4b73e7bdc206b68c750390f9c3cc2ccb199 100644
--- a/drivers/i3c/master/Makefile
+++ b/drivers/i3c/master/Makefile
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_ADI_I3C_MASTER)		+= adi-i3c-master.o
 obj-$(CONFIG_CDNS_I3C_MASTER)		+= i3c-master-cdns.o
 obj-$(CONFIG_DW_I3C_MASTER)		+= dw-i3c-master.o
 obj-$(CONFIG_AST2600_I3C_MASTER)	+= ast2600-i3c-master.o
diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c
new file mode 100644
index 0000000000000000000000000000000000000000..d4e0756efec9588383ac55ca9ea9228368ce767d
--- /dev/null
+++ b/drivers/i3c/master/adi-i3c-master.c
@@ -0,0 +1,1026 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * I3C Controller driver
+ * Copyright 2025 Analog Devices Inc.
+ * Author: Jorge Marques <jorge.marques@analog.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/i3c/master.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#define VERSION_MAJOR(x)		((u32)FIELD_GET(GENMASK(23, 16), (x)))
+#define VERSION_MINOR(x)		((u32)FIELD_GET(GENMASK(15, 8), (x)))
+#define VERSION_PATCH(x)		((u32)FIELD_GET(GENMASK(7, 0), (x)))
+
+#define MAX_DEVS			16
+
+#define REG_VERSION			0x000
+#define REG_ENABLE			0x040
+#define REG_IRQ_MASK			0x080
+#define REG_IRQ_PENDING			0x084
+#define   REG_IRQ_PENDING_CMDR		BIT(5)
+#define   REG_IRQ_PENDING_IBI		((u32)BIT(6))
+#define   REG_IRQ_PENDING_DAA		BIT(7)
+#define REG_CMD_FIFO			0x0d4
+#define	  REG_CMD_FIFO_0_IS_CCC		BIT(22)
+#define   REG_CMD_FIFO_0_BCAST		BIT(21)
+#define   REG_CMD_FIFO_0_SR		BIT(20)
+#define   REG_CMD_FIFO_0_LEN(l)		FIELD_PREP(GENMASK(19, 8), (l))
+#define   REG_CMD_FIFO_0_DEV_ADDR(a)	FIELD_PREP(GENMASK(7, 1), a)
+#define   REG_CMD_FIFO_0_RNW		BIT(0)
+#define   REG_CMD_FIFO_1_CCC(id)	FIELD_PREP(GENMASK(7, 0), (id))
+#define REG_CMD_FIFO_ROOM		0x0c0
+#define REG_CMDR_FIFO			0x0d8
+#define   REG_CMDR_FIFO_NO_ERROR	0
+#define   REG_CMDR_FIFO_CE0_ERROR	1
+#define   REG_CMDR_FIFO_CE2_ERROR	4
+#define   REG_CMDR_FIFO_NACK_RESP	6
+#define   REG_CMDR_FIFO_UDA_ERROR	8
+#define   REG_CMDR_FIFO_ERROR(x)	FIELD_GET(GENMASK(23, 20), (x))
+#define   REG_CMDR_FIFO_XFER_BYTES(x)	FIELD_GET(GENMASK(19, 8), (x))
+#define REG_SDO_FIFO			0x0dc
+#define REG_SDO_FIFO_ROOM		0x0c8
+#define REG_SDI_FIFO			0x0e0
+#define REG_IBI_FIFO			0x0e4
+#define REG_FIFO_STATUS			0x0e8
+#define   REG_FIFO_STATUS_CMDR_EMPTY	BIT(0)
+#define   REG_FIFO_STATUS_IBI_EMPTY	BIT(1)
+#define REG_OPS				0x100
+#define   REG_OPS_SET_SG(x)		FIELD_PREP(GENMASK(6, 5), (x))
+#define   REG_OPS_PP_SG_MASK		GENMASK(6, 5)
+#define REG_IBI_CONFIG			0x140
+#define   REG_IBI_CONFIG_LISTEN		BIT(1)
+#define   REG_IBI_CONFIG_ENABLE		BIT(0)
+#define REG_DEV_CHAR			0x180
+#define   REG_DEV_CHAR_IS_I2C		BIT(0)
+#define   REG_DEV_CHAR_IS_ATTACHED	BIT(1)
+#define   REG_DEV_CHAR_BCR_IBI(x)	(((x) & GENMASK(2, 1)) << 1)
+#define   REG_DEV_CHAR_WEN		BIT(8)
+#define   REG_DEV_CHAR_ADDR(x)		FIELD_PREP(GENMASK(15, 9), (x))
+
+enum speed_grade {PP_SG_UNSET, PP_SG_1MHZ, PP_SG_3MHZ, PP_SG_6MHZ, PP_SG_12MHZ};
+struct adi_i3c_cmd {
+	u32 cmd0;
+	u32 cmd1;
+	u32 tx_len;
+	const void *tx_buf;
+	u32 rx_len;
+	void *rx_buf;
+	u32 error;
+};
+
+struct adi_i3c_xfer {
+	struct list_head node;
+	struct completion comp;
+	int ret;
+	unsigned int ncmds;
+	unsigned int ncmds_comp;
+	struct adi_i3c_cmd cmds[];
+};
+
+struct adi_i3c_master {
+	struct i3c_master_controller base;
+	u32 free_rr_slots;
+	unsigned int maxdevs;
+	struct {
+		unsigned int num_slots;
+		struct i3c_dev_desc **slots;
+		spinlock_t lock; /* Protect IBI slot access */
+	} ibi;
+	struct {
+		struct list_head list;
+		struct adi_i3c_xfer *cur;
+		spinlock_t lock; /* Protect transfer */
+	} xferqueue;
+	void __iomem *regs;
+	struct clk *clk;
+	unsigned long i3c_scl_lim;
+	struct {
+		u8 addrs[MAX_DEVS];
+		u8 index;
+	} daa;
+};
+
+static inline struct adi_i3c_master *to_adi_i3c_master(struct i3c_master_controller *master)
+{
+	return container_of(master, struct adi_i3c_master, base);
+}
+
+static void adi_i3c_master_wr_to_tx_fifo(struct adi_i3c_master *master,
+					 const u8 *bytes, unsigned int nbytes)
+{
+	unsigned int n, m;
+	u32 tmp;
+
+	n = readl(master->regs + REG_SDO_FIFO_ROOM);
+	m = min(n, nbytes);
+	writesl(master->regs + REG_SDO_FIFO, bytes, m / 4);
+
+	if (m & 3) {
+		memcpy(&tmp, bytes + (m & ~3), m & 3);
+		writel(tmp, master->regs + REG_SDO_FIFO);
+	}
+}
+
+static void adi_i3c_master_rd_from_rx_fifo(struct adi_i3c_master *master,
+					   u8 *bytes, unsigned int nbytes)
+{
+	readsl(master->regs + REG_SDI_FIFO, bytes, nbytes / 4);
+	if (nbytes & 3) {
+		u32 tmp;
+
+		tmp = readl(master->regs + REG_SDI_FIFO);
+		memcpy(bytes + (nbytes & ~3), &tmp, nbytes & 3);
+	}
+}
+
+static bool adi_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m,
+					    const struct i3c_ccc_cmd *cmd)
+{
+	if (cmd->ndests > 1)
+		return false;
+
+	switch (cmd->id) {
+	case I3C_CCC_ENEC(true):
+	case I3C_CCC_ENEC(false):
+	case I3C_CCC_DISEC(true):
+	case I3C_CCC_DISEC(false):
+	case I3C_CCC_RSTDAA(true):
+	case I3C_CCC_RSTDAA(false):
+	case I3C_CCC_ENTDAA:
+	case I3C_CCC_SETDASA:
+	case I3C_CCC_SETNEWDA:
+	case I3C_CCC_GETMWL:
+	case I3C_CCC_GETMRL:
+	case I3C_CCC_GETPID:
+	case I3C_CCC_GETBCR:
+	case I3C_CCC_GETDCR:
+	case I3C_CCC_GETSTATUS:
+	case I3C_CCC_GETHDRCAP:
+		return true;
+	default:
+		break;
+	}
+
+	return false;
+}
+
+static int adi_i3c_master_disable(struct adi_i3c_master *master)
+{
+	writel(0, master->regs + REG_IBI_CONFIG);
+
+	return 0;
+}
+
+static struct adi_i3c_xfer *adi_i3c_master_alloc_xfer(struct adi_i3c_master *master,
+						      unsigned int ncmds)
+{
+	struct adi_i3c_xfer *xfer;
+
+	xfer = kzalloc(struct_size(xfer, cmds, ncmds), GFP_KERNEL);
+	if (!xfer)
+		return NULL;
+
+	INIT_LIST_HEAD(&xfer->node);
+	xfer->ncmds = ncmds;
+	xfer->ret = -ETIMEDOUT;
+
+	return xfer;
+}
+
+static void adi_i3c_master_start_xfer_locked(struct adi_i3c_master *master)
+{
+	struct adi_i3c_xfer *xfer = master->xferqueue.cur;
+	unsigned int i, n, m;
+
+	if (!xfer)
+		return;
+
+	for (i = 0; i < xfer->ncmds; i++) {
+		struct adi_i3c_cmd *cmd = &xfer->cmds[i];
+
+		if (!(cmd->cmd0 & REG_CMD_FIFO_0_RNW))
+			adi_i3c_master_wr_to_tx_fifo(master, cmd->tx_buf, cmd->tx_len);
+	}
+
+	n = readl(master->regs + REG_CMD_FIFO_ROOM);
+	for (i = 0; i < xfer->ncmds; i++) {
+		struct adi_i3c_cmd *cmd = &xfer->cmds[i];
+
+		m = cmd->cmd0 & REG_CMD_FIFO_0_IS_CCC ? 2 : 1;
+		if (m > n)
+			break;
+		writel(cmd->cmd0, master->regs + REG_CMD_FIFO);
+		if (cmd->cmd0 & REG_CMD_FIFO_0_IS_CCC)
+			writel(cmd->cmd1, master->regs + REG_CMD_FIFO);
+		n -= m;
+	}
+}
+
+static void adi_i3c_master_end_xfer_locked(struct adi_i3c_master *master,
+					   u32 pending)
+{
+	struct adi_i3c_xfer *xfer = master->xferqueue.cur;
+	int i, ret = 0;
+	u32 status0;
+
+	if (!xfer)
+		return;
+
+	for (status0 = readl(master->regs + REG_FIFO_STATUS);
+	     !(status0 & REG_FIFO_STATUS_CMDR_EMPTY);
+	     status0 = readl(master->regs + REG_FIFO_STATUS)) {
+		struct adi_i3c_cmd *cmd;
+		u32 cmdr, rx_len;
+
+		cmdr = readl(master->regs + REG_CMDR_FIFO);
+
+		cmd = &xfer->cmds[xfer->ncmds_comp++];
+		if (cmd->cmd0 & REG_CMD_FIFO_0_RNW) {
+			rx_len = min_t(u32, REG_CMDR_FIFO_XFER_BYTES(cmdr), cmd->rx_len);
+			adi_i3c_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len);
+		}
+		cmd->error = REG_CMDR_FIFO_ERROR(cmdr);
+	}
+
+	for (i = 0; i < xfer->ncmds; i++) {
+		switch (xfer->cmds[i].error) {
+		case REG_CMDR_FIFO_NO_ERROR:
+			break;
+
+		case REG_CMDR_FIFO_CE0_ERROR:
+		case REG_CMDR_FIFO_CE2_ERROR:
+		case REG_CMDR_FIFO_NACK_RESP:
+		case REG_CMDR_FIFO_UDA_ERROR:
+			ret = -EIO;
+			break;
+
+		default:
+			ret = -EINVAL;
+			break;
+		}
+	}
+
+	xfer->ret = ret;
+
+	if (xfer->ncmds_comp != xfer->ncmds)
+		return;
+
+	complete(&xfer->comp);
+
+	xfer = list_first_entry_or_null(&master->xferqueue.list,
+					struct adi_i3c_xfer, node);
+	if (xfer)
+		list_del_init(&xfer->node);
+
+	master->xferqueue.cur = xfer;
+	adi_i3c_master_start_xfer_locked(master);
+}
+
+static void adi_i3c_master_queue_xfer(struct adi_i3c_master *master,
+				      struct adi_i3c_xfer *xfer)
+{
+	init_completion(&xfer->comp);
+	guard(spinlock_irqsave)(&master->xferqueue.lock);
+	if (master->xferqueue.cur) {
+		list_add_tail(&xfer->node, &master->xferqueue.list);
+	} else {
+		master->xferqueue.cur = xfer;
+		adi_i3c_master_start_xfer_locked(master);
+	}
+}
+
+static void adi_i3c_master_unqueue_xfer(struct adi_i3c_master *master,
+					struct adi_i3c_xfer *xfer)
+{
+	guard(spinlock_irqsave)(&master->xferqueue.lock);
+	if (master->xferqueue.cur == xfer)
+		master->xferqueue.cur = NULL;
+	else
+		list_del_init(&xfer->node);
+
+	writel(0x01, master->regs + REG_ENABLE);
+	writel(0x00, master->regs + REG_ENABLE);
+	writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
+}
+
+static enum i3c_error_code adi_i3c_cmd_get_err(struct adi_i3c_cmd *cmd)
+{
+	switch (cmd->error) {
+	case REG_CMDR_FIFO_CE0_ERROR:
+		return I3C_ERROR_M0;
+
+	case REG_CMDR_FIFO_CE2_ERROR:
+	case REG_CMDR_FIFO_NACK_RESP:
+		return I3C_ERROR_M2;
+
+	default:
+		break;
+	}
+
+	return I3C_ERROR_UNKNOWN;
+}
+
+static int adi_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
+				       struct i3c_ccc_cmd *cmd)
+{
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+	struct adi_i3c_xfer *xfer;
+	struct adi_i3c_cmd *ccmd;
+
+	xfer = adi_i3c_master_alloc_xfer(master, 1);
+	if (!xfer)
+		return -ENOMEM;
+
+	ccmd = xfer->cmds;
+	ccmd->cmd1 = REG_CMD_FIFO_1_CCC(cmd->id);
+	ccmd->cmd0 = REG_CMD_FIFO_0_IS_CCC |
+		     REG_CMD_FIFO_0_LEN(cmd->dests[0].payload.len);
+
+	if (cmd->id & I3C_CCC_DIRECT)
+		ccmd->cmd0 |= REG_CMD_FIFO_0_DEV_ADDR(cmd->dests[0].addr);
+
+	if (cmd->rnw) {
+		ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
+		ccmd->rx_buf = cmd->dests[0].payload.data;
+		ccmd->rx_len = cmd->dests[0].payload.len;
+	} else {
+		ccmd->tx_buf = cmd->dests[0].payload.data;
+		ccmd->tx_len = cmd->dests[0].payload.len;
+	}
+
+	adi_i3c_master_queue_xfer(master, xfer);
+	if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000)))
+		adi_i3c_master_unqueue_xfer(master, xfer);
+
+	cmd->err = adi_i3c_cmd_get_err(&xfer->cmds[0]);
+	kfree(xfer);
+
+	return 0;
+}
+
+static int adi_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
+				     struct i3c_priv_xfer *xfers,
+				     int nxfers)
+{
+	struct i3c_master_controller *m = i3c_dev_get_master(dev);
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+	struct adi_i3c_xfer *xfer;
+	int i, ret;
+
+	if (!nxfers)
+		return 0;
+	xfer = adi_i3c_master_alloc_xfer(master, nxfers);
+	if (!xfer)
+		return -ENOMEM;
+
+	for (i = 0; i < nxfers; i++) {
+		struct adi_i3c_cmd *ccmd = &xfer->cmds[i];
+
+		ccmd->cmd0 = REG_CMD_FIFO_0_DEV_ADDR(dev->info.dyn_addr);
+
+		if (xfers[i].rnw) {
+			ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
+			ccmd->rx_buf = xfers[i].data.in;
+			ccmd->rx_len = xfers[i].len;
+		} else {
+			ccmd->tx_buf = xfers[i].data.out;
+			ccmd->tx_len = xfers[i].len;
+		}
+
+		ccmd->cmd0 |= REG_CMD_FIFO_0_LEN(xfers[i].len);
+
+		if (i < nxfers - 1)
+			ccmd->cmd0 |= REG_CMD_FIFO_0_SR;
+
+		if (!i)
+			ccmd->cmd0 |= REG_CMD_FIFO_0_BCAST;
+	}
+
+	adi_i3c_master_queue_xfer(master, xfer);
+	if (!wait_for_completion_timeout(&xfer->comp,
+					 msecs_to_jiffies(1000)))
+		adi_i3c_master_unqueue_xfer(master, xfer);
+
+	ret = xfer->ret;
+
+	for (i = 0; i < nxfers; i++)
+		xfers[i].err = adi_i3c_cmd_get_err(&xfer->cmds[i]);
+
+	kfree(xfer);
+
+	return ret;
+}
+
+struct adi_i3c_i2c_dev_data {
+	u16 id;
+	s16 ibi;
+	struct i3c_generic_ibi_pool *ibi_pool;
+};
+
+static int adi_i3c_master_get_rr_slot(struct adi_i3c_master *master,
+				      u8 dyn_addr)
+{
+	if (!master->free_rr_slots)
+		return -ENOSPC;
+
+	return ffs(master->free_rr_slots) - 1;
+}
+
+static int adi_i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 dyn_addr)
+{
+	struct i3c_master_controller *m = i3c_dev_get_master(dev);
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+	u8 addr;
+
+	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
+
+	writel(REG_DEV_CHAR_ADDR(dyn_addr), master->regs + REG_DEV_CHAR);
+	writel((readl(master->regs + REG_DEV_CHAR) &
+		~REG_DEV_CHAR_IS_ATTACHED) | REG_DEV_CHAR_WEN,
+	       master->regs + REG_DEV_CHAR);
+
+	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
+	writel(readl(master->regs + REG_DEV_CHAR) |
+	       REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
+	       master->regs + REG_DEV_CHAR);
+
+	return 0;
+}
+
+static int adi_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev)
+{
+	struct i3c_master_controller *m = i3c_dev_get_master(dev);
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+	struct adi_i3c_i2c_dev_data *data;
+	int slot;
+	u8 addr;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	slot = adi_i3c_master_get_rr_slot(master, dev->info.dyn_addr);
+	if (slot < 0) {
+		kfree(data);
+		return slot;
+	}
+
+	data->ibi = -1;
+	data->id = slot;
+	i3c_dev_set_master_data(dev, data);
+	master->free_rr_slots &= ~BIT(slot);
+
+	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
+
+	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
+	writel(readl(master->regs + REG_DEV_CHAR) |
+	       REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
+	       master->regs + REG_DEV_CHAR);
+
+	return 0;
+}
+
+static void adi_i3c_master_sync_dev_char(struct i3c_master_controller *m)
+{
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+	struct i3c_dev_desc *i3cdev;
+	u8 addr;
+
+	i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
+		addr = i3cdev->info.dyn_addr ?
+		       i3cdev->info.dyn_addr : i3cdev->info.static_addr;
+		writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
+		writel(readl(master->regs + REG_DEV_CHAR) |
+		       REG_DEV_CHAR_BCR_IBI(i3cdev->info.bcr) | REG_DEV_CHAR_WEN,
+		       master->regs + REG_DEV_CHAR);
+	}
+}
+
+static void adi_i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev)
+{
+	struct i3c_master_controller *m = i3c_dev_get_master(dev);
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
+	u8 addr;
+
+	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
+
+	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
+	writel((readl(master->regs + REG_DEV_CHAR) &
+		~REG_DEV_CHAR_IS_ATTACHED) | REG_DEV_CHAR_WEN,
+	       master->regs + REG_DEV_CHAR);
+
+	i3c_dev_set_master_data(dev, NULL);
+	master->free_rr_slots |= BIT(data->id);
+	kfree(data);
+}
+
+static int adi_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev)
+{
+	struct i3c_master_controller *m = i2c_dev_get_master(dev);
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+	struct adi_i3c_i2c_dev_data *data;
+	int slot;
+
+	slot = adi_i3c_master_get_rr_slot(master, 0);
+	if (slot < 0)
+		return slot;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->id = slot;
+	master->free_rr_slots &= ~BIT(slot);
+	i2c_dev_set_master_data(dev, data);
+
+	writel(REG_DEV_CHAR_ADDR(dev->addr) |
+	       REG_DEV_CHAR_IS_I2C | REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
+	       master->regs + REG_DEV_CHAR);
+
+	return 0;
+}
+
+static void adi_i3c_master_detach_i2c_dev(struct i2c_dev_desc *dev)
+{
+	struct i3c_master_controller *m = i2c_dev_get_master(dev);
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+	struct adi_i3c_i2c_dev_data *data = i2c_dev_get_master_data(dev);
+
+	writel(REG_DEV_CHAR_ADDR(dev->addr) |
+	       REG_DEV_CHAR_IS_I2C | REG_DEV_CHAR_WEN,
+	       master->regs + REG_DEV_CHAR);
+
+	i2c_dev_set_master_data(dev, NULL);
+	master->free_rr_slots |= BIT(data->id);
+	kfree(data);
+}
+
+static void adi_i3c_master_bus_cleanup(struct i3c_master_controller *m)
+{
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+
+	adi_i3c_master_disable(master);
+}
+
+static void adi_i3c_master_upd_i3c_scl_lim(struct adi_i3c_master *master)
+{
+	struct i3c_master_controller *m = &master->base;
+	struct i3c_bus *bus = i3c_master_get_bus(m);
+	u8 i3c_scl_lim = 0;
+	struct i3c_dev_desc *dev;
+	u8 pp_sg;
+
+	i3c_bus_for_each_i3cdev(bus, dev) {
+		u8 max_fscl;
+
+		max_fscl = max(I3C_CCC_MAX_SDR_FSCL(dev->info.max_read_ds),
+			       I3C_CCC_MAX_SDR_FSCL(dev->info.max_write_ds));
+
+		switch (max_fscl) {
+		case I3C_SDR1_FSCL_8MHZ:
+			max_fscl = PP_SG_6MHZ;
+			break;
+		case I3C_SDR2_FSCL_6MHZ:
+			max_fscl = PP_SG_3MHZ;
+			break;
+		case I3C_SDR3_FSCL_4MHZ:
+			max_fscl = PP_SG_3MHZ;
+			break;
+		case I3C_SDR4_FSCL_2MHZ:
+			max_fscl = PP_SG_1MHZ;
+			break;
+		case I3C_SDR0_FSCL_MAX:
+		default:
+			max_fscl = PP_SG_12MHZ;
+			break;
+		}
+
+		if (max_fscl &&
+		    (i3c_scl_lim > max_fscl || !i3c_scl_lim))
+			i3c_scl_lim = max_fscl;
+	}
+
+	if (!i3c_scl_lim)
+		return;
+
+	master->i3c_scl_lim = i3c_scl_lim - 1;
+
+	pp_sg = readl(master->regs + REG_OPS) & ~REG_OPS_PP_SG_MASK;
+	pp_sg |= REG_OPS_SET_SG(master->i3c_scl_lim);
+
+	writel(pp_sg, master->regs + REG_OPS);
+}
+
+static void adi_i3c_master_get_features(struct adi_i3c_master *master,
+					unsigned int slot,
+					struct i3c_device_info *info)
+{
+	memset(info, 0, sizeof(*info));
+
+	info->dyn_addr = 0x31;
+	info->dcr = 0x00;
+	info->bcr = 0x40;
+	info->pid = 0;
+}
+
+static int adi_i3c_master_do_daa(struct i3c_master_controller *m)
+{
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+	u32 irq_mask;
+	int ret, addr;
+
+	addr = 0x8;
+	for (u8 i = 0; i < MAX_DEVS; i++) {
+		addr = i3c_master_get_free_addr(m, addr);
+		if (addr < 0)
+			return addr;
+		master->daa.addrs[i] = addr;
+	}
+
+	irq_mask = readl(master->regs + REG_IRQ_MASK);
+	writel(irq_mask | REG_IRQ_PENDING_DAA,
+	       master->regs + REG_IRQ_MASK);
+
+	master->daa.index = 0;
+	ret = i3c_master_entdaa_locked(&master->base);
+
+	writel(irq_mask, master->regs + REG_IRQ_MASK);
+
+	/* DAA always finishes with CE2_ERROR or NACK_RESP */
+	if (ret && ret != I3C_ERROR_M2)
+		return ret;
+
+	/* Add I3C devices discovered */
+	for (u8 i = 0; i < master->daa.index; i++)
+		i3c_master_add_i3c_dev_locked(m, master->daa.addrs[i]);
+	/* Sync retrieved devs info with the IP */
+	adi_i3c_master_sync_dev_char(m);
+
+	i3c_master_defslvs_locked(&master->base);
+
+	adi_i3c_master_upd_i3c_scl_lim(master);
+
+	return 0;
+}
+
+static int adi_i3c_master_bus_init(struct i3c_master_controller *m)
+{
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+	struct i3c_device_info info = { };
+	int ret;
+
+	ret = i3c_master_get_free_addr(m, 0);
+	if (ret < 0)
+		return ret;
+
+	adi_i3c_master_get_features(master, 0, &info);
+	ret = i3c_master_set_info(&master->base, &info);
+	if (ret)
+		return ret;
+
+	writel(REG_IBI_CONFIG_LISTEN,
+	       master->regs + REG_IBI_CONFIG);
+
+	return 0;
+}
+
+static void adi_i3c_master_handle_ibi(struct adi_i3c_master *master,
+				      u32 ibi)
+{
+	struct adi_i3c_i2c_dev_data *data;
+	struct i3c_ibi_slot *slot;
+	struct i3c_dev_desc *dev;
+	u8 da, id;
+	u8 *mdb;
+
+	da = (ibi >> 17) & GENMASK(6, 0);
+	for (id = 0; id < master->ibi.num_slots; id++) {
+		if (master->ibi.slots[id] &&
+		    master->ibi.slots[id]->info.dyn_addr == da)
+			break;
+	}
+
+	if (id == master->ibi.num_slots)
+		return;
+
+	dev = master->ibi.slots[id];
+	guard(spinlock)(&master->ibi.lock);
+
+	data = i3c_dev_get_master_data(dev);
+	slot = i3c_generic_ibi_get_free_slot(data->ibi_pool);
+	if (!slot)
+		return;
+
+	mdb = slot->data;
+	mdb[0] = (ibi >> 8) & GENMASK(7, 0);
+
+	slot->len = 1;
+	i3c_master_queue_ibi(dev, slot);
+}
+
+static void adi_i3c_master_demux_ibis(struct adi_i3c_master *master)
+{
+	u32 status0;
+
+	for (status0 = readl(master->regs + REG_FIFO_STATUS);
+	     !(status0 & REG_FIFO_STATUS_IBI_EMPTY);
+	     status0 = readl(master->regs + REG_FIFO_STATUS)) {
+		u32 ibi = readl(master->regs + REG_IBI_FIFO);
+
+		adi_i3c_master_handle_ibi(master, ibi);
+	}
+}
+
+static void adi_i3c_master_handle_da_req(struct adi_i3c_master *master)
+{
+	u8 payload0[8];
+	u32 addr;
+
+	/* Clear device characteristics */
+	adi_i3c_master_rd_from_rx_fifo(master, payload0, 6);
+	addr = master->daa.addrs[master->daa.index++];
+	addr = (addr << 1) | (parity8(addr) ? 0 : 1);
+
+	writel(addr, master->regs + REG_SDO_FIFO);
+}
+
+static irqreturn_t adi_i3c_master_irq(int irq, void *data)
+{
+	struct adi_i3c_master *master = data;
+	u32 pending;
+
+	pending = readl(master->regs + REG_IRQ_PENDING);
+	writel(pending, master->regs + REG_IRQ_PENDING);
+	if (pending & REG_IRQ_PENDING_CMDR) {
+		spin_lock(&master->xferqueue.lock);
+		adi_i3c_master_end_xfer_locked(master, pending);
+		spin_unlock(&master->xferqueue.lock);
+	}
+	if (pending & REG_IRQ_PENDING_IBI)
+		adi_i3c_master_demux_ibis(master);
+	if (pending & REG_IRQ_PENDING_DAA)
+		adi_i3c_master_handle_da_req(master);
+
+	return IRQ_HANDLED;
+}
+
+static int adi_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
+				    struct i2c_msg *xfers,
+				    int nxfers)
+{
+	struct i3c_master_controller *m = i2c_dev_get_master(dev);
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+	struct adi_i3c_xfer *xfer;
+	int i, ret;
+
+	if (!nxfers)
+		return 0;
+	for (i = 0; i < nxfers; i++) {
+		if (xfers[i].flags & I2C_M_TEN)
+			return -EOPNOTSUPP;
+	}
+	xfer = adi_i3c_master_alloc_xfer(master, nxfers);
+	if (!xfer)
+		return -ENOMEM;
+
+	for (i = 0; i < nxfers; i++) {
+		struct adi_i3c_cmd *ccmd = &xfer->cmds[i];
+
+		ccmd->cmd0 = REG_CMD_FIFO_0_DEV_ADDR(xfers[i].addr);
+
+		if (xfers[i].flags & I2C_M_RD) {
+			ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
+			ccmd->rx_buf = xfers[i].buf;
+			ccmd->rx_len = xfers[i].len;
+		} else {
+			ccmd->tx_buf = xfers[i].buf;
+			ccmd->tx_len = xfers[i].len;
+		}
+
+		ccmd->cmd0 |= REG_CMD_FIFO_0_LEN(xfers[i].len);
+	}
+
+	adi_i3c_master_queue_xfer(master, xfer);
+	if (!wait_for_completion_timeout(&xfer->comp,
+					 m->i2c.timeout))
+		adi_i3c_master_unqueue_xfer(master, xfer);
+
+	ret = xfer->ret;
+	kfree(xfer);
+	return ret;
+}
+
+static int adi_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
+{
+	struct i3c_master_controller *m = i3c_dev_get_master(dev);
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+	struct i3c_dev_desc *i3cdev;
+	u32 enabled = 0;
+	int ret;
+
+	ret = i3c_master_disec_locked(m, dev->info.dyn_addr,
+				      I3C_CCC_EVENT_SIR);
+
+	i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
+		if (dev != i3cdev && i3cdev->ibi)
+			enabled |= i3cdev->ibi->enabled;
+	}
+	if (!enabled) {
+		writel(REG_IBI_CONFIG_LISTEN,
+		       master->regs + REG_IBI_CONFIG);
+		writel(readl(master->regs + REG_IRQ_MASK) & ~REG_IRQ_PENDING_IBI,
+		       master->regs + REG_IRQ_MASK);
+	}
+
+	return ret;
+}
+
+static int adi_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
+{
+	struct i3c_master_controller *m = i3c_dev_get_master(dev);
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+
+	writel(REG_IBI_CONFIG_LISTEN | REG_IBI_CONFIG_ENABLE,
+	       master->regs + REG_IBI_CONFIG);
+
+	writel(readl(master->regs + REG_IRQ_MASK) | REG_IRQ_PENDING_IBI,
+	       master->regs + REG_IRQ_MASK);
+
+	return i3c_master_enec_locked(m, dev->info.dyn_addr,
+				      I3C_CCC_EVENT_SIR);
+}
+
+static int adi_i3c_master_request_ibi(struct i3c_dev_desc *dev,
+				      const struct i3c_ibi_setup *req)
+{
+	struct i3c_master_controller *m = i3c_dev_get_master(dev);
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+	struct adi_i3c_i2c_dev_data *data;
+	unsigned long flags;
+	unsigned int i;
+
+	data = i3c_dev_get_master_data(dev);
+	data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req);
+	if (IS_ERR(data->ibi_pool))
+		return PTR_ERR(data->ibi_pool);
+
+	spin_lock_irqsave(&master->ibi.lock, flags);
+	for (i = 0; i < master->ibi.num_slots; i++) {
+		if (!master->ibi.slots[i]) {
+			data->ibi = i;
+			master->ibi.slots[i] = dev;
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&master->ibi.lock, flags);
+
+	if (i < master->ibi.num_slots)
+		return 0;
+
+	i3c_generic_ibi_free_pool(data->ibi_pool);
+	data->ibi_pool = NULL;
+
+	return -ENOSPC;
+}
+
+static void adi_i3c_master_free_ibi(struct i3c_dev_desc *dev)
+{
+	struct i3c_master_controller *m = i3c_dev_get_master(dev);
+	struct adi_i3c_master *master = to_adi_i3c_master(m);
+	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&master->ibi.lock, flags);
+	master->ibi.slots[data->ibi] = NULL;
+	data->ibi = -1;
+	spin_unlock_irqrestore(&master->ibi.lock, flags);
+
+	i3c_generic_ibi_free_pool(data->ibi_pool);
+}
+
+static void adi_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev,
+					    struct i3c_ibi_slot *slot)
+{
+	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
+
+	i3c_generic_ibi_recycle_slot(data->ibi_pool, slot);
+}
+
+static const struct i3c_master_controller_ops adi_i3c_master_ops = {
+	.bus_init = adi_i3c_master_bus_init,
+	.bus_cleanup = adi_i3c_master_bus_cleanup,
+	.attach_i3c_dev = adi_i3c_master_attach_i3c_dev,
+	.reattach_i3c_dev = adi_i3c_master_reattach_i3c_dev,
+	.detach_i3c_dev = adi_i3c_master_detach_i3c_dev,
+	.attach_i2c_dev = adi_i3c_master_attach_i2c_dev,
+	.detach_i2c_dev = adi_i3c_master_detach_i2c_dev,
+	.do_daa = adi_i3c_master_do_daa,
+	.supports_ccc_cmd = adi_i3c_master_supports_ccc_cmd,
+	.send_ccc_cmd = adi_i3c_master_send_ccc_cmd,
+	.priv_xfers = adi_i3c_master_priv_xfers,
+	.i2c_xfers = adi_i3c_master_i2c_xfers,
+	.request_ibi = adi_i3c_master_request_ibi,
+	.enable_ibi = adi_i3c_master_enable_ibi,
+	.disable_ibi = adi_i3c_master_disable_ibi,
+	.free_ibi = adi_i3c_master_free_ibi,
+	.recycle_ibi_slot = adi_i3c_master_recycle_ibi_slot,
+};
+
+static const struct of_device_id adi_i3c_master_of_match[] = {
+	{ .compatible = "adi,i3c-master" },
+	{}
+};
+
+static int adi_i3c_master_probe(struct platform_device *pdev)
+{
+	struct adi_i3c_master *master;
+	unsigned int version;
+	int ret, irq;
+
+	master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL);
+	if (!master)
+		return -ENOMEM;
+
+	master->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(master->regs))
+		return PTR_ERR(master->regs);
+
+	master->clk = devm_clk_get_enabled(&pdev->dev, "axi");
+	if (IS_ERR(master->clk))
+		return PTR_ERR(master->clk);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	version = readl(master->regs + REG_VERSION);
+	if (VERSION_MAJOR(version) != 0) {
+		dev_err(&pdev->dev, "Unsupported IP version %u.%u.%c\n",
+			VERSION_MAJOR(version),
+			VERSION_MINOR(version),
+			VERSION_PATCH(version));
+		return -EINVAL;
+	}
+
+	writel(0x00, master->regs + REG_ENABLE);
+	writel(0x00, master->regs + REG_IRQ_MASK);
+
+	ret = devm_request_irq(&pdev->dev, irq, adi_i3c_master_irq, 0,
+			       dev_name(&pdev->dev), master);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, master);
+
+	master->maxdevs = MAX_DEVS;
+	master->free_rr_slots = GENMASK(master->maxdevs, 1);
+
+	writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
+
+	spin_lock_init(&master->ibi.lock);
+	master->ibi.num_slots = 15;
+	master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots,
+					 sizeof(*master->ibi.slots),
+					 GFP_KERNEL);
+	if (!master->ibi.slots)
+		return -ENOMEM;
+
+	return i3c_master_register(&master->base, &pdev->dev,
+				   &adi_i3c_master_ops, false);
+}
+
+static void adi_i3c_master_remove(struct platform_device *pdev)
+{
+	struct adi_i3c_master *master = platform_get_drvdata(pdev);
+
+	writel(0xff, master->regs + REG_IRQ_PENDING);
+	writel(0x00, master->regs + REG_IRQ_MASK);
+	writel(0x01, master->regs + REG_ENABLE);
+
+	i3c_master_unregister(&master->base);
+}
+
+static struct platform_driver adi_i3c_master = {
+	.probe = adi_i3c_master_probe,
+	.remove = adi_i3c_master_remove,
+	.driver = {
+		.name = "adi-i3c-master",
+		.of_match_table = adi_i3c_master_of_match,
+	},
+};
+module_platform_driver(adi_i3c_master);
+
+MODULE_AUTHOR("Jorge Marques <jorge.marques@analog.com>");
+MODULE_DESCRIPTION("Analog Devices I3C master driver");
+MODULE_LICENSE("GPL");

-- 
2.49.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 1/2] dt-bindings: i3c: Add adi-i3c-master
  2025-06-18  7:16 ` [PATCH v3 1/2] dt-bindings: i3c: Add adi-i3c-master Jorge Marques
@ 2025-06-18  8:40   ` Rob Herring (Arm)
  2025-06-18  9:21   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 14+ messages in thread
From: Rob Herring (Arm) @ 2025-06-18  8:40 UTC (permalink / raw)
  To: Jorge Marques
  Cc: Krzysztof Kozlowski, gastmaier, linux-kernel, Frank Li, linux-i3c,
	devicetree, Alexandre Belloni, Conor Dooley


On Wed, 18 Jun 2025 09:16:43 +0200, Jorge Marques wrote:
> Add bindings doc for ADI I3C Controller IP core, a FPGA synthesizable IP
> core that implements the MIPI I3C Basic controller specification.
> 
> Signed-off-by: Jorge Marques <jorge.marques@analog.com>
> ---
>  .../devicetree/bindings/i3c/adi,i3c-master.yaml    | 63 ++++++++++++++++++++++
>  MAINTAINERS                                        |  5 ++
>  2 files changed, 68 insertions(+)
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
Documentation/devicetree/bindings/i3c/adi,i3c-master.example.dtb: /example-0/i3c@44a00000: failed to match any schema with compatible: ['adi,i3c-master']

doc reference errors (make refcheckdocs):

See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20250618-adi-i3c-master-v3-1-e66170a6cb95@analog.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 1/2] dt-bindings: i3c: Add adi-i3c-master
  2025-06-18  7:16 ` [PATCH v3 1/2] dt-bindings: i3c: Add adi-i3c-master Jorge Marques
  2025-06-18  8:40   ` Rob Herring (Arm)
@ 2025-06-18  9:21   ` Krzysztof Kozlowski
  2025-06-18 12:15     ` Jorge Marques
  1 sibling, 1 reply; 14+ messages in thread
From: Krzysztof Kozlowski @ 2025-06-18  9:21 UTC (permalink / raw)
  To: Jorge Marques
  Cc: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-i3c, devicetree, linux-kernel, gastmaier

On Wed, Jun 18, 2025 at 09:16:43AM GMT, Jorge Marques wrote:
> Add bindings doc for ADI I3C Controller IP core, a FPGA synthesizable IP
> core that implements the MIPI I3C Basic controller specification.

Here you put outcome of previous questions - why such compatible was
chosen, that hardware is this and that.

> 
> Signed-off-by: Jorge Marques <jorge.marques@analog.com>
> ---
>  .../devicetree/bindings/i3c/adi,i3c-master.yaml    | 63 ++++++++++++++++++++++
>  MAINTAINERS                                        |  5 ++
>  2 files changed, 68 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml b/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
> new file mode 100644
> index 0000000000000000000000000000000000000000..718733bbb450c34c5d4924050cc6f85d8a80fe4b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml

Filename based on the compatible, so adi,i3c-master-1.00.a.yaml

> @@ -0,0 +1,63 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/i3c/adi,i3c-master.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices I3C Controller
> +
> +description: |
> +  FPGA-based I3C controller designed to interface with I3C and I2C peripherals,
> +  implementing a subset of the I3C-basic specification.
> +
> +  https://analogdevicesinc.github.io/hdl/library/i3c_controller
> +
> +maintainers:
> +  - Jorge Marques <jorge.marques@analog.com>
> +
> +properties:
> +  compatible:
> +    const: adi,i3c-master-1.00.a
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    minItems: 1

Why?

> +    items:
> +      - description: The AXI interconnect clock.
> +      - description: The I3C controller clock.
> +
> +  clock-names:

Not synced with clocks.

> +    items:
> +      - const: axi
> +      - const: i3c
> +
> +  interrupts:
> +    maxItems: 1
> +
> +required:
> +  - compatible
> +  - reg
> +  - clocks
> +  - clock-names
> +  - interrupts
> +
> +allOf:
> +  - $ref: i3c.yaml#
> +
> +unevaluatedProperties: false
> +
> +examples:
> +  - |
> +    i3c@44a00000 {
> +        compatible = "adi,i3c-master";
> +        reg = <0x44a00000 0x1000>;
> +        interrupts = <0 56 4>;

Use proper defines.

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 1/2] dt-bindings: i3c: Add adi-i3c-master
  2025-06-18  9:21   ` Krzysztof Kozlowski
@ 2025-06-18 12:15     ` Jorge Marques
  2025-06-18 15:45       ` Krzysztof Kozlowski
  0 siblings, 1 reply; 14+ messages in thread
From: Jorge Marques @ 2025-06-18 12:15 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Jorge Marques, Alexandre Belloni, Frank Li, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-i3c, devicetree,
	linux-kernel

On Wed, Jun 18, 2025 at 11:21:22AM +0200, Krzysztof Kozlowski wrote:
> On Wed, Jun 18, 2025 at 09:16:43AM GMT, Jorge Marques wrote:
> > Add bindings doc for ADI I3C Controller IP core, a FPGA synthesizable IP
> > core that implements the MIPI I3C Basic controller specification.
> 
> Here you put outcome of previous questions - why such compatible was
> chosen, that hardware is this and that.
> 
> > 
> > Signed-off-by: Jorge Marques <jorge.marques@analog.com>
> > ---
> >  .../devicetree/bindings/i3c/adi,i3c-master.yaml    | 63 ++++++++++++++++++++++
> >  MAINTAINERS                                        |  5 ++
> >  2 files changed, 68 insertions(+)
> > 
> > diff --git a/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml b/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..718733bbb450c34c5d4924050cc6f85d8a80fe4b
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
> 
> Filename based on the compatible, so adi,i3c-master-1.00.a.yaml
> 
I agree, but I ended up following the pattern for the other adi,
bindings. I will move for v4. IMO the version suffix has no much use
since IP updates are handled in the driver.
> > @@ -0,0 +1,63 @@
> > +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/i3c/adi,i3c-master.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Analog Devices I3C Controller
> > +
> > +description: |
> > +  FPGA-based I3C controller designed to interface with I3C and I2C peripherals,
> > +  implementing a subset of the I3C-basic specification.
> > +
> > +  https://analogdevicesinc.github.io/hdl/library/i3c_controller
> > +
> > +maintainers:
> > +  - Jorge Marques <jorge.marques@analog.com>
> > +
> > +properties:
> > +  compatible:
> > +    const: adi,i3c-master-1.00.a
> > +
> > +  reg:
> > +    maxItems: 1
> > +
> > +  clocks:
> > +    minItems: 1
> 
> Why?
> 
The IP core requires a clock, and the second is optional.
minItems sets the minimum number of required clocks and the maxItems is
inferred from the number of items.

On the IP core itself, one clock is required (axi), and if it is the
only provided, it means that the same clock for the AXI bus is used
also for the rest of the RTL logic.

If a second clock is provided, i3c, it means it drives the RTL logic and is
asynchronous to the axi clock, which then just drives the register map logic.
For i3c specified nominal speeds, the RTL logic should run with a speed of
100MHz. Some FPGAs, such as Altera CycloneV, have a default bus clock speed of
50MHz. Changing the bus speed is possible, but affects timing and it may not be
possible from users to double the bus speed since it will affect timing of all
IP cores using the bus clock.
> > +    items:
> > +      - description: The AXI interconnect clock.
> > +      - description: The I3C controller clock.
I will update the descriptions to:

        - description: The AXI interconnect clock, drives the register map.
        - description: The I3C controller clock. AXI clock drives all logic if not provided.

> > +
> > +  clock-names:
> 
> Not synced with clocks.
> 
I will add `minItems: 1`.
> > +    items:
> > +      - const: axi
> > +      - const: i3c
> > +
> > +  interrupts:
> > +    maxItems: 1
> > +
> > +required:
> > +  - compatible
> > +  - reg
> > +  - clocks
> > +  - clock-names
> > +  - interrupts
> > +
> > +allOf:
> > +  - $ref: i3c.yaml#
> > +
> > +unevaluatedProperties: false
> > +
> > +examples:
> > +  - |
> > +    i3c@44a00000 {
> > +        compatible = "adi,i3c-master";
> > +        reg = <0x44a00000 0x1000>;
> > +        interrupts = <0 56 4>;
> 
> Use proper defines.
> 
The following can added:

  #include <dt-bindings/interrupt-controller/irq.h>

  interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>;

Is there any other to be replaced?

> Best regards,
> Krzysztof
> 

Best regards,
Jorge

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 1/2] dt-bindings: i3c: Add adi-i3c-master
  2025-06-18 12:15     ` Jorge Marques
@ 2025-06-18 15:45       ` Krzysztof Kozlowski
  2025-06-18 19:55         ` Jorge Marques
  0 siblings, 1 reply; 14+ messages in thread
From: Krzysztof Kozlowski @ 2025-06-18 15:45 UTC (permalink / raw)
  To: Jorge Marques
  Cc: Jorge Marques, Alexandre Belloni, Frank Li, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-i3c, devicetree,
	linux-kernel

On 18/06/2025 14:15, Jorge Marques wrote:
>>>
>>> Signed-off-by: Jorge Marques <jorge.marques@analog.com>
>>> ---
>>>  .../devicetree/bindings/i3c/adi,i3c-master.yaml    | 63 ++++++++++++++++++++++
>>>  MAINTAINERS                                        |  5 ++
>>>  2 files changed, 68 insertions(+)
>>>
>>> diff --git a/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml b/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
>>> new file mode 100644
>>> index 0000000000000000000000000000000000000000..718733bbb450c34c5d4924050cc6f85d8a80fe4b
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
>>
>> Filename based on the compatible, so adi,i3c-master-1.00.a.yaml
>>
> I agree, but I ended up following the pattern for the other adi,
> bindings. I will move for v4. IMO the version suffix has no much use
> since IP updates are handled in the driver.

Filename is not related to whether given ABI works with every device.
Filename helps us to organize bindings and existing convention is that
we want it to follow the compatible.

>>> @@ -0,0 +1,63 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
>>> +%YAML 1.2
>>> +---
>>> +$id: http://devicetree.org/schemas/i3c/adi,i3c-master.yaml#
>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>> +
>>> +title: Analog Devices I3C Controller
>>> +
>>> +description: |
>>> +  FPGA-based I3C controller designed to interface with I3C and I2C peripherals,
>>> +  implementing a subset of the I3C-basic specification.
>>> +
>>> +  https://analogdevicesinc.github.io/hdl/library/i3c_controller
>>> +
>>> +maintainers:
>>> +  - Jorge Marques <jorge.marques@analog.com>
>>> +
>>> +properties:
>>> +  compatible:
>>> +    const: adi,i3c-master-1.00.a
>>> +
>>> +  reg:
>>> +    maxItems: 1
>>> +
>>> +  clocks:
>>> +    minItems: 1
>>
>> Why?
>>
> The IP core requires a clock, and the second is optional.

OK

> minItems sets the minimum number of required clocks and the maxItems is
> inferred from the number of items.
> 
> On the IP core itself, one clock is required (axi), and if it is the
> only provided, it means that the same clock for the AXI bus is used
> also for the rest of the RTL logic.

Hm? What does it exactly mean - same clock? You mean one clock is routed
to two pins? That's still two clocks. Or you mean that IP core will
notice grounded clock input and do the routing inside?

> 
> If a second clock is provided, i3c, it means it drives the RTL logic and is
> asynchronous to the axi clock, which then just drives the register map logic.
> For i3c specified nominal speeds, the RTL logic should run with a speed of
> 100MHz. Some FPGAs, such as Altera CycloneV, have a default bus clock speed of
> 50MHz. Changing the bus speed is possible, but affects timing and it may not be
> possible from users to double the bus speed since it will affect timing of all
> IP cores using the bus clock.
>>> +    items:
>>> +      - description: The AXI interconnect clock.
>>> +      - description: The I3C controller clock.
> I will update the descriptions to:
> 
>         - description: The AXI interconnect clock, drives the register map.
>         - description: The I3C controller clock. AXI clock drives all logic if not provided.
> 
>>> +
>>> +  clock-names:
>>
>> Not synced with clocks.
>>
> I will add `minItems: 1`.
>>> +    items:
>>> +      - const: axi
>>> +      - const: i3c
>>> +
>>> +  interrupts:
>>> +    maxItems: 1
>>> +
>>> +required:
>>> +  - compatible
>>> +  - reg
>>> +  - clocks
>>> +  - clock-names
>>> +  - interrupts
>>> +
>>> +allOf:
>>> +  - $ref: i3c.yaml#
>>> +
>>> +unevaluatedProperties: false
>>> +
>>> +examples:
>>> +  - |
>>> +    i3c@44a00000 {
>>> +        compatible = "adi,i3c-master";
>>> +        reg = <0x44a00000 0x1000>;
>>> +        interrupts = <0 56 4>;
>>
>> Use proper defines.
>>
> The following can added:
> 
>   #include <dt-bindings/interrupt-controller/irq.h>
> 
>   interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>;
> 
> Is there any other to be replaced?

Usually 0 has a meaning as well. Where is this used DTS snippet used (on
which platform)?

Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP
  2025-06-18  7:16 ` [PATCH v3 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP Jorge Marques
@ 2025-06-18 19:40   ` Frank Li
  2025-06-18 22:17     ` Jorge Marques
  0 siblings, 1 reply; 14+ messages in thread
From: Frank Li @ 2025-06-18 19:40 UTC (permalink / raw)
  To: Jorge Marques
  Cc: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	linux-i3c, devicetree, linux-kernel, gastmaier

On Wed, Jun 18, 2025 at 09:16:44AM +0200, Jorge Marques wrote:
> Add support for Analog Devices I3C Controller IP, an AXI-interfaced IP
> core that supports I3C and I2C devices, multiple speed-grades and
> I3C IBIs.
>
> Signed-off-by: Jorge Marques <jorge.marques@analog.com>
> ---
>  MAINTAINERS                         |    1 +
>  drivers/i3c/master/Kconfig          |   11 +
>  drivers/i3c/master/Makefile         |    1 +
>  drivers/i3c/master/adi-i3c-master.c | 1026 +++++++++++++++++++++++++++++++++++
>  4 files changed, 1039 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6f56e17dcecf902c6812827c1ec3e067c65e9894..9eb5b6c327590725d1641fd4b73e48fc1d1a3954 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -11247,6 +11247,7 @@ I3C DRIVER FOR ANALOG DEVICES I3C CONTROLLER IP
>  M:	Jorge Marques <jorge.marques@analog.com>
>  S:	Maintained
>  F:	Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
> +F:	drivers/i3c/master/adi-i3c-master.c
>
>  I3C DRIVER FOR CADENCE I3C MASTER IP
>  M:	Przemysław Gaj <pgaj@cadence.com>
> diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig
> index 7b30db3253af9d5c6aee6544c060e491bfbeb643..328b7145cdefa20e708ebfa3383e849ce51c5a71 100644
> --- a/drivers/i3c/master/Kconfig
> +++ b/drivers/i3c/master/Kconfig
> @@ -1,4 +1,15 @@
>  # SPDX-License-Identifier: GPL-2.0-only
> +config ADI_I3C_MASTER
> +	tristate "Analog Devices I3C master driver"
> +	depends on HAS_IOMEM
> +	help
> +	  Support for Analog Devices I3C Controller IP, an AXI-interfaced IP
> +	  core that supports I3C and I2C devices, multiple speed-grades and
> +	  I3C IBIs.
> +
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called adi-i3c-master.
> +
>  config CDNS_I3C_MASTER
>  	tristate "Cadence I3C master driver"
>  	depends on HAS_IOMEM
> diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile
> index 3e97960160bc85e5eaf2966ec0c3fae458c2711e..6cc4f4b73e7bdc206b68c750390f9c3cc2ccb199 100644
> --- a/drivers/i3c/master/Makefile
> +++ b/drivers/i3c/master/Makefile
> @@ -1,4 +1,5 @@
>  # SPDX-License-Identifier: GPL-2.0-only
> +obj-$(CONFIG_ADI_I3C_MASTER)		+= adi-i3c-master.o
>  obj-$(CONFIG_CDNS_I3C_MASTER)		+= i3c-master-cdns.o
>  obj-$(CONFIG_DW_I3C_MASTER)		+= dw-i3c-master.o
>  obj-$(CONFIG_AST2600_I3C_MASTER)	+= ast2600-i3c-master.o
> diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..d4e0756efec9588383ac55ca9ea9228368ce767d
> --- /dev/null
> +++ b/drivers/i3c/master/adi-i3c-master.c
> @@ -0,0 +1,1026 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * I3C Controller driver
> + * Copyright 2025 Analog Devices Inc.
> + * Author: Jorge Marques <jorge.marques@analog.com>
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/i3c/master.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +
> +#define VERSION_MAJOR(x)		((u32)FIELD_GET(GENMASK(23, 16), (x)))
> +#define VERSION_MINOR(x)		((u32)FIELD_GET(GENMASK(15, 8), (x)))
> +#define VERSION_PATCH(x)		((u32)FIELD_GET(GENMASK(7, 0), (x)))

I suppose that needn't (u32), any warnings report without u32?

> +
> +#define MAX_DEVS			16
> +
> +#define REG_VERSION			0x000
> +#define REG_ENABLE			0x040
> +#define REG_IRQ_MASK			0x080
> +#define REG_IRQ_PENDING			0x084
> +#define   REG_IRQ_PENDING_CMDR		BIT(5)
> +#define   REG_IRQ_PENDING_IBI		((u32)BIT(6))

needn't u32
> +#define   REG_IRQ_PENDING_DAA		BIT(7)

Add empty line between registers

> +#define REG_CMD_FIFO			0x0d4
> +#define	  REG_CMD_FIFO_0_IS_CCC		BIT(22)
> +#define   REG_CMD_FIFO_0_BCAST		BIT(21)
> +#define   REG_CMD_FIFO_0_SR		BIT(20)
> +#define   REG_CMD_FIFO_0_LEN(l)		FIELD_PREP(GENMASK(19, 8), (l))
> +#define   REG_CMD_FIFO_0_DEV_ADDR(a)	FIELD_PREP(GENMASK(7, 1), a)
> +#define   REG_CMD_FIFO_0_RNW		BIT(0)
> +#define   REG_CMD_FIFO_1_CCC(id)	FIELD_PREP(GENMASK(7, 0), (id))
> +#define REG_CMD_FIFO_ROOM		0x0c0
> +#define REG_CMDR_FIFO			0x0d8
> +#define   REG_CMDR_FIFO_NO_ERROR	0
> +#define   REG_CMDR_FIFO_CE0_ERROR	1
> +#define   REG_CMDR_FIFO_CE2_ERROR	4
> +#define   REG_CMDR_FIFO_NACK_RESP	6
> +#define   REG_CMDR_FIFO_UDA_ERROR	8
> +#define   REG_CMDR_FIFO_ERROR(x)	FIELD_GET(GENMASK(23, 20), (x))
> +#define   REG_CMDR_FIFO_XFER_BYTES(x)	FIELD_GET(GENMASK(19, 8), (x))

Sort according bit pos. REG_CMDR_FIFO_XFER_BYTES should before
REG_CMDR_FIFO_ERROR

> +#define REG_SDO_FIFO			0x0dc
> +#define REG_SDO_FIFO_ROOM		0x0c8
> +#define REG_SDI_FIFO			0x0e0
> +#define REG_IBI_FIFO			0x0e4
> +#define REG_FIFO_STATUS			0x0e8
> +#define   REG_FIFO_STATUS_CMDR_EMPTY	BIT(0)
> +#define   REG_FIFO_STATUS_IBI_EMPTY	BIT(1)
> +#define REG_OPS				0x100
> +#define   REG_OPS_SET_SG(x)		FIELD_PREP(GENMASK(6, 5), (x))
> +#define   REG_OPS_PP_SG_MASK		GENMASK(6, 5)

REG_OPS_SET_SG(x) FIELD_PREP(REG_OPS_PP_SG_MASK, (x))

> +#define REG_IBI_CONFIG			0x140
> +#define   REG_IBI_CONFIG_LISTEN		BIT(1)
> +#define   REG_IBI_CONFIG_ENABLE		BIT(0)

sort it. keep consisent in whole file, from 0->31 or 31->0

> +#define REG_DEV_CHAR			0x180
> +#define   REG_DEV_CHAR_IS_I2C		BIT(0)
> +#define   REG_DEV_CHAR_IS_ATTACHED	BIT(1)
> +#define   REG_DEV_CHAR_BCR_IBI(x)	(((x) & GENMASK(2, 1)) << 1)

FIELD_PREP()

> +#define   REG_DEV_CHAR_WEN		BIT(8)
> +#define   REG_DEV_CHAR_ADDR(x)		FIELD_PREP(GENMASK(15, 9), (x))
> +
> +enum speed_grade {PP_SG_UNSET, PP_SG_1MHZ, PP_SG_3MHZ, PP_SG_6MHZ, PP_SG_12MHZ};
> +struct adi_i3c_cmd {
> +	u32 cmd0;
> +	u32 cmd1;
> +	u32 tx_len;
> +	const void *tx_buf;
> +	u32 rx_len;
> +	void *rx_buf;
> +	u32 error;
> +};
> +
> +struct adi_i3c_xfer {
> +	struct list_head node;
> +	struct completion comp;
> +	int ret;
> +	unsigned int ncmds;
> +	unsigned int ncmds_comp;
> +	struct adi_i3c_cmd cmds[];
> +};
> +
> +struct adi_i3c_master {
> +	struct i3c_master_controller base;
> +	u32 free_rr_slots;
> +	unsigned int maxdevs;
> +	struct {
> +		unsigned int num_slots;
> +		struct i3c_dev_desc **slots;
> +		spinlock_t lock; /* Protect IBI slot access */
> +	} ibi;
> +	struct {
> +		struct list_head list;
> +		struct adi_i3c_xfer *cur;
> +		spinlock_t lock; /* Protect transfer */
> +	} xferqueue;
> +	void __iomem *regs;
> +	struct clk *clk;
> +	unsigned long i3c_scl_lim;
> +	struct {
> +		u8 addrs[MAX_DEVS];
> +		u8 index;
> +	} daa;
> +};
> +
> +static inline struct adi_i3c_master *to_adi_i3c_master(struct i3c_master_controller *master)
> +{
> +	return container_of(master, struct adi_i3c_master, base);
> +}
> +
> +static void adi_i3c_master_wr_to_tx_fifo(struct adi_i3c_master *master,
> +					 const u8 *bytes, unsigned int nbytes)
> +{
> +	unsigned int n, m;
> +	u32 tmp;
> +
> +	n = readl(master->regs + REG_SDO_FIFO_ROOM);
> +	m = min(n, nbytes);
> +	writesl(master->regs + REG_SDO_FIFO, bytes, m / 4);
> +
> +	if (m & 3) {
> +		memcpy(&tmp, bytes + (m & ~3), m & 3);
> +		writel(tmp, master->regs + REG_SDO_FIFO);
> +	}
> +}
> +
> +static void adi_i3c_master_rd_from_rx_fifo(struct adi_i3c_master *master,
> +					   u8 *bytes, unsigned int nbytes)
> +{
> +	readsl(master->regs + REG_SDI_FIFO, bytes, nbytes / 4);
> +	if (nbytes & 3) {
> +		u32 tmp;
> +
> +		tmp = readl(master->regs + REG_SDI_FIFO);
> +		memcpy(bytes + (nbytes & ~3), &tmp, nbytes & 3);
> +	}
> +}

can you move above two helper function as inline into drivers/i3c/internals.h

or create new one drivers/i3c/master/helper.h or other naming.

Renesas IP have similar above two functions to access FIFO.
https://lore.kernel.org/linux-i3c/174964724485.330045.2181706921272138816.robh@kernel.org/T/#t

> +
> +static bool adi_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m,
> +					    const struct i3c_ccc_cmd *cmd)
> +{
> +	if (cmd->ndests > 1)
> +		return false;
> +
> +	switch (cmd->id) {
> +	case I3C_CCC_ENEC(true):
> +	case I3C_CCC_ENEC(false):
> +	case I3C_CCC_DISEC(true):
> +	case I3C_CCC_DISEC(false):
> +	case I3C_CCC_RSTDAA(true):
> +	case I3C_CCC_RSTDAA(false):
> +	case I3C_CCC_ENTDAA:
> +	case I3C_CCC_SETDASA:
> +	case I3C_CCC_SETNEWDA:
> +	case I3C_CCC_GETMWL:
> +	case I3C_CCC_GETMRL:
> +	case I3C_CCC_GETPID:
> +	case I3C_CCC_GETBCR:
> +	case I3C_CCC_GETDCR:
> +	case I3C_CCC_GETSTATUS:
> +	case I3C_CCC_GETHDRCAP:
> +		return true;
> +	default:
> +		break;
> +	}
> +
> +	return false;
> +}
> +
> +static int adi_i3c_master_disable(struct adi_i3c_master *master)
> +{
> +	writel(0, master->regs + REG_IBI_CONFIG);
> +
> +	return 0;
> +}
> +
> +static struct adi_i3c_xfer *adi_i3c_master_alloc_xfer(struct adi_i3c_master *master,
> +						      unsigned int ncmds)
> +{
> +	struct adi_i3c_xfer *xfer;
> +
> +	xfer = kzalloc(struct_size(xfer, cmds, ncmds), GFP_KERNEL);
> +	if (!xfer)
> +		return NULL;
> +
> +	INIT_LIST_HEAD(&xfer->node);
> +	xfer->ncmds = ncmds;
> +	xfer->ret = -ETIMEDOUT;
> +
> +	return xfer;
> +}
> +
> +static void adi_i3c_master_start_xfer_locked(struct adi_i3c_master *master)
> +{
> +	struct adi_i3c_xfer *xfer = master->xferqueue.cur;
> +	unsigned int i, n, m;
> +
> +	if (!xfer)
> +		return;
> +
> +	for (i = 0; i < xfer->ncmds; i++) {
> +		struct adi_i3c_cmd *cmd = &xfer->cmds[i];
> +
> +		if (!(cmd->cmd0 & REG_CMD_FIFO_0_RNW))
> +			adi_i3c_master_wr_to_tx_fifo(master, cmd->tx_buf, cmd->tx_len);
> +	}
> +
> +	n = readl(master->regs + REG_CMD_FIFO_ROOM);
> +	for (i = 0; i < xfer->ncmds; i++) {
> +		struct adi_i3c_cmd *cmd = &xfer->cmds[i];
> +
> +		m = cmd->cmd0 & REG_CMD_FIFO_0_IS_CCC ? 2 : 1;
> +		if (m > n)
> +			break;
> +		writel(cmd->cmd0, master->regs + REG_CMD_FIFO);
> +		if (cmd->cmd0 & REG_CMD_FIFO_0_IS_CCC)
> +			writel(cmd->cmd1, master->regs + REG_CMD_FIFO);
> +		n -= m;
> +	}
> +}
> +
> +static void adi_i3c_master_end_xfer_locked(struct adi_i3c_master *master,
> +					   u32 pending)
> +{
> +	struct adi_i3c_xfer *xfer = master->xferqueue.cur;
> +	int i, ret = 0;
> +	u32 status0;
> +
> +	if (!xfer)
> +		return;
> +
> +	for (status0 = readl(master->regs + REG_FIFO_STATUS);
> +	     !(status0 & REG_FIFO_STATUS_CMDR_EMPTY);
> +	     status0 = readl(master->regs + REG_FIFO_STATUS)) {

how about

while (!(readl(master->regs + REG_FIFO_STATUS) & REG_FIFO_STATUS_CMDR_EMPTY))) {

> +		struct adi_i3c_cmd *cmd;
> +		u32 cmdr, rx_len;
> +
> +		cmdr = readl(master->regs + REG_CMDR_FIFO);
> +
> +		cmd = &xfer->cmds[xfer->ncmds_comp++];
> +		if (cmd->cmd0 & REG_CMD_FIFO_0_RNW) {
> +			rx_len = min_t(u32, REG_CMDR_FIFO_XFER_BYTES(cmdr), cmd->rx_len);
> +			adi_i3c_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len);
> +		}
> +		cmd->error = REG_CMDR_FIFO_ERROR(cmdr);
> +	}
> +
> +	for (i = 0; i < xfer->ncmds; i++) {

I think you iterate to ncmds_comp?

> +		switch (xfer->cmds[i].error) {
> +		case REG_CMDR_FIFO_NO_ERROR:
> +			break;
> +
> +		case REG_CMDR_FIFO_CE0_ERROR:
> +		case REG_CMDR_FIFO_CE2_ERROR:
> +		case REG_CMDR_FIFO_NACK_RESP:
> +		case REG_CMDR_FIFO_UDA_ERROR:
> +			ret = -EIO;
> +			break;
> +
> +		default:
> +			ret = -EINVAL;
> +			break;
> +		}
> +	}
> +
> +	xfer->ret = ret;
> +
> +	if (xfer->ncmds_comp != xfer->ncmds)
> +		return;
> +
> +	complete(&xfer->comp);
> +
> +	xfer = list_first_entry_or_null(&master->xferqueue.list,
> +					struct adi_i3c_xfer, node);
> +	if (xfer)
> +		list_del_init(&xfer->node);
> +
> +	master->xferqueue.cur = xfer;
> +	adi_i3c_master_start_xfer_locked(master);
> +}
> +
> +static void adi_i3c_master_queue_xfer(struct adi_i3c_master *master,
> +				      struct adi_i3c_xfer *xfer)
> +{
> +	init_completion(&xfer->comp);
> +	guard(spinlock_irqsave)(&master->xferqueue.lock);
> +	if (master->xferqueue.cur) {
> +		list_add_tail(&xfer->node, &master->xferqueue.list);
> +	} else {
> +		master->xferqueue.cur = xfer;
> +		adi_i3c_master_start_xfer_locked(master);
> +	}
> +}
> +
> +static void adi_i3c_master_unqueue_xfer(struct adi_i3c_master *master,
> +					struct adi_i3c_xfer *xfer)
> +{
> +	guard(spinlock_irqsave)(&master->xferqueue.lock);
> +	if (master->xferqueue.cur == xfer)
> +		master->xferqueue.cur = NULL;
> +	else
> +		list_del_init(&xfer->node);
> +
> +	writel(0x01, master->regs + REG_ENABLE);
> +	writel(0x00, master->regs + REG_ENABLE);
> +	writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
> +}
> +
> +static enum i3c_error_code adi_i3c_cmd_get_err(struct adi_i3c_cmd *cmd)
> +{
> +	switch (cmd->error) {
> +	case REG_CMDR_FIFO_CE0_ERROR:
> +		return I3C_ERROR_M0;
> +
> +	case REG_CMDR_FIFO_CE2_ERROR:
> +	case REG_CMDR_FIFO_NACK_RESP:
> +		return I3C_ERROR_M2;
> +
> +	default:
> +		break;
> +	}
> +
> +	return I3C_ERROR_UNKNOWN;
> +}
> +
> +static int adi_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
> +				       struct i3c_ccc_cmd *cmd)
> +{
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +	struct adi_i3c_xfer *xfer;
> +	struct adi_i3c_cmd *ccmd;
> +
> +	xfer = adi_i3c_master_alloc_xfer(master, 1);
> +	if (!xfer)
> +		return -ENOMEM;
> +
> +	ccmd = xfer->cmds;
> +	ccmd->cmd1 = REG_CMD_FIFO_1_CCC(cmd->id);
> +	ccmd->cmd0 = REG_CMD_FIFO_0_IS_CCC |
> +		     REG_CMD_FIFO_0_LEN(cmd->dests[0].payload.len);
> +
> +	if (cmd->id & I3C_CCC_DIRECT)
> +		ccmd->cmd0 |= REG_CMD_FIFO_0_DEV_ADDR(cmd->dests[0].addr);
> +
> +	if (cmd->rnw) {
> +		ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> +		ccmd->rx_buf = cmd->dests[0].payload.data;
> +		ccmd->rx_len = cmd->dests[0].payload.len;
> +	} else {
> +		ccmd->tx_buf = cmd->dests[0].payload.data;
> +		ccmd->tx_len = cmd->dests[0].payload.len;
> +	}
> +
> +	adi_i3c_master_queue_xfer(master, xfer);
> +	if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000)))
> +		adi_i3c_master_unqueue_xfer(master, xfer);
> +
> +	cmd->err = adi_i3c_cmd_get_err(&xfer->cmds[0]);
> +	kfree(xfer);

you can use
struct adi_i3c_xfer *xfer __free(kfree) = NULL;

 to simple code.

> +
> +	return 0;
> +}
> +
> +static int adi_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
> +				     struct i3c_priv_xfer *xfers,
> +				     int nxfers)
> +{
> +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +	struct adi_i3c_xfer *xfer;
> +	int i, ret;
> +
> +	if (!nxfers)
> +		return 0;

add an empty line.

> +	xfer = adi_i3c_master_alloc_xfer(master, nxfers);
> +	if (!xfer)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < nxfers; i++) {
> +		struct adi_i3c_cmd *ccmd = &xfer->cmds[i];
> +
> +		ccmd->cmd0 = REG_CMD_FIFO_0_DEV_ADDR(dev->info.dyn_addr);
> +
> +		if (xfers[i].rnw) {
> +			ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> +			ccmd->rx_buf = xfers[i].data.in;
> +			ccmd->rx_len = xfers[i].len;
> +		} else {
> +			ccmd->tx_buf = xfers[i].data.out;
> +			ccmd->tx_len = xfers[i].len;
> +		}
> +
> +		ccmd->cmd0 |= REG_CMD_FIFO_0_LEN(xfers[i].len);
> +
> +		if (i < nxfers - 1)
> +			ccmd->cmd0 |= REG_CMD_FIFO_0_SR;
> +
> +		if (!i)
> +			ccmd->cmd0 |= REG_CMD_FIFO_0_BCAST;
> +	}
> +
> +	adi_i3c_master_queue_xfer(master, xfer);
> +	if (!wait_for_completion_timeout(&xfer->comp,
> +					 msecs_to_jiffies(1000)))
> +		adi_i3c_master_unqueue_xfer(master, xfer);
> +
> +	ret = xfer->ret;
> +
> +	for (i = 0; i < nxfers; i++)
> +		xfers[i].err = adi_i3c_cmd_get_err(&xfer->cmds[i]);
> +
> +	kfree(xfer);
> +
> +	return ret;
> +}
> +
> +struct adi_i3c_i2c_dev_data {
> +	u16 id;
> +	s16 ibi;
> +	struct i3c_generic_ibi_pool *ibi_pool;
> +};
> +
> +static int adi_i3c_master_get_rr_slot(struct adi_i3c_master *master,
> +				      u8 dyn_addr)
> +{
> +	if (!master->free_rr_slots)
> +		return -ENOSPC;
> +
> +	return ffs(master->free_rr_slots) - 1;
> +}
> +
> +static int adi_i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 dyn_addr)
> +{
> +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +	u8 addr;
> +
> +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> +
> +	writel(REG_DEV_CHAR_ADDR(dyn_addr), master->regs + REG_DEV_CHAR);
> +	writel((readl(master->regs + REG_DEV_CHAR) &
> +		~REG_DEV_CHAR_IS_ATTACHED) | REG_DEV_CHAR_WEN,
> +	       master->regs + REG_DEV_CHAR);
> +
> +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> +	writel(readl(master->regs + REG_DEV_CHAR) |
> +	       REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> +	       master->regs + REG_DEV_CHAR);
> +
> +	return 0;
> +}
> +
> +static int adi_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev)
> +{
> +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +	struct adi_i3c_i2c_dev_data *data;
> +	int slot;
> +	u8 addr;
> +
> +	data = kzalloc(sizeof(*data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	slot = adi_i3c_master_get_rr_slot(master, dev->info.dyn_addr);
> +	if (slot < 0) {
> +		kfree(data);
> +		return slot;
> +	}
> +
> +	data->ibi = -1;

why -1 here?

> +	data->id = slot;
> +	i3c_dev_set_master_data(dev, data);
> +	master->free_rr_slots &= ~BIT(slot);
> +
> +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> +
> +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> +	writel(readl(master->regs + REG_DEV_CHAR) |
> +	       REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> +	       master->regs + REG_DEV_CHAR);
> +
> +	return 0;
> +}
> +
> +static void adi_i3c_master_sync_dev_char(struct i3c_master_controller *m)
> +{
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +	struct i3c_dev_desc *i3cdev;
> +	u8 addr;
> +
> +	i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
> +		addr = i3cdev->info.dyn_addr ?
> +		       i3cdev->info.dyn_addr : i3cdev->info.static_addr;
> +		writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> +		writel(readl(master->regs + REG_DEV_CHAR) |
> +		       REG_DEV_CHAR_BCR_IBI(i3cdev->info.bcr) | REG_DEV_CHAR_WEN,
> +		       master->regs + REG_DEV_CHAR);
> +	}
> +}
> +
> +static void adi_i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev)
> +{
> +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> +	u8 addr;
> +
> +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> +
> +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> +	writel((readl(master->regs + REG_DEV_CHAR) &
> +		~REG_DEV_CHAR_IS_ATTACHED) | REG_DEV_CHAR_WEN,
> +	       master->regs + REG_DEV_CHAR);
> +
> +	i3c_dev_set_master_data(dev, NULL);
> +	master->free_rr_slots |= BIT(data->id);
> +	kfree(data);
> +}
> +
> +static int adi_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev)
> +{
> +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +	struct adi_i3c_i2c_dev_data *data;
> +	int slot;
> +
> +	slot = adi_i3c_master_get_rr_slot(master, 0);
> +	if (slot < 0)
> +		return slot;
> +
> +	data = kzalloc(sizeof(*data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	data->id = slot;
> +	master->free_rr_slots &= ~BIT(slot);
> +	i2c_dev_set_master_data(dev, data);
> +
> +	writel(REG_DEV_CHAR_ADDR(dev->addr) |
> +	       REG_DEV_CHAR_IS_I2C | REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> +	       master->regs + REG_DEV_CHAR);
> +
> +	return 0;
> +}
> +
> +static void adi_i3c_master_detach_i2c_dev(struct i2c_dev_desc *dev)
> +{
> +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +	struct adi_i3c_i2c_dev_data *data = i2c_dev_get_master_data(dev);
> +
> +	writel(REG_DEV_CHAR_ADDR(dev->addr) |
> +	       REG_DEV_CHAR_IS_I2C | REG_DEV_CHAR_WEN,
> +	       master->regs + REG_DEV_CHAR);
> +
> +	i2c_dev_set_master_data(dev, NULL);
> +	master->free_rr_slots |= BIT(data->id);
> +	kfree(data);
> +}
> +
> +static void adi_i3c_master_bus_cleanup(struct i3c_master_controller *m)
> +{
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +
> +	adi_i3c_master_disable(master);
> +}
> +
> +static void adi_i3c_master_upd_i3c_scl_lim(struct adi_i3c_master *master)
> +{
> +	struct i3c_master_controller *m = &master->base;
> +	struct i3c_bus *bus = i3c_master_get_bus(m);
> +	u8 i3c_scl_lim = 0;
> +	struct i3c_dev_desc *dev;
> +	u8 pp_sg;
> +
> +	i3c_bus_for_each_i3cdev(bus, dev) {
> +		u8 max_fscl;
> +
> +		max_fscl = max(I3C_CCC_MAX_SDR_FSCL(dev->info.max_read_ds),
> +			       I3C_CCC_MAX_SDR_FSCL(dev->info.max_write_ds));
> +
> +		switch (max_fscl) {
> +		case I3C_SDR1_FSCL_8MHZ:
> +			max_fscl = PP_SG_6MHZ;
> +			break;
> +		case I3C_SDR2_FSCL_6MHZ:
> +			max_fscl = PP_SG_3MHZ;
> +			break;
> +		case I3C_SDR3_FSCL_4MHZ:
> +			max_fscl = PP_SG_3MHZ;
> +			break;
> +		case I3C_SDR4_FSCL_2MHZ:
> +			max_fscl = PP_SG_1MHZ;
> +			break;
> +		case I3C_SDR0_FSCL_MAX:
> +		default:
> +			max_fscl = PP_SG_12MHZ;
> +			break;
> +		}
> +
> +		if (max_fscl &&
> +		    (i3c_scl_lim > max_fscl || !i3c_scl_lim))
> +			i3c_scl_lim = max_fscl;
> +	}
> +
> +	if (!i3c_scl_lim)
> +		return;
> +
> +	master->i3c_scl_lim = i3c_scl_lim - 1;
> +
> +	pp_sg = readl(master->regs + REG_OPS) & ~REG_OPS_PP_SG_MASK;
> +	pp_sg |= REG_OPS_SET_SG(master->i3c_scl_lim);
> +
> +	writel(pp_sg, master->regs + REG_OPS);
> +}
> +
> +static void adi_i3c_master_get_features(struct adi_i3c_master *master,
> +					unsigned int slot,
> +					struct i3c_device_info *info)
> +{
> +	memset(info, 0, sizeof(*info));
> +
> +	info->dyn_addr = 0x31;

master address is hardcode?

> +	info->dcr = 0x00;
> +	info->bcr = 0x40;
> +	info->pid = 0;
> +}
> +
> +static int adi_i3c_master_do_daa(struct i3c_master_controller *m)
> +{
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +	u32 irq_mask;
> +	int ret, addr;
> +
> +	addr = 0x8;
> +	for (u8 i = 0; i < MAX_DEVS; i++) {
> +		addr = i3c_master_get_free_addr(m, addr);
> +		if (addr < 0)
> +			return addr;

assume MAX_DEVS 128, if only get 4 address, suppose you should enumerate
for such 4 devices, instead of return error and give up do_daa.

> +		master->daa.addrs[i] = addr;
> +	}
> +
> +	irq_mask = readl(master->regs + REG_IRQ_MASK);
> +	writel(irq_mask | REG_IRQ_PENDING_DAA,
> +	       master->regs + REG_IRQ_MASK);
> +
> +	master->daa.index = 0;
> +	ret = i3c_master_entdaa_locked(&master->base);
> +
> +	writel(irq_mask, master->regs + REG_IRQ_MASK);
> +
> +	/* DAA always finishes with CE2_ERROR or NACK_RESP */
> +	if (ret && ret != I3C_ERROR_M2)
> +		return ret;
> +
> +	/* Add I3C devices discovered */
> +	for (u8 i = 0; i < master->daa.index; i++)
> +		i3c_master_add_i3c_dev_locked(m, master->daa.addrs[i]);
> +	/* Sync retrieved devs info with the IP */
> +	adi_i3c_master_sync_dev_char(m);
> +
> +	i3c_master_defslvs_locked(&master->base);
> +
> +	adi_i3c_master_upd_i3c_scl_lim(master);
> +
> +	return 0;
> +}
> +
> +static int adi_i3c_master_bus_init(struct i3c_master_controller *m)
> +{
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +	struct i3c_device_info info = { };
> +	int ret;
> +
> +	ret = i3c_master_get_free_addr(m, 0);
> +	if (ret < 0)
> +		return ret;
> +
> +	adi_i3c_master_get_features(master, 0, &info);
> +	ret = i3c_master_set_info(&master->base, &info);
> +	if (ret)
> +		return ret;
> +
> +	writel(REG_IBI_CONFIG_LISTEN,
> +	       master->regs + REG_IBI_CONFIG);
> +
> +	return 0;
> +}
> +
> +static void adi_i3c_master_handle_ibi(struct adi_i3c_master *master,
> +				      u32 ibi)
> +{
> +	struct adi_i3c_i2c_dev_data *data;
> +	struct i3c_ibi_slot *slot;
> +	struct i3c_dev_desc *dev;
> +	u8 da, id;
> +	u8 *mdb;
> +
> +	da = (ibi >> 17) & GENMASK(6, 0);
> +	for (id = 0; id < master->ibi.num_slots; id++) {
> +		if (master->ibi.slots[id] &&
> +		    master->ibi.slots[id]->info.dyn_addr == da)
> +			break;
> +	}
> +
> +	if (id == master->ibi.num_slots)
> +		return;
> +
> +	dev = master->ibi.slots[id];
> +	guard(spinlock)(&master->ibi.lock);
> +
> +	data = i3c_dev_get_master_data(dev);
> +	slot = i3c_generic_ibi_get_free_slot(data->ibi_pool);
> +	if (!slot)
> +		return;
> +
> +	mdb = slot->data;
> +	mdb[0] = (ibi >> 8) & GENMASK(7, 0);
> +
> +	slot->len = 1;
> +	i3c_master_queue_ibi(dev, slot);
> +}
> +
> +static void adi_i3c_master_demux_ibis(struct adi_i3c_master *master)
> +{
> +	u32 status0;
> +
> +	for (status0 = readl(master->regs + REG_FIFO_STATUS);
> +	     !(status0 & REG_FIFO_STATUS_IBI_EMPTY);
> +	     status0 = readl(master->regs + REG_FIFO_STATUS)) {

use while()

> +		u32 ibi = readl(master->regs + REG_IBI_FIFO);
> +
> +		adi_i3c_master_handle_ibi(master, ibi);
> +	}
> +}
> +
> +static void adi_i3c_master_handle_da_req(struct adi_i3c_master *master)
> +{
> +	u8 payload0[8];
> +	u32 addr;
> +
> +	/* Clear device characteristics */
> +	adi_i3c_master_rd_from_rx_fifo(master, payload0, 6);
> +	addr = master->daa.addrs[master->daa.index++];
> +	addr = (addr << 1) | (parity8(addr) ? 0 : 1);
> +
> +	writel(addr, master->regs + REG_SDO_FIFO);
> +}
> +
> +static irqreturn_t adi_i3c_master_irq(int irq, void *data)
> +{
> +	struct adi_i3c_master *master = data;
> +	u32 pending;
> +
> +	pending = readl(master->regs + REG_IRQ_PENDING);
> +	writel(pending, master->regs + REG_IRQ_PENDING);

Add an empty line here

> +	if (pending & REG_IRQ_PENDING_CMDR) {
> +		spin_lock(&master->xferqueue.lock);
> +		adi_i3c_master_end_xfer_locked(master, pending);
> +		spin_unlock(&master->xferqueue.lock);

you use guard, keep consisent, use guard or scoped_guard here

> +	}
> +	if (pending & REG_IRQ_PENDING_IBI)
> +		adi_i3c_master_demux_ibis(master);
> +	if (pending & REG_IRQ_PENDING_DAA)
> +		adi_i3c_master_handle_da_req(master);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int adi_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
> +				    struct i2c_msg *xfers,
> +				    int nxfers)
> +{
> +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +	struct adi_i3c_xfer *xfer;
> +	int i, ret;
> +
> +	if (!nxfers)
> +		return 0;
> +	for (i = 0; i < nxfers; i++) {
> +		if (xfers[i].flags & I2C_M_TEN)
> +			return -EOPNOTSUPP;
> +	}
> +	xfer = adi_i3c_master_alloc_xfer(master, nxfers);
> +	if (!xfer)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < nxfers; i++) {
> +		struct adi_i3c_cmd *ccmd = &xfer->cmds[i];
> +
> +		ccmd->cmd0 = REG_CMD_FIFO_0_DEV_ADDR(xfers[i].addr);
> +
> +		if (xfers[i].flags & I2C_M_RD) {
> +			ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> +			ccmd->rx_buf = xfers[i].buf;
> +			ccmd->rx_len = xfers[i].len;
> +		} else {
> +			ccmd->tx_buf = xfers[i].buf;
> +			ccmd->tx_len = xfers[i].len;
> +		}
> +
> +		ccmd->cmd0 |= REG_CMD_FIFO_0_LEN(xfers[i].len);

Just curiouse
What's happen if IBI happen (address arbitrate) at first command?

> +	}
> +
> +	adi_i3c_master_queue_xfer(master, xfer);
> +	if (!wait_for_completion_timeout(&xfer->comp,
> +					 m->i2c.timeout))
> +		adi_i3c_master_unqueue_xfer(master, xfer);
> +
> +	ret = xfer->ret;
> +	kfree(xfer);
> +	return ret;
> +}
> +
> +static int adi_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
> +{
> +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +	struct i3c_dev_desc *i3cdev;
> +	u32 enabled = 0;
> +	int ret;
> +
> +	ret = i3c_master_disec_locked(m, dev->info.dyn_addr,
> +				      I3C_CCC_EVENT_SIR);
> +
> +	i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
> +		if (dev != i3cdev && i3cdev->ibi)
> +			enabled |= i3cdev->ibi->enabled;
> +	}
> +	if (!enabled) {
> +		writel(REG_IBI_CONFIG_LISTEN,
> +		       master->regs + REG_IBI_CONFIG);
> +		writel(readl(master->regs + REG_IRQ_MASK) & ~REG_IRQ_PENDING_IBI,
> +		       master->regs + REG_IRQ_MASK);
> +	}
> +
> +	return ret;
> +}
> +
> +static int adi_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
> +{
> +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +
> +	writel(REG_IBI_CONFIG_LISTEN | REG_IBI_CONFIG_ENABLE,
> +	       master->regs + REG_IBI_CONFIG);
> +
> +	writel(readl(master->regs + REG_IRQ_MASK) | REG_IRQ_PENDING_IBI,
> +	       master->regs + REG_IRQ_MASK);
> +
> +	return i3c_master_enec_locked(m, dev->info.dyn_addr,
> +				      I3C_CCC_EVENT_SIR);
> +}
> +
> +static int adi_i3c_master_request_ibi(struct i3c_dev_desc *dev,
> +				      const struct i3c_ibi_setup *req)
> +{
> +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +	struct adi_i3c_i2c_dev_data *data;
> +	unsigned long flags;
> +	unsigned int i;
> +
> +	data = i3c_dev_get_master_data(dev);
> +	data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req);
> +	if (IS_ERR(data->ibi_pool))
> +		return PTR_ERR(data->ibi_pool);
> +
> +	spin_lock_irqsave(&master->ibi.lock, flags);
> +	for (i = 0; i < master->ibi.num_slots; i++) {
> +		if (!master->ibi.slots[i]) {
> +			data->ibi = i;
> +			master->ibi.slots[i] = dev;
> +			break;
> +		}
> +	}
> +	spin_unlock_irqrestore(&master->ibi.lock, flags);

same here, use scope_guard to keep consistent.

> +
> +	if (i < master->ibi.num_slots)
> +		return 0;
> +
> +	i3c_generic_ibi_free_pool(data->ibi_pool);
> +	data->ibi_pool = NULL;
> +
> +	return -ENOSPC;
> +}
> +
> +static void adi_i3c_master_free_ibi(struct i3c_dev_desc *dev)
> +{
> +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&master->ibi.lock, flags);
> +	master->ibi.slots[data->ibi] = NULL;
> +	data->ibi = -1;
> +	spin_unlock_irqrestore(&master->ibi.lock, flags);
> +
> +	i3c_generic_ibi_free_pool(data->ibi_pool);
> +}
> +
> +static void adi_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev,
> +					    struct i3c_ibi_slot *slot)
> +{
> +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> +
> +	i3c_generic_ibi_recycle_slot(data->ibi_pool, slot);
> +}
> +
> +static const struct i3c_master_controller_ops adi_i3c_master_ops = {
> +	.bus_init = adi_i3c_master_bus_init,
> +	.bus_cleanup = adi_i3c_master_bus_cleanup,
> +	.attach_i3c_dev = adi_i3c_master_attach_i3c_dev,
> +	.reattach_i3c_dev = adi_i3c_master_reattach_i3c_dev,
> +	.detach_i3c_dev = adi_i3c_master_detach_i3c_dev,
> +	.attach_i2c_dev = adi_i3c_master_attach_i2c_dev,
> +	.detach_i2c_dev = adi_i3c_master_detach_i2c_dev,
> +	.do_daa = adi_i3c_master_do_daa,
> +	.supports_ccc_cmd = adi_i3c_master_supports_ccc_cmd,
> +	.send_ccc_cmd = adi_i3c_master_send_ccc_cmd,
> +	.priv_xfers = adi_i3c_master_priv_xfers,
> +	.i2c_xfers = adi_i3c_master_i2c_xfers,
> +	.request_ibi = adi_i3c_master_request_ibi,
> +	.enable_ibi = adi_i3c_master_enable_ibi,
> +	.disable_ibi = adi_i3c_master_disable_ibi,
> +	.free_ibi = adi_i3c_master_free_ibi,
> +	.recycle_ibi_slot = adi_i3c_master_recycle_ibi_slot,
> +};
> +
> +static const struct of_device_id adi_i3c_master_of_match[] = {
> +	{ .compatible = "adi,i3c-master" },
> +	{}
> +};
> +
> +static int adi_i3c_master_probe(struct platform_device *pdev)
> +{
> +	struct adi_i3c_master *master;
> +	unsigned int version;
> +	int ret, irq;
> +
> +	master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL);
> +	if (!master)
> +		return -ENOMEM;
> +
> +	master->regs = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(master->regs))
> +		return PTR_ERR(master->regs);
> +
> +	master->clk = devm_clk_get_enabled(&pdev->dev, "axi");
> +	if (IS_ERR(master->clk))
> +		return PTR_ERR(master->clk);

you have optional clock i3c, not use it in driver?

> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return irq;
> +
> +	version = readl(master->regs + REG_VERSION);
> +	if (VERSION_MAJOR(version) != 0) {
> +		dev_err(&pdev->dev, "Unsupported IP version %u.%u.%c\n",
> +			VERSION_MAJOR(version),
> +			VERSION_MINOR(version),
> +			VERSION_PATCH(version));
> +		return -EINVAL;

return dev_err_probe();

Frank
> +	}
> +
> +	writel(0x00, master->regs + REG_ENABLE);
> +	writel(0x00, master->regs + REG_IRQ_MASK);
> +
> +	ret = devm_request_irq(&pdev->dev, irq, adi_i3c_master_irq, 0,
> +			       dev_name(&pdev->dev), master);
> +	if (ret)
> +		return ret;
> +
> +	platform_set_drvdata(pdev, master);
> +
> +	master->maxdevs = MAX_DEVS;
> +	master->free_rr_slots = GENMASK(master->maxdevs, 1);
> +
> +	writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
> +
> +	spin_lock_init(&master->ibi.lock);
> +	master->ibi.num_slots = 15;
> +	master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots,
> +					 sizeof(*master->ibi.slots),
> +					 GFP_KERNEL);
> +	if (!master->ibi.slots)
> +		return -ENOMEM;
> +
> +	return i3c_master_register(&master->base, &pdev->dev,
> +				   &adi_i3c_master_ops, false);
> +}
> +
> +static void adi_i3c_master_remove(struct platform_device *pdev)
> +{
> +	struct adi_i3c_master *master = platform_get_drvdata(pdev);
> +
> +	writel(0xff, master->regs + REG_IRQ_PENDING);
> +	writel(0x00, master->regs + REG_IRQ_MASK);
> +	writel(0x01, master->regs + REG_ENABLE);
> +
> +	i3c_master_unregister(&master->base);
> +}
> +
> +static struct platform_driver adi_i3c_master = {
> +	.probe = adi_i3c_master_probe,
> +	.remove = adi_i3c_master_remove,
> +	.driver = {
> +		.name = "adi-i3c-master",
> +		.of_match_table = adi_i3c_master_of_match,
> +	},
> +};
> +module_platform_driver(adi_i3c_master);
> +
> +MODULE_AUTHOR("Jorge Marques <jorge.marques@analog.com>");
> +MODULE_DESCRIPTION("Analog Devices I3C master driver");
> +MODULE_LICENSE("GPL");
>
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 1/2] dt-bindings: i3c: Add adi-i3c-master
  2025-06-18 15:45       ` Krzysztof Kozlowski
@ 2025-06-18 19:55         ` Jorge Marques
  2025-06-19  6:15           ` Krzysztof Kozlowski
  0 siblings, 1 reply; 14+ messages in thread
From: Jorge Marques @ 2025-06-18 19:55 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Jorge Marques, Alexandre Belloni, Frank Li, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-i3c, devicetree,
	linux-kernel

On Wed, Jun 18, 2025 at 05:45:22PM +0200, Krzysztof Kozlowski wrote:
> On 18/06/2025 14:15, Jorge Marques wrote:
> >>>
> >>> Signed-off-by: Jorge Marques <jorge.marques@analog.com>
> >>> ---
> >>>  .../devicetree/bindings/i3c/adi,i3c-master.yaml    | 63 ++++++++++++++++++++++
> >>>  MAINTAINERS                                        |  5 ++
> >>>  2 files changed, 68 insertions(+)
> >>>
> >>> diff --git a/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml b/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
> >>> new file mode 100644
> >>> index 0000000000000000000000000000000000000000..718733bbb450c34c5d4924050cc6f85d8a80fe4b
> >>> --- /dev/null
> >>> +++ b/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
> >>
> >> Filename based on the compatible, so adi,i3c-master-1.00.a.yaml
> >>
> > I agree, but I ended up following the pattern for the other adi,
> > bindings. I will move for v4. IMO the version suffix has no much use
> > since IP updates are handled in the driver.
> 
> Filename is not related to whether given ABI works with every device.
> Filename helps us to organize bindings and existing convention is that
> we want it to follow the compatible.
> 
Hi Krzysztof,

Understood.

> >>> @@ -0,0 +1,63 @@
> >>> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> >>> +%YAML 1.2
> >>> +---
> >>> +$id: http://devicetree.org/schemas/i3c/adi,i3c-master.yaml#
> >>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> >>> +
> >>> +title: Analog Devices I3C Controller
> >>> +
> >>> +description: |
> >>> +  FPGA-based I3C controller designed to interface with I3C and I2C peripherals,
> >>> +  implementing a subset of the I3C-basic specification.
> >>> +
> >>> +  https://analogdevicesinc.github.io/hdl/library/i3c_controller
> >>> +
> >>> +maintainers:
> >>> +  - Jorge Marques <jorge.marques@analog.com>
> >>> +
> >>> +properties:
> >>> +  compatible:
> >>> +    const: adi,i3c-master-1.00.a
> >>> +
> >>> +  reg:
> >>> +    maxItems: 1
> >>> +
> >>> +  clocks:
> >>> +    minItems: 1
> >>
> >> Why?
> >>
> > The IP core requires a clock, and the second is optional.
> 
> OK
> 
> > minItems sets the minimum number of required clocks and the maxItems is
> > inferred from the number of items.
> > 
> > On the IP core itself, one clock is required (axi), and if it is the
> > only provided, it means that the same clock for the AXI bus is used
> > also for the rest of the RTL logic.
> 
> Hm? What does it exactly mean - same clock? You mean one clock is routed
> to two pins? That's still two clocks. Or you mean that IP core will
> notice grounded clock input and do the routing inside?
> 

The routing is inside the IP core, and only one clock pin is used. In
fullness, since it is a FPGA-based IP Core, the number of input clock
pins are defined by the parameter [1] ASYNC_CLK that enables the
asynchronous i3c clock input pin. The devicetree then describes how
things are wired, if two clocks provided, it describes that both clock
inputs, axi and i3c, are wired to the IP Core, if only axi, then there
is no clock signal to the i3c input clock pin and axi clock drives the
whole IP.

[1] https://analogdevicesinc.github.io/hdl/library/i3c_controller/i3c_controller_host_interface.html#configuration-parameters

> > 
> > If a second clock is provided, i3c, it means it drives the RTL logic and is
> > asynchronous to the axi clock, which then just drives the register map logic.
> > For i3c specified nominal speeds, the RTL logic should run with a speed of
> > 100MHz. Some FPGAs, such as Altera CycloneV, have a default bus clock speed of
> > 50MHz. Changing the bus speed is possible, but affects timing and it may not be
> > possible from users to double the bus speed since it will affect timing of all
> > IP cores using the bus clock.
> >>> +    items:
> >>> +      - description: The AXI interconnect clock.
> >>> +      - description: The I3C controller clock.
> > I will update the descriptions to:
> > 
> >         - description: The AXI interconnect clock, drives the register map.
> >         - description: The I3C controller clock. AXI clock drives all logic if not provided.
> > 
> >>> +
> >>> +  clock-names:
> >>
> >> Not synced with clocks.
> >>
> > I will add `minItems: 1`.
> >>> +    items:
> >>> +      - const: axi
> >>> +      - const: i3c
> >>> +
> >>> +  interrupts:
> >>> +    maxItems: 1
> >>> +
> >>> +required:
> >>> +  - compatible
> >>> +  - reg
> >>> +  - clocks
> >>> +  - clock-names
> >>> +  - interrupts
> >>> +
> >>> +allOf:
> >>> +  - $ref: i3c.yaml#
> >>> +
> >>> +unevaluatedProperties: false
> >>> +
> >>> +examples:
> >>> +  - |
> >>> +    i3c@44a00000 {
> >>> +        compatible = "adi,i3c-master";
> >>> +        reg = <0x44a00000 0x1000>;
> >>> +        interrupts = <0 56 4>;
> >>
> >> Use proper defines.
> >>
> > The following can added:
> > 
> >   #include <dt-bindings/interrupt-controller/irq.h>
> > 
> >   interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>;
> > 
> > Is there any other to be replaced?
> 
> Usually 0 has a meaning as well. Where is this used DTS snippet used (on
> which platform)?
> 

The example provided is used on AMD xilinx arm,cortex-a9-gic.
The IP core is tested on AMD Xilinx, Altera FPGAs (arm, microblaze, arm64).
Test/support to RiscV-based Lattice FPGAs should eventually also come.

In this example 0 means Shared Peripheral Interrupt, but has no header
file defining. I guess I could make it generic and set to

  interrupts = <3 IRQ_TYPE_LEVEL_HIGH>;

and let the user figure out for his target platform.
> Best regards,
> Krzysztof

Best regards,
Jorge

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP
  2025-06-18 19:40   ` Frank Li
@ 2025-06-18 22:17     ` Jorge Marques
  2025-06-19  2:18       ` Frank Li
  0 siblings, 1 reply; 14+ messages in thread
From: Jorge Marques @ 2025-06-18 22:17 UTC (permalink / raw)
  To: Frank Li
  Cc: Jorge Marques, Alexandre Belloni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-i3c, devicetree,
	linux-kernel

On Wed, Jun 18, 2025 at 03:40:58PM -0400, Frank Li wrote:
> On Wed, Jun 18, 2025 at 09:16:44AM +0200, Jorge Marques wrote:
> > Add support for Analog Devices I3C Controller IP, an AXI-interfaced IP
> > core that supports I3C and I2C devices, multiple speed-grades and
> > I3C IBIs.
> >
> > Signed-off-by: Jorge Marques <jorge.marques@analog.com>
> > ---
> >  MAINTAINERS                         |    1 +
> >  drivers/i3c/master/Kconfig          |   11 +
> >  drivers/i3c/master/Makefile         |    1 +
> >  drivers/i3c/master/adi-i3c-master.c | 1026 +++++++++++++++++++++++++++++++++++
> >  4 files changed, 1039 insertions(+)
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 6f56e17dcecf902c6812827c1ec3e067c65e9894..9eb5b6c327590725d1641fd4b73e48fc1d1a3954 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -11247,6 +11247,7 @@ I3C DRIVER FOR ANALOG DEVICES I3C CONTROLLER IP
> >  M:	Jorge Marques <jorge.marques@analog.com>
> >  S:	Maintained
> >  F:	Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
> > +F:	drivers/i3c/master/adi-i3c-master.c
> >
> >  I3C DRIVER FOR CADENCE I3C MASTER IP
> >  M:	Przemysław Gaj <pgaj@cadence.com>
> > diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig
> > index 7b30db3253af9d5c6aee6544c060e491bfbeb643..328b7145cdefa20e708ebfa3383e849ce51c5a71 100644
> > --- a/drivers/i3c/master/Kconfig
> > +++ b/drivers/i3c/master/Kconfig
> > @@ -1,4 +1,15 @@
> >  # SPDX-License-Identifier: GPL-2.0-only
> > +config ADI_I3C_MASTER
> > +	tristate "Analog Devices I3C master driver"
> > +	depends on HAS_IOMEM
> > +	help
> > +	  Support for Analog Devices I3C Controller IP, an AXI-interfaced IP
> > +	  core that supports I3C and I2C devices, multiple speed-grades and
> > +	  I3C IBIs.
> > +
> > +	  This driver can also be built as a module.  If so, the module
> > +	  will be called adi-i3c-master.
> > +
> >  config CDNS_I3C_MASTER
> >  	tristate "Cadence I3C master driver"
> >  	depends on HAS_IOMEM
> > diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile
> > index 3e97960160bc85e5eaf2966ec0c3fae458c2711e..6cc4f4b73e7bdc206b68c750390f9c3cc2ccb199 100644
> > --- a/drivers/i3c/master/Makefile
> > +++ b/drivers/i3c/master/Makefile
> > @@ -1,4 +1,5 @@
> >  # SPDX-License-Identifier: GPL-2.0-only
> > +obj-$(CONFIG_ADI_I3C_MASTER)		+= adi-i3c-master.o
> >  obj-$(CONFIG_CDNS_I3C_MASTER)		+= i3c-master-cdns.o
> >  obj-$(CONFIG_DW_I3C_MASTER)		+= dw-i3c-master.o
> >  obj-$(CONFIG_AST2600_I3C_MASTER)	+= ast2600-i3c-master.o
> > diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..d4e0756efec9588383ac55ca9ea9228368ce767d
> > --- /dev/null
> > +++ b/drivers/i3c/master/adi-i3c-master.c
> > @@ -0,0 +1,1026 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * I3C Controller driver
> > + * Copyright 2025 Analog Devices Inc.
> > + * Author: Jorge Marques <jorge.marques@analog.com>
> > + */
> > +
> > +#include <linux/bitops.h>
> > +#include <linux/bitfield.h>
> > +#include <linux/clk.h>
> > +#include <linux/err.h>
> > +#include <linux/errno.h>
> > +#include <linux/i3c/master.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/io.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/platform_device.h>
> > +
> > +#define VERSION_MAJOR(x)		((u32)FIELD_GET(GENMASK(23, 16), (x)))
> > +#define VERSION_MINOR(x)		((u32)FIELD_GET(GENMASK(15, 8), (x)))
> > +#define VERSION_PATCH(x)		((u32)FIELD_GET(GENMASK(7, 0), (x)))
Hi Frank,
> 
> I suppose that needn't (u32), any warnings report without u32?
> 
Here I will actually remove and use the macros from
linux/fpga/adi-axi-common.h

> > +
> > +#define MAX_DEVS			16
> > +
> > +#define REG_VERSION			0x000
to be replaced removed, and ADI_AXI_REG_VERSION used instead.
> > +#define REG_ENABLE			0x040
> > +#define REG_IRQ_MASK			0x080
> > +#define REG_IRQ_PENDING			0x084
> > +#define   REG_IRQ_PENDING_CMDR		BIT(5)
> > +#define   REG_IRQ_PENDING_IBI		((u32)BIT(6))
> 
> needn't u32
In earlier CI runs, I used to get with gcc_x86_64, due to the inversion
~REG_IRQ_PENDING_IBI:

  > conversion from 'long unsigned int' to 'unsigned int' changes value
  > from '18446744073709551615' to '4294967295' [-Werror=overflow]

Couldn't replicate in this patch version, so I will remove.

> > +#define   REG_IRQ_PENDING_DAA		BIT(7)
> 
> Add empty line between registers
> 
Ok
> > +#define REG_CMD_FIFO			0x0d4
> > +#define	  REG_CMD_FIFO_0_IS_CCC		BIT(22)
> > +#define   REG_CMD_FIFO_0_BCAST		BIT(21)
> > +#define   REG_CMD_FIFO_0_SR		BIT(20)
> > +#define   REG_CMD_FIFO_0_LEN(l)		FIELD_PREP(GENMASK(19, 8), (l))
> > +#define   REG_CMD_FIFO_0_DEV_ADDR(a)	FIELD_PREP(GENMASK(7, 1), a)
> > +#define   REG_CMD_FIFO_0_RNW		BIT(0)
> > +#define   REG_CMD_FIFO_1_CCC(id)	FIELD_PREP(GENMASK(7, 0), (id))
> > +#define REG_CMD_FIFO_ROOM		0x0c0
> > +#define REG_CMDR_FIFO			0x0d8
> > +#define   REG_CMDR_FIFO_NO_ERROR	0
> > +#define   REG_CMDR_FIFO_CE0_ERROR	1
> > +#define   REG_CMDR_FIFO_CE2_ERROR	4
> > +#define   REG_CMDR_FIFO_NACK_RESP	6
> > +#define   REG_CMDR_FIFO_UDA_ERROR	8
> > +#define   REG_CMDR_FIFO_ERROR(x)	FIELD_GET(GENMASK(23, 20), (x))
> > +#define   REG_CMDR_FIFO_XFER_BYTES(x)	FIELD_GET(GENMASK(19, 8), (x))
> 
> Sort according bit pos. REG_CMDR_FIFO_XFER_BYTES should before
> REG_CMDR_FIFO_ERROR
> 
Ahh true
> > +#define REG_SDO_FIFO			0x0dc
> > +#define REG_SDO_FIFO_ROOM		0x0c8
> > +#define REG_SDI_FIFO			0x0e0
> > +#define REG_IBI_FIFO			0x0e4
> > +#define REG_FIFO_STATUS			0x0e8
> > +#define   REG_FIFO_STATUS_CMDR_EMPTY	BIT(0)
> > +#define   REG_FIFO_STATUS_IBI_EMPTY	BIT(1)
> > +#define REG_OPS				0x100
> > +#define   REG_OPS_SET_SG(x)		FIELD_PREP(GENMASK(6, 5), (x))
> > +#define   REG_OPS_PP_SG_MASK		GENMASK(6, 5)
> 
> REG_OPS_SET_SG(x) FIELD_PREP(REG_OPS_PP_SG_MASK, (x))
Ack.
> 
> > +#define REG_IBI_CONFIG			0x140
> > +#define   REG_IBI_CONFIG_LISTEN		BIT(1)
> > +#define   REG_IBI_CONFIG_ENABLE		BIT(0)
> 
> sort it. keep consisent in whole file, from 0->31 or 31->0
> 
Ack, decrementing
> > +#define REG_DEV_CHAR			0x180
> > +#define   REG_DEV_CHAR_IS_I2C		BIT(0)
> > +#define   REG_DEV_CHAR_IS_ATTACHED	BIT(1)
> > +#define   REG_DEV_CHAR_BCR_IBI(x)	(((x) & GENMASK(2, 1)) << 1)
> 
> FIELD_PREP()
Ok, I will remove the simplification and make it clearer by setting

     #define   REG_DEV_CHAR_BCR_IBI(x)       FIELD_PREP(GENMASK(3, 2), (x))

Then usage:

     #include <linux/i3c/device.h>
     ...
     bcr_ibi = FIELD_GET(I3C_BCR_IBI_PAYLOAD | I3C_BCR_IBI_REQ_CAP, (i3cdev->info.bcr));
     ...REG_DEV_CHAR_BCR_IBI(bcr_ibi)...;

> 
> > +#define   REG_DEV_CHAR_WEN		BIT(8)
> > +#define   REG_DEV_CHAR_ADDR(x)		FIELD_PREP(GENMASK(15, 9), (x))
> > +
> > +enum speed_grade {PP_SG_UNSET, PP_SG_1MHZ, PP_SG_3MHZ, PP_SG_6MHZ, PP_SG_12MHZ};
> > +struct adi_i3c_cmd {
> > +	u32 cmd0;
> > +	u32 cmd1;
> > +	u32 tx_len;
> > +	const void *tx_buf;
> > +	u32 rx_len;
> > +	void *rx_buf;
> > +	u32 error;
> > +};
> > +
> > +struct adi_i3c_xfer {
> > +	struct list_head node;
> > +	struct completion comp;
> > +	int ret;
> > +	unsigned int ncmds;
> > +	unsigned int ncmds_comp;
> > +	struct adi_i3c_cmd cmds[];
> > +};
> > +
> > +struct adi_i3c_master {
> > +	struct i3c_master_controller base;
> > +	u32 free_rr_slots;
> > +	unsigned int maxdevs;
> > +	struct {
> > +		unsigned int num_slots;
> > +		struct i3c_dev_desc **slots;
> > +		spinlock_t lock; /* Protect IBI slot access */
> > +	} ibi;
> > +	struct {
> > +		struct list_head list;
> > +		struct adi_i3c_xfer *cur;
> > +		spinlock_t lock; /* Protect transfer */
> > +	} xferqueue;
> > +	void __iomem *regs;
> > +	struct clk *clk;
> > +	unsigned long i3c_scl_lim;
> > +	struct {
> > +		u8 addrs[MAX_DEVS];
> > +		u8 index;
> > +	} daa;
> > +};
> > +
> > +static inline struct adi_i3c_master *to_adi_i3c_master(struct i3c_master_controller *master)
> > +{
> > +	return container_of(master, struct adi_i3c_master, base);
> > +}
> > +
> > +static void adi_i3c_master_wr_to_tx_fifo(struct adi_i3c_master *master,
> > +					 const u8 *bytes, unsigned int nbytes)
> > +{
> > +	unsigned int n, m;
> > +	u32 tmp;
> > +
> > +	n = readl(master->regs + REG_SDO_FIFO_ROOM);
> > +	m = min(n, nbytes);
> > +	writesl(master->regs + REG_SDO_FIFO, bytes, m / 4);
> > +
> > +	if (m & 3) {
> > +		memcpy(&tmp, bytes + (m & ~3), m & 3);
> > +		writel(tmp, master->regs + REG_SDO_FIFO);
> > +	}
> > +}
> > +
> > +static void adi_i3c_master_rd_from_rx_fifo(struct adi_i3c_master *master,
> > +					   u8 *bytes, unsigned int nbytes)
> > +{
> > +	readsl(master->regs + REG_SDI_FIFO, bytes, nbytes / 4);
> > +	if (nbytes & 3) {
> > +		u32 tmp;
> > +
> > +		tmp = readl(master->regs + REG_SDI_FIFO);
> > +		memcpy(bytes + (nbytes & ~3), &tmp, nbytes & 3);
> > +	}
> > +}
> 
> can you move above two helper function as inline into drivers/i3c/internals.h
> 
> or create new one drivers/i3c/master/helper.h or other naming.
> 
> Renesas IP have similar above two functions to access FIFO.
> https://lore.kernel.org/linux-i3c/174964724485.330045.2181706921272138816.robh@kernel.org/T/#t
> 
Ok, yes that's common because the passes type u8 doesn't ensure
multiples of 32 at least. Added to my TODO list.
> > +
> > +static bool adi_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m,
> > +					    const struct i3c_ccc_cmd *cmd)
> > +{
> > +	if (cmd->ndests > 1)
> > +		return false;
> > +
> > +	switch (cmd->id) {
> > +	case I3C_CCC_ENEC(true):
> > +	case I3C_CCC_ENEC(false):
> > +	case I3C_CCC_DISEC(true):
> > +	case I3C_CCC_DISEC(false):
> > +	case I3C_CCC_RSTDAA(true):
> > +	case I3C_CCC_RSTDAA(false):
> > +	case I3C_CCC_ENTDAA:
> > +	case I3C_CCC_SETDASA:
> > +	case I3C_CCC_SETNEWDA:
> > +	case I3C_CCC_GETMWL:
> > +	case I3C_CCC_GETMRL:
> > +	case I3C_CCC_GETPID:
> > +	case I3C_CCC_GETBCR:
> > +	case I3C_CCC_GETDCR:
> > +	case I3C_CCC_GETSTATUS:
> > +	case I3C_CCC_GETHDRCAP:
> > +		return true;
> > +	default:
> > +		break;
> > +	}
> > +
> > +	return false;
> > +}
> > +
> > +static int adi_i3c_master_disable(struct adi_i3c_master *master)
> > +{
> > +	writel(0, master->regs + REG_IBI_CONFIG);
> > +
> > +	return 0;
> > +}
> > +
> > +static struct adi_i3c_xfer *adi_i3c_master_alloc_xfer(struct adi_i3c_master *master,
> > +						      unsigned int ncmds)
> > +{
> > +	struct adi_i3c_xfer *xfer;
> > +
> > +	xfer = kzalloc(struct_size(xfer, cmds, ncmds), GFP_KERNEL);
> > +	if (!xfer)
> > +		return NULL;
> > +
> > +	INIT_LIST_HEAD(&xfer->node);
> > +	xfer->ncmds = ncmds;
> > +	xfer->ret = -ETIMEDOUT;
> > +
> > +	return xfer;
> > +}
> > +
> > +static void adi_i3c_master_start_xfer_locked(struct adi_i3c_master *master)
> > +{
> > +	struct adi_i3c_xfer *xfer = master->xferqueue.cur;
> > +	unsigned int i, n, m;
> > +
> > +	if (!xfer)
> > +		return;
> > +
> > +	for (i = 0; i < xfer->ncmds; i++) {
> > +		struct adi_i3c_cmd *cmd = &xfer->cmds[i];
> > +
> > +		if (!(cmd->cmd0 & REG_CMD_FIFO_0_RNW))
> > +			adi_i3c_master_wr_to_tx_fifo(master, cmd->tx_buf, cmd->tx_len);
> > +	}
> > +
> > +	n = readl(master->regs + REG_CMD_FIFO_ROOM);
> > +	for (i = 0; i < xfer->ncmds; i++) {
> > +		struct adi_i3c_cmd *cmd = &xfer->cmds[i];
> > +
> > +		m = cmd->cmd0 & REG_CMD_FIFO_0_IS_CCC ? 2 : 1;
> > +		if (m > n)
> > +			break;
> > +		writel(cmd->cmd0, master->regs + REG_CMD_FIFO);
> > +		if (cmd->cmd0 & REG_CMD_FIFO_0_IS_CCC)
> > +			writel(cmd->cmd1, master->regs + REG_CMD_FIFO);
> > +		n -= m;
> > +	}
> > +}
> > +
> > +static void adi_i3c_master_end_xfer_locked(struct adi_i3c_master *master,
> > +					   u32 pending)
> > +{
> > +	struct adi_i3c_xfer *xfer = master->xferqueue.cur;
> > +	int i, ret = 0;
> > +	u32 status0;
> > +
> > +	if (!xfer)
> > +		return;
> > +
> > +	for (status0 = readl(master->regs + REG_FIFO_STATUS);
> > +	     !(status0 & REG_FIFO_STATUS_CMDR_EMPTY);
> > +	     status0 = readl(master->regs + REG_FIFO_STATUS)) {
> 
> how about
> 
> while (!(readl(master->regs + REG_FIFO_STATUS) & REG_FIFO_STATUS_CMDR_EMPTY))) {
> 
Ack, thanks!
Same for 'REG_FIFO_STATUS_IBI_EMPTY for'.
> > +		struct adi_i3c_cmd *cmd;
> > +		u32 cmdr, rx_len;
> > +
> > +		cmdr = readl(master->regs + REG_CMDR_FIFO);
> > +
> > +		cmd = &xfer->cmds[xfer->ncmds_comp++];
> > +		if (cmd->cmd0 & REG_CMD_FIFO_0_RNW) {
> > +			rx_len = min_t(u32, REG_CMDR_FIFO_XFER_BYTES(cmdr), cmd->rx_len);
> > +			adi_i3c_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len);
> > +		}
> > +		cmd->error = REG_CMDR_FIFO_ERROR(cmdr);
> > +	}
> > +
> > +	for (i = 0; i < xfer->ncmds; i++) {
> 
> I think you iterate to ncmds_comp?
> 
You are correct thanks! Each command yields a command receipt interrupt.
> > +		switch (xfer->cmds[i].error) {
> > +		case REG_CMDR_FIFO_NO_ERROR:
> > +			break;
> > +
> > +		case REG_CMDR_FIFO_CE0_ERROR:
> > +		case REG_CMDR_FIFO_CE2_ERROR:
> > +		case REG_CMDR_FIFO_NACK_RESP:
> > +		case REG_CMDR_FIFO_UDA_ERROR:
> > +			ret = -EIO;
> > +			break;
> > +
> > +		default:
> > +			ret = -EINVAL;
> > +			break;
> > +		}
> > +	}
> > +
> > +	xfer->ret = ret;
> > +
> > +	if (xfer->ncmds_comp != xfer->ncmds)
> > +		return;
> > +
> > +	complete(&xfer->comp);
> > +
> > +	xfer = list_first_entry_or_null(&master->xferqueue.list,
> > +					struct adi_i3c_xfer, node);
> > +	if (xfer)
> > +		list_del_init(&xfer->node);
> > +
> > +	master->xferqueue.cur = xfer;
> > +	adi_i3c_master_start_xfer_locked(master);
> > +}
> > +
> > +static void adi_i3c_master_queue_xfer(struct adi_i3c_master *master,
> > +				      struct adi_i3c_xfer *xfer)
> > +{
> > +	init_completion(&xfer->comp);
> > +	guard(spinlock_irqsave)(&master->xferqueue.lock);
> > +	if (master->xferqueue.cur) {
> > +		list_add_tail(&xfer->node, &master->xferqueue.list);
> > +	} else {
> > +		master->xferqueue.cur = xfer;
> > +		adi_i3c_master_start_xfer_locked(master);
> > +	}
> > +}
> > +
> > +static void adi_i3c_master_unqueue_xfer(struct adi_i3c_master *master,
> > +					struct adi_i3c_xfer *xfer)
> > +{
> > +	guard(spinlock_irqsave)(&master->xferqueue.lock);
> > +	if (master->xferqueue.cur == xfer)
> > +		master->xferqueue.cur = NULL;
> > +	else
> > +		list_del_init(&xfer->node);
> > +
> > +	writel(0x01, master->regs + REG_ENABLE);
> > +	writel(0x00, master->regs + REG_ENABLE);
> > +	writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
> > +}
> > +
> > +static enum i3c_error_code adi_i3c_cmd_get_err(struct adi_i3c_cmd *cmd)
> > +{
> > +	switch (cmd->error) {
> > +	case REG_CMDR_FIFO_CE0_ERROR:
> > +		return I3C_ERROR_M0;
> > +
> > +	case REG_CMDR_FIFO_CE2_ERROR:
> > +	case REG_CMDR_FIFO_NACK_RESP:
> > +		return I3C_ERROR_M2;
> > +
> > +	default:
> > +		break;
> > +	}
> > +
> > +	return I3C_ERROR_UNKNOWN;
> > +}
> > +
> > +static int adi_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
> > +				       struct i3c_ccc_cmd *cmd)
> > +{
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +	struct adi_i3c_xfer *xfer;
> > +	struct adi_i3c_cmd *ccmd;
> > +
> > +	xfer = adi_i3c_master_alloc_xfer(master, 1);
> > +	if (!xfer)
> > +		return -ENOMEM;
> > +
> > +	ccmd = xfer->cmds;
> > +	ccmd->cmd1 = REG_CMD_FIFO_1_CCC(cmd->id);
> > +	ccmd->cmd0 = REG_CMD_FIFO_0_IS_CCC |
> > +		     REG_CMD_FIFO_0_LEN(cmd->dests[0].payload.len);
> > +
> > +	if (cmd->id & I3C_CCC_DIRECT)
> > +		ccmd->cmd0 |= REG_CMD_FIFO_0_DEV_ADDR(cmd->dests[0].addr);
> > +
> > +	if (cmd->rnw) {
> > +		ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> > +		ccmd->rx_buf = cmd->dests[0].payload.data;
> > +		ccmd->rx_len = cmd->dests[0].payload.len;
> > +	} else {
> > +		ccmd->tx_buf = cmd->dests[0].payload.data;
> > +		ccmd->tx_len = cmd->dests[0].payload.len;
> > +	}
> > +
> > +	adi_i3c_master_queue_xfer(master, xfer);
> > +	if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000)))
> > +		adi_i3c_master_unqueue_xfer(master, xfer);
> > +
> > +	cmd->err = adi_i3c_cmd_get_err(&xfer->cmds[0]);
> > +	kfree(xfer);
> 
> you can use
> struct adi_i3c_xfer *xfer __free(kfree) = NULL;
> 
>  to simple code.
> 
Ah cool, will use in all xfer instances.
> > +
> > +	return 0;
> > +}
> > +
> > +static int adi_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
> > +				     struct i3c_priv_xfer *xfers,
> > +				     int nxfers)
> > +{
> > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +	struct adi_i3c_xfer *xfer;
> > +	int i, ret;
> > +
> > +	if (!nxfers)
> > +		return 0;
> 
> add an empty line.
> 
Ack.
> > +	xfer = adi_i3c_master_alloc_xfer(master, nxfers);
> > +	if (!xfer)
> > +		return -ENOMEM;
> > +
> > +	for (i = 0; i < nxfers; i++) {
> > +		struct adi_i3c_cmd *ccmd = &xfer->cmds[i];
> > +
> > +		ccmd->cmd0 = REG_CMD_FIFO_0_DEV_ADDR(dev->info.dyn_addr);
> > +
> > +		if (xfers[i].rnw) {
> > +			ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> > +			ccmd->rx_buf = xfers[i].data.in;
> > +			ccmd->rx_len = xfers[i].len;
> > +		} else {
> > +			ccmd->tx_buf = xfers[i].data.out;
> > +			ccmd->tx_len = xfers[i].len;
> > +		}
> > +
> > +		ccmd->cmd0 |= REG_CMD_FIFO_0_LEN(xfers[i].len);
> > +
> > +		if (i < nxfers - 1)
> > +			ccmd->cmd0 |= REG_CMD_FIFO_0_SR;
> > +
> > +		if (!i)
> > +			ccmd->cmd0 |= REG_CMD_FIFO_0_BCAST;
> > +	}
> > +
> > +	adi_i3c_master_queue_xfer(master, xfer);
> > +	if (!wait_for_completion_timeout(&xfer->comp,
> > +					 msecs_to_jiffies(1000)))
> > +		adi_i3c_master_unqueue_xfer(master, xfer);
> > +
> > +	ret = xfer->ret;
> > +
> > +	for (i = 0; i < nxfers; i++)
> > +		xfers[i].err = adi_i3c_cmd_get_err(&xfer->cmds[i]);
> > +
> > +	kfree(xfer);
> > +
> > +	return ret;
> > +}
> > +
> > +struct adi_i3c_i2c_dev_data {
> > +	u16 id;
> > +	s16 ibi;
> > +	struct i3c_generic_ibi_pool *ibi_pool;
> > +};
> > +
> > +static int adi_i3c_master_get_rr_slot(struct adi_i3c_master *master,
> > +				      u8 dyn_addr)
> > +{
> > +	if (!master->free_rr_slots)
> > +		return -ENOSPC;
> > +
> > +	return ffs(master->free_rr_slots) - 1;
> > +}
> > +
> > +static int adi_i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 dyn_addr)
> > +{
> > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +	u8 addr;
> > +
> > +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> > +
> > +	writel(REG_DEV_CHAR_ADDR(dyn_addr), master->regs + REG_DEV_CHAR);
> > +	writel((readl(master->regs + REG_DEV_CHAR) &
> > +		~REG_DEV_CHAR_IS_ATTACHED) | REG_DEV_CHAR_WEN,
> > +	       master->regs + REG_DEV_CHAR);
> > +
> > +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > +	writel(readl(master->regs + REG_DEV_CHAR) |
> > +	       REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> > +	       master->regs + REG_DEV_CHAR);
> > +
> > +	return 0;
> > +}
> > +
> > +static int adi_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev)
> > +{
> > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +	struct adi_i3c_i2c_dev_data *data;
> > +	int slot;
> > +	u8 addr;
> > +
> > +	data = kzalloc(sizeof(*data), GFP_KERNEL);
> > +	if (!data)
> > +		return -ENOMEM;
> > +
> > +	slot = adi_i3c_master_get_rr_slot(master, dev->info.dyn_addr);
> > +	if (slot < 0) {
> > +		kfree(data);
> > +		return slot;
> > +	}
> > +
> > +	data->ibi = -1;
> 
> why -1 here?
> 
Doesn't mean much, just stores the ibi slot occupied by the device.
NULL would be position 0 of the array.
It can be left uninitialized and not cleaned-up when removed.
> > +	data->id = slot;
> > +	i3c_dev_set_master_data(dev, data);
> > +	master->free_rr_slots &= ~BIT(slot);
> > +
> > +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> > +
> > +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > +	writel(readl(master->regs + REG_DEV_CHAR) |
> > +	       REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> > +	       master->regs + REG_DEV_CHAR);
> > +
> > +	return 0;
> > +}
> > +
> > +static void adi_i3c_master_sync_dev_char(struct i3c_master_controller *m)
> > +{
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +	struct i3c_dev_desc *i3cdev;
> > +	u8 addr;
> > +
> > +	i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
> > +		addr = i3cdev->info.dyn_addr ?
> > +		       i3cdev->info.dyn_addr : i3cdev->info.static_addr;
> > +		writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > +		writel(readl(master->regs + REG_DEV_CHAR) |
> > +		       REG_DEV_CHAR_BCR_IBI(i3cdev->info.bcr) | REG_DEV_CHAR_WEN,
> > +		       master->regs + REG_DEV_CHAR);
> > +	}
> > +}
> > +
> > +static void adi_i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev)
> > +{
> > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> > +	u8 addr;
> > +
> > +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> > +
> > +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > +	writel((readl(master->regs + REG_DEV_CHAR) &
> > +		~REG_DEV_CHAR_IS_ATTACHED) | REG_DEV_CHAR_WEN,
> > +	       master->regs + REG_DEV_CHAR);
> > +
> > +	i3c_dev_set_master_data(dev, NULL);
> > +	master->free_rr_slots |= BIT(data->id);
> > +	kfree(data);
> > +}
> > +
> > +static int adi_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev)
> > +{
> > +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +	struct adi_i3c_i2c_dev_data *data;
> > +	int slot;
> > +
> > +	slot = adi_i3c_master_get_rr_slot(master, 0);
> > +	if (slot < 0)
> > +		return slot;
> > +
> > +	data = kzalloc(sizeof(*data), GFP_KERNEL);
> > +	if (!data)
> > +		return -ENOMEM;
> > +
> > +	data->id = slot;
> > +	master->free_rr_slots &= ~BIT(slot);
> > +	i2c_dev_set_master_data(dev, data);
> > +
> > +	writel(REG_DEV_CHAR_ADDR(dev->addr) |
> > +	       REG_DEV_CHAR_IS_I2C | REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> > +	       master->regs + REG_DEV_CHAR);
> > +
> > +	return 0;
> > +}
> > +
> > +static void adi_i3c_master_detach_i2c_dev(struct i2c_dev_desc *dev)
> > +{
> > +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +	struct adi_i3c_i2c_dev_data *data = i2c_dev_get_master_data(dev);
> > +
> > +	writel(REG_DEV_CHAR_ADDR(dev->addr) |
> > +	       REG_DEV_CHAR_IS_I2C | REG_DEV_CHAR_WEN,
> > +	       master->regs + REG_DEV_CHAR);
> > +
> > +	i2c_dev_set_master_data(dev, NULL);
> > +	master->free_rr_slots |= BIT(data->id);
> > +	kfree(data);
> > +}
> > +
> > +static void adi_i3c_master_bus_cleanup(struct i3c_master_controller *m)
> > +{
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +
> > +	adi_i3c_master_disable(master);
> > +}
> > +
> > +static void adi_i3c_master_upd_i3c_scl_lim(struct adi_i3c_master *master)
> > +{
> > +	struct i3c_master_controller *m = &master->base;
> > +	struct i3c_bus *bus = i3c_master_get_bus(m);
> > +	u8 i3c_scl_lim = 0;
> > +	struct i3c_dev_desc *dev;
> > +	u8 pp_sg;
> > +
> > +	i3c_bus_for_each_i3cdev(bus, dev) {
> > +		u8 max_fscl;
> > +
> > +		max_fscl = max(I3C_CCC_MAX_SDR_FSCL(dev->info.max_read_ds),
> > +			       I3C_CCC_MAX_SDR_FSCL(dev->info.max_write_ds));
> > +
> > +		switch (max_fscl) {
> > +		case I3C_SDR1_FSCL_8MHZ:
> > +			max_fscl = PP_SG_6MHZ;
> > +			break;
> > +		case I3C_SDR2_FSCL_6MHZ:
> > +			max_fscl = PP_SG_3MHZ;
> > +			break;
> > +		case I3C_SDR3_FSCL_4MHZ:
> > +			max_fscl = PP_SG_3MHZ;
> > +			break;
> > +		case I3C_SDR4_FSCL_2MHZ:
> > +			max_fscl = PP_SG_1MHZ;
> > +			break;
> > +		case I3C_SDR0_FSCL_MAX:
> > +		default:
> > +			max_fscl = PP_SG_12MHZ;
> > +			break;
> > +		}
> > +
> > +		if (max_fscl &&
> > +		    (i3c_scl_lim > max_fscl || !i3c_scl_lim))
> > +			i3c_scl_lim = max_fscl;
> > +	}
> > +
> > +	if (!i3c_scl_lim)
> > +		return;
> > +
> > +	master->i3c_scl_lim = i3c_scl_lim - 1;
> > +
> > +	pp_sg = readl(master->regs + REG_OPS) & ~REG_OPS_PP_SG_MASK;
> > +	pp_sg |= REG_OPS_SET_SG(master->i3c_scl_lim);
> > +
> > +	writel(pp_sg, master->regs + REG_OPS);
> > +}
> > +
> > +static void adi_i3c_master_get_features(struct adi_i3c_master *master,
> > +					unsigned int slot,
> > +					struct i3c_device_info *info)
> > +{
> > +	memset(info, 0, sizeof(*info));
> > +
> > +	info->dyn_addr = 0x31;
> 
> master address is hardcode?
> 
Yes.
There is/was no register to read it from because the controller does not
support secondary mastership or changing role, so this address is not
used for anything (as is). Since it is odd, I will move add these
hardcoded values to the register map (read-only), still won't do
anything.
> > +	info->dcr = 0x00;
> > +	info->bcr = 0x40;
> > +	info->pid = 0;
> > +}
> > +
> > +static int adi_i3c_master_do_daa(struct i3c_master_controller *m)
> > +{
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +	u32 irq_mask;
> > +	int ret, addr;
> > +
> > +	addr = 0x8;
> > +	for (u8 i = 0; i < MAX_DEVS; i++) {
> > +		addr = i3c_master_get_free_addr(m, addr);
> > +		if (addr < 0)
> > +			return addr;
> 
> assume MAX_DEVS 128, if only get 4 address, suppose you should enumerate
> for such 4 devices, instead of return error and give up do_daa.
> 
The IP Core supports just 16 devices (max # on the bus), so it should
not run-out of addresses, but I understand the assignment. As is, the IP
core has 16 slots and will only terminate the DAA from its side if it
run out of slots (if no device request an address (NACK bit), the DAA is
also terminated). Not writing a DA during the DA IRQ will just hang the
bus.

For the suggestion to work I need to re-read the spec and rework the RTL
to allow this early termination. However, I am currently a little
opposed of changing the RTL beyond basic read-only registers
modifications.
> > +		master->daa.addrs[i] = addr;
> > +	}
> > +
> > +	irq_mask = readl(master->regs + REG_IRQ_MASK);
> > +	writel(irq_mask | REG_IRQ_PENDING_DAA,
> > +	       master->regs + REG_IRQ_MASK);
> > +
> > +	master->daa.index = 0;
> > +	ret = i3c_master_entdaa_locked(&master->base);
> > +
> > +	writel(irq_mask, master->regs + REG_IRQ_MASK);
> > +
> > +	/* DAA always finishes with CE2_ERROR or NACK_RESP */
> > +	if (ret && ret != I3C_ERROR_M2)
> > +		return ret;
> > +
> > +	/* Add I3C devices discovered */
> > +	for (u8 i = 0; i < master->daa.index; i++)
> > +		i3c_master_add_i3c_dev_locked(m, master->daa.addrs[i]);
> > +	/* Sync retrieved devs info with the IP */
> > +	adi_i3c_master_sync_dev_char(m);
> > +
> > +	i3c_master_defslvs_locked(&master->base);
> > +
> > +	adi_i3c_master_upd_i3c_scl_lim(master);
> > +
> > +	return 0;
> > +}
> > +
> > +static int adi_i3c_master_bus_init(struct i3c_master_controller *m)
> > +{
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +	struct i3c_device_info info = { };
> > +	int ret;
> > +
> > +	ret = i3c_master_get_free_addr(m, 0);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	adi_i3c_master_get_features(master, 0, &info);
> > +	ret = i3c_master_set_info(&master->base, &info);
> > +	if (ret)
> > +		return ret;
> > +
> > +	writel(REG_IBI_CONFIG_LISTEN,
> > +	       master->regs + REG_IBI_CONFIG);
> > +
> > +	return 0;
> > +}
> > +
> > +static void adi_i3c_master_handle_ibi(struct adi_i3c_master *master,
> > +				      u32 ibi)
> > +{
> > +	struct adi_i3c_i2c_dev_data *data;
> > +	struct i3c_ibi_slot *slot;
> > +	struct i3c_dev_desc *dev;
> > +	u8 da, id;
> > +	u8 *mdb;
> > +
> > +	da = (ibi >> 17) & GENMASK(6, 0);
> > +	for (id = 0; id < master->ibi.num_slots; id++) {
> > +		if (master->ibi.slots[id] &&
> > +		    master->ibi.slots[id]->info.dyn_addr == da)
> > +			break;
> > +	}
> > +
> > +	if (id == master->ibi.num_slots)
> > +		return;
> > +
> > +	dev = master->ibi.slots[id];
> > +	guard(spinlock)(&master->ibi.lock);
> > +
> > +	data = i3c_dev_get_master_data(dev);
> > +	slot = i3c_generic_ibi_get_free_slot(data->ibi_pool);
> > +	if (!slot)
> > +		return;
> > +
> > +	mdb = slot->data;
> > +	mdb[0] = (ibi >> 8) & GENMASK(7, 0);
> > +
> > +	slot->len = 1;
> > +	i3c_master_queue_ibi(dev, slot);
> > +}
> > +
> > +static void adi_i3c_master_demux_ibis(struct adi_i3c_master *master)
> > +{
> > +	u32 status0;
> > +
> > +	for (status0 = readl(master->regs + REG_FIFO_STATUS);
> > +	     !(status0 & REG_FIFO_STATUS_IBI_EMPTY);
> > +	     status0 = readl(master->regs + REG_FIFO_STATUS)) {
> 
> use while()
> 
Ack.
> > +		u32 ibi = readl(master->regs + REG_IBI_FIFO);
> > +
> > +		adi_i3c_master_handle_ibi(master, ibi);
> > +	}
> > +}
> > +
> > +static void adi_i3c_master_handle_da_req(struct adi_i3c_master *master)
> > +{
> > +	u8 payload0[8];
> > +	u32 addr;
> > +
> > +	/* Clear device characteristics */
> > +	adi_i3c_master_rd_from_rx_fifo(master, payload0, 6);
> > +	addr = master->daa.addrs[master->daa.index++];
> > +	addr = (addr << 1) | (parity8(addr) ? 0 : 1);
> > +
> > +	writel(addr, master->regs + REG_SDO_FIFO);
> > +}
> > +
> > +static irqreturn_t adi_i3c_master_irq(int irq, void *data)
> > +{
> > +	struct adi_i3c_master *master = data;
> > +	u32 pending;
> > +
> > +	pending = readl(master->regs + REG_IRQ_PENDING);
> > +	writel(pending, master->regs + REG_IRQ_PENDING);
> 
> Add an empty line here
> 
Ack.
> > +	if (pending & REG_IRQ_PENDING_CMDR) {
> > +		spin_lock(&master->xferqueue.lock);
> > +		adi_i3c_master_end_xfer_locked(master, pending);
> > +		spin_unlock(&master->xferqueue.lock);
> 
> you use guard, keep consisent, use guard or scoped_guard here
> 
scoped_guard
> > +	}
> > +	if (pending & REG_IRQ_PENDING_IBI)
> > +		adi_i3c_master_demux_ibis(master);
> > +	if (pending & REG_IRQ_PENDING_DAA)
> > +		adi_i3c_master_handle_da_req(master);
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static int adi_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
> > +				    struct i2c_msg *xfers,
> > +				    int nxfers)
> > +{
> > +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +	struct adi_i3c_xfer *xfer;
> > +	int i, ret;
> > +
> > +	if (!nxfers)
> > +		return 0;
> > +	for (i = 0; i < nxfers; i++) {
> > +		if (xfers[i].flags & I2C_M_TEN)
> > +			return -EOPNOTSUPP;
> > +	}
> > +	xfer = adi_i3c_master_alloc_xfer(master, nxfers);
> > +	if (!xfer)
> > +		return -ENOMEM;
> > +
> > +	for (i = 0; i < nxfers; i++) {
> > +		struct adi_i3c_cmd *ccmd = &xfer->cmds[i];
> > +
> > +		ccmd->cmd0 = REG_CMD_FIFO_0_DEV_ADDR(xfers[i].addr);
> > +
> > +		if (xfers[i].flags & I2C_M_RD) {
> > +			ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> > +			ccmd->rx_buf = xfers[i].buf;
> > +			ccmd->rx_len = xfers[i].len;
> > +		} else {
> > +			ccmd->tx_buf = xfers[i].buf;
> > +			ccmd->tx_len = xfers[i].len;
> > +		}
> > +
> > +		ccmd->cmd0 |= REG_CMD_FIFO_0_LEN(xfers[i].len);
> 
> Just curiouse
> What's happen if IBI happen (address arbitrate) at first command?
> 
The controller does the state machine for the IBI and yield a IBIR
interrupt, then proceeds with the xfer, yielding the CMDR for each CMD.
> > +	}
> > +
> > +	adi_i3c_master_queue_xfer(master, xfer);
> > +	if (!wait_for_completion_timeout(&xfer->comp,
> > +					 m->i2c.timeout))
> > +		adi_i3c_master_unqueue_xfer(master, xfer);
> > +
> > +	ret = xfer->ret;
> > +	kfree(xfer);
> > +	return ret;
> > +}
> > +
> > +static int adi_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
> > +{
> > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +	struct i3c_dev_desc *i3cdev;
> > +	u32 enabled = 0;
> > +	int ret;
> > +
> > +	ret = i3c_master_disec_locked(m, dev->info.dyn_addr,
> > +				      I3C_CCC_EVENT_SIR);
> > +
> > +	i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
> > +		if (dev != i3cdev && i3cdev->ibi)
> > +			enabled |= i3cdev->ibi->enabled;
> > +	}
> > +	if (!enabled) {
> > +		writel(REG_IBI_CONFIG_LISTEN,
> > +		       master->regs + REG_IBI_CONFIG);
> > +		writel(readl(master->regs + REG_IRQ_MASK) & ~REG_IRQ_PENDING_IBI,
> > +		       master->regs + REG_IRQ_MASK);
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static int adi_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
> > +{
> > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +
> > +	writel(REG_IBI_CONFIG_LISTEN | REG_IBI_CONFIG_ENABLE,
> > +	       master->regs + REG_IBI_CONFIG);
> > +
> > +	writel(readl(master->regs + REG_IRQ_MASK) | REG_IRQ_PENDING_IBI,
> > +	       master->regs + REG_IRQ_MASK);
> > +
> > +	return i3c_master_enec_locked(m, dev->info.dyn_addr,
> > +				      I3C_CCC_EVENT_SIR);
> > +}
> > +
> > +static int adi_i3c_master_request_ibi(struct i3c_dev_desc *dev,
> > +				      const struct i3c_ibi_setup *req)
> > +{
> > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +	struct adi_i3c_i2c_dev_data *data;
> > +	unsigned long flags;
> > +	unsigned int i;
> > +
> > +	data = i3c_dev_get_master_data(dev);
> > +	data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req);
> > +	if (IS_ERR(data->ibi_pool))
> > +		return PTR_ERR(data->ibi_pool);
> > +
> > +	spin_lock_irqsave(&master->ibi.lock, flags);
> > +	for (i = 0; i < master->ibi.num_slots; i++) {
> > +		if (!master->ibi.slots[i]) {
> > +			data->ibi = i;
> > +			master->ibi.slots[i] = dev;
> > +			break;
> > +		}
> > +	}
> > +	spin_unlock_irqrestore(&master->ibi.lock, flags);
> 
> same here, use scope_guard to keep consistent.
> 
Ack.
> > +
> > +	if (i < master->ibi.num_slots)
> > +		return 0;
> > +
> > +	i3c_generic_ibi_free_pool(data->ibi_pool);
> > +	data->ibi_pool = NULL;
> > +
> > +	return -ENOSPC;
> > +}
> > +
> > +static void adi_i3c_master_free_ibi(struct i3c_dev_desc *dev)
> > +{
> > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&master->ibi.lock, flags);
> > +	master->ibi.slots[data->ibi] = NULL;
> > +	data->ibi = -1;
> > +	spin_unlock_irqrestore(&master->ibi.lock, flags);
> > +
> > +	i3c_generic_ibi_free_pool(data->ibi_pool);
> > +}
> > +
> > +static void adi_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev,
> > +					    struct i3c_ibi_slot *slot)
> > +{
> > +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> > +
> > +	i3c_generic_ibi_recycle_slot(data->ibi_pool, slot);
> > +}
> > +
> > +static const struct i3c_master_controller_ops adi_i3c_master_ops = {
> > +	.bus_init = adi_i3c_master_bus_init,
> > +	.bus_cleanup = adi_i3c_master_bus_cleanup,
> > +	.attach_i3c_dev = adi_i3c_master_attach_i3c_dev,
> > +	.reattach_i3c_dev = adi_i3c_master_reattach_i3c_dev,
> > +	.detach_i3c_dev = adi_i3c_master_detach_i3c_dev,
> > +	.attach_i2c_dev = adi_i3c_master_attach_i2c_dev,
> > +	.detach_i2c_dev = adi_i3c_master_detach_i2c_dev,
> > +	.do_daa = adi_i3c_master_do_daa,
> > +	.supports_ccc_cmd = adi_i3c_master_supports_ccc_cmd,
> > +	.send_ccc_cmd = adi_i3c_master_send_ccc_cmd,
> > +	.priv_xfers = adi_i3c_master_priv_xfers,
> > +	.i2c_xfers = adi_i3c_master_i2c_xfers,
> > +	.request_ibi = adi_i3c_master_request_ibi,
> > +	.enable_ibi = adi_i3c_master_enable_ibi,
> > +	.disable_ibi = adi_i3c_master_disable_ibi,
> > +	.free_ibi = adi_i3c_master_free_ibi,
> > +	.recycle_ibi_slot = adi_i3c_master_recycle_ibi_slot,
> > +};
> > +
> > +static const struct of_device_id adi_i3c_master_of_match[] = {
> > +	{ .compatible = "adi,i3c-master" },
> > +	{}
> > +};
> > +
> > +static int adi_i3c_master_probe(struct platform_device *pdev)
> > +{
> > +	struct adi_i3c_master *master;
> > +	unsigned int version;
> > +	int ret, irq;
> > +
> > +	master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL);
> > +	if (!master)
> > +		return -ENOMEM;
> > +
> > +	master->regs = devm_platform_ioremap_resource(pdev, 0);
> > +	if (IS_ERR(master->regs))
> > +		return PTR_ERR(master->regs);
> > +
> > +	master->clk = devm_clk_get_enabled(&pdev->dev, "axi");
> > +	if (IS_ERR(master->clk))
> > +		return PTR_ERR(master->clk);
> 
> you have optional clock i3c, not use it in driver?
> 
It is not used in the driver indeed, it describes how the IP Core is
compiled and wired, but doesn't affect the logic in the driver itself.
The RTL logic is always driven by a 100MHz clock, and the source is
either axi or i3c, if the latter present.
> > +
> > +	irq = platform_get_irq(pdev, 0);
> > +	if (irq < 0)
> > +		return irq;
> > +
> > +	version = readl(master->regs + REG_VERSION);
> > +	if (VERSION_MAJOR(version) != 0) {
> > +		dev_err(&pdev->dev, "Unsupported IP version %u.%u.%c\n",
> > +			VERSION_MAJOR(version),
> > +			VERSION_MINOR(version),
> > +			VERSION_PATCH(version));
> > +		return -EINVAL;
> 
> return dev_err_probe();
Ack.
> 
> Frank
Best regards,
Jorge
> > +	}
> > +
> > +	writel(0x00, master->regs + REG_ENABLE);
> > +	writel(0x00, master->regs + REG_IRQ_MASK);
> > +
> > +	ret = devm_request_irq(&pdev->dev, irq, adi_i3c_master_irq, 0,
> > +			       dev_name(&pdev->dev), master);
> > +	if (ret)
> > +		return ret;
> > +
> > +	platform_set_drvdata(pdev, master);
> > +
> > +	master->maxdevs = MAX_DEVS;
> > +	master->free_rr_slots = GENMASK(master->maxdevs, 1);
> > +
> > +	writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
> > +
> > +	spin_lock_init(&master->ibi.lock);
> > +	master->ibi.num_slots = 15;
> > +	master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots,
> > +					 sizeof(*master->ibi.slots),
> > +					 GFP_KERNEL);
> > +	if (!master->ibi.slots)
> > +		return -ENOMEM;
> > +
> > +	return i3c_master_register(&master->base, &pdev->dev,
> > +				   &adi_i3c_master_ops, false);
> > +}
> > +
> > +static void adi_i3c_master_remove(struct platform_device *pdev)
> > +{
> > +	struct adi_i3c_master *master = platform_get_drvdata(pdev);
> > +
> > +	writel(0xff, master->regs + REG_IRQ_PENDING);
> > +	writel(0x00, master->regs + REG_IRQ_MASK);
> > +	writel(0x01, master->regs + REG_ENABLE);
> > +
> > +	i3c_master_unregister(&master->base);
> > +}
> > +
> > +static struct platform_driver adi_i3c_master = {
> > +	.probe = adi_i3c_master_probe,
> > +	.remove = adi_i3c_master_remove,
> > +	.driver = {
> > +		.name = "adi-i3c-master",
> > +		.of_match_table = adi_i3c_master_of_match,
> > +	},
> > +};
> > +module_platform_driver(adi_i3c_master);
> > +
> > +MODULE_AUTHOR("Jorge Marques <jorge.marques@analog.com>");
> > +MODULE_DESCRIPTION("Analog Devices I3C master driver");
> > +MODULE_LICENSE("GPL");
> >
> > --
> > 2.49.0
> >

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP
  2025-06-18 22:17     ` Jorge Marques
@ 2025-06-19  2:18       ` Frank Li
  2025-06-19 10:32         ` Jorge Marques
  0 siblings, 1 reply; 14+ messages in thread
From: Frank Li @ 2025-06-19  2:18 UTC (permalink / raw)
  To: Jorge Marques
  Cc: Jorge Marques, Alexandre Belloni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-i3c, devicetree,
	linux-kernel

On Thu, Jun 19, 2025 at 12:17:26AM +0200, Jorge Marques wrote:
> On Wed, Jun 18, 2025 at 03:40:58PM -0400, Frank Li wrote:
> > On Wed, Jun 18, 2025 at 09:16:44AM +0200, Jorge Marques wrote:
> > > Add support for Analog Devices I3C Controller IP, an AXI-interfaced IP
> > > core that supports I3C and I2C devices, multiple speed-grades and
> > > I3C IBIs.
> > >
> > > Signed-off-by: Jorge Marques <jorge.marques@analog.com>
> > > ---
> > >  MAINTAINERS                         |    1 +
> > >  drivers/i3c/master/Kconfig          |   11 +
> > >  drivers/i3c/master/Makefile         |    1 +
> > >  drivers/i3c/master/adi-i3c-master.c | 1026 +++++++++++++++++++++++++++++++++++
> > >  4 files changed, 1039 insertions(+)
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index 6f56e17dcecf902c6812827c1ec3e067c65e9894..9eb5b6c327590725d1641fd4b73e48fc1d1a3954 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -11247,6 +11247,7 @@ I3C DRIVER FOR ANALOG DEVICES I3C CONTROLLER IP
> > >  M:	Jorge Marques <jorge.marques@analog.com>
> > >  S:	Maintained
> > >  F:	Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
> > > +F:	drivers/i3c/master/adi-i3c-master.c
> > >
> > >  I3C DRIVER FOR CADENCE I3C MASTER IP
> > >  M:	Przemysław Gaj <pgaj@cadence.com>
> > > diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig
> > > index 7b30db3253af9d5c6aee6544c060e491bfbeb643..328b7145cdefa20e708ebfa3383e849ce51c5a71 100644
> > > --- a/drivers/i3c/master/Kconfig
> > > +++ b/drivers/i3c/master/Kconfig
> > > @@ -1,4 +1,15 @@
> > >  # SPDX-License-Identifier: GPL-2.0-only
> > > +config ADI_I3C_MASTER
> > > +	tristate "Analog Devices I3C master driver"
> > > +	depends on HAS_IOMEM
> > > +	help
> > > +	  Support for Analog Devices I3C Controller IP, an AXI-interfaced IP
> > > +	  core that supports I3C and I2C devices, multiple speed-grades and
> > > +	  I3C IBIs.
> > > +
> > > +	  This driver can also be built as a module.  If so, the module
> > > +	  will be called adi-i3c-master.
> > > +
> > >  config CDNS_I3C_MASTER
> > >  	tristate "Cadence I3C master driver"
> > >  	depends on HAS_IOMEM
> > > diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile
> > > index 3e97960160bc85e5eaf2966ec0c3fae458c2711e..6cc4f4b73e7bdc206b68c750390f9c3cc2ccb199 100644
> > > --- a/drivers/i3c/master/Makefile
> > > +++ b/drivers/i3c/master/Makefile
> > > @@ -1,4 +1,5 @@
> > >  # SPDX-License-Identifier: GPL-2.0-only
> > > +obj-$(CONFIG_ADI_I3C_MASTER)		+= adi-i3c-master.o
> > >  obj-$(CONFIG_CDNS_I3C_MASTER)		+= i3c-master-cdns.o
> > >  obj-$(CONFIG_DW_I3C_MASTER)		+= dw-i3c-master.o
> > >  obj-$(CONFIG_AST2600_I3C_MASTER)	+= ast2600-i3c-master.o
> > > diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c
> > > new file mode 100644
> > > index 0000000000000000000000000000000000000000..d4e0756efec9588383ac55ca9ea9228368ce767d
> > > --- /dev/null
> > > +++ b/drivers/i3c/master/adi-i3c-master.c
> > > @@ -0,0 +1,1026 @@
> > > +// SPDX-License-Identifier: GPL-2.0-only
> > > +/*
> > > + * I3C Controller driver
> > > + * Copyright 2025 Analog Devices Inc.
> > > + * Author: Jorge Marques <jorge.marques@analog.com>
> > > + */
> > > +
> > > +#include <linux/bitops.h>
> > > +#include <linux/bitfield.h>
> > > +#include <linux/clk.h>
> > > +#include <linux/err.h>
> > > +#include <linux/errno.h>
> > > +#include <linux/i3c/master.h>
> > > +#include <linux/interrupt.h>
> > > +#include <linux/io.h>
> > > +#include <linux/module.h>
> > > +#include <linux/of.h>
> > > +#include <linux/platform_device.h>
> > > +
> > > +#define VERSION_MAJOR(x)		((u32)FIELD_GET(GENMASK(23, 16), (x)))
> > > +#define VERSION_MINOR(x)		((u32)FIELD_GET(GENMASK(15, 8), (x)))
> > > +#define VERSION_PATCH(x)		((u32)FIELD_GET(GENMASK(7, 0), (x)))
> Hi Frank,
> >
> > I suppose that needn't (u32), any warnings report without u32?
> >
> Here I will actually remove and use the macros from
> linux/fpga/adi-axi-common.h
>
> > > +
> > > +#define MAX_DEVS			16
> > > +
> > > +#define REG_VERSION			0x000
> to be replaced removed, and ADI_AXI_REG_VERSION used instead.
> > > +#define REG_ENABLE			0x040
> > > +#define REG_IRQ_MASK			0x080
> > > +#define REG_IRQ_PENDING			0x084
> > > +#define   REG_IRQ_PENDING_CMDR		BIT(5)
> > > +#define   REG_IRQ_PENDING_IBI		((u32)BIT(6))
> >
> > needn't u32
> In earlier CI runs, I used to get with gcc_x86_64, due to the inversion
> ~REG_IRQ_PENDING_IBI:
>
>   > conversion from 'long unsigned int' to 'unsigned int' changes value
>   > from '18446744073709551615' to '4294967295' [-Werror=overflow]
>
> Couldn't replicate in this patch version, so I will remove.
>
> > > +#define   REG_IRQ_PENDING_DAA		BIT(7)
> >
> > Add empty line between registers
> >
> Ok
> > > +#define REG_CMD_FIFO			0x0d4
> > > +#define	  REG_CMD_FIFO_0_IS_CCC		BIT(22)
> > > +#define   REG_CMD_FIFO_0_BCAST		BIT(21)
> > > +#define   REG_CMD_FIFO_0_SR		BIT(20)
> > > +#define   REG_CMD_FIFO_0_LEN(l)		FIELD_PREP(GENMASK(19, 8), (l))
> > > +#define   REG_CMD_FIFO_0_DEV_ADDR(a)	FIELD_PREP(GENMASK(7, 1), a)
> > > +#define   REG_CMD_FIFO_0_RNW		BIT(0)
> > > +#define   REG_CMD_FIFO_1_CCC(id)	FIELD_PREP(GENMASK(7, 0), (id))
> > > +#define REG_CMD_FIFO_ROOM		0x0c0
> > > +#define REG_CMDR_FIFO			0x0d8
> > > +#define   REG_CMDR_FIFO_NO_ERROR	0
> > > +#define   REG_CMDR_FIFO_CE0_ERROR	1
> > > +#define   REG_CMDR_FIFO_CE2_ERROR	4
> > > +#define   REG_CMDR_FIFO_NACK_RESP	6
> > > +#define   REG_CMDR_FIFO_UDA_ERROR	8
> > > +#define   REG_CMDR_FIFO_ERROR(x)	FIELD_GET(GENMASK(23, 20), (x))
> > > +#define   REG_CMDR_FIFO_XFER_BYTES(x)	FIELD_GET(GENMASK(19, 8), (x))
> >
> > Sort according bit pos. REG_CMDR_FIFO_XFER_BYTES should before
> > REG_CMDR_FIFO_ERROR
> >
> Ahh true
> > > +#define REG_SDO_FIFO			0x0dc
> > > +#define REG_SDO_FIFO_ROOM		0x0c8
> > > +#define REG_SDI_FIFO			0x0e0
> > > +#define REG_IBI_FIFO			0x0e4
> > > +#define REG_FIFO_STATUS			0x0e8
> > > +#define   REG_FIFO_STATUS_CMDR_EMPTY	BIT(0)
> > > +#define   REG_FIFO_STATUS_IBI_EMPTY	BIT(1)
> > > +#define REG_OPS				0x100
> > > +#define   REG_OPS_SET_SG(x)		FIELD_PREP(GENMASK(6, 5), (x))
> > > +#define   REG_OPS_PP_SG_MASK		GENMASK(6, 5)
> >
> > REG_OPS_SET_SG(x) FIELD_PREP(REG_OPS_PP_SG_MASK, (x))
> Ack.
> >
> > > +#define REG_IBI_CONFIG			0x140
> > > +#define   REG_IBI_CONFIG_LISTEN		BIT(1)
> > > +#define   REG_IBI_CONFIG_ENABLE		BIT(0)
> >
> > sort it. keep consisent in whole file, from 0->31 or 31->0
> >
> Ack, decrementing
> > > +#define REG_DEV_CHAR			0x180
> > > +#define   REG_DEV_CHAR_IS_I2C		BIT(0)
> > > +#define   REG_DEV_CHAR_IS_ATTACHED	BIT(1)
> > > +#define   REG_DEV_CHAR_BCR_IBI(x)	(((x) & GENMASK(2, 1)) << 1)
> >
> > FIELD_PREP()
> Ok, I will remove the simplification and make it clearer by setting
>
>      #define   REG_DEV_CHAR_BCR_IBI(x)       FIELD_PREP(GENMASK(3, 2), (x))
>
> Then usage:
>
>      #include <linux/i3c/device.h>
>      ...
>      bcr_ibi = FIELD_GET(I3C_BCR_IBI_PAYLOAD | I3C_BCR_IBI_REQ_CAP, (i3cdev->info.bcr));
>      ...REG_DEV_CHAR_BCR_IBI(bcr_ibi)...;
>
> >
> > > +#define   REG_DEV_CHAR_WEN		BIT(8)
> > > +#define   REG_DEV_CHAR_ADDR(x)		FIELD_PREP(GENMASK(15, 9), (x))
> > > +
> > > +enum speed_grade {PP_SG_UNSET, PP_SG_1MHZ, PP_SG_3MHZ, PP_SG_6MHZ, PP_SG_12MHZ};
> > > +struct adi_i3c_cmd {
> > > +	u32 cmd0;
> > > +	u32 cmd1;
> > > +	u32 tx_len;
> > > +	const void *tx_buf;
> > > +	u32 rx_len;
> > > +	void *rx_buf;
> > > +	u32 error;
> > > +};
> > > +
> > > +struct adi_i3c_xfer {
> > > +	struct list_head node;
> > > +	struct completion comp;
> > > +	int ret;
> > > +	unsigned int ncmds;
> > > +	unsigned int ncmds_comp;
> > > +	struct adi_i3c_cmd cmds[];
> > > +};
> > > +
> > > +struct adi_i3c_master {
> > > +	struct i3c_master_controller base;
> > > +	u32 free_rr_slots;
> > > +	unsigned int maxdevs;
> > > +	struct {
> > > +		unsigned int num_slots;
> > > +		struct i3c_dev_desc **slots;
> > > +		spinlock_t lock; /* Protect IBI slot access */
> > > +	} ibi;
> > > +	struct {
> > > +		struct list_head list;
> > > +		struct adi_i3c_xfer *cur;
> > > +		spinlock_t lock; /* Protect transfer */
> > > +	} xferqueue;
> > > +	void __iomem *regs;
> > > +	struct clk *clk;
> > > +	unsigned long i3c_scl_lim;
> > > +	struct {
> > > +		u8 addrs[MAX_DEVS];
> > > +		u8 index;
> > > +	} daa;
> > > +};
> > > +
> > > +static inline struct adi_i3c_master *to_adi_i3c_master(struct i3c_master_controller *master)
> > > +{
> > > +	return container_of(master, struct adi_i3c_master, base);
> > > +}
> > > +
> > > +static void adi_i3c_master_wr_to_tx_fifo(struct adi_i3c_master *master,
> > > +					 const u8 *bytes, unsigned int nbytes)
> > > +{
> > > +	unsigned int n, m;
> > > +	u32 tmp;
> > > +
> > > +	n = readl(master->regs + REG_SDO_FIFO_ROOM);
> > > +	m = min(n, nbytes);
> > > +	writesl(master->regs + REG_SDO_FIFO, bytes, m / 4);
> > > +
> > > +	if (m & 3) {
> > > +		memcpy(&tmp, bytes + (m & ~3), m & 3);
> > > +		writel(tmp, master->regs + REG_SDO_FIFO);
> > > +	}
> > > +}
> > > +
> > > +static void adi_i3c_master_rd_from_rx_fifo(struct adi_i3c_master *master,
> > > +					   u8 *bytes, unsigned int nbytes)
> > > +{
> > > +	readsl(master->regs + REG_SDI_FIFO, bytes, nbytes / 4);
> > > +	if (nbytes & 3) {
> > > +		u32 tmp;
> > > +
> > > +		tmp = readl(master->regs + REG_SDI_FIFO);
> > > +		memcpy(bytes + (nbytes & ~3), &tmp, nbytes & 3);
> > > +	}
> > > +}
> >
> > can you move above two helper function as inline into drivers/i3c/internals.h
> >
> > or create new one drivers/i3c/master/helper.h or other naming.
> >
> > Renesas IP have similar above two functions to access FIFO.
> > https://lore.kernel.org/linux-i3c/174964724485.330045.2181706921272138816.robh@kernel.org/T/#t
> >
> Ok, yes that's common because the passes type u8 doesn't ensure
> multiples of 32 at least. Added to my TODO list.
> > > +
> > > +static bool adi_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m,
> > > +					    const struct i3c_ccc_cmd *cmd)
> > > +{
> > > +	if (cmd->ndests > 1)
> > > +		return false;
> > > +
> > > +	switch (cmd->id) {
> > > +	case I3C_CCC_ENEC(true):
> > > +	case I3C_CCC_ENEC(false):
> > > +	case I3C_CCC_DISEC(true):
> > > +	case I3C_CCC_DISEC(false):
> > > +	case I3C_CCC_RSTDAA(true):
> > > +	case I3C_CCC_RSTDAA(false):
> > > +	case I3C_CCC_ENTDAA:
> > > +	case I3C_CCC_SETDASA:
> > > +	case I3C_CCC_SETNEWDA:
> > > +	case I3C_CCC_GETMWL:
> > > +	case I3C_CCC_GETMRL:
> > > +	case I3C_CCC_GETPID:
> > > +	case I3C_CCC_GETBCR:
> > > +	case I3C_CCC_GETDCR:
> > > +	case I3C_CCC_GETSTATUS:
> > > +	case I3C_CCC_GETHDRCAP:
> > > +		return true;
> > > +	default:
> > > +		break;
> > > +	}
> > > +
> > > +	return false;
> > > +}
> > > +
> > > +static int adi_i3c_master_disable(struct adi_i3c_master *master)
> > > +{
> > > +	writel(0, master->regs + REG_IBI_CONFIG);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static struct adi_i3c_xfer *adi_i3c_master_alloc_xfer(struct adi_i3c_master *master,
> > > +						      unsigned int ncmds)
> > > +{
> > > +	struct adi_i3c_xfer *xfer;
> > > +
> > > +	xfer = kzalloc(struct_size(xfer, cmds, ncmds), GFP_KERNEL);
> > > +	if (!xfer)
> > > +		return NULL;
> > > +
> > > +	INIT_LIST_HEAD(&xfer->node);
> > > +	xfer->ncmds = ncmds;
> > > +	xfer->ret = -ETIMEDOUT;
> > > +
> > > +	return xfer;
> > > +}
> > > +
> > > +static void adi_i3c_master_start_xfer_locked(struct adi_i3c_master *master)
> > > +{
> > > +	struct adi_i3c_xfer *xfer = master->xferqueue.cur;
> > > +	unsigned int i, n, m;
> > > +
> > > +	if (!xfer)
> > > +		return;
> > > +
> > > +	for (i = 0; i < xfer->ncmds; i++) {
> > > +		struct adi_i3c_cmd *cmd = &xfer->cmds[i];
> > > +
> > > +		if (!(cmd->cmd0 & REG_CMD_FIFO_0_RNW))
> > > +			adi_i3c_master_wr_to_tx_fifo(master, cmd->tx_buf, cmd->tx_len);
> > > +	}
> > > +
> > > +	n = readl(master->regs + REG_CMD_FIFO_ROOM);
> > > +	for (i = 0; i < xfer->ncmds; i++) {
> > > +		struct adi_i3c_cmd *cmd = &xfer->cmds[i];
> > > +
> > > +		m = cmd->cmd0 & REG_CMD_FIFO_0_IS_CCC ? 2 : 1;
> > > +		if (m > n)
> > > +			break;
> > > +		writel(cmd->cmd0, master->regs + REG_CMD_FIFO);
> > > +		if (cmd->cmd0 & REG_CMD_FIFO_0_IS_CCC)
> > > +			writel(cmd->cmd1, master->regs + REG_CMD_FIFO);
> > > +		n -= m;
> > > +	}
> > > +}
> > > +
> > > +static void adi_i3c_master_end_xfer_locked(struct adi_i3c_master *master,
> > > +					   u32 pending)
> > > +{
> > > +	struct adi_i3c_xfer *xfer = master->xferqueue.cur;
> > > +	int i, ret = 0;
> > > +	u32 status0;
> > > +
> > > +	if (!xfer)
> > > +		return;
> > > +
> > > +	for (status0 = readl(master->regs + REG_FIFO_STATUS);
> > > +	     !(status0 & REG_FIFO_STATUS_CMDR_EMPTY);
> > > +	     status0 = readl(master->regs + REG_FIFO_STATUS)) {
> >
> > how about
> >
> > while (!(readl(master->regs + REG_FIFO_STATUS) & REG_FIFO_STATUS_CMDR_EMPTY))) {
> >
> Ack, thanks!
> Same for 'REG_FIFO_STATUS_IBI_EMPTY for'.
> > > +		struct adi_i3c_cmd *cmd;
> > > +		u32 cmdr, rx_len;
> > > +
> > > +		cmdr = readl(master->regs + REG_CMDR_FIFO);
> > > +
> > > +		cmd = &xfer->cmds[xfer->ncmds_comp++];
> > > +		if (cmd->cmd0 & REG_CMD_FIFO_0_RNW) {
> > > +			rx_len = min_t(u32, REG_CMDR_FIFO_XFER_BYTES(cmdr), cmd->rx_len);
> > > +			adi_i3c_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len);
> > > +		}
> > > +		cmd->error = REG_CMDR_FIFO_ERROR(cmdr);
> > > +	}
> > > +
> > > +	for (i = 0; i < xfer->ncmds; i++) {
> >
> > I think you iterate to ncmds_comp?
> >
> You are correct thanks! Each command yields a command receipt interrupt.
> > > +		switch (xfer->cmds[i].error) {
> > > +		case REG_CMDR_FIFO_NO_ERROR:
> > > +			break;
> > > +
> > > +		case REG_CMDR_FIFO_CE0_ERROR:
> > > +		case REG_CMDR_FIFO_CE2_ERROR:
> > > +		case REG_CMDR_FIFO_NACK_RESP:
> > > +		case REG_CMDR_FIFO_UDA_ERROR:
> > > +			ret = -EIO;
> > > +			break;
> > > +
> > > +		default:
> > > +			ret = -EINVAL;
> > > +			break;
> > > +		}
> > > +	}
> > > +
> > > +	xfer->ret = ret;
> > > +
> > > +	if (xfer->ncmds_comp != xfer->ncmds)
> > > +		return;
> > > +
> > > +	complete(&xfer->comp);
> > > +
> > > +	xfer = list_first_entry_or_null(&master->xferqueue.list,
> > > +					struct adi_i3c_xfer, node);
> > > +	if (xfer)
> > > +		list_del_init(&xfer->node);
> > > +
> > > +	master->xferqueue.cur = xfer;
> > > +	adi_i3c_master_start_xfer_locked(master);
> > > +}
> > > +
> > > +static void adi_i3c_master_queue_xfer(struct adi_i3c_master *master,
> > > +				      struct adi_i3c_xfer *xfer)
> > > +{
> > > +	init_completion(&xfer->comp);
> > > +	guard(spinlock_irqsave)(&master->xferqueue.lock);
> > > +	if (master->xferqueue.cur) {
> > > +		list_add_tail(&xfer->node, &master->xferqueue.list);
> > > +	} else {
> > > +		master->xferqueue.cur = xfer;
> > > +		adi_i3c_master_start_xfer_locked(master);
> > > +	}
> > > +}
> > > +
> > > +static void adi_i3c_master_unqueue_xfer(struct adi_i3c_master *master,
> > > +					struct adi_i3c_xfer *xfer)
> > > +{
> > > +	guard(spinlock_irqsave)(&master->xferqueue.lock);
> > > +	if (master->xferqueue.cur == xfer)
> > > +		master->xferqueue.cur = NULL;
> > > +	else
> > > +		list_del_init(&xfer->node);
> > > +
> > > +	writel(0x01, master->regs + REG_ENABLE);
> > > +	writel(0x00, master->regs + REG_ENABLE);
> > > +	writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
> > > +}
> > > +
> > > +static enum i3c_error_code adi_i3c_cmd_get_err(struct adi_i3c_cmd *cmd)
> > > +{
> > > +	switch (cmd->error) {
> > > +	case REG_CMDR_FIFO_CE0_ERROR:
> > > +		return I3C_ERROR_M0;
> > > +
> > > +	case REG_CMDR_FIFO_CE2_ERROR:
> > > +	case REG_CMDR_FIFO_NACK_RESP:
> > > +		return I3C_ERROR_M2;
> > > +
> > > +	default:
> > > +		break;
> > > +	}
> > > +
> > > +	return I3C_ERROR_UNKNOWN;
> > > +}
> > > +
> > > +static int adi_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
> > > +				       struct i3c_ccc_cmd *cmd)
> > > +{
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +	struct adi_i3c_xfer *xfer;
> > > +	struct adi_i3c_cmd *ccmd;
> > > +
> > > +	xfer = adi_i3c_master_alloc_xfer(master, 1);
> > > +	if (!xfer)
> > > +		return -ENOMEM;
> > > +
> > > +	ccmd = xfer->cmds;
> > > +	ccmd->cmd1 = REG_CMD_FIFO_1_CCC(cmd->id);
> > > +	ccmd->cmd0 = REG_CMD_FIFO_0_IS_CCC |
> > > +		     REG_CMD_FIFO_0_LEN(cmd->dests[0].payload.len);
> > > +
> > > +	if (cmd->id & I3C_CCC_DIRECT)
> > > +		ccmd->cmd0 |= REG_CMD_FIFO_0_DEV_ADDR(cmd->dests[0].addr);
> > > +
> > > +	if (cmd->rnw) {
> > > +		ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> > > +		ccmd->rx_buf = cmd->dests[0].payload.data;
> > > +		ccmd->rx_len = cmd->dests[0].payload.len;
> > > +	} else {
> > > +		ccmd->tx_buf = cmd->dests[0].payload.data;
> > > +		ccmd->tx_len = cmd->dests[0].payload.len;
> > > +	}
> > > +
> > > +	adi_i3c_master_queue_xfer(master, xfer);
> > > +	if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000)))
> > > +		adi_i3c_master_unqueue_xfer(master, xfer);
> > > +
> > > +	cmd->err = adi_i3c_cmd_get_err(&xfer->cmds[0]);
> > > +	kfree(xfer);
> >
> > you can use
> > struct adi_i3c_xfer *xfer __free(kfree) = NULL;
> >
> >  to simple code.
> >
> Ah cool, will use in all xfer instances.
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int adi_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
> > > +				     struct i3c_priv_xfer *xfers,
> > > +				     int nxfers)
> > > +{
> > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +	struct adi_i3c_xfer *xfer;
> > > +	int i, ret;
> > > +
> > > +	if (!nxfers)
> > > +		return 0;
> >
> > add an empty line.
> >
> Ack.
> > > +	xfer = adi_i3c_master_alloc_xfer(master, nxfers);
> > > +	if (!xfer)
> > > +		return -ENOMEM;
> > > +
> > > +	for (i = 0; i < nxfers; i++) {
> > > +		struct adi_i3c_cmd *ccmd = &xfer->cmds[i];
> > > +
> > > +		ccmd->cmd0 = REG_CMD_FIFO_0_DEV_ADDR(dev->info.dyn_addr);
> > > +
> > > +		if (xfers[i].rnw) {
> > > +			ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> > > +			ccmd->rx_buf = xfers[i].data.in;
> > > +			ccmd->rx_len = xfers[i].len;
> > > +		} else {
> > > +			ccmd->tx_buf = xfers[i].data.out;
> > > +			ccmd->tx_len = xfers[i].len;
> > > +		}
> > > +
> > > +		ccmd->cmd0 |= REG_CMD_FIFO_0_LEN(xfers[i].len);
> > > +
> > > +		if (i < nxfers - 1)
> > > +			ccmd->cmd0 |= REG_CMD_FIFO_0_SR;
> > > +
> > > +		if (!i)
> > > +			ccmd->cmd0 |= REG_CMD_FIFO_0_BCAST;
> > > +	}
> > > +
> > > +	adi_i3c_master_queue_xfer(master, xfer);
> > > +	if (!wait_for_completion_timeout(&xfer->comp,
> > > +					 msecs_to_jiffies(1000)))
> > > +		adi_i3c_master_unqueue_xfer(master, xfer);
> > > +
> > > +	ret = xfer->ret;
> > > +
> > > +	for (i = 0; i < nxfers; i++)
> > > +		xfers[i].err = adi_i3c_cmd_get_err(&xfer->cmds[i]);
> > > +
> > > +	kfree(xfer);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +struct adi_i3c_i2c_dev_data {
> > > +	u16 id;
> > > +	s16 ibi;
> > > +	struct i3c_generic_ibi_pool *ibi_pool;
> > > +};
> > > +
> > > +static int adi_i3c_master_get_rr_slot(struct adi_i3c_master *master,
> > > +				      u8 dyn_addr)
> > > +{
> > > +	if (!master->free_rr_slots)
> > > +		return -ENOSPC;
> > > +
> > > +	return ffs(master->free_rr_slots) - 1;
> > > +}
> > > +
> > > +static int adi_i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 dyn_addr)
> > > +{
> > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +	u8 addr;
> > > +
> > > +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> > > +
> > > +	writel(REG_DEV_CHAR_ADDR(dyn_addr), master->regs + REG_DEV_CHAR);
> > > +	writel((readl(master->regs + REG_DEV_CHAR) &
> > > +		~REG_DEV_CHAR_IS_ATTACHED) | REG_DEV_CHAR_WEN,
> > > +	       master->regs + REG_DEV_CHAR);
> > > +
> > > +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > > +	writel(readl(master->regs + REG_DEV_CHAR) |
> > > +	       REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> > > +	       master->regs + REG_DEV_CHAR);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int adi_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev)
> > > +{
> > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +	struct adi_i3c_i2c_dev_data *data;
> > > +	int slot;
> > > +	u8 addr;
> > > +
> > > +	data = kzalloc(sizeof(*data), GFP_KERNEL);
> > > +	if (!data)
> > > +		return -ENOMEM;
> > > +
> > > +	slot = adi_i3c_master_get_rr_slot(master, dev->info.dyn_addr);
> > > +	if (slot < 0) {
> > > +		kfree(data);
> > > +		return slot;
> > > +	}
> > > +
> > > +	data->ibi = -1;
> >
> > why -1 here?
> >
> Doesn't mean much, just stores the ibi slot occupied by the device.
> NULL would be position 0 of the array.
> It can be left uninitialized and not cleaned-up when removed.
> > > +	data->id = slot;
> > > +	i3c_dev_set_master_data(dev, data);
> > > +	master->free_rr_slots &= ~BIT(slot);
> > > +
> > > +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> > > +
> > > +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > > +	writel(readl(master->regs + REG_DEV_CHAR) |
> > > +	       REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> > > +	       master->regs + REG_DEV_CHAR);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static void adi_i3c_master_sync_dev_char(struct i3c_master_controller *m)
> > > +{
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +	struct i3c_dev_desc *i3cdev;
> > > +	u8 addr;
> > > +
> > > +	i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
> > > +		addr = i3cdev->info.dyn_addr ?
> > > +		       i3cdev->info.dyn_addr : i3cdev->info.static_addr;
> > > +		writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > > +		writel(readl(master->regs + REG_DEV_CHAR) |
> > > +		       REG_DEV_CHAR_BCR_IBI(i3cdev->info.bcr) | REG_DEV_CHAR_WEN,
> > > +		       master->regs + REG_DEV_CHAR);
> > > +	}
> > > +}
> > > +
> > > +static void adi_i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev)
> > > +{
> > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> > > +	u8 addr;
> > > +
> > > +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> > > +
> > > +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > > +	writel((readl(master->regs + REG_DEV_CHAR) &
> > > +		~REG_DEV_CHAR_IS_ATTACHED) | REG_DEV_CHAR_WEN,
> > > +	       master->regs + REG_DEV_CHAR);
> > > +
> > > +	i3c_dev_set_master_data(dev, NULL);
> > > +	master->free_rr_slots |= BIT(data->id);
> > > +	kfree(data);
> > > +}
> > > +
> > > +static int adi_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev)
> > > +{
> > > +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +	struct adi_i3c_i2c_dev_data *data;
> > > +	int slot;
> > > +
> > > +	slot = adi_i3c_master_get_rr_slot(master, 0);
> > > +	if (slot < 0)
> > > +		return slot;
> > > +
> > > +	data = kzalloc(sizeof(*data), GFP_KERNEL);
> > > +	if (!data)
> > > +		return -ENOMEM;
> > > +
> > > +	data->id = slot;
> > > +	master->free_rr_slots &= ~BIT(slot);
> > > +	i2c_dev_set_master_data(dev, data);
> > > +
> > > +	writel(REG_DEV_CHAR_ADDR(dev->addr) |
> > > +	       REG_DEV_CHAR_IS_I2C | REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> > > +	       master->regs + REG_DEV_CHAR);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static void adi_i3c_master_detach_i2c_dev(struct i2c_dev_desc *dev)
> > > +{
> > > +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +	struct adi_i3c_i2c_dev_data *data = i2c_dev_get_master_data(dev);
> > > +
> > > +	writel(REG_DEV_CHAR_ADDR(dev->addr) |
> > > +	       REG_DEV_CHAR_IS_I2C | REG_DEV_CHAR_WEN,
> > > +	       master->regs + REG_DEV_CHAR);
> > > +
> > > +	i2c_dev_set_master_data(dev, NULL);
> > > +	master->free_rr_slots |= BIT(data->id);
> > > +	kfree(data);
> > > +}
> > > +
> > > +static void adi_i3c_master_bus_cleanup(struct i3c_master_controller *m)
> > > +{
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +
> > > +	adi_i3c_master_disable(master);
> > > +}
> > > +
> > > +static void adi_i3c_master_upd_i3c_scl_lim(struct adi_i3c_master *master)
> > > +{
> > > +	struct i3c_master_controller *m = &master->base;
> > > +	struct i3c_bus *bus = i3c_master_get_bus(m);
> > > +	u8 i3c_scl_lim = 0;
> > > +	struct i3c_dev_desc *dev;
> > > +	u8 pp_sg;
> > > +
> > > +	i3c_bus_for_each_i3cdev(bus, dev) {
> > > +		u8 max_fscl;
> > > +
> > > +		max_fscl = max(I3C_CCC_MAX_SDR_FSCL(dev->info.max_read_ds),
> > > +			       I3C_CCC_MAX_SDR_FSCL(dev->info.max_write_ds));
> > > +
> > > +		switch (max_fscl) {
> > > +		case I3C_SDR1_FSCL_8MHZ:
> > > +			max_fscl = PP_SG_6MHZ;
> > > +			break;
> > > +		case I3C_SDR2_FSCL_6MHZ:
> > > +			max_fscl = PP_SG_3MHZ;
> > > +			break;
> > > +		case I3C_SDR3_FSCL_4MHZ:
> > > +			max_fscl = PP_SG_3MHZ;
> > > +			break;
> > > +		case I3C_SDR4_FSCL_2MHZ:
> > > +			max_fscl = PP_SG_1MHZ;
> > > +			break;
> > > +		case I3C_SDR0_FSCL_MAX:
> > > +		default:
> > > +			max_fscl = PP_SG_12MHZ;
> > > +			break;
> > > +		}
> > > +
> > > +		if (max_fscl &&
> > > +		    (i3c_scl_lim > max_fscl || !i3c_scl_lim))
> > > +			i3c_scl_lim = max_fscl;
> > > +	}
> > > +
> > > +	if (!i3c_scl_lim)
> > > +		return;
> > > +
> > > +	master->i3c_scl_lim = i3c_scl_lim - 1;
> > > +
> > > +	pp_sg = readl(master->regs + REG_OPS) & ~REG_OPS_PP_SG_MASK;
> > > +	pp_sg |= REG_OPS_SET_SG(master->i3c_scl_lim);
> > > +
> > > +	writel(pp_sg, master->regs + REG_OPS);
> > > +}
> > > +
> > > +static void adi_i3c_master_get_features(struct adi_i3c_master *master,
> > > +					unsigned int slot,
> > > +					struct i3c_device_info *info)
> > > +{
> > > +	memset(info, 0, sizeof(*info));
> > > +
> > > +	info->dyn_addr = 0x31;
> >
> > master address is hardcode?
> >
> Yes.
> There is/was no register to read it from because the controller does not
> support secondary mastership or changing role, so this address is not
> used for anything (as is). Since it is odd, I will move add these
> hardcoded values to the register map (read-only), still won't do
> anything.

need comments for 0x31

> > > +	info->dcr = 0x00;
> > > +	info->bcr = 0x40;
> > > +	info->pid = 0;
> > > +}
> > > +
> > > +static int adi_i3c_master_do_daa(struct i3c_master_controller *m)
> > > +{
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +	u32 irq_mask;
> > > +	int ret, addr;
> > > +
> > > +	addr = 0x8;
> > > +	for (u8 i = 0; i < MAX_DEVS; i++) {
> > > +		addr = i3c_master_get_free_addr(m, addr);
> > > +		if (addr < 0)
> > > +			return addr;
> >
> > assume MAX_DEVS 128, if only get 4 address, suppose you should enumerate
> > for such 4 devices, instead of return error and give up do_daa.
> >
> The IP Core supports just 16 devices (max # on the bus), so it should
> not run-out of addresses, but I understand the assignment. As is, the IP
> core has 16 slots and will only terminate the DAA from its side if it
> run out of slots (if no device request an address (NACK bit), the DAA is
> also terminated). Not writing a DA during the DA IRQ will just hang the
> bus.
>
> For the suggestion to work I need to re-read the spec and rework the RTL
> to allow this early termination. However, I am currently a little
> opposed of changing the RTL beyond basic read-only registers
> modifications.
> > > +		master->daa.addrs[i] = addr;
> > > +	}
> > > +
> > > +	irq_mask = readl(master->regs + REG_IRQ_MASK);
> > > +	writel(irq_mask | REG_IRQ_PENDING_DAA,
> > > +	       master->regs + REG_IRQ_MASK);
> > > +
> > > +	master->daa.index = 0;
> > > +	ret = i3c_master_entdaa_locked(&master->base);
> > > +
> > > +	writel(irq_mask, master->regs + REG_IRQ_MASK);
> > > +
> > > +	/* DAA always finishes with CE2_ERROR or NACK_RESP */
> > > +	if (ret && ret != I3C_ERROR_M2)
> > > +		return ret;
> > > +
> > > +	/* Add I3C devices discovered */
> > > +	for (u8 i = 0; i < master->daa.index; i++)
> > > +		i3c_master_add_i3c_dev_locked(m, master->daa.addrs[i]);
> > > +	/* Sync retrieved devs info with the IP */
> > > +	adi_i3c_master_sync_dev_char(m);
> > > +
> > > +	i3c_master_defslvs_locked(&master->base);
> > > +
> > > +	adi_i3c_master_upd_i3c_scl_lim(master);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int adi_i3c_master_bus_init(struct i3c_master_controller *m)
> > > +{
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +	struct i3c_device_info info = { };
> > > +	int ret;
> > > +
> > > +	ret = i3c_master_get_free_addr(m, 0);
> > > +	if (ret < 0)
> > > +		return ret;
> > > +
> > > +	adi_i3c_master_get_features(master, 0, &info);
> > > +	ret = i3c_master_set_info(&master->base, &info);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	writel(REG_IBI_CONFIG_LISTEN,
> > > +	       master->regs + REG_IBI_CONFIG);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static void adi_i3c_master_handle_ibi(struct adi_i3c_master *master,
> > > +				      u32 ibi)
> > > +{
> > > +	struct adi_i3c_i2c_dev_data *data;
> > > +	struct i3c_ibi_slot *slot;
> > > +	struct i3c_dev_desc *dev;
> > > +	u8 da, id;
> > > +	u8 *mdb;
> > > +
> > > +	da = (ibi >> 17) & GENMASK(6, 0);
> > > +	for (id = 0; id < master->ibi.num_slots; id++) {
> > > +		if (master->ibi.slots[id] &&
> > > +		    master->ibi.slots[id]->info.dyn_addr == da)
> > > +			break;
> > > +	}
> > > +
> > > +	if (id == master->ibi.num_slots)
> > > +		return;
> > > +
> > > +	dev = master->ibi.slots[id];
> > > +	guard(spinlock)(&master->ibi.lock);
> > > +
> > > +	data = i3c_dev_get_master_data(dev);
> > > +	slot = i3c_generic_ibi_get_free_slot(data->ibi_pool);
> > > +	if (!slot)
> > > +		return;
> > > +
> > > +	mdb = slot->data;
> > > +	mdb[0] = (ibi >> 8) & GENMASK(7, 0);
> > > +
> > > +	slot->len = 1;
> > > +	i3c_master_queue_ibi(dev, slot);
> > > +}
> > > +
> > > +static void adi_i3c_master_demux_ibis(struct adi_i3c_master *master)
> > > +{
> > > +	u32 status0;
> > > +
> > > +	for (status0 = readl(master->regs + REG_FIFO_STATUS);
> > > +	     !(status0 & REG_FIFO_STATUS_IBI_EMPTY);
> > > +	     status0 = readl(master->regs + REG_FIFO_STATUS)) {
> >
> > use while()
> >
> Ack.
> > > +		u32 ibi = readl(master->regs + REG_IBI_FIFO);
> > > +
> > > +		adi_i3c_master_handle_ibi(master, ibi);
> > > +	}
> > > +}
> > > +
> > > +static void adi_i3c_master_handle_da_req(struct adi_i3c_master *master)
> > > +{
> > > +	u8 payload0[8];
> > > +	u32 addr;
> > > +
> > > +	/* Clear device characteristics */
> > > +	adi_i3c_master_rd_from_rx_fifo(master, payload0, 6);
> > > +	addr = master->daa.addrs[master->daa.index++];
> > > +	addr = (addr << 1) | (parity8(addr) ? 0 : 1);
> > > +
> > > +	writel(addr, master->regs + REG_SDO_FIFO);
> > > +}
> > > +
> > > +static irqreturn_t adi_i3c_master_irq(int irq, void *data)
> > > +{
> > > +	struct adi_i3c_master *master = data;
> > > +	u32 pending;
> > > +
> > > +	pending = readl(master->regs + REG_IRQ_PENDING);
> > > +	writel(pending, master->regs + REG_IRQ_PENDING);
> >
> > Add an empty line here
> >
> Ack.
> > > +	if (pending & REG_IRQ_PENDING_CMDR) {
> > > +		spin_lock(&master->xferqueue.lock);
> > > +		adi_i3c_master_end_xfer_locked(master, pending);
> > > +		spin_unlock(&master->xferqueue.lock);
> >
> > you use guard, keep consisent, use guard or scoped_guard here
> >
> scoped_guard
> > > +	}
> > > +	if (pending & REG_IRQ_PENDING_IBI)
> > > +		adi_i3c_master_demux_ibis(master);
> > > +	if (pending & REG_IRQ_PENDING_DAA)
> > > +		adi_i3c_master_handle_da_req(master);
> > > +
> > > +	return IRQ_HANDLED;
> > > +}
> > > +
> > > +static int adi_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
> > > +				    struct i2c_msg *xfers,
> > > +				    int nxfers)
> > > +{
> > > +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +	struct adi_i3c_xfer *xfer;
> > > +	int i, ret;
> > > +
> > > +	if (!nxfers)
> > > +		return 0;
> > > +	for (i = 0; i < nxfers; i++) {
> > > +		if (xfers[i].flags & I2C_M_TEN)
> > > +			return -EOPNOTSUPP;
> > > +	}
> > > +	xfer = adi_i3c_master_alloc_xfer(master, nxfers);
> > > +	if (!xfer)
> > > +		return -ENOMEM;
> > > +
> > > +	for (i = 0; i < nxfers; i++) {
> > > +		struct adi_i3c_cmd *ccmd = &xfer->cmds[i];
> > > +
> > > +		ccmd->cmd0 = REG_CMD_FIFO_0_DEV_ADDR(xfers[i].addr);
> > > +
> > > +		if (xfers[i].flags & I2C_M_RD) {
> > > +			ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> > > +			ccmd->rx_buf = xfers[i].buf;
> > > +			ccmd->rx_len = xfers[i].len;
> > > +		} else {
> > > +			ccmd->tx_buf = xfers[i].buf;
> > > +			ccmd->tx_len = xfers[i].len;
> > > +		}
> > > +
> > > +		ccmd->cmd0 |= REG_CMD_FIFO_0_LEN(xfers[i].len);
> >
> > Just curiouse
> > What's happen if IBI happen (address arbitrate) at first command?
> >
> The controller does the state machine for the IBI and yield a IBIR
> interrupt, then proceeds with the xfer, yielding the CMDR for each CMD.

Does controller retry current command again after handle IBI?

Frank Li

> > > +	}
> > > +
> > > +	adi_i3c_master_queue_xfer(master, xfer);
> > > +	if (!wait_for_completion_timeout(&xfer->comp,
> > > +					 m->i2c.timeout))
> > > +		adi_i3c_master_unqueue_xfer(master, xfer);
> > > +
> > > +	ret = xfer->ret;
> > > +	kfree(xfer);
> > > +	return ret;
> > > +}
> > > +
> > > +static int adi_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
> > > +{
> > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +	struct i3c_dev_desc *i3cdev;
> > > +	u32 enabled = 0;
> > > +	int ret;
> > > +
> > > +	ret = i3c_master_disec_locked(m, dev->info.dyn_addr,
> > > +				      I3C_CCC_EVENT_SIR);
> > > +
> > > +	i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
> > > +		if (dev != i3cdev && i3cdev->ibi)
> > > +			enabled |= i3cdev->ibi->enabled;
> > > +	}
> > > +	if (!enabled) {
> > > +		writel(REG_IBI_CONFIG_LISTEN,
> > > +		       master->regs + REG_IBI_CONFIG);
> > > +		writel(readl(master->regs + REG_IRQ_MASK) & ~REG_IRQ_PENDING_IBI,
> > > +		       master->regs + REG_IRQ_MASK);
> > > +	}
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int adi_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
> > > +{
> > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +
> > > +	writel(REG_IBI_CONFIG_LISTEN | REG_IBI_CONFIG_ENABLE,
> > > +	       master->regs + REG_IBI_CONFIG);
> > > +
> > > +	writel(readl(master->regs + REG_IRQ_MASK) | REG_IRQ_PENDING_IBI,
> > > +	       master->regs + REG_IRQ_MASK);
> > > +
> > > +	return i3c_master_enec_locked(m, dev->info.dyn_addr,
> > > +				      I3C_CCC_EVENT_SIR);
> > > +}
> > > +
> > > +static int adi_i3c_master_request_ibi(struct i3c_dev_desc *dev,
> > > +				      const struct i3c_ibi_setup *req)
> > > +{
> > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +	struct adi_i3c_i2c_dev_data *data;
> > > +	unsigned long flags;
> > > +	unsigned int i;
> > > +
> > > +	data = i3c_dev_get_master_data(dev);
> > > +	data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req);
> > > +	if (IS_ERR(data->ibi_pool))
> > > +		return PTR_ERR(data->ibi_pool);
> > > +
> > > +	spin_lock_irqsave(&master->ibi.lock, flags);
> > > +	for (i = 0; i < master->ibi.num_slots; i++) {
> > > +		if (!master->ibi.slots[i]) {
> > > +			data->ibi = i;
> > > +			master->ibi.slots[i] = dev;
> > > +			break;
> > > +		}
> > > +	}
> > > +	spin_unlock_irqrestore(&master->ibi.lock, flags);
> >
> > same here, use scope_guard to keep consistent.
> >
> Ack.
> > > +
> > > +	if (i < master->ibi.num_slots)
> > > +		return 0;
> > > +
> > > +	i3c_generic_ibi_free_pool(data->ibi_pool);
> > > +	data->ibi_pool = NULL;
> > > +
> > > +	return -ENOSPC;
> > > +}
> > > +
> > > +static void adi_i3c_master_free_ibi(struct i3c_dev_desc *dev)
> > > +{
> > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> > > +	unsigned long flags;
> > > +
> > > +	spin_lock_irqsave(&master->ibi.lock, flags);
> > > +	master->ibi.slots[data->ibi] = NULL;
> > > +	data->ibi = -1;
> > > +	spin_unlock_irqrestore(&master->ibi.lock, flags);
> > > +
> > > +	i3c_generic_ibi_free_pool(data->ibi_pool);
> > > +}
> > > +
> > > +static void adi_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev,
> > > +					    struct i3c_ibi_slot *slot)
> > > +{
> > > +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> > > +
> > > +	i3c_generic_ibi_recycle_slot(data->ibi_pool, slot);
> > > +}
> > > +
> > > +static const struct i3c_master_controller_ops adi_i3c_master_ops = {
> > > +	.bus_init = adi_i3c_master_bus_init,
> > > +	.bus_cleanup = adi_i3c_master_bus_cleanup,
> > > +	.attach_i3c_dev = adi_i3c_master_attach_i3c_dev,
> > > +	.reattach_i3c_dev = adi_i3c_master_reattach_i3c_dev,
> > > +	.detach_i3c_dev = adi_i3c_master_detach_i3c_dev,
> > > +	.attach_i2c_dev = adi_i3c_master_attach_i2c_dev,
> > > +	.detach_i2c_dev = adi_i3c_master_detach_i2c_dev,
> > > +	.do_daa = adi_i3c_master_do_daa,
> > > +	.supports_ccc_cmd = adi_i3c_master_supports_ccc_cmd,
> > > +	.send_ccc_cmd = adi_i3c_master_send_ccc_cmd,
> > > +	.priv_xfers = adi_i3c_master_priv_xfers,
> > > +	.i2c_xfers = adi_i3c_master_i2c_xfers,
> > > +	.request_ibi = adi_i3c_master_request_ibi,
> > > +	.enable_ibi = adi_i3c_master_enable_ibi,
> > > +	.disable_ibi = adi_i3c_master_disable_ibi,
> > > +	.free_ibi = adi_i3c_master_free_ibi,
> > > +	.recycle_ibi_slot = adi_i3c_master_recycle_ibi_slot,
> > > +};
> > > +
> > > +static const struct of_device_id adi_i3c_master_of_match[] = {
> > > +	{ .compatible = "adi,i3c-master" },
> > > +	{}
> > > +};
> > > +
> > > +static int adi_i3c_master_probe(struct platform_device *pdev)
> > > +{
> > > +	struct adi_i3c_master *master;
> > > +	unsigned int version;
> > > +	int ret, irq;
> > > +
> > > +	master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL);
> > > +	if (!master)
> > > +		return -ENOMEM;
> > > +
> > > +	master->regs = devm_platform_ioremap_resource(pdev, 0);
> > > +	if (IS_ERR(master->regs))
> > > +		return PTR_ERR(master->regs);
> > > +
> > > +	master->clk = devm_clk_get_enabled(&pdev->dev, "axi");
> > > +	if (IS_ERR(master->clk))
> > > +		return PTR_ERR(master->clk);
> >
> > you have optional clock i3c, not use it in driver?
> >
> It is not used in the driver indeed, it describes how the IP Core is
> compiled and wired, but doesn't affect the logic in the driver itself.
> The RTL logic is always driven by a 100MHz clock, and the source is
> either axi or i3c, if the latter present.

can you use devm_clk_bulk_get_all_enabled() ?

> > > +
> > > +	irq = platform_get_irq(pdev, 0);
> > > +	if (irq < 0)
> > > +		return irq;
> > > +
> > > +	version = readl(master->regs + REG_VERSION);
> > > +	if (VERSION_MAJOR(version) != 0) {
> > > +		dev_err(&pdev->dev, "Unsupported IP version %u.%u.%c\n",
> > > +			VERSION_MAJOR(version),
> > > +			VERSION_MINOR(version),
> > > +			VERSION_PATCH(version));
> > > +		return -EINVAL;
> >
> > return dev_err_probe();
> Ack.
> >
> > Frank
> Best regards,
> Jorge
> > > +	}
> > > +
> > > +	writel(0x00, master->regs + REG_ENABLE);
> > > +	writel(0x00, master->regs + REG_IRQ_MASK);
> > > +
> > > +	ret = devm_request_irq(&pdev->dev, irq, adi_i3c_master_irq, 0,
> > > +			       dev_name(&pdev->dev), master);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	platform_set_drvdata(pdev, master);
> > > +
> > > +	master->maxdevs = MAX_DEVS;
> > > +	master->free_rr_slots = GENMASK(master->maxdevs, 1);
> > > +
> > > +	writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
> > > +
> > > +	spin_lock_init(&master->ibi.lock);
> > > +	master->ibi.num_slots = 15;
> > > +	master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots,
> > > +					 sizeof(*master->ibi.slots),
> > > +					 GFP_KERNEL);
> > > +	if (!master->ibi.slots)
> > > +		return -ENOMEM;
> > > +
> > > +	return i3c_master_register(&master->base, &pdev->dev,
> > > +				   &adi_i3c_master_ops, false);
> > > +}
> > > +
> > > +static void adi_i3c_master_remove(struct platform_device *pdev)
> > > +{
> > > +	struct adi_i3c_master *master = platform_get_drvdata(pdev);
> > > +
> > > +	writel(0xff, master->regs + REG_IRQ_PENDING);
> > > +	writel(0x00, master->regs + REG_IRQ_MASK);
> > > +	writel(0x01, master->regs + REG_ENABLE);
> > > +
> > > +	i3c_master_unregister(&master->base);
> > > +}
> > > +
> > > +static struct platform_driver adi_i3c_master = {
> > > +	.probe = adi_i3c_master_probe,
> > > +	.remove = adi_i3c_master_remove,
> > > +	.driver = {
> > > +		.name = "adi-i3c-master",
> > > +		.of_match_table = adi_i3c_master_of_match,
> > > +	},
> > > +};
> > > +module_platform_driver(adi_i3c_master);
> > > +
> > > +MODULE_AUTHOR("Jorge Marques <jorge.marques@analog.com>");
> > > +MODULE_DESCRIPTION("Analog Devices I3C master driver");
> > > +MODULE_LICENSE("GPL");
> > >
> > > --
> > > 2.49.0
> > >

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 1/2] dt-bindings: i3c: Add adi-i3c-master
  2025-06-18 19:55         ` Jorge Marques
@ 2025-06-19  6:15           ` Krzysztof Kozlowski
  0 siblings, 0 replies; 14+ messages in thread
From: Krzysztof Kozlowski @ 2025-06-19  6:15 UTC (permalink / raw)
  To: Jorge Marques
  Cc: Jorge Marques, Alexandre Belloni, Frank Li, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-i3c, devicetree,
	linux-kernel

On 18/06/2025 21:55, Jorge Marques wrote:
>> Usually 0 has a meaning as well. Where is this used DTS snippet used (on
>> which platform)?
>>
> 
> The example provided is used on AMD xilinx arm,cortex-a9-gic.
> The IP core is tested on AMD Xilinx, Altera FPGAs (arm, microblaze, arm64).
> Test/support to RiscV-based Lattice FPGAs should eventually also come.
> 
> In this example 0 means Shared Peripheral Interrupt, but has no header

Of course it has... just like 100% of bindings for devices in a SoC.

> file defining. I guess I could make it generic and set to
> 
>   interrupts = <3 IRQ_TYPE_LEVEL_HIGH>;
> 

That's fine as well.



Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP
  2025-06-19  2:18       ` Frank Li
@ 2025-06-19 10:32         ` Jorge Marques
  2025-06-19 15:10           ` Frank Li
  0 siblings, 1 reply; 14+ messages in thread
From: Jorge Marques @ 2025-06-19 10:32 UTC (permalink / raw)
  To: Frank Li
  Cc: Jorge Marques, Alexandre Belloni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-i3c, devicetree,
	linux-kernel

On Wed, Jun 18, 2025 at 10:18:48PM -0400, Frank Li wrote:
> On Thu, Jun 19, 2025 at 12:17:26AM +0200, Jorge Marques wrote:
> > On Wed, Jun 18, 2025 at 03:40:58PM -0400, Frank Li wrote:
> > > On Wed, Jun 18, 2025 at 09:16:44AM +0200, Jorge Marques wrote:
> > > > Add support for Analog Devices I3C Controller IP, an AXI-interfaced IP
> > > > core that supports I3C and I2C devices, multiple speed-grades and
> > > > I3C IBIs.
> > > >
> > > > Signed-off-by: Jorge Marques <jorge.marques@analog.com>
> > > > ---
> > > >  MAINTAINERS                         |    1 +
> > > >  drivers/i3c/master/Kconfig          |   11 +
> > > >  drivers/i3c/master/Makefile         |    1 +
> > > >  drivers/i3c/master/adi-i3c-master.c | 1026 +++++++++++++++++++++++++++++++++++
> > > >  4 files changed, 1039 insertions(+)
> > > >
> > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > index 6f56e17dcecf902c6812827c1ec3e067c65e9894..9eb5b6c327590725d1641fd4b73e48fc1d1a3954 100644
> > > > --- a/MAINTAINERS
> > > > +++ b/MAINTAINERS
> > > > @@ -11247,6 +11247,7 @@ I3C DRIVER FOR ANALOG DEVICES I3C CONTROLLER IP
> > > >  M:	Jorge Marques <jorge.marques@analog.com>
> > > >  S:	Maintained
> > > >  F:	Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
> > > > +F:	drivers/i3c/master/adi-i3c-master.c
> > > >
> > > >  I3C DRIVER FOR CADENCE I3C MASTER IP
> > > >  M:	Przemysław Gaj <pgaj@cadence.com>
> > > > diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig
> > > > index 7b30db3253af9d5c6aee6544c060e491bfbeb643..328b7145cdefa20e708ebfa3383e849ce51c5a71 100644
> > > > --- a/drivers/i3c/master/Kconfig
> > > > +++ b/drivers/i3c/master/Kconfig
> > > > @@ -1,4 +1,15 @@
> > > >  # SPDX-License-Identifier: GPL-2.0-only
> > > > +config ADI_I3C_MASTER
> > > > +	tristate "Analog Devices I3C master driver"
> > > > +	depends on HAS_IOMEM
> > > > +	help
> > > > +	  Support for Analog Devices I3C Controller IP, an AXI-interfaced IP
> > > > +	  core that supports I3C and I2C devices, multiple speed-grades and
> > > > +	  I3C IBIs.
> > > > +
> > > > +	  This driver can also be built as a module.  If so, the module
> > > > +	  will be called adi-i3c-master.
> > > > +
> > > >  config CDNS_I3C_MASTER
> > > >  	tristate "Cadence I3C master driver"
> > > >  	depends on HAS_IOMEM
> > > > diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile
> > > > index 3e97960160bc85e5eaf2966ec0c3fae458c2711e..6cc4f4b73e7bdc206b68c750390f9c3cc2ccb199 100644
> > > > --- a/drivers/i3c/master/Makefile
> > > > +++ b/drivers/i3c/master/Makefile
> > > > @@ -1,4 +1,5 @@
> > > >  # SPDX-License-Identifier: GPL-2.0-only
> > > > +obj-$(CONFIG_ADI_I3C_MASTER)		+= adi-i3c-master.o
> > > >  obj-$(CONFIG_CDNS_I3C_MASTER)		+= i3c-master-cdns.o
> > > >  obj-$(CONFIG_DW_I3C_MASTER)		+= dw-i3c-master.o
> > > >  obj-$(CONFIG_AST2600_I3C_MASTER)	+= ast2600-i3c-master.o
> > > > diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c
> > > > new file mode 100644
> > > > index 0000000000000000000000000000000000000000..d4e0756efec9588383ac55ca9ea9228368ce767d
> > > > --- /dev/null
> > > > +++ b/drivers/i3c/master/adi-i3c-master.c
> > > > @@ -0,0 +1,1026 @@
> > > > +// SPDX-License-Identifier: GPL-2.0-only
> > > > +/*
> > > > + * I3C Controller driver
> > > > + * Copyright 2025 Analog Devices Inc.
> > > > + * Author: Jorge Marques <jorge.marques@analog.com>
> > > > + */
> > > > +
> > > > +#include <linux/bitops.h>
> > > > +#include <linux/bitfield.h>
> > > > +#include <linux/clk.h>
> > > > +#include <linux/err.h>
> > > > +#include <linux/errno.h>
> > > > +#include <linux/i3c/master.h>
> > > > +#include <linux/interrupt.h>
> > > > +#include <linux/io.h>
> > > > +#include <linux/module.h>
> > > > +#include <linux/of.h>
> > > > +#include <linux/platform_device.h>
> > > > +
> > > > +#define VERSION_MAJOR(x)		((u32)FIELD_GET(GENMASK(23, 16), (x)))
> > > > +#define VERSION_MINOR(x)		((u32)FIELD_GET(GENMASK(15, 8), (x)))
> > > > +#define VERSION_PATCH(x)		((u32)FIELD_GET(GENMASK(7, 0), (x)))
> > Hi Frank,
> > >
> > > I suppose that needn't (u32), any warnings report without u32?
> > >
> > Here I will actually remove and use the macros from
> > linux/fpga/adi-axi-common.h
> >
> > > > +
> > > > +#define MAX_DEVS			16
> > > > +
> > > > +#define REG_VERSION			0x000
> > to be replaced removed, and ADI_AXI_REG_VERSION used instead.
> > > > +#define REG_ENABLE			0x040
> > > > +#define REG_IRQ_MASK			0x080
> > > > +#define REG_IRQ_PENDING			0x084
> > > > +#define   REG_IRQ_PENDING_CMDR		BIT(5)
> > > > +#define   REG_IRQ_PENDING_IBI		((u32)BIT(6))
> > >
> > > needn't u32
> > In earlier CI runs, I used to get with gcc_x86_64, due to the inversion
> > ~REG_IRQ_PENDING_IBI:
> >
> >   > conversion from 'long unsigned int' to 'unsigned int' changes value
> >   > from '18446744073709551615' to '4294967295' [-Werror=overflow]
> >
> > Couldn't replicate in this patch version, so I will remove.
> >
> > > > +#define   REG_IRQ_PENDING_DAA		BIT(7)
> > >
> > > Add empty line between registers
> > >
> > Ok
> > > > +#define REG_CMD_FIFO			0x0d4
> > > > +#define	  REG_CMD_FIFO_0_IS_CCC		BIT(22)
> > > > +#define   REG_CMD_FIFO_0_BCAST		BIT(21)
> > > > +#define   REG_CMD_FIFO_0_SR		BIT(20)
> > > > +#define   REG_CMD_FIFO_0_LEN(l)		FIELD_PREP(GENMASK(19, 8), (l))
> > > > +#define   REG_CMD_FIFO_0_DEV_ADDR(a)	FIELD_PREP(GENMASK(7, 1), a)
> > > > +#define   REG_CMD_FIFO_0_RNW		BIT(0)
> > > > +#define   REG_CMD_FIFO_1_CCC(id)	FIELD_PREP(GENMASK(7, 0), (id))
> > > > +#define REG_CMD_FIFO_ROOM		0x0c0
> > > > +#define REG_CMDR_FIFO			0x0d8
> > > > +#define   REG_CMDR_FIFO_NO_ERROR	0
> > > > +#define   REG_CMDR_FIFO_CE0_ERROR	1
> > > > +#define   REG_CMDR_FIFO_CE2_ERROR	4
> > > > +#define   REG_CMDR_FIFO_NACK_RESP	6
> > > > +#define   REG_CMDR_FIFO_UDA_ERROR	8
> > > > +#define   REG_CMDR_FIFO_ERROR(x)	FIELD_GET(GENMASK(23, 20), (x))
> > > > +#define   REG_CMDR_FIFO_XFER_BYTES(x)	FIELD_GET(GENMASK(19, 8), (x))
> > >
> > > Sort according bit pos. REG_CMDR_FIFO_XFER_BYTES should before
> > > REG_CMDR_FIFO_ERROR
> > >
> > Ahh true
> > > > +#define REG_SDO_FIFO			0x0dc
> > > > +#define REG_SDO_FIFO_ROOM		0x0c8
> > > > +#define REG_SDI_FIFO			0x0e0
> > > > +#define REG_IBI_FIFO			0x0e4
> > > > +#define REG_FIFO_STATUS			0x0e8
> > > > +#define   REG_FIFO_STATUS_CMDR_EMPTY	BIT(0)
> > > > +#define   REG_FIFO_STATUS_IBI_EMPTY	BIT(1)
> > > > +#define REG_OPS				0x100
> > > > +#define   REG_OPS_SET_SG(x)		FIELD_PREP(GENMASK(6, 5), (x))
> > > > +#define   REG_OPS_PP_SG_MASK		GENMASK(6, 5)
> > >
> > > REG_OPS_SET_SG(x) FIELD_PREP(REG_OPS_PP_SG_MASK, (x))
> > Ack.
> > >
> > > > +#define REG_IBI_CONFIG			0x140
> > > > +#define   REG_IBI_CONFIG_LISTEN		BIT(1)
> > > > +#define   REG_IBI_CONFIG_ENABLE		BIT(0)
> > >
> > > sort it. keep consisent in whole file, from 0->31 or 31->0
> > >
> > Ack, decrementing
> > > > +#define REG_DEV_CHAR			0x180
> > > > +#define   REG_DEV_CHAR_IS_I2C		BIT(0)
> > > > +#define   REG_DEV_CHAR_IS_ATTACHED	BIT(1)
> > > > +#define   REG_DEV_CHAR_BCR_IBI(x)	(((x) & GENMASK(2, 1)) << 1)
> > >
> > > FIELD_PREP()
> > Ok, I will remove the simplification and make it clearer by setting
> >
> >      #define   REG_DEV_CHAR_BCR_IBI(x)       FIELD_PREP(GENMASK(3, 2), (x))
> >
> > Then usage:
> >
> >      #include <linux/i3c/device.h>
> >      ...
> >      bcr_ibi = FIELD_GET(I3C_BCR_IBI_PAYLOAD | I3C_BCR_IBI_REQ_CAP, (i3cdev->info.bcr));
> >      ...REG_DEV_CHAR_BCR_IBI(bcr_ibi)...;
> >
> > >
> > > > +#define   REG_DEV_CHAR_WEN		BIT(8)
> > > > +#define   REG_DEV_CHAR_ADDR(x)		FIELD_PREP(GENMASK(15, 9), (x))
> > > > +
> > > > +enum speed_grade {PP_SG_UNSET, PP_SG_1MHZ, PP_SG_3MHZ, PP_SG_6MHZ, PP_SG_12MHZ};
> > > > +struct adi_i3c_cmd {
> > > > +	u32 cmd0;
> > > > +	u32 cmd1;
> > > > +	u32 tx_len;
> > > > +	const void *tx_buf;
> > > > +	u32 rx_len;
> > > > +	void *rx_buf;
> > > > +	u32 error;
> > > > +};
> > > > +
> > > > +struct adi_i3c_xfer {
> > > > +	struct list_head node;
> > > > +	struct completion comp;
> > > > +	int ret;
> > > > +	unsigned int ncmds;
> > > > +	unsigned int ncmds_comp;
> > > > +	struct adi_i3c_cmd cmds[];
> > > > +};
> > > > +
> > > > +struct adi_i3c_master {
> > > > +	struct i3c_master_controller base;
> > > > +	u32 free_rr_slots;
> > > > +	unsigned int maxdevs;
> > > > +	struct {
> > > > +		unsigned int num_slots;
> > > > +		struct i3c_dev_desc **slots;
> > > > +		spinlock_t lock; /* Protect IBI slot access */
> > > > +	} ibi;
> > > > +	struct {
> > > > +		struct list_head list;
> > > > +		struct adi_i3c_xfer *cur;
> > > > +		spinlock_t lock; /* Protect transfer */
> > > > +	} xferqueue;
> > > > +	void __iomem *regs;
> > > > +	struct clk *clk;
> > > > +	unsigned long i3c_scl_lim;
> > > > +	struct {
> > > > +		u8 addrs[MAX_DEVS];
> > > > +		u8 index;
> > > > +	} daa;
> > > > +};
> > > > +
> > > > +static inline struct adi_i3c_master *to_adi_i3c_master(struct i3c_master_controller *master)
> > > > +{
> > > > +	return container_of(master, struct adi_i3c_master, base);
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_wr_to_tx_fifo(struct adi_i3c_master *master,
> > > > +					 const u8 *bytes, unsigned int nbytes)
> > > > +{
> > > > +	unsigned int n, m;
> > > > +	u32 tmp;
> > > > +
> > > > +	n = readl(master->regs + REG_SDO_FIFO_ROOM);
> > > > +	m = min(n, nbytes);
> > > > +	writesl(master->regs + REG_SDO_FIFO, bytes, m / 4);
> > > > +
> > > > +	if (m & 3) {
> > > > +		memcpy(&tmp, bytes + (m & ~3), m & 3);
> > > > +		writel(tmp, master->regs + REG_SDO_FIFO);
> > > > +	}
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_rd_from_rx_fifo(struct adi_i3c_master *master,
> > > > +					   u8 *bytes, unsigned int nbytes)
> > > > +{
> > > > +	readsl(master->regs + REG_SDI_FIFO, bytes, nbytes / 4);
> > > > +	if (nbytes & 3) {
> > > > +		u32 tmp;
> > > > +
> > > > +		tmp = readl(master->regs + REG_SDI_FIFO);
> > > > +		memcpy(bytes + (nbytes & ~3), &tmp, nbytes & 3);
> > > > +	}
> > > > +}
> > >
> > > can you move above two helper function as inline into drivers/i3c/internals.h
> > >
> > > or create new one drivers/i3c/master/helper.h or other naming.
> > >
> > > Renesas IP have similar above two functions to access FIFO.
> > > https://lore.kernel.org/linux-i3c/174964724485.330045.2181706921272138816.robh@kernel.org/T/#t
> > >
> > Ok, yes that's common because the passes type u8 doesn't ensure
> > multiples of 32 at least. Added to my TODO list.
> > > > +
> > > > +static bool adi_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m,
> > > > +					    const struct i3c_ccc_cmd *cmd)
> > > > +{
> > > > +	if (cmd->ndests > 1)
> > > > +		return false;
> > > > +
> > > > +	switch (cmd->id) {
> > > > +	case I3C_CCC_ENEC(true):
> > > > +	case I3C_CCC_ENEC(false):
> > > > +	case I3C_CCC_DISEC(true):
> > > > +	case I3C_CCC_DISEC(false):
> > > > +	case I3C_CCC_RSTDAA(true):
> > > > +	case I3C_CCC_RSTDAA(false):
> > > > +	case I3C_CCC_ENTDAA:
> > > > +	case I3C_CCC_SETDASA:
> > > > +	case I3C_CCC_SETNEWDA:
> > > > +	case I3C_CCC_GETMWL:
> > > > +	case I3C_CCC_GETMRL:
> > > > +	case I3C_CCC_GETPID:
> > > > +	case I3C_CCC_GETBCR:
> > > > +	case I3C_CCC_GETDCR:
> > > > +	case I3C_CCC_GETSTATUS:
> > > > +	case I3C_CCC_GETHDRCAP:
> > > > +		return true;
> > > > +	default:
> > > > +		break;
> > > > +	}
> > > > +
> > > > +	return false;
> > > > +}
> > > > +
> > > > +static int adi_i3c_master_disable(struct adi_i3c_master *master)
> > > > +{
> > > > +	writel(0, master->regs + REG_IBI_CONFIG);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static struct adi_i3c_xfer *adi_i3c_master_alloc_xfer(struct adi_i3c_master *master,
> > > > +						      unsigned int ncmds)
> > > > +{
> > > > +	struct adi_i3c_xfer *xfer;
> > > > +
> > > > +	xfer = kzalloc(struct_size(xfer, cmds, ncmds), GFP_KERNEL);
> > > > +	if (!xfer)
> > > > +		return NULL;
> > > > +
> > > > +	INIT_LIST_HEAD(&xfer->node);
> > > > +	xfer->ncmds = ncmds;
> > > > +	xfer->ret = -ETIMEDOUT;
> > > > +
> > > > +	return xfer;
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_start_xfer_locked(struct adi_i3c_master *master)
> > > > +{
> > > > +	struct adi_i3c_xfer *xfer = master->xferqueue.cur;
> > > > +	unsigned int i, n, m;
> > > > +
> > > > +	if (!xfer)
> > > > +		return;
> > > > +
> > > > +	for (i = 0; i < xfer->ncmds; i++) {
> > > > +		struct adi_i3c_cmd *cmd = &xfer->cmds[i];
> > > > +
> > > > +		if (!(cmd->cmd0 & REG_CMD_FIFO_0_RNW))
> > > > +			adi_i3c_master_wr_to_tx_fifo(master, cmd->tx_buf, cmd->tx_len);
> > > > +	}
> > > > +
> > > > +	n = readl(master->regs + REG_CMD_FIFO_ROOM);
> > > > +	for (i = 0; i < xfer->ncmds; i++) {
> > > > +		struct adi_i3c_cmd *cmd = &xfer->cmds[i];
> > > > +
> > > > +		m = cmd->cmd0 & REG_CMD_FIFO_0_IS_CCC ? 2 : 1;
> > > > +		if (m > n)
> > > > +			break;
> > > > +		writel(cmd->cmd0, master->regs + REG_CMD_FIFO);
> > > > +		if (cmd->cmd0 & REG_CMD_FIFO_0_IS_CCC)
> > > > +			writel(cmd->cmd1, master->regs + REG_CMD_FIFO);
> > > > +		n -= m;
> > > > +	}
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_end_xfer_locked(struct adi_i3c_master *master,
> > > > +					   u32 pending)
> > > > +{
> > > > +	struct adi_i3c_xfer *xfer = master->xferqueue.cur;
> > > > +	int i, ret = 0;
> > > > +	u32 status0;
> > > > +
> > > > +	if (!xfer)
> > > > +		return;
> > > > +
> > > > +	for (status0 = readl(master->regs + REG_FIFO_STATUS);
> > > > +	     !(status0 & REG_FIFO_STATUS_CMDR_EMPTY);
> > > > +	     status0 = readl(master->regs + REG_FIFO_STATUS)) {
> > >
> > > how about
> > >
> > > while (!(readl(master->regs + REG_FIFO_STATUS) & REG_FIFO_STATUS_CMDR_EMPTY))) {
> > >
> > Ack, thanks!
> > Same for 'REG_FIFO_STATUS_IBI_EMPTY for'.
> > > > +		struct adi_i3c_cmd *cmd;
> > > > +		u32 cmdr, rx_len;
> > > > +
> > > > +		cmdr = readl(master->regs + REG_CMDR_FIFO);
> > > > +
> > > > +		cmd = &xfer->cmds[xfer->ncmds_comp++];
> > > > +		if (cmd->cmd0 & REG_CMD_FIFO_0_RNW) {
> > > > +			rx_len = min_t(u32, REG_CMDR_FIFO_XFER_BYTES(cmdr), cmd->rx_len);
> > > > +			adi_i3c_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len);
> > > > +		}
> > > > +		cmd->error = REG_CMDR_FIFO_ERROR(cmdr);
> > > > +	}
> > > > +
> > > > +	for (i = 0; i < xfer->ncmds; i++) {
> > >
> > > I think you iterate to ncmds_comp?
> > >
> > You are correct thanks! Each command yields a command receipt interrupt.
> > > > +		switch (xfer->cmds[i].error) {
> > > > +		case REG_CMDR_FIFO_NO_ERROR:
> > > > +			break;
> > > > +
> > > > +		case REG_CMDR_FIFO_CE0_ERROR:
> > > > +		case REG_CMDR_FIFO_CE2_ERROR:
> > > > +		case REG_CMDR_FIFO_NACK_RESP:
> > > > +		case REG_CMDR_FIFO_UDA_ERROR:
> > > > +			ret = -EIO;
> > > > +			break;
> > > > +
> > > > +		default:
> > > > +			ret = -EINVAL;
> > > > +			break;
> > > > +		}
> > > > +	}
> > > > +
> > > > +	xfer->ret = ret;
> > > > +
> > > > +	if (xfer->ncmds_comp != xfer->ncmds)
> > > > +		return;
> > > > +
> > > > +	complete(&xfer->comp);
> > > > +
> > > > +	xfer = list_first_entry_or_null(&master->xferqueue.list,
> > > > +					struct adi_i3c_xfer, node);
> > > > +	if (xfer)
> > > > +		list_del_init(&xfer->node);
> > > > +
> > > > +	master->xferqueue.cur = xfer;
> > > > +	adi_i3c_master_start_xfer_locked(master);
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_queue_xfer(struct adi_i3c_master *master,
> > > > +				      struct adi_i3c_xfer *xfer)
> > > > +{
> > > > +	init_completion(&xfer->comp);
> > > > +	guard(spinlock_irqsave)(&master->xferqueue.lock);
> > > > +	if (master->xferqueue.cur) {
> > > > +		list_add_tail(&xfer->node, &master->xferqueue.list);
> > > > +	} else {
> > > > +		master->xferqueue.cur = xfer;
> > > > +		adi_i3c_master_start_xfer_locked(master);
> > > > +	}
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_unqueue_xfer(struct adi_i3c_master *master,
> > > > +					struct adi_i3c_xfer *xfer)
> > > > +{
> > > > +	guard(spinlock_irqsave)(&master->xferqueue.lock);
> > > > +	if (master->xferqueue.cur == xfer)
> > > > +		master->xferqueue.cur = NULL;
> > > > +	else
> > > > +		list_del_init(&xfer->node);
> > > > +
> > > > +	writel(0x01, master->regs + REG_ENABLE);
> > > > +	writel(0x00, master->regs + REG_ENABLE);
> > > > +	writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
> > > > +}
> > > > +
> > > > +static enum i3c_error_code adi_i3c_cmd_get_err(struct adi_i3c_cmd *cmd)
> > > > +{
> > > > +	switch (cmd->error) {
> > > > +	case REG_CMDR_FIFO_CE0_ERROR:
> > > > +		return I3C_ERROR_M0;
> > > > +
> > > > +	case REG_CMDR_FIFO_CE2_ERROR:
> > > > +	case REG_CMDR_FIFO_NACK_RESP:
> > > > +		return I3C_ERROR_M2;
> > > > +
> > > > +	default:
> > > > +		break;
> > > > +	}
> > > > +
> > > > +	return I3C_ERROR_UNKNOWN;
> > > > +}
> > > > +
> > > > +static int adi_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
> > > > +				       struct i3c_ccc_cmd *cmd)
> > > > +{
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +	struct adi_i3c_xfer *xfer;
> > > > +	struct adi_i3c_cmd *ccmd;
> > > > +
> > > > +	xfer = adi_i3c_master_alloc_xfer(master, 1);
> > > > +	if (!xfer)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	ccmd = xfer->cmds;
> > > > +	ccmd->cmd1 = REG_CMD_FIFO_1_CCC(cmd->id);
> > > > +	ccmd->cmd0 = REG_CMD_FIFO_0_IS_CCC |
> > > > +		     REG_CMD_FIFO_0_LEN(cmd->dests[0].payload.len);
> > > > +
> > > > +	if (cmd->id & I3C_CCC_DIRECT)
> > > > +		ccmd->cmd0 |= REG_CMD_FIFO_0_DEV_ADDR(cmd->dests[0].addr);
> > > > +
> > > > +	if (cmd->rnw) {
> > > > +		ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> > > > +		ccmd->rx_buf = cmd->dests[0].payload.data;
> > > > +		ccmd->rx_len = cmd->dests[0].payload.len;
> > > > +	} else {
> > > > +		ccmd->tx_buf = cmd->dests[0].payload.data;
> > > > +		ccmd->tx_len = cmd->dests[0].payload.len;
> > > > +	}
> > > > +
> > > > +	adi_i3c_master_queue_xfer(master, xfer);
> > > > +	if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000)))
> > > > +		adi_i3c_master_unqueue_xfer(master, xfer);
> > > > +
> > > > +	cmd->err = adi_i3c_cmd_get_err(&xfer->cmds[0]);
> > > > +	kfree(xfer);
> > >
> > > you can use
> > > struct adi_i3c_xfer *xfer __free(kfree) = NULL;
> > >
> > >  to simple code.
> > >
> > Ah cool, will use in all xfer instances.
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static int adi_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
> > > > +				     struct i3c_priv_xfer *xfers,
> > > > +				     int nxfers)
> > > > +{
> > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +	struct adi_i3c_xfer *xfer;
> > > > +	int i, ret;
> > > > +
> > > > +	if (!nxfers)
> > > > +		return 0;
> > >
> > > add an empty line.
> > >
> > Ack.
> > > > +	xfer = adi_i3c_master_alloc_xfer(master, nxfers);
> > > > +	if (!xfer)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	for (i = 0; i < nxfers; i++) {
> > > > +		struct adi_i3c_cmd *ccmd = &xfer->cmds[i];
> > > > +
> > > > +		ccmd->cmd0 = REG_CMD_FIFO_0_DEV_ADDR(dev->info.dyn_addr);
> > > > +
> > > > +		if (xfers[i].rnw) {
> > > > +			ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> > > > +			ccmd->rx_buf = xfers[i].data.in;
> > > > +			ccmd->rx_len = xfers[i].len;
> > > > +		} else {
> > > > +			ccmd->tx_buf = xfers[i].data.out;
> > > > +			ccmd->tx_len = xfers[i].len;
> > > > +		}
> > > > +
> > > > +		ccmd->cmd0 |= REG_CMD_FIFO_0_LEN(xfers[i].len);
> > > > +
> > > > +		if (i < nxfers - 1)
> > > > +			ccmd->cmd0 |= REG_CMD_FIFO_0_SR;
> > > > +
> > > > +		if (!i)
> > > > +			ccmd->cmd0 |= REG_CMD_FIFO_0_BCAST;
> > > > +	}
> > > > +
> > > > +	adi_i3c_master_queue_xfer(master, xfer);
> > > > +	if (!wait_for_completion_timeout(&xfer->comp,
> > > > +					 msecs_to_jiffies(1000)))
> > > > +		adi_i3c_master_unqueue_xfer(master, xfer);
> > > > +
> > > > +	ret = xfer->ret;
> > > > +
> > > > +	for (i = 0; i < nxfers; i++)
> > > > +		xfers[i].err = adi_i3c_cmd_get_err(&xfer->cmds[i]);
> > > > +
> > > > +	kfree(xfer);
> > > > +
> > > > +	return ret;
> > > > +}
> > > > +
> > > > +struct adi_i3c_i2c_dev_data {
> > > > +	u16 id;
> > > > +	s16 ibi;
> > > > +	struct i3c_generic_ibi_pool *ibi_pool;
> > > > +};
> > > > +
> > > > +static int adi_i3c_master_get_rr_slot(struct adi_i3c_master *master,
> > > > +				      u8 dyn_addr)
> > > > +{
> > > > +	if (!master->free_rr_slots)
> > > > +		return -ENOSPC;
> > > > +
> > > > +	return ffs(master->free_rr_slots) - 1;
> > > > +}
> > > > +
> > > > +static int adi_i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 dyn_addr)
> > > > +{
> > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +	u8 addr;
> > > > +
> > > > +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> > > > +
> > > > +	writel(REG_DEV_CHAR_ADDR(dyn_addr), master->regs + REG_DEV_CHAR);
> > > > +	writel((readl(master->regs + REG_DEV_CHAR) &
> > > > +		~REG_DEV_CHAR_IS_ATTACHED) | REG_DEV_CHAR_WEN,
> > > > +	       master->regs + REG_DEV_CHAR);
> > > > +
> > > > +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > > > +	writel(readl(master->regs + REG_DEV_CHAR) |
> > > > +	       REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> > > > +	       master->regs + REG_DEV_CHAR);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static int adi_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev)
> > > > +{
> > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +	struct adi_i3c_i2c_dev_data *data;
> > > > +	int slot;
> > > > +	u8 addr;
> > > > +
> > > > +	data = kzalloc(sizeof(*data), GFP_KERNEL);
> > > > +	if (!data)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	slot = adi_i3c_master_get_rr_slot(master, dev->info.dyn_addr);
> > > > +	if (slot < 0) {
> > > > +		kfree(data);
> > > > +		return slot;
> > > > +	}
> > > > +
> > > > +	data->ibi = -1;
> > >
> > > why -1 here?
> > >
> > Doesn't mean much, just stores the ibi slot occupied by the device.
> > NULL would be position 0 of the array.
> > It can be left uninitialized and not cleaned-up when removed.
> > > > +	data->id = slot;
> > > > +	i3c_dev_set_master_data(dev, data);
> > > > +	master->free_rr_slots &= ~BIT(slot);
> > > > +
> > > > +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> > > > +
> > > > +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > > > +	writel(readl(master->regs + REG_DEV_CHAR) |
> > > > +	       REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> > > > +	       master->regs + REG_DEV_CHAR);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_sync_dev_char(struct i3c_master_controller *m)
> > > > +{
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +	struct i3c_dev_desc *i3cdev;
> > > > +	u8 addr;
> > > > +
> > > > +	i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
> > > > +		addr = i3cdev->info.dyn_addr ?
> > > > +		       i3cdev->info.dyn_addr : i3cdev->info.static_addr;
> > > > +		writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > > > +		writel(readl(master->regs + REG_DEV_CHAR) |
> > > > +		       REG_DEV_CHAR_BCR_IBI(i3cdev->info.bcr) | REG_DEV_CHAR_WEN,
> > > > +		       master->regs + REG_DEV_CHAR);
> > > > +	}
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev)
> > > > +{
> > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> > > > +	u8 addr;
> > > > +
> > > > +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> > > > +
> > > > +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > > > +	writel((readl(master->regs + REG_DEV_CHAR) &
> > > > +		~REG_DEV_CHAR_IS_ATTACHED) | REG_DEV_CHAR_WEN,
> > > > +	       master->regs + REG_DEV_CHAR);
> > > > +
> > > > +	i3c_dev_set_master_data(dev, NULL);
> > > > +	master->free_rr_slots |= BIT(data->id);
> > > > +	kfree(data);
> > > > +}
> > > > +
> > > > +static int adi_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev)
> > > > +{
> > > > +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +	struct adi_i3c_i2c_dev_data *data;
> > > > +	int slot;
> > > > +
> > > > +	slot = adi_i3c_master_get_rr_slot(master, 0);
> > > > +	if (slot < 0)
> > > > +		return slot;
> > > > +
> > > > +	data = kzalloc(sizeof(*data), GFP_KERNEL);
> > > > +	if (!data)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	data->id = slot;
> > > > +	master->free_rr_slots &= ~BIT(slot);
> > > > +	i2c_dev_set_master_data(dev, data);
> > > > +
> > > > +	writel(REG_DEV_CHAR_ADDR(dev->addr) |
> > > > +	       REG_DEV_CHAR_IS_I2C | REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> > > > +	       master->regs + REG_DEV_CHAR);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_detach_i2c_dev(struct i2c_dev_desc *dev)
> > > > +{
> > > > +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +	struct adi_i3c_i2c_dev_data *data = i2c_dev_get_master_data(dev);
> > > > +
> > > > +	writel(REG_DEV_CHAR_ADDR(dev->addr) |
> > > > +	       REG_DEV_CHAR_IS_I2C | REG_DEV_CHAR_WEN,
> > > > +	       master->regs + REG_DEV_CHAR);
> > > > +
> > > > +	i2c_dev_set_master_data(dev, NULL);
> > > > +	master->free_rr_slots |= BIT(data->id);
> > > > +	kfree(data);
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_bus_cleanup(struct i3c_master_controller *m)
> > > > +{
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +
> > > > +	adi_i3c_master_disable(master);
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_upd_i3c_scl_lim(struct adi_i3c_master *master)
> > > > +{
> > > > +	struct i3c_master_controller *m = &master->base;
> > > > +	struct i3c_bus *bus = i3c_master_get_bus(m);
> > > > +	u8 i3c_scl_lim = 0;
> > > > +	struct i3c_dev_desc *dev;
> > > > +	u8 pp_sg;
> > > > +
> > > > +	i3c_bus_for_each_i3cdev(bus, dev) {
> > > > +		u8 max_fscl;
> > > > +
> > > > +		max_fscl = max(I3C_CCC_MAX_SDR_FSCL(dev->info.max_read_ds),
> > > > +			       I3C_CCC_MAX_SDR_FSCL(dev->info.max_write_ds));
> > > > +
> > > > +		switch (max_fscl) {
> > > > +		case I3C_SDR1_FSCL_8MHZ:
> > > > +			max_fscl = PP_SG_6MHZ;
> > > > +			break;
> > > > +		case I3C_SDR2_FSCL_6MHZ:
> > > > +			max_fscl = PP_SG_3MHZ;
> > > > +			break;
> > > > +		case I3C_SDR3_FSCL_4MHZ:
> > > > +			max_fscl = PP_SG_3MHZ;
> > > > +			break;
> > > > +		case I3C_SDR4_FSCL_2MHZ:
> > > > +			max_fscl = PP_SG_1MHZ;
> > > > +			break;
> > > > +		case I3C_SDR0_FSCL_MAX:
> > > > +		default:
> > > > +			max_fscl = PP_SG_12MHZ;
> > > > +			break;
> > > > +		}
> > > > +
> > > > +		if (max_fscl &&
> > > > +		    (i3c_scl_lim > max_fscl || !i3c_scl_lim))
> > > > +			i3c_scl_lim = max_fscl;
> > > > +	}
> > > > +
> > > > +	if (!i3c_scl_lim)
> > > > +		return;
> > > > +
> > > > +	master->i3c_scl_lim = i3c_scl_lim - 1;
> > > > +
> > > > +	pp_sg = readl(master->regs + REG_OPS) & ~REG_OPS_PP_SG_MASK;
> > > > +	pp_sg |= REG_OPS_SET_SG(master->i3c_scl_lim);
> > > > +
> > > > +	writel(pp_sg, master->regs + REG_OPS);
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_get_features(struct adi_i3c_master *master,
> > > > +					unsigned int slot,
> > > > +					struct i3c_device_info *info)
> > > > +{
> > > > +	memset(info, 0, sizeof(*info));
> > > > +
> > > > +	info->dyn_addr = 0x31;
> > >
> > > master address is hardcode?
> > >
> > Yes.
> > There is/was no register to read it from because the controller does not
> > support secondary mastership or changing role, so this address is not
> > used for anything (as is). Since it is odd, I will move add these
> > hardcoded values to the register map (read-only), still won't do
> > anything.
> 
> need comments for 0x31
> 
Ack, will likely become a register read but will also add a comment.
> > > > +	info->dcr = 0x00;
> > > > +	info->bcr = 0x40;
> > > > +	info->pid = 0;
> > > > +}
> > > > +
> > > > +static int adi_i3c_master_do_daa(struct i3c_master_controller *m)
> > > > +{
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +	u32 irq_mask;
> > > > +	int ret, addr;
> > > > +
> > > > +	addr = 0x8;
> > > > +	for (u8 i = 0; i < MAX_DEVS; i++) {
> > > > +		addr = i3c_master_get_free_addr(m, addr);
> > > > +		if (addr < 0)
> > > > +			return addr;
> > >
> > > assume MAX_DEVS 128, if only get 4 address, suppose you should enumerate
> > > for such 4 devices, instead of return error and give up do_daa.
> > >
> > The IP Core supports just 16 devices (max # on the bus), so it should
> > not run-out of addresses, but I understand the assignment. As is, the IP
> > core has 16 slots and will only terminate the DAA from its side if it
> > run out of slots (if no device request an address (NACK bit), the DAA is
> > also terminated). Not writing a DA during the DA IRQ will just hang the
> > bus.
> >
> > For the suggestion to work I need to re-read the spec and rework the RTL
> > to allow this early termination. However, I am currently a little
> > opposed of changing the RTL beyond basic read-only registers
> > modifications.
> > > > +		master->daa.addrs[i] = addr;
> > > > +	}
> > > > +
> > > > +	irq_mask = readl(master->regs + REG_IRQ_MASK);
> > > > +	writel(irq_mask | REG_IRQ_PENDING_DAA,
> > > > +	       master->regs + REG_IRQ_MASK);
> > > > +
> > > > +	master->daa.index = 0;
> > > > +	ret = i3c_master_entdaa_locked(&master->base);
> > > > +
> > > > +	writel(irq_mask, master->regs + REG_IRQ_MASK);
> > > > +
> > > > +	/* DAA always finishes with CE2_ERROR or NACK_RESP */
> > > > +	if (ret && ret != I3C_ERROR_M2)
> > > > +		return ret;
> > > > +
> > > > +	/* Add I3C devices discovered */
> > > > +	for (u8 i = 0; i < master->daa.index; i++)
> > > > +		i3c_master_add_i3c_dev_locked(m, master->daa.addrs[i]);
> > > > +	/* Sync retrieved devs info with the IP */
> > > > +	adi_i3c_master_sync_dev_char(m);
> > > > +
> > > > +	i3c_master_defslvs_locked(&master->base);
> > > > +
> > > > +	adi_i3c_master_upd_i3c_scl_lim(master);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static int adi_i3c_master_bus_init(struct i3c_master_controller *m)
> > > > +{
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +	struct i3c_device_info info = { };
> > > > +	int ret;
> > > > +
> > > > +	ret = i3c_master_get_free_addr(m, 0);
> > > > +	if (ret < 0)
> > > > +		return ret;
> > > > +
> > > > +	adi_i3c_master_get_features(master, 0, &info);
> > > > +	ret = i3c_master_set_info(&master->base, &info);
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	writel(REG_IBI_CONFIG_LISTEN,
> > > > +	       master->regs + REG_IBI_CONFIG);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_handle_ibi(struct adi_i3c_master *master,
> > > > +				      u32 ibi)
> > > > +{
> > > > +	struct adi_i3c_i2c_dev_data *data;
> > > > +	struct i3c_ibi_slot *slot;
> > > > +	struct i3c_dev_desc *dev;
> > > > +	u8 da, id;
> > > > +	u8 *mdb;
> > > > +
> > > > +	da = (ibi >> 17) & GENMASK(6, 0);
> > > > +	for (id = 0; id < master->ibi.num_slots; id++) {
> > > > +		if (master->ibi.slots[id] &&
> > > > +		    master->ibi.slots[id]->info.dyn_addr == da)
> > > > +			break;
> > > > +	}
> > > > +
> > > > +	if (id == master->ibi.num_slots)
> > > > +		return;
> > > > +
> > > > +	dev = master->ibi.slots[id];
> > > > +	guard(spinlock)(&master->ibi.lock);
> > > > +
> > > > +	data = i3c_dev_get_master_data(dev);
> > > > +	slot = i3c_generic_ibi_get_free_slot(data->ibi_pool);
> > > > +	if (!slot)
> > > > +		return;
> > > > +
> > > > +	mdb = slot->data;
> > > > +	mdb[0] = (ibi >> 8) & GENMASK(7, 0);
> > > > +
> > > > +	slot->len = 1;
> > > > +	i3c_master_queue_ibi(dev, slot);
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_demux_ibis(struct adi_i3c_master *master)
> > > > +{
> > > > +	u32 status0;
> > > > +
> > > > +	for (status0 = readl(master->regs + REG_FIFO_STATUS);
> > > > +	     !(status0 & REG_FIFO_STATUS_IBI_EMPTY);
> > > > +	     status0 = readl(master->regs + REG_FIFO_STATUS)) {
> > >
> > > use while()
> > >
> > Ack.
> > > > +		u32 ibi = readl(master->regs + REG_IBI_FIFO);
> > > > +
> > > > +		adi_i3c_master_handle_ibi(master, ibi);
> > > > +	}
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_handle_da_req(struct adi_i3c_master *master)
> > > > +{
> > > > +	u8 payload0[8];
> > > > +	u32 addr;
> > > > +
> > > > +	/* Clear device characteristics */
> > > > +	adi_i3c_master_rd_from_rx_fifo(master, payload0, 6);
> > > > +	addr = master->daa.addrs[master->daa.index++];
> > > > +	addr = (addr << 1) | (parity8(addr) ? 0 : 1);
> > > > +
> > > > +	writel(addr, master->regs + REG_SDO_FIFO);
> > > > +}
> > > > +
> > > > +static irqreturn_t adi_i3c_master_irq(int irq, void *data)
> > > > +{
> > > > +	struct adi_i3c_master *master = data;
> > > > +	u32 pending;
> > > > +
> > > > +	pending = readl(master->regs + REG_IRQ_PENDING);
> > > > +	writel(pending, master->regs + REG_IRQ_PENDING);
> > >
> > > Add an empty line here
> > >
> > Ack.
> > > > +	if (pending & REG_IRQ_PENDING_CMDR) {
> > > > +		spin_lock(&master->xferqueue.lock);
> > > > +		adi_i3c_master_end_xfer_locked(master, pending);
> > > > +		spin_unlock(&master->xferqueue.lock);
> > >
> > > you use guard, keep consisent, use guard or scoped_guard here
> > >
> > scoped_guard
> > > > +	}
> > > > +	if (pending & REG_IRQ_PENDING_IBI)
> > > > +		adi_i3c_master_demux_ibis(master);
> > > > +	if (pending & REG_IRQ_PENDING_DAA)
> > > > +		adi_i3c_master_handle_da_req(master);
> > > > +
> > > > +	return IRQ_HANDLED;
> > > > +}
> > > > +
> > > > +static int adi_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
> > > > +				    struct i2c_msg *xfers,
> > > > +				    int nxfers)
> > > > +{
> > > > +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +	struct adi_i3c_xfer *xfer;
> > > > +	int i, ret;
> > > > +
> > > > +	if (!nxfers)
> > > > +		return 0;
> > > > +	for (i = 0; i < nxfers; i++) {
> > > > +		if (xfers[i].flags & I2C_M_TEN)
> > > > +			return -EOPNOTSUPP;
> > > > +	}
> > > > +	xfer = adi_i3c_master_alloc_xfer(master, nxfers);
> > > > +	if (!xfer)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	for (i = 0; i < nxfers; i++) {
> > > > +		struct adi_i3c_cmd *ccmd = &xfer->cmds[i];
> > > > +
> > > > +		ccmd->cmd0 = REG_CMD_FIFO_0_DEV_ADDR(xfers[i].addr);
> > > > +
> > > > +		if (xfers[i].flags & I2C_M_RD) {
> > > > +			ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> > > > +			ccmd->rx_buf = xfers[i].buf;
> > > > +			ccmd->rx_len = xfers[i].len;
> > > > +		} else {
> > > > +			ccmd->tx_buf = xfers[i].buf;
> > > > +			ccmd->tx_len = xfers[i].len;
> > > > +		}
> > > > +
> > > > +		ccmd->cmd0 |= REG_CMD_FIFO_0_LEN(xfers[i].len);
> > >
> > > Just curiouse
> > > What's happen if IBI happen (address arbitrate) at first command?
> > >
> > The controller does the state machine for the IBI and yield a IBIR
> > interrupt, then proceeds with the xfer, yielding the CMDR for each CMD.
> 
> Does controller retry current command again after handle IBI?
> 
> Frank Li
> 
Yes, it resumes/retries the current command. After the IBI has been
resolved, the controller issues either a stop, to return to the Bus
Free/Idle condition if the command stack is empty, or repeated start,
to proceed with current command.

Looking into the spec i3c basic 1.1.1, fig 169, this controller never
Disable Target Interrupts after a NACK followed by a repeated start. The
driver would issue a command to do so in other opportunity.

Best regards,
Jorge
> > > > +	}
> > > > +
> > > > +	adi_i3c_master_queue_xfer(master, xfer);
> > > > +	if (!wait_for_completion_timeout(&xfer->comp,
> > > > +					 m->i2c.timeout))
> > > > +		adi_i3c_master_unqueue_xfer(master, xfer);
> > > > +
> > > > +	ret = xfer->ret;
> > > > +	kfree(xfer);
> > > > +	return ret;
> > > > +}
> > > > +
> > > > +static int adi_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
> > > > +{
> > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +	struct i3c_dev_desc *i3cdev;
> > > > +	u32 enabled = 0;
> > > > +	int ret;
> > > > +
> > > > +	ret = i3c_master_disec_locked(m, dev->info.dyn_addr,
> > > > +				      I3C_CCC_EVENT_SIR);
> > > > +
> > > > +	i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
> > > > +		if (dev != i3cdev && i3cdev->ibi)
> > > > +			enabled |= i3cdev->ibi->enabled;
> > > > +	}
> > > > +	if (!enabled) {
> > > > +		writel(REG_IBI_CONFIG_LISTEN,
> > > > +		       master->regs + REG_IBI_CONFIG);
> > > > +		writel(readl(master->regs + REG_IRQ_MASK) & ~REG_IRQ_PENDING_IBI,
> > > > +		       master->regs + REG_IRQ_MASK);
> > > > +	}
> > > > +
> > > > +	return ret;
> > > > +}
> > > > +
> > > > +static int adi_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
> > > > +{
> > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +
> > > > +	writel(REG_IBI_CONFIG_LISTEN | REG_IBI_CONFIG_ENABLE,
> > > > +	       master->regs + REG_IBI_CONFIG);
> > > > +
> > > > +	writel(readl(master->regs + REG_IRQ_MASK) | REG_IRQ_PENDING_IBI,
> > > > +	       master->regs + REG_IRQ_MASK);
> > > > +
> > > > +	return i3c_master_enec_locked(m, dev->info.dyn_addr,
> > > > +				      I3C_CCC_EVENT_SIR);
> > > > +}
> > > > +
> > > > +static int adi_i3c_master_request_ibi(struct i3c_dev_desc *dev,
> > > > +				      const struct i3c_ibi_setup *req)
> > > > +{
> > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +	struct adi_i3c_i2c_dev_data *data;
> > > > +	unsigned long flags;
> > > > +	unsigned int i;
> > > > +
> > > > +	data = i3c_dev_get_master_data(dev);
> > > > +	data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req);
> > > > +	if (IS_ERR(data->ibi_pool))
> > > > +		return PTR_ERR(data->ibi_pool);
> > > > +
> > > > +	spin_lock_irqsave(&master->ibi.lock, flags);
> > > > +	for (i = 0; i < master->ibi.num_slots; i++) {
> > > > +		if (!master->ibi.slots[i]) {
> > > > +			data->ibi = i;
> > > > +			master->ibi.slots[i] = dev;
> > > > +			break;
> > > > +		}
> > > > +	}
> > > > +	spin_unlock_irqrestore(&master->ibi.lock, flags);
> > >
> > > same here, use scope_guard to keep consistent.
> > >
> > Ack.
> > > > +
> > > > +	if (i < master->ibi.num_slots)
> > > > +		return 0;
> > > > +
> > > > +	i3c_generic_ibi_free_pool(data->ibi_pool);
> > > > +	data->ibi_pool = NULL;
> > > > +
> > > > +	return -ENOSPC;
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_free_ibi(struct i3c_dev_desc *dev)
> > > > +{
> > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> > > > +	unsigned long flags;
> > > > +
> > > > +	spin_lock_irqsave(&master->ibi.lock, flags);
> > > > +	master->ibi.slots[data->ibi] = NULL;
> > > > +	data->ibi = -1;
> > > > +	spin_unlock_irqrestore(&master->ibi.lock, flags);
> > > > +
> > > > +	i3c_generic_ibi_free_pool(data->ibi_pool);
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev,
> > > > +					    struct i3c_ibi_slot *slot)
> > > > +{
> > > > +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> > > > +
> > > > +	i3c_generic_ibi_recycle_slot(data->ibi_pool, slot);
> > > > +}
> > > > +
> > > > +static const struct i3c_master_controller_ops adi_i3c_master_ops = {
> > > > +	.bus_init = adi_i3c_master_bus_init,
> > > > +	.bus_cleanup = adi_i3c_master_bus_cleanup,
> > > > +	.attach_i3c_dev = adi_i3c_master_attach_i3c_dev,
> > > > +	.reattach_i3c_dev = adi_i3c_master_reattach_i3c_dev,
> > > > +	.detach_i3c_dev = adi_i3c_master_detach_i3c_dev,
> > > > +	.attach_i2c_dev = adi_i3c_master_attach_i2c_dev,
> > > > +	.detach_i2c_dev = adi_i3c_master_detach_i2c_dev,
> > > > +	.do_daa = adi_i3c_master_do_daa,
> > > > +	.supports_ccc_cmd = adi_i3c_master_supports_ccc_cmd,
> > > > +	.send_ccc_cmd = adi_i3c_master_send_ccc_cmd,
> > > > +	.priv_xfers = adi_i3c_master_priv_xfers,
> > > > +	.i2c_xfers = adi_i3c_master_i2c_xfers,
> > > > +	.request_ibi = adi_i3c_master_request_ibi,
> > > > +	.enable_ibi = adi_i3c_master_enable_ibi,
> > > > +	.disable_ibi = adi_i3c_master_disable_ibi,
> > > > +	.free_ibi = adi_i3c_master_free_ibi,
> > > > +	.recycle_ibi_slot = adi_i3c_master_recycle_ibi_slot,
> > > > +};
> > > > +
> > > > +static const struct of_device_id adi_i3c_master_of_match[] = {
> > > > +	{ .compatible = "adi,i3c-master" },
> > > > +	{}
> > > > +};
> > > > +
> > > > +static int adi_i3c_master_probe(struct platform_device *pdev)
> > > > +{
> > > > +	struct adi_i3c_master *master;
> > > > +	unsigned int version;
> > > > +	int ret, irq;
> > > > +
> > > > +	master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL);
> > > > +	if (!master)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	master->regs = devm_platform_ioremap_resource(pdev, 0);
> > > > +	if (IS_ERR(master->regs))
> > > > +		return PTR_ERR(master->regs);
> > > > +
> > > > +	master->clk = devm_clk_get_enabled(&pdev->dev, "axi");
> > > > +	if (IS_ERR(master->clk))
> > > > +		return PTR_ERR(master->clk);
> > >
> > > you have optional clock i3c, not use it in driver?
> > >
> > It is not used in the driver indeed, it describes how the IP Core is
> > compiled and wired, but doesn't affect the logic in the driver itself.
> > The RTL logic is always driven by a 100MHz clock, and the source is
> > either axi or i3c, if the latter present.
> 
> can you use devm_clk_bulk_get_all_enabled() ?
> 
I believe so, I would keep the required assertion like this then:

	ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &clk);
	if (ret < 0)
		return dev_err_probe(&pdev->dev, ret,
				     "Failed to get clocks\n");
	while (ret--)
		if (clk[ret].id && !strcmp(clk[ret].id, "axi"))
			break;
	if (ret == -1)
		return dev_err_probe(&pdev->dev, -ENODEV,
				     "Failed to get clock axi\n");

Best regards,
Jorge

> > > > +
> > > > +	irq = platform_get_irq(pdev, 0);
> > > > +	if (irq < 0)
> > > > +		return irq;
> > > > +
> > > > +	version = readl(master->regs + REG_VERSION);
> > > > +	if (VERSION_MAJOR(version) != 0) {
> > > > +		dev_err(&pdev->dev, "Unsupported IP version %u.%u.%c\n",
> > > > +			VERSION_MAJOR(version),
> > > > +			VERSION_MINOR(version),
> > > > +			VERSION_PATCH(version));
> > > > +		return -EINVAL;
> > >
> > > return dev_err_probe();
> > Ack.
> > >
> > > Frank
> > Best regards,
> > Jorge
> > > > +	}
> > > > +
> > > > +	writel(0x00, master->regs + REG_ENABLE);
> > > > +	writel(0x00, master->regs + REG_IRQ_MASK);
> > > > +
> > > > +	ret = devm_request_irq(&pdev->dev, irq, adi_i3c_master_irq, 0,
> > > > +			       dev_name(&pdev->dev), master);
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	platform_set_drvdata(pdev, master);
> > > > +
> > > > +	master->maxdevs = MAX_DEVS;
> > > > +	master->free_rr_slots = GENMASK(master->maxdevs, 1);
> > > > +
> > > > +	writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
> > > > +
> > > > +	spin_lock_init(&master->ibi.lock);
> > > > +	master->ibi.num_slots = 15;
> > > > +	master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots,
> > > > +					 sizeof(*master->ibi.slots),
> > > > +					 GFP_KERNEL);
> > > > +	if (!master->ibi.slots)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	return i3c_master_register(&master->base, &pdev->dev,
> > > > +				   &adi_i3c_master_ops, false);
> > > > +}
> > > > +
> > > > +static void adi_i3c_master_remove(struct platform_device *pdev)
> > > > +{
> > > > +	struct adi_i3c_master *master = platform_get_drvdata(pdev);
> > > > +
> > > > +	writel(0xff, master->regs + REG_IRQ_PENDING);
> > > > +	writel(0x00, master->regs + REG_IRQ_MASK);
> > > > +	writel(0x01, master->regs + REG_ENABLE);
> > > > +
> > > > +	i3c_master_unregister(&master->base);
> > > > +}
> > > > +
> > > > +static struct platform_driver adi_i3c_master = {
> > > > +	.probe = adi_i3c_master_probe,
> > > > +	.remove = adi_i3c_master_remove,
> > > > +	.driver = {
> > > > +		.name = "adi-i3c-master",
> > > > +		.of_match_table = adi_i3c_master_of_match,
> > > > +	},
> > > > +};
> > > > +module_platform_driver(adi_i3c_master);
> > > > +
> > > > +MODULE_AUTHOR("Jorge Marques <jorge.marques@analog.com>");
> > > > +MODULE_DESCRIPTION("Analog Devices I3C master driver");
> > > > +MODULE_LICENSE("GPL");
> > > >
> > > > --
> > > > 2.49.0
> > > >

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP
  2025-06-19 10:32         ` Jorge Marques
@ 2025-06-19 15:10           ` Frank Li
  0 siblings, 0 replies; 14+ messages in thread
From: Frank Li @ 2025-06-19 15:10 UTC (permalink / raw)
  To: Jorge Marques
  Cc: Jorge Marques, Alexandre Belloni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-i3c, devicetree,
	linux-kernel

On Thu, Jun 19, 2025 at 12:32:33PM +0200, Jorge Marques wrote:
> On Wed, Jun 18, 2025 at 10:18:48PM -0400, Frank Li wrote:
> > On Thu, Jun 19, 2025 at 12:17:26AM +0200, Jorge Marques wrote:
> > > On Wed, Jun 18, 2025 at 03:40:58PM -0400, Frank Li wrote:
> > > > On Wed, Jun 18, 2025 at 09:16:44AM +0200, Jorge Marques wrote:
> > > > > Add support for Analog Devices I3C Controller IP, an AXI-interfaced IP
> > > > > core that supports I3C and I2C devices, multiple speed-grades and
> > > > > I3C IBIs.
> > > > >
> > > > > Signed-off-by: Jorge Marques <jorge.marques@analog.com>
> > > > > ---
> > > > >  MAINTAINERS                         |    1 +
> > > > >  drivers/i3c/master/Kconfig          |   11 +
> > > > >  drivers/i3c/master/Makefile         |    1 +
> > > > >  drivers/i3c/master/adi-i3c-master.c | 1026 +++++++++++++++++++++++++++++++++++
> > > > >  4 files changed, 1039 insertions(+)
> > > > >
> > > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > > index 6f56e17dcecf902c6812827c1ec3e067c65e9894..9eb5b6c327590725d1641fd4b73e48fc1d1a3954 100644
> > > > > --- a/MAINTAINERS
> > > > > +++ b/MAINTAINERS
> > > > > @@ -11247,6 +11247,7 @@ I3C DRIVER FOR ANALOG DEVICES I3C CONTROLLER IP
> > > > >  M:	Jorge Marques <jorge.marques@analog.com>
> > > > >  S:	Maintained
> > > > >  F:	Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
> > > > > +F:	drivers/i3c/master/adi-i3c-master.c
> > > > >
> > > > >  I3C DRIVER FOR CADENCE I3C MASTER IP
> > > > >  M:	Przemysław Gaj <pgaj@cadence.com>
> > > > > diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig
> > > > > index 7b30db3253af9d5c6aee6544c060e491bfbeb643..328b7145cdefa20e708ebfa3383e849ce51c5a71 100644
> > > > > --- a/drivers/i3c/master/Kconfig
> > > > > +++ b/drivers/i3c/master/Kconfig
> > > > > @@ -1,4 +1,15 @@
> > > > >  # SPDX-License-Identifier: GPL-2.0-only
> > > > > +config ADI_I3C_MASTER
> > > > > +	tristate "Analog Devices I3C master driver"
> > > > > +	depends on HAS_IOMEM
> > > > > +	help
> > > > > +	  Support for Analog Devices I3C Controller IP, an AXI-interfaced IP
> > > > > +	  core that supports I3C and I2C devices, multiple speed-grades and
> > > > > +	  I3C IBIs.
> > > > > +
> > > > > +	  This driver can also be built as a module.  If so, the module
> > > > > +	  will be called adi-i3c-master.
> > > > > +
> > > > >  config CDNS_I3C_MASTER
> > > > >  	tristate "Cadence I3C master driver"
> > > > >  	depends on HAS_IOMEM
> > > > > diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile
> > > > > index 3e97960160bc85e5eaf2966ec0c3fae458c2711e..6cc4f4b73e7bdc206b68c750390f9c3cc2ccb199 100644
> > > > > --- a/drivers/i3c/master/Makefile
> > > > > +++ b/drivers/i3c/master/Makefile
> > > > > @@ -1,4 +1,5 @@
> > > > >  # SPDX-License-Identifier: GPL-2.0-only
> > > > > +obj-$(CONFIG_ADI_I3C_MASTER)		+= adi-i3c-master.o
> > > > >  obj-$(CONFIG_CDNS_I3C_MASTER)		+= i3c-master-cdns.o
> > > > >  obj-$(CONFIG_DW_I3C_MASTER)		+= dw-i3c-master.o
> > > > >  obj-$(CONFIG_AST2600_I3C_MASTER)	+= ast2600-i3c-master.o
> > > > > diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c
> > > > > new file mode 100644
> > > > > index 0000000000000000000000000000000000000000..d4e0756efec9588383ac55ca9ea9228368ce767d
> > > > > --- /dev/null
> > > > > +++ b/drivers/i3c/master/adi-i3c-master.c
> > > > > @@ -0,0 +1,1026 @@
> > > > > +// SPDX-License-Identifier: GPL-2.0-only
> > > > > +/*
> > > > > + * I3C Controller driver
> > > > > + * Copyright 2025 Analog Devices Inc.
> > > > > + * Author: Jorge Marques <jorge.marques@analog.com>
> > > > > + */
> > > > > +
> > > > > +#include <linux/bitops.h>
> > > > > +#include <linux/bitfield.h>
> > > > > +#include <linux/clk.h>
> > > > > +#include <linux/err.h>
> > > > > +#include <linux/errno.h>
> > > > > +#include <linux/i3c/master.h>
> > > > > +#include <linux/interrupt.h>
> > > > > +#include <linux/io.h>
> > > > > +#include <linux/module.h>
> > > > > +#include <linux/of.h>
> > > > > +#include <linux/platform_device.h>
> > > > > +
> > > > > +#define VERSION_MAJOR(x)		((u32)FIELD_GET(GENMASK(23, 16), (x)))
> > > > > +#define VERSION_MINOR(x)		((u32)FIELD_GET(GENMASK(15, 8), (x)))
> > > > > +#define VERSION_PATCH(x)		((u32)FIELD_GET(GENMASK(7, 0), (x)))
> > > Hi Frank,
> > > >
> > > > I suppose that needn't (u32), any warnings report without u32?
> > > >
> > > Here I will actually remove and use the macros from
> > > linux/fpga/adi-axi-common.h
> > >
> > > > > +
> > > > > +#define MAX_DEVS			16
> > > > > +
> > > > > +#define REG_VERSION			0x000
> > > to be replaced removed, and ADI_AXI_REG_VERSION used instead.
> > > > > +#define REG_ENABLE			0x040
> > > > > +#define REG_IRQ_MASK			0x080
> > > > > +#define REG_IRQ_PENDING			0x084
> > > > > +#define   REG_IRQ_PENDING_CMDR		BIT(5)
> > > > > +#define   REG_IRQ_PENDING_IBI		((u32)BIT(6))
> > > >
> > > > needn't u32
> > > In earlier CI runs, I used to get with gcc_x86_64, due to the inversion
> > > ~REG_IRQ_PENDING_IBI:
> > >
> > >   > conversion from 'long unsigned int' to 'unsigned int' changes value
> > >   > from '18446744073709551615' to '4294967295' [-Werror=overflow]
> > >
> > > Couldn't replicate in this patch version, so I will remove.
> > >
> > > > > +#define   REG_IRQ_PENDING_DAA		BIT(7)
> > > >
> > > > Add empty line between registers
> > > >
> > > Ok
> > > > > +#define REG_CMD_FIFO			0x0d4
> > > > > +#define	  REG_CMD_FIFO_0_IS_CCC		BIT(22)
> > > > > +#define   REG_CMD_FIFO_0_BCAST		BIT(21)
> > > > > +#define   REG_CMD_FIFO_0_SR		BIT(20)
> > > > > +#define   REG_CMD_FIFO_0_LEN(l)		FIELD_PREP(GENMASK(19, 8), (l))
> > > > > +#define   REG_CMD_FIFO_0_DEV_ADDR(a)	FIELD_PREP(GENMASK(7, 1), a)
> > > > > +#define   REG_CMD_FIFO_0_RNW		BIT(0)
> > > > > +#define   REG_CMD_FIFO_1_CCC(id)	FIELD_PREP(GENMASK(7, 0), (id))
> > > > > +#define REG_CMD_FIFO_ROOM		0x0c0
> > > > > +#define REG_CMDR_FIFO			0x0d8
> > > > > +#define   REG_CMDR_FIFO_NO_ERROR	0
> > > > > +#define   REG_CMDR_FIFO_CE0_ERROR	1
> > > > > +#define   REG_CMDR_FIFO_CE2_ERROR	4
> > > > > +#define   REG_CMDR_FIFO_NACK_RESP	6
> > > > > +#define   REG_CMDR_FIFO_UDA_ERROR	8
> > > > > +#define   REG_CMDR_FIFO_ERROR(x)	FIELD_GET(GENMASK(23, 20), (x))
> > > > > +#define   REG_CMDR_FIFO_XFER_BYTES(x)	FIELD_GET(GENMASK(19, 8), (x))
> > > >
> > > > Sort according bit pos. REG_CMDR_FIFO_XFER_BYTES should before
> > > > REG_CMDR_FIFO_ERROR
> > > >
> > > Ahh true
> > > > > +#define REG_SDO_FIFO			0x0dc
> > > > > +#define REG_SDO_FIFO_ROOM		0x0c8
> > > > > +#define REG_SDI_FIFO			0x0e0
> > > > > +#define REG_IBI_FIFO			0x0e4
> > > > > +#define REG_FIFO_STATUS			0x0e8
> > > > > +#define   REG_FIFO_STATUS_CMDR_EMPTY	BIT(0)
> > > > > +#define   REG_FIFO_STATUS_IBI_EMPTY	BIT(1)
> > > > > +#define REG_OPS				0x100
> > > > > +#define   REG_OPS_SET_SG(x)		FIELD_PREP(GENMASK(6, 5), (x))
> > > > > +#define   REG_OPS_PP_SG_MASK		GENMASK(6, 5)
> > > >
> > > > REG_OPS_SET_SG(x) FIELD_PREP(REG_OPS_PP_SG_MASK, (x))
> > > Ack.
> > > >
> > > > > +#define REG_IBI_CONFIG			0x140
> > > > > +#define   REG_IBI_CONFIG_LISTEN		BIT(1)
> > > > > +#define   REG_IBI_CONFIG_ENABLE		BIT(0)
> > > >
> > > > sort it. keep consisent in whole file, from 0->31 or 31->0
> > > >
> > > Ack, decrementing
> > > > > +#define REG_DEV_CHAR			0x180
> > > > > +#define   REG_DEV_CHAR_IS_I2C		BIT(0)
> > > > > +#define   REG_DEV_CHAR_IS_ATTACHED	BIT(1)
> > > > > +#define   REG_DEV_CHAR_BCR_IBI(x)	(((x) & GENMASK(2, 1)) << 1)
> > > >
> > > > FIELD_PREP()
> > > Ok, I will remove the simplification and make it clearer by setting
> > >
> > >      #define   REG_DEV_CHAR_BCR_IBI(x)       FIELD_PREP(GENMASK(3, 2), (x))
> > >
> > > Then usage:
> > >
> > >      #include <linux/i3c/device.h>
> > >      ...
> > >      bcr_ibi = FIELD_GET(I3C_BCR_IBI_PAYLOAD | I3C_BCR_IBI_REQ_CAP, (i3cdev->info.bcr));
> > >      ...REG_DEV_CHAR_BCR_IBI(bcr_ibi)...;
> > >
> > > >
> > > > > +#define   REG_DEV_CHAR_WEN		BIT(8)
> > > > > +#define   REG_DEV_CHAR_ADDR(x)		FIELD_PREP(GENMASK(15, 9), (x))
> > > > > +
> > > > > +enum speed_grade {PP_SG_UNSET, PP_SG_1MHZ, PP_SG_3MHZ, PP_SG_6MHZ, PP_SG_12MHZ};
> > > > > +struct adi_i3c_cmd {
> > > > > +	u32 cmd0;
> > > > > +	u32 cmd1;
> > > > > +	u32 tx_len;
> > > > > +	const void *tx_buf;
> > > > > +	u32 rx_len;
> > > > > +	void *rx_buf;
> > > > > +	u32 error;
> > > > > +};
> > > > > +
> > > > > +struct adi_i3c_xfer {
> > > > > +	struct list_head node;
> > > > > +	struct completion comp;
> > > > > +	int ret;
> > > > > +	unsigned int ncmds;
> > > > > +	unsigned int ncmds_comp;
> > > > > +	struct adi_i3c_cmd cmds[];
> > > > > +};
> > > > > +
> > > > > +struct adi_i3c_master {
> > > > > +	struct i3c_master_controller base;
> > > > > +	u32 free_rr_slots;
> > > > > +	unsigned int maxdevs;
> > > > > +	struct {
> > > > > +		unsigned int num_slots;
> > > > > +		struct i3c_dev_desc **slots;
> > > > > +		spinlock_t lock; /* Protect IBI slot access */
> > > > > +	} ibi;
> > > > > +	struct {
> > > > > +		struct list_head list;
> > > > > +		struct adi_i3c_xfer *cur;
> > > > > +		spinlock_t lock; /* Protect transfer */
> > > > > +	} xferqueue;
> > > > > +	void __iomem *regs;
> > > > > +	struct clk *clk;
> > > > > +	unsigned long i3c_scl_lim;
> > > > > +	struct {
> > > > > +		u8 addrs[MAX_DEVS];
> > > > > +		u8 index;
> > > > > +	} daa;
> > > > > +};
> > > > > +
> > > > > +static inline struct adi_i3c_master *to_adi_i3c_master(struct i3c_master_controller *master)
> > > > > +{
> > > > > +	return container_of(master, struct adi_i3c_master, base);
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_wr_to_tx_fifo(struct adi_i3c_master *master,
> > > > > +					 const u8 *bytes, unsigned int nbytes)
> > > > > +{
> > > > > +	unsigned int n, m;
> > > > > +	u32 tmp;
> > > > > +
> > > > > +	n = readl(master->regs + REG_SDO_FIFO_ROOM);
> > > > > +	m = min(n, nbytes);
> > > > > +	writesl(master->regs + REG_SDO_FIFO, bytes, m / 4);
> > > > > +
> > > > > +	if (m & 3) {
> > > > > +		memcpy(&tmp, bytes + (m & ~3), m & 3);
> > > > > +		writel(tmp, master->regs + REG_SDO_FIFO);
> > > > > +	}
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_rd_from_rx_fifo(struct adi_i3c_master *master,
> > > > > +					   u8 *bytes, unsigned int nbytes)
> > > > > +{
> > > > > +	readsl(master->regs + REG_SDI_FIFO, bytes, nbytes / 4);
> > > > > +	if (nbytes & 3) {
> > > > > +		u32 tmp;
> > > > > +
> > > > > +		tmp = readl(master->regs + REG_SDI_FIFO);
> > > > > +		memcpy(bytes + (nbytes & ~3), &tmp, nbytes & 3);
> > > > > +	}
> > > > > +}
> > > >
> > > > can you move above two helper function as inline into drivers/i3c/internals.h
> > > >
> > > > or create new one drivers/i3c/master/helper.h or other naming.
> > > >
> > > > Renesas IP have similar above two functions to access FIFO.
> > > > https://lore.kernel.org/linux-i3c/174964724485.330045.2181706921272138816.robh@kernel.org/T/#t
> > > >
> > > Ok, yes that's common because the passes type u8 doesn't ensure
> > > multiples of 32 at least. Added to my TODO list.
> > > > > +
> > > > > +static bool adi_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m,
> > > > > +					    const struct i3c_ccc_cmd *cmd)
> > > > > +{
> > > > > +	if (cmd->ndests > 1)
> > > > > +		return false;
> > > > > +
> > > > > +	switch (cmd->id) {
> > > > > +	case I3C_CCC_ENEC(true):
> > > > > +	case I3C_CCC_ENEC(false):
> > > > > +	case I3C_CCC_DISEC(true):
> > > > > +	case I3C_CCC_DISEC(false):
> > > > > +	case I3C_CCC_RSTDAA(true):
> > > > > +	case I3C_CCC_RSTDAA(false):
> > > > > +	case I3C_CCC_ENTDAA:
> > > > > +	case I3C_CCC_SETDASA:
> > > > > +	case I3C_CCC_SETNEWDA:
> > > > > +	case I3C_CCC_GETMWL:
> > > > > +	case I3C_CCC_GETMRL:
> > > > > +	case I3C_CCC_GETPID:
> > > > > +	case I3C_CCC_GETBCR:
> > > > > +	case I3C_CCC_GETDCR:
> > > > > +	case I3C_CCC_GETSTATUS:
> > > > > +	case I3C_CCC_GETHDRCAP:
> > > > > +		return true;
> > > > > +	default:
> > > > > +		break;
> > > > > +	}
> > > > > +
> > > > > +	return false;
> > > > > +}
> > > > > +
> > > > > +static int adi_i3c_master_disable(struct adi_i3c_master *master)
> > > > > +{
> > > > > +	writel(0, master->regs + REG_IBI_CONFIG);
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +
> > > > > +static struct adi_i3c_xfer *adi_i3c_master_alloc_xfer(struct adi_i3c_master *master,
> > > > > +						      unsigned int ncmds)
> > > > > +{
> > > > > +	struct adi_i3c_xfer *xfer;
> > > > > +
> > > > > +	xfer = kzalloc(struct_size(xfer, cmds, ncmds), GFP_KERNEL);
> > > > > +	if (!xfer)
> > > > > +		return NULL;
> > > > > +
> > > > > +	INIT_LIST_HEAD(&xfer->node);
> > > > > +	xfer->ncmds = ncmds;
> > > > > +	xfer->ret = -ETIMEDOUT;
> > > > > +
> > > > > +	return xfer;
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_start_xfer_locked(struct adi_i3c_master *master)
> > > > > +{
> > > > > +	struct adi_i3c_xfer *xfer = master->xferqueue.cur;
> > > > > +	unsigned int i, n, m;
> > > > > +
> > > > > +	if (!xfer)
> > > > > +		return;
> > > > > +
> > > > > +	for (i = 0; i < xfer->ncmds; i++) {
> > > > > +		struct adi_i3c_cmd *cmd = &xfer->cmds[i];
> > > > > +
> > > > > +		if (!(cmd->cmd0 & REG_CMD_FIFO_0_RNW))
> > > > > +			adi_i3c_master_wr_to_tx_fifo(master, cmd->tx_buf, cmd->tx_len);
> > > > > +	}
> > > > > +
> > > > > +	n = readl(master->regs + REG_CMD_FIFO_ROOM);
> > > > > +	for (i = 0; i < xfer->ncmds; i++) {
> > > > > +		struct adi_i3c_cmd *cmd = &xfer->cmds[i];
> > > > > +
> > > > > +		m = cmd->cmd0 & REG_CMD_FIFO_0_IS_CCC ? 2 : 1;
> > > > > +		if (m > n)
> > > > > +			break;
> > > > > +		writel(cmd->cmd0, master->regs + REG_CMD_FIFO);
> > > > > +		if (cmd->cmd0 & REG_CMD_FIFO_0_IS_CCC)
> > > > > +			writel(cmd->cmd1, master->regs + REG_CMD_FIFO);
> > > > > +		n -= m;
> > > > > +	}
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_end_xfer_locked(struct adi_i3c_master *master,
> > > > > +					   u32 pending)
> > > > > +{
> > > > > +	struct adi_i3c_xfer *xfer = master->xferqueue.cur;
> > > > > +	int i, ret = 0;
> > > > > +	u32 status0;
> > > > > +
> > > > > +	if (!xfer)
> > > > > +		return;
> > > > > +
> > > > > +	for (status0 = readl(master->regs + REG_FIFO_STATUS);
> > > > > +	     !(status0 & REG_FIFO_STATUS_CMDR_EMPTY);
> > > > > +	     status0 = readl(master->regs + REG_FIFO_STATUS)) {
> > > >
> > > > how about
> > > >
> > > > while (!(readl(master->regs + REG_FIFO_STATUS) & REG_FIFO_STATUS_CMDR_EMPTY))) {
> > > >
> > > Ack, thanks!
> > > Same for 'REG_FIFO_STATUS_IBI_EMPTY for'.
> > > > > +		struct adi_i3c_cmd *cmd;
> > > > > +		u32 cmdr, rx_len;
> > > > > +
> > > > > +		cmdr = readl(master->regs + REG_CMDR_FIFO);
> > > > > +
> > > > > +		cmd = &xfer->cmds[xfer->ncmds_comp++];
> > > > > +		if (cmd->cmd0 & REG_CMD_FIFO_0_RNW) {
> > > > > +			rx_len = min_t(u32, REG_CMDR_FIFO_XFER_BYTES(cmdr), cmd->rx_len);
> > > > > +			adi_i3c_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len);
> > > > > +		}
> > > > > +		cmd->error = REG_CMDR_FIFO_ERROR(cmdr);
> > > > > +	}
> > > > > +
> > > > > +	for (i = 0; i < xfer->ncmds; i++) {
> > > >
> > > > I think you iterate to ncmds_comp?
> > > >
> > > You are correct thanks! Each command yields a command receipt interrupt.
> > > > > +		switch (xfer->cmds[i].error) {
> > > > > +		case REG_CMDR_FIFO_NO_ERROR:
> > > > > +			break;
> > > > > +
> > > > > +		case REG_CMDR_FIFO_CE0_ERROR:
> > > > > +		case REG_CMDR_FIFO_CE2_ERROR:
> > > > > +		case REG_CMDR_FIFO_NACK_RESP:
> > > > > +		case REG_CMDR_FIFO_UDA_ERROR:
> > > > > +			ret = -EIO;
> > > > > +			break;
> > > > > +
> > > > > +		default:
> > > > > +			ret = -EINVAL;
> > > > > +			break;
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	xfer->ret = ret;
> > > > > +
> > > > > +	if (xfer->ncmds_comp != xfer->ncmds)
> > > > > +		return;
> > > > > +
> > > > > +	complete(&xfer->comp);
> > > > > +
> > > > > +	xfer = list_first_entry_or_null(&master->xferqueue.list,
> > > > > +					struct adi_i3c_xfer, node);
> > > > > +	if (xfer)
> > > > > +		list_del_init(&xfer->node);
> > > > > +
> > > > > +	master->xferqueue.cur = xfer;
> > > > > +	adi_i3c_master_start_xfer_locked(master);
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_queue_xfer(struct adi_i3c_master *master,
> > > > > +				      struct adi_i3c_xfer *xfer)
> > > > > +{
> > > > > +	init_completion(&xfer->comp);
> > > > > +	guard(spinlock_irqsave)(&master->xferqueue.lock);
> > > > > +	if (master->xferqueue.cur) {
> > > > > +		list_add_tail(&xfer->node, &master->xferqueue.list);
> > > > > +	} else {
> > > > > +		master->xferqueue.cur = xfer;
> > > > > +		adi_i3c_master_start_xfer_locked(master);
> > > > > +	}
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_unqueue_xfer(struct adi_i3c_master *master,
> > > > > +					struct adi_i3c_xfer *xfer)
> > > > > +{
> > > > > +	guard(spinlock_irqsave)(&master->xferqueue.lock);
> > > > > +	if (master->xferqueue.cur == xfer)
> > > > > +		master->xferqueue.cur = NULL;
> > > > > +	else
> > > > > +		list_del_init(&xfer->node);
> > > > > +
> > > > > +	writel(0x01, master->regs + REG_ENABLE);
> > > > > +	writel(0x00, master->regs + REG_ENABLE);
> > > > > +	writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
> > > > > +}
> > > > > +
> > > > > +static enum i3c_error_code adi_i3c_cmd_get_err(struct adi_i3c_cmd *cmd)
> > > > > +{
> > > > > +	switch (cmd->error) {
> > > > > +	case REG_CMDR_FIFO_CE0_ERROR:
> > > > > +		return I3C_ERROR_M0;
> > > > > +
> > > > > +	case REG_CMDR_FIFO_CE2_ERROR:
> > > > > +	case REG_CMDR_FIFO_NACK_RESP:
> > > > > +		return I3C_ERROR_M2;
> > > > > +
> > > > > +	default:
> > > > > +		break;
> > > > > +	}
> > > > > +
> > > > > +	return I3C_ERROR_UNKNOWN;
> > > > > +}
> > > > > +
> > > > > +static int adi_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
> > > > > +				       struct i3c_ccc_cmd *cmd)
> > > > > +{
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +	struct adi_i3c_xfer *xfer;
> > > > > +	struct adi_i3c_cmd *ccmd;
> > > > > +
> > > > > +	xfer = adi_i3c_master_alloc_xfer(master, 1);
> > > > > +	if (!xfer)
> > > > > +		return -ENOMEM;
> > > > > +
> > > > > +	ccmd = xfer->cmds;
> > > > > +	ccmd->cmd1 = REG_CMD_FIFO_1_CCC(cmd->id);
> > > > > +	ccmd->cmd0 = REG_CMD_FIFO_0_IS_CCC |
> > > > > +		     REG_CMD_FIFO_0_LEN(cmd->dests[0].payload.len);
> > > > > +
> > > > > +	if (cmd->id & I3C_CCC_DIRECT)
> > > > > +		ccmd->cmd0 |= REG_CMD_FIFO_0_DEV_ADDR(cmd->dests[0].addr);
> > > > > +
> > > > > +	if (cmd->rnw) {
> > > > > +		ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> > > > > +		ccmd->rx_buf = cmd->dests[0].payload.data;
> > > > > +		ccmd->rx_len = cmd->dests[0].payload.len;
> > > > > +	} else {
> > > > > +		ccmd->tx_buf = cmd->dests[0].payload.data;
> > > > > +		ccmd->tx_len = cmd->dests[0].payload.len;
> > > > > +	}
> > > > > +
> > > > > +	adi_i3c_master_queue_xfer(master, xfer);
> > > > > +	if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000)))
> > > > > +		adi_i3c_master_unqueue_xfer(master, xfer);
> > > > > +
> > > > > +	cmd->err = adi_i3c_cmd_get_err(&xfer->cmds[0]);
> > > > > +	kfree(xfer);
> > > >
> > > > you can use
> > > > struct adi_i3c_xfer *xfer __free(kfree) = NULL;
> > > >
> > > >  to simple code.
> > > >
> > > Ah cool, will use in all xfer instances.
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +
> > > > > +static int adi_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
> > > > > +				     struct i3c_priv_xfer *xfers,
> > > > > +				     int nxfers)
> > > > > +{
> > > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +	struct adi_i3c_xfer *xfer;
> > > > > +	int i, ret;
> > > > > +
> > > > > +	if (!nxfers)
> > > > > +		return 0;
> > > >
> > > > add an empty line.
> > > >
> > > Ack.
> > > > > +	xfer = adi_i3c_master_alloc_xfer(master, nxfers);
> > > > > +	if (!xfer)
> > > > > +		return -ENOMEM;
> > > > > +
> > > > > +	for (i = 0; i < nxfers; i++) {
> > > > > +		struct adi_i3c_cmd *ccmd = &xfer->cmds[i];
> > > > > +
> > > > > +		ccmd->cmd0 = REG_CMD_FIFO_0_DEV_ADDR(dev->info.dyn_addr);
> > > > > +
> > > > > +		if (xfers[i].rnw) {
> > > > > +			ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> > > > > +			ccmd->rx_buf = xfers[i].data.in;
> > > > > +			ccmd->rx_len = xfers[i].len;
> > > > > +		} else {
> > > > > +			ccmd->tx_buf = xfers[i].data.out;
> > > > > +			ccmd->tx_len = xfers[i].len;
> > > > > +		}
> > > > > +
> > > > > +		ccmd->cmd0 |= REG_CMD_FIFO_0_LEN(xfers[i].len);
> > > > > +
> > > > > +		if (i < nxfers - 1)
> > > > > +			ccmd->cmd0 |= REG_CMD_FIFO_0_SR;
> > > > > +
> > > > > +		if (!i)
> > > > > +			ccmd->cmd0 |= REG_CMD_FIFO_0_BCAST;
> > > > > +	}
> > > > > +
> > > > > +	adi_i3c_master_queue_xfer(master, xfer);
> > > > > +	if (!wait_for_completion_timeout(&xfer->comp,
> > > > > +					 msecs_to_jiffies(1000)))
> > > > > +		adi_i3c_master_unqueue_xfer(master, xfer);
> > > > > +
> > > > > +	ret = xfer->ret;
> > > > > +
> > > > > +	for (i = 0; i < nxfers; i++)
> > > > > +		xfers[i].err = adi_i3c_cmd_get_err(&xfer->cmds[i]);
> > > > > +
> > > > > +	kfree(xfer);
> > > > > +
> > > > > +	return ret;
> > > > > +}
> > > > > +
> > > > > +struct adi_i3c_i2c_dev_data {
> > > > > +	u16 id;
> > > > > +	s16 ibi;
> > > > > +	struct i3c_generic_ibi_pool *ibi_pool;
> > > > > +};
> > > > > +
> > > > > +static int adi_i3c_master_get_rr_slot(struct adi_i3c_master *master,
> > > > > +				      u8 dyn_addr)
> > > > > +{
> > > > > +	if (!master->free_rr_slots)
> > > > > +		return -ENOSPC;
> > > > > +
> > > > > +	return ffs(master->free_rr_slots) - 1;
> > > > > +}
> > > > > +
> > > > > +static int adi_i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 dyn_addr)
> > > > > +{
> > > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +	u8 addr;
> > > > > +
> > > > > +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> > > > > +
> > > > > +	writel(REG_DEV_CHAR_ADDR(dyn_addr), master->regs + REG_DEV_CHAR);
> > > > > +	writel((readl(master->regs + REG_DEV_CHAR) &
> > > > > +		~REG_DEV_CHAR_IS_ATTACHED) | REG_DEV_CHAR_WEN,
> > > > > +	       master->regs + REG_DEV_CHAR);
> > > > > +
> > > > > +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > > > > +	writel(readl(master->regs + REG_DEV_CHAR) |
> > > > > +	       REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> > > > > +	       master->regs + REG_DEV_CHAR);
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +
> > > > > +static int adi_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev)
> > > > > +{
> > > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +	struct adi_i3c_i2c_dev_data *data;
> > > > > +	int slot;
> > > > > +	u8 addr;
> > > > > +
> > > > > +	data = kzalloc(sizeof(*data), GFP_KERNEL);
> > > > > +	if (!data)
> > > > > +		return -ENOMEM;
> > > > > +
> > > > > +	slot = adi_i3c_master_get_rr_slot(master, dev->info.dyn_addr);
> > > > > +	if (slot < 0) {
> > > > > +		kfree(data);
> > > > > +		return slot;
> > > > > +	}
> > > > > +
> > > > > +	data->ibi = -1;
> > > >
> > > > why -1 here?
> > > >
> > > Doesn't mean much, just stores the ibi slot occupied by the device.
> > > NULL would be position 0 of the array.
> > > It can be left uninitialized and not cleaned-up when removed.
> > > > > +	data->id = slot;
> > > > > +	i3c_dev_set_master_data(dev, data);
> > > > > +	master->free_rr_slots &= ~BIT(slot);
> > > > > +
> > > > > +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> > > > > +
> > > > > +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > > > > +	writel(readl(master->regs + REG_DEV_CHAR) |
> > > > > +	       REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> > > > > +	       master->regs + REG_DEV_CHAR);
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_sync_dev_char(struct i3c_master_controller *m)
> > > > > +{
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +	struct i3c_dev_desc *i3cdev;
> > > > > +	u8 addr;
> > > > > +
> > > > > +	i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
> > > > > +		addr = i3cdev->info.dyn_addr ?
> > > > > +		       i3cdev->info.dyn_addr : i3cdev->info.static_addr;
> > > > > +		writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > > > > +		writel(readl(master->regs + REG_DEV_CHAR) |
> > > > > +		       REG_DEV_CHAR_BCR_IBI(i3cdev->info.bcr) | REG_DEV_CHAR_WEN,
> > > > > +		       master->regs + REG_DEV_CHAR);
> > > > > +	}
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev)
> > > > > +{
> > > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> > > > > +	u8 addr;
> > > > > +
> > > > > +	addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
> > > > > +
> > > > > +	writel(REG_DEV_CHAR_ADDR(addr), master->regs + REG_DEV_CHAR);
> > > > > +	writel((readl(master->regs + REG_DEV_CHAR) &
> > > > > +		~REG_DEV_CHAR_IS_ATTACHED) | REG_DEV_CHAR_WEN,
> > > > > +	       master->regs + REG_DEV_CHAR);
> > > > > +
> > > > > +	i3c_dev_set_master_data(dev, NULL);
> > > > > +	master->free_rr_slots |= BIT(data->id);
> > > > > +	kfree(data);
> > > > > +}
> > > > > +
> > > > > +static int adi_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev)
> > > > > +{
> > > > > +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +	struct adi_i3c_i2c_dev_data *data;
> > > > > +	int slot;
> > > > > +
> > > > > +	slot = adi_i3c_master_get_rr_slot(master, 0);
> > > > > +	if (slot < 0)
> > > > > +		return slot;
> > > > > +
> > > > > +	data = kzalloc(sizeof(*data), GFP_KERNEL);
> > > > > +	if (!data)
> > > > > +		return -ENOMEM;
> > > > > +
> > > > > +	data->id = slot;
> > > > > +	master->free_rr_slots &= ~BIT(slot);
> > > > > +	i2c_dev_set_master_data(dev, data);
> > > > > +
> > > > > +	writel(REG_DEV_CHAR_ADDR(dev->addr) |
> > > > > +	       REG_DEV_CHAR_IS_I2C | REG_DEV_CHAR_IS_ATTACHED | REG_DEV_CHAR_WEN,
> > > > > +	       master->regs + REG_DEV_CHAR);
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_detach_i2c_dev(struct i2c_dev_desc *dev)
> > > > > +{
> > > > > +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +	struct adi_i3c_i2c_dev_data *data = i2c_dev_get_master_data(dev);
> > > > > +
> > > > > +	writel(REG_DEV_CHAR_ADDR(dev->addr) |
> > > > > +	       REG_DEV_CHAR_IS_I2C | REG_DEV_CHAR_WEN,
> > > > > +	       master->regs + REG_DEV_CHAR);
> > > > > +
> > > > > +	i2c_dev_set_master_data(dev, NULL);
> > > > > +	master->free_rr_slots |= BIT(data->id);
> > > > > +	kfree(data);
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_bus_cleanup(struct i3c_master_controller *m)
> > > > > +{
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +
> > > > > +	adi_i3c_master_disable(master);
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_upd_i3c_scl_lim(struct adi_i3c_master *master)
> > > > > +{
> > > > > +	struct i3c_master_controller *m = &master->base;
> > > > > +	struct i3c_bus *bus = i3c_master_get_bus(m);
> > > > > +	u8 i3c_scl_lim = 0;
> > > > > +	struct i3c_dev_desc *dev;
> > > > > +	u8 pp_sg;
> > > > > +
> > > > > +	i3c_bus_for_each_i3cdev(bus, dev) {
> > > > > +		u8 max_fscl;
> > > > > +
> > > > > +		max_fscl = max(I3C_CCC_MAX_SDR_FSCL(dev->info.max_read_ds),
> > > > > +			       I3C_CCC_MAX_SDR_FSCL(dev->info.max_write_ds));
> > > > > +
> > > > > +		switch (max_fscl) {
> > > > > +		case I3C_SDR1_FSCL_8MHZ:
> > > > > +			max_fscl = PP_SG_6MHZ;
> > > > > +			break;
> > > > > +		case I3C_SDR2_FSCL_6MHZ:
> > > > > +			max_fscl = PP_SG_3MHZ;
> > > > > +			break;
> > > > > +		case I3C_SDR3_FSCL_4MHZ:
> > > > > +			max_fscl = PP_SG_3MHZ;
> > > > > +			break;
> > > > > +		case I3C_SDR4_FSCL_2MHZ:
> > > > > +			max_fscl = PP_SG_1MHZ;
> > > > > +			break;
> > > > > +		case I3C_SDR0_FSCL_MAX:
> > > > > +		default:
> > > > > +			max_fscl = PP_SG_12MHZ;
> > > > > +			break;
> > > > > +		}
> > > > > +
> > > > > +		if (max_fscl &&
> > > > > +		    (i3c_scl_lim > max_fscl || !i3c_scl_lim))
> > > > > +			i3c_scl_lim = max_fscl;
> > > > > +	}
> > > > > +
> > > > > +	if (!i3c_scl_lim)
> > > > > +		return;
> > > > > +
> > > > > +	master->i3c_scl_lim = i3c_scl_lim - 1;
> > > > > +
> > > > > +	pp_sg = readl(master->regs + REG_OPS) & ~REG_OPS_PP_SG_MASK;
> > > > > +	pp_sg |= REG_OPS_SET_SG(master->i3c_scl_lim);
> > > > > +
> > > > > +	writel(pp_sg, master->regs + REG_OPS);
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_get_features(struct adi_i3c_master *master,
> > > > > +					unsigned int slot,
> > > > > +					struct i3c_device_info *info)
> > > > > +{
> > > > > +	memset(info, 0, sizeof(*info));
> > > > > +
> > > > > +	info->dyn_addr = 0x31;
> > > >
> > > > master address is hardcode?
> > > >
> > > Yes.
> > > There is/was no register to read it from because the controller does not
> > > support secondary mastership or changing role, so this address is not
> > > used for anything (as is). Since it is odd, I will move add these
> > > hardcoded values to the register map (read-only), still won't do
> > > anything.
> >
> > need comments for 0x31
> >
> Ack, will likely become a register read but will also add a comment.
> > > > > +	info->dcr = 0x00;
> > > > > +	info->bcr = 0x40;
> > > > > +	info->pid = 0;
> > > > > +}
> > > > > +
> > > > > +static int adi_i3c_master_do_daa(struct i3c_master_controller *m)
> > > > > +{
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +	u32 irq_mask;
> > > > > +	int ret, addr;
> > > > > +
> > > > > +	addr = 0x8;
> > > > > +	for (u8 i = 0; i < MAX_DEVS; i++) {
> > > > > +		addr = i3c_master_get_free_addr(m, addr);
> > > > > +		if (addr < 0)
> > > > > +			return addr;
> > > >
> > > > assume MAX_DEVS 128, if only get 4 address, suppose you should enumerate
> > > > for such 4 devices, instead of return error and give up do_daa.
> > > >
> > > The IP Core supports just 16 devices (max # on the bus), so it should
> > > not run-out of addresses, but I understand the assignment. As is, the IP
> > > core has 16 slots and will only terminate the DAA from its side if it
> > > run out of slots (if no device request an address (NACK bit), the DAA is
> > > also terminated). Not writing a DA during the DA IRQ will just hang the
> > > bus.
> > >
> > > For the suggestion to work I need to re-read the spec and rework the RTL
> > > to allow this early termination. However, I am currently a little
> > > opposed of changing the RTL beyond basic read-only registers
> > > modifications.
> > > > > +		master->daa.addrs[i] = addr;
> > > > > +	}
> > > > > +
> > > > > +	irq_mask = readl(master->regs + REG_IRQ_MASK);
> > > > > +	writel(irq_mask | REG_IRQ_PENDING_DAA,
> > > > > +	       master->regs + REG_IRQ_MASK);
> > > > > +
> > > > > +	master->daa.index = 0;
> > > > > +	ret = i3c_master_entdaa_locked(&master->base);
> > > > > +
> > > > > +	writel(irq_mask, master->regs + REG_IRQ_MASK);
> > > > > +
> > > > > +	/* DAA always finishes with CE2_ERROR or NACK_RESP */
> > > > > +	if (ret && ret != I3C_ERROR_M2)
> > > > > +		return ret;
> > > > > +
> > > > > +	/* Add I3C devices discovered */
> > > > > +	for (u8 i = 0; i < master->daa.index; i++)
> > > > > +		i3c_master_add_i3c_dev_locked(m, master->daa.addrs[i]);
> > > > > +	/* Sync retrieved devs info with the IP */
> > > > > +	adi_i3c_master_sync_dev_char(m);
> > > > > +
> > > > > +	i3c_master_defslvs_locked(&master->base);
> > > > > +
> > > > > +	adi_i3c_master_upd_i3c_scl_lim(master);
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +
> > > > > +static int adi_i3c_master_bus_init(struct i3c_master_controller *m)
> > > > > +{
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +	struct i3c_device_info info = { };
> > > > > +	int ret;
> > > > > +
> > > > > +	ret = i3c_master_get_free_addr(m, 0);
> > > > > +	if (ret < 0)
> > > > > +		return ret;
> > > > > +
> > > > > +	adi_i3c_master_get_features(master, 0, &info);
> > > > > +	ret = i3c_master_set_info(&master->base, &info);
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +
> > > > > +	writel(REG_IBI_CONFIG_LISTEN,
> > > > > +	       master->regs + REG_IBI_CONFIG);
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_handle_ibi(struct adi_i3c_master *master,
> > > > > +				      u32 ibi)
> > > > > +{
> > > > > +	struct adi_i3c_i2c_dev_data *data;
> > > > > +	struct i3c_ibi_slot *slot;
> > > > > +	struct i3c_dev_desc *dev;
> > > > > +	u8 da, id;
> > > > > +	u8 *mdb;
> > > > > +
> > > > > +	da = (ibi >> 17) & GENMASK(6, 0);
> > > > > +	for (id = 0; id < master->ibi.num_slots; id++) {
> > > > > +		if (master->ibi.slots[id] &&
> > > > > +		    master->ibi.slots[id]->info.dyn_addr == da)
> > > > > +			break;
> > > > > +	}
> > > > > +
> > > > > +	if (id == master->ibi.num_slots)
> > > > > +		return;
> > > > > +
> > > > > +	dev = master->ibi.slots[id];
> > > > > +	guard(spinlock)(&master->ibi.lock);
> > > > > +
> > > > > +	data = i3c_dev_get_master_data(dev);
> > > > > +	slot = i3c_generic_ibi_get_free_slot(data->ibi_pool);
> > > > > +	if (!slot)
> > > > > +		return;
> > > > > +
> > > > > +	mdb = slot->data;
> > > > > +	mdb[0] = (ibi >> 8) & GENMASK(7, 0);
> > > > > +
> > > > > +	slot->len = 1;
> > > > > +	i3c_master_queue_ibi(dev, slot);
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_demux_ibis(struct adi_i3c_master *master)
> > > > > +{
> > > > > +	u32 status0;
> > > > > +
> > > > > +	for (status0 = readl(master->regs + REG_FIFO_STATUS);
> > > > > +	     !(status0 & REG_FIFO_STATUS_IBI_EMPTY);
> > > > > +	     status0 = readl(master->regs + REG_FIFO_STATUS)) {
> > > >
> > > > use while()
> > > >
> > > Ack.
> > > > > +		u32 ibi = readl(master->regs + REG_IBI_FIFO);
> > > > > +
> > > > > +		adi_i3c_master_handle_ibi(master, ibi);
> > > > > +	}
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_handle_da_req(struct adi_i3c_master *master)
> > > > > +{
> > > > > +	u8 payload0[8];
> > > > > +	u32 addr;
> > > > > +
> > > > > +	/* Clear device characteristics */
> > > > > +	adi_i3c_master_rd_from_rx_fifo(master, payload0, 6);
> > > > > +	addr = master->daa.addrs[master->daa.index++];
> > > > > +	addr = (addr << 1) | (parity8(addr) ? 0 : 1);
> > > > > +
> > > > > +	writel(addr, master->regs + REG_SDO_FIFO);
> > > > > +}
> > > > > +
> > > > > +static irqreturn_t adi_i3c_master_irq(int irq, void *data)
> > > > > +{
> > > > > +	struct adi_i3c_master *master = data;
> > > > > +	u32 pending;
> > > > > +
> > > > > +	pending = readl(master->regs + REG_IRQ_PENDING);
> > > > > +	writel(pending, master->regs + REG_IRQ_PENDING);
> > > >
> > > > Add an empty line here
> > > >
> > > Ack.
> > > > > +	if (pending & REG_IRQ_PENDING_CMDR) {
> > > > > +		spin_lock(&master->xferqueue.lock);
> > > > > +		adi_i3c_master_end_xfer_locked(master, pending);
> > > > > +		spin_unlock(&master->xferqueue.lock);
> > > >
> > > > you use guard, keep consisent, use guard or scoped_guard here
> > > >
> > > scoped_guard
> > > > > +	}
> > > > > +	if (pending & REG_IRQ_PENDING_IBI)
> > > > > +		adi_i3c_master_demux_ibis(master);
> > > > > +	if (pending & REG_IRQ_PENDING_DAA)
> > > > > +		adi_i3c_master_handle_da_req(master);
> > > > > +
> > > > > +	return IRQ_HANDLED;
> > > > > +}
> > > > > +
> > > > > +static int adi_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
> > > > > +				    struct i2c_msg *xfers,
> > > > > +				    int nxfers)
> > > > > +{
> > > > > +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +	struct adi_i3c_xfer *xfer;
> > > > > +	int i, ret;
> > > > > +
> > > > > +	if (!nxfers)
> > > > > +		return 0;
> > > > > +	for (i = 0; i < nxfers; i++) {
> > > > > +		if (xfers[i].flags & I2C_M_TEN)
> > > > > +			return -EOPNOTSUPP;
> > > > > +	}
> > > > > +	xfer = adi_i3c_master_alloc_xfer(master, nxfers);
> > > > > +	if (!xfer)
> > > > > +		return -ENOMEM;
> > > > > +
> > > > > +	for (i = 0; i < nxfers; i++) {
> > > > > +		struct adi_i3c_cmd *ccmd = &xfer->cmds[i];
> > > > > +
> > > > > +		ccmd->cmd0 = REG_CMD_FIFO_0_DEV_ADDR(xfers[i].addr);
> > > > > +
> > > > > +		if (xfers[i].flags & I2C_M_RD) {
> > > > > +			ccmd->cmd0 |= REG_CMD_FIFO_0_RNW;
> > > > > +			ccmd->rx_buf = xfers[i].buf;
> > > > > +			ccmd->rx_len = xfers[i].len;
> > > > > +		} else {
> > > > > +			ccmd->tx_buf = xfers[i].buf;
> > > > > +			ccmd->tx_len = xfers[i].len;
> > > > > +		}
> > > > > +
> > > > > +		ccmd->cmd0 |= REG_CMD_FIFO_0_LEN(xfers[i].len);
> > > >
> > > > Just curiouse
> > > > What's happen if IBI happen (address arbitrate) at first command?
> > > >
> > > The controller does the state machine for the IBI and yield a IBIR
> > > interrupt, then proceeds with the xfer, yielding the CMDR for each CMD.
> >
> > Does controller retry current command again after handle IBI?
> >
> > Frank Li
> >
> Yes, it resumes/retries the current command. After the IBI has been
> resolved, the controller issues either a stop, to return to the Bus
> Free/Idle condition if the command stack is empty, or repeated start,
> to proceed with current command.
>
> Looking into the spec i3c basic 1.1.1, fig 169, this controller never
> Disable Target Interrupts after a NACK followed by a repeated start. The
> driver would issue a command to do so in other opportunity.

Thanks, that's good.

>
> Best regards,
> Jorge
> > > > > +	}
> > > > > +
> > > > > +	adi_i3c_master_queue_xfer(master, xfer);
> > > > > +	if (!wait_for_completion_timeout(&xfer->comp,
> > > > > +					 m->i2c.timeout))
> > > > > +		adi_i3c_master_unqueue_xfer(master, xfer);
> > > > > +
> > > > > +	ret = xfer->ret;
> > > > > +	kfree(xfer);
> > > > > +	return ret;
> > > > > +}
> > > > > +
> > > > > +static int adi_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
> > > > > +{
> > > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +	struct i3c_dev_desc *i3cdev;
> > > > > +	u32 enabled = 0;
> > > > > +	int ret;
> > > > > +
> > > > > +	ret = i3c_master_disec_locked(m, dev->info.dyn_addr,
> > > > > +				      I3C_CCC_EVENT_SIR);
> > > > > +
> > > > > +	i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
> > > > > +		if (dev != i3cdev && i3cdev->ibi)
> > > > > +			enabled |= i3cdev->ibi->enabled;
> > > > > +	}
> > > > > +	if (!enabled) {
> > > > > +		writel(REG_IBI_CONFIG_LISTEN,
> > > > > +		       master->regs + REG_IBI_CONFIG);
> > > > > +		writel(readl(master->regs + REG_IRQ_MASK) & ~REG_IRQ_PENDING_IBI,
> > > > > +		       master->regs + REG_IRQ_MASK);
> > > > > +	}
> > > > > +
> > > > > +	return ret;
> > > > > +}
> > > > > +
> > > > > +static int adi_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
> > > > > +{
> > > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +
> > > > > +	writel(REG_IBI_CONFIG_LISTEN | REG_IBI_CONFIG_ENABLE,
> > > > > +	       master->regs + REG_IBI_CONFIG);
> > > > > +
> > > > > +	writel(readl(master->regs + REG_IRQ_MASK) | REG_IRQ_PENDING_IBI,
> > > > > +	       master->regs + REG_IRQ_MASK);
> > > > > +
> > > > > +	return i3c_master_enec_locked(m, dev->info.dyn_addr,
> > > > > +				      I3C_CCC_EVENT_SIR);
> > > > > +}
> > > > > +
> > > > > +static int adi_i3c_master_request_ibi(struct i3c_dev_desc *dev,
> > > > > +				      const struct i3c_ibi_setup *req)
> > > > > +{
> > > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +	struct adi_i3c_i2c_dev_data *data;
> > > > > +	unsigned long flags;
> > > > > +	unsigned int i;
> > > > > +
> > > > > +	data = i3c_dev_get_master_data(dev);
> > > > > +	data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req);
> > > > > +	if (IS_ERR(data->ibi_pool))
> > > > > +		return PTR_ERR(data->ibi_pool);
> > > > > +
> > > > > +	spin_lock_irqsave(&master->ibi.lock, flags);
> > > > > +	for (i = 0; i < master->ibi.num_slots; i++) {
> > > > > +		if (!master->ibi.slots[i]) {
> > > > > +			data->ibi = i;
> > > > > +			master->ibi.slots[i] = dev;
> > > > > +			break;
> > > > > +		}
> > > > > +	}
> > > > > +	spin_unlock_irqrestore(&master->ibi.lock, flags);
> > > >
> > > > same here, use scope_guard to keep consistent.
> > > >
> > > Ack.
> > > > > +
> > > > > +	if (i < master->ibi.num_slots)
> > > > > +		return 0;
> > > > > +
> > > > > +	i3c_generic_ibi_free_pool(data->ibi_pool);
> > > > > +	data->ibi_pool = NULL;
> > > > > +
> > > > > +	return -ENOSPC;
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_free_ibi(struct i3c_dev_desc *dev)
> > > > > +{
> > > > > +	struct i3c_master_controller *m = i3c_dev_get_master(dev);
> > > > > +	struct adi_i3c_master *master = to_adi_i3c_master(m);
> > > > > +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> > > > > +	unsigned long flags;
> > > > > +
> > > > > +	spin_lock_irqsave(&master->ibi.lock, flags);
> > > > > +	master->ibi.slots[data->ibi] = NULL;
> > > > > +	data->ibi = -1;
> > > > > +	spin_unlock_irqrestore(&master->ibi.lock, flags);
> > > > > +
> > > > > +	i3c_generic_ibi_free_pool(data->ibi_pool);
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev,
> > > > > +					    struct i3c_ibi_slot *slot)
> > > > > +{
> > > > > +	struct adi_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
> > > > > +
> > > > > +	i3c_generic_ibi_recycle_slot(data->ibi_pool, slot);
> > > > > +}
> > > > > +
> > > > > +static const struct i3c_master_controller_ops adi_i3c_master_ops = {
> > > > > +	.bus_init = adi_i3c_master_bus_init,
> > > > > +	.bus_cleanup = adi_i3c_master_bus_cleanup,
> > > > > +	.attach_i3c_dev = adi_i3c_master_attach_i3c_dev,
> > > > > +	.reattach_i3c_dev = adi_i3c_master_reattach_i3c_dev,
> > > > > +	.detach_i3c_dev = adi_i3c_master_detach_i3c_dev,
> > > > > +	.attach_i2c_dev = adi_i3c_master_attach_i2c_dev,
> > > > > +	.detach_i2c_dev = adi_i3c_master_detach_i2c_dev,
> > > > > +	.do_daa = adi_i3c_master_do_daa,
> > > > > +	.supports_ccc_cmd = adi_i3c_master_supports_ccc_cmd,
> > > > > +	.send_ccc_cmd = adi_i3c_master_send_ccc_cmd,
> > > > > +	.priv_xfers = adi_i3c_master_priv_xfers,
> > > > > +	.i2c_xfers = adi_i3c_master_i2c_xfers,
> > > > > +	.request_ibi = adi_i3c_master_request_ibi,
> > > > > +	.enable_ibi = adi_i3c_master_enable_ibi,
> > > > > +	.disable_ibi = adi_i3c_master_disable_ibi,
> > > > > +	.free_ibi = adi_i3c_master_free_ibi,
> > > > > +	.recycle_ibi_slot = adi_i3c_master_recycle_ibi_slot,
> > > > > +};
> > > > > +
> > > > > +static const struct of_device_id adi_i3c_master_of_match[] = {
> > > > > +	{ .compatible = "adi,i3c-master" },
> > > > > +	{}
> > > > > +};
> > > > > +
> > > > > +static int adi_i3c_master_probe(struct platform_device *pdev)
> > > > > +{
> > > > > +	struct adi_i3c_master *master;
> > > > > +	unsigned int version;
> > > > > +	int ret, irq;
> > > > > +
> > > > > +	master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL);
> > > > > +	if (!master)
> > > > > +		return -ENOMEM;
> > > > > +
> > > > > +	master->regs = devm_platform_ioremap_resource(pdev, 0);
> > > > > +	if (IS_ERR(master->regs))
> > > > > +		return PTR_ERR(master->regs);
> > > > > +
> > > > > +	master->clk = devm_clk_get_enabled(&pdev->dev, "axi");
> > > > > +	if (IS_ERR(master->clk))
> > > > > +		return PTR_ERR(master->clk);
> > > >
> > > > you have optional clock i3c, not use it in driver?
> > > >
> > > It is not used in the driver indeed, it describes how the IP Core is
> > > compiled and wired, but doesn't affect the logic in the driver itself.
> > > The RTL logic is always driven by a 100MHz clock, and the source is
> > > either axi or i3c, if the latter present.
> >
> > can you use devm_clk_bulk_get_all_enabled() ?
> >
> I believe so, I would keep the required assertion like this then:
>
> 	ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &clk);
> 	if (ret < 0)
> 		return dev_err_probe(&pdev->dev, ret,
> 				     "Failed to get clocks\n");
> 	while (ret--)
> 		if (clk[ret].id && !strcmp(clk[ret].id, "axi"))
> 			break;
> 	if (ret == -1)
> 		return dev_err_probe(&pdev->dev, -ENODEV,
> 				     "Failed to get clock axi\n");

Generally, you needn't check it. It depend on CHECK_DTB to verify dts is
correct.

You only need iterate it when need get a special clock rate or advance
requiremnt.

Frank
>
> Best regards,
> Jorge
>
> > > > > +
> > > > > +	irq = platform_get_irq(pdev, 0);
> > > > > +	if (irq < 0)
> > > > > +		return irq;
> > > > > +
> > > > > +	version = readl(master->regs + REG_VERSION);
> > > > > +	if (VERSION_MAJOR(version) != 0) {
> > > > > +		dev_err(&pdev->dev, "Unsupported IP version %u.%u.%c\n",
> > > > > +			VERSION_MAJOR(version),
> > > > > +			VERSION_MINOR(version),
> > > > > +			VERSION_PATCH(version));
> > > > > +		return -EINVAL;
> > > >
> > > > return dev_err_probe();
> > > Ack.
> > > >
> > > > Frank
> > > Best regards,
> > > Jorge
> > > > > +	}
> > > > > +
> > > > > +	writel(0x00, master->regs + REG_ENABLE);
> > > > > +	writel(0x00, master->regs + REG_IRQ_MASK);
> > > > > +
> > > > > +	ret = devm_request_irq(&pdev->dev, irq, adi_i3c_master_irq, 0,
> > > > > +			       dev_name(&pdev->dev), master);
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +
> > > > > +	platform_set_drvdata(pdev, master);
> > > > > +
> > > > > +	master->maxdevs = MAX_DEVS;
> > > > > +	master->free_rr_slots = GENMASK(master->maxdevs, 1);
> > > > > +
> > > > > +	writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
> > > > > +
> > > > > +	spin_lock_init(&master->ibi.lock);
> > > > > +	master->ibi.num_slots = 15;
> > > > > +	master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots,
> > > > > +					 sizeof(*master->ibi.slots),
> > > > > +					 GFP_KERNEL);
> > > > > +	if (!master->ibi.slots)
> > > > > +		return -ENOMEM;
> > > > > +
> > > > > +	return i3c_master_register(&master->base, &pdev->dev,
> > > > > +				   &adi_i3c_master_ops, false);
> > > > > +}
> > > > > +
> > > > > +static void adi_i3c_master_remove(struct platform_device *pdev)
> > > > > +{
> > > > > +	struct adi_i3c_master *master = platform_get_drvdata(pdev);
> > > > > +
> > > > > +	writel(0xff, master->regs + REG_IRQ_PENDING);
> > > > > +	writel(0x00, master->regs + REG_IRQ_MASK);
> > > > > +	writel(0x01, master->regs + REG_ENABLE);
> > > > > +
> > > > > +	i3c_master_unregister(&master->base);
> > > > > +}
> > > > > +
> > > > > +static struct platform_driver adi_i3c_master = {
> > > > > +	.probe = adi_i3c_master_probe,
> > > > > +	.remove = adi_i3c_master_remove,
> > > > > +	.driver = {
> > > > > +		.name = "adi-i3c-master",
> > > > > +		.of_match_table = adi_i3c_master_of_match,
> > > > > +	},
> > > > > +};
> > > > > +module_platform_driver(adi_i3c_master);
> > > > > +
> > > > > +MODULE_AUTHOR("Jorge Marques <jorge.marques@analog.com>");
> > > > > +MODULE_DESCRIPTION("Analog Devices I3C master driver");
> > > > > +MODULE_LICENSE("GPL");
> > > > >
> > > > > --
> > > > > 2.49.0
> > > > >

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2025-06-19 15:11 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-18  7:16 [PATCH v3 0/2] Add ADI I3C Controller Jorge Marques
2025-06-18  7:16 ` [PATCH v3 1/2] dt-bindings: i3c: Add adi-i3c-master Jorge Marques
2025-06-18  8:40   ` Rob Herring (Arm)
2025-06-18  9:21   ` Krzysztof Kozlowski
2025-06-18 12:15     ` Jorge Marques
2025-06-18 15:45       ` Krzysztof Kozlowski
2025-06-18 19:55         ` Jorge Marques
2025-06-19  6:15           ` Krzysztof Kozlowski
2025-06-18  7:16 ` [PATCH v3 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP Jorge Marques
2025-06-18 19:40   ` Frank Li
2025-06-18 22:17     ` Jorge Marques
2025-06-19  2:18       ` Frank Li
2025-06-19 10:32         ` Jorge Marques
2025-06-19 15:10           ` Frank Li

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).