* [PATCH v7 0/2] Add ADI I3C Controller
@ 2025-08-18 11:51 Jorge Marques
2025-08-18 11:51 ` [PATCH v7 1/2] dt-bindings: i3c: Add adi-i3c-master Jorge Marques
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: Jorge Marques @ 2025-08-18 11:51 UTC (permalink / raw)
To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Kees Cook, Gustavo A. R. Silva
Cc: linux-i3c, devicetree, linux-kernel, gastmaier, linux-hardening,
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.
The IP Core is versioned following ADI's open source HDL guidelines
for devicetree bindings and drivers described at
https://analogdevicesinc.github.io/hdl/user_guide/contributing.html#devicetree-bindings-drivers
in summary, follows Semantic Versioning 2.0.0, with the dt-binding suffixed
by -v<major>.
If necessary, the contents of
https://analogdevicesinc.github.io/hdl/user_guide/contributing.html#devicetree-bindings-drivers
can be replicated to a file in a different series, similar to AMD Xilinx
at Documentation/devicetree/bindings/xilinx.txt, but as adi.txt or
similar.
Depends on https://lore.kernel.org/linux-i3c/20250622-i3c-writesl-readsl-v2-0-2afd34ec6306@analog.com/T/#t
Signed-off-by: Jorge Marques <jorge.marques@analog.com>
---
Changes in v7:
- Edit cover linking guidelines to ADI IP Core versioning.
Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml:
- Extend second clock description to explain relation to synthesized topology.
- Link to v6: https://lore.kernel.org/r/20250717-adi-i3c-master-v6-0-6c687ed71bed@analog.com
Changes in v6:
- Format 0x05C undercase
- Link to v5: https://lore.kernel.org/r/20250715-adi-i3c-master-v5-0-c89434cbaf5e@analog.com
Changes in v5:
Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml:
- Use semantic versioning major field for dt-binding compatible, with
the format adi,<ip-name>-v<major>
adi-i3c-master.c:
- Rename MAX_DEVS to ADI_MAX_DEVS
- Encapsulate REG_CMD_FIFO_0_DEV_ADDR var
- Reorder struct adi_i3c_i2c_dev_data fields
- Start addr at 0 instead of 8 at adi_i3c_master_get_rr_slot
- Minor rework on adi_i3c_master_handle_ibi to most logic out of the
lock. Even if length is 0 (BCR[2]=0), the mdb field is extracted and
written to the slot buffer. Since the length is 0, the written data
doesn't matter.
In a future update with additional bytes support (e.g., bits 31-23),
len would be incremented and an IBI FIFO would be read.
- Version check against first stable release, major v1.
Driver+RTL features updates affect the minor field, therefore check
for major == 1.
- Link to v4: https://lore.kernel.org/r/20250626-adi-i3c-master-v4-0-3846a1f66d5e@analog.com
Changes in v4:
Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml:
- Add -1.00.a suffix where missing
- Extend clocks descriptions
- Add minItems to clock-names, to match clocks
- Use header in example
adi-i3c-master.c:
- Regmap:
- Add new controller info registers (dyn_addr, dcr, bcr, pid)
- Always decreasing fields
- Add line break between registers
- Reformat REG_DEV_CHAR_BSCR_IBI to use easier to read FIELD_GET,
FIELD_PREP
- Read controller info from regmap with explanation comment
- Use linux/fpga/adi-axi-common.h macros
- Use __counter_by macro on ncmds
- Use __free macro
- Use new i3c_writel_fifo and i3c_readl_fifo macros
- Rename bytes to buf when nbytes is present
- Use scoped_guard instead of spin_lock, spin_unlock
- Reformat loops to read fifo status, use while single line alternative
- Drop adi_i3c_master.max_devs, use MAX_DEVS directly
- Use devm_clk_bulk_get_all_enabled, dropping clock name match (CHECK_DTB does it)
- Init spin_lock
- Init list head
- Link to v3: https://lore.kernel.org/r/20250618-adi-i3c-master-v3-0-e66170a6cb95@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 | 72 ++
MAINTAINERS | 6 +
drivers/i3c/master/Kconfig | 11 +
drivers/i3c/master/Makefile | 1 +
drivers/i3c/master/adi-i3c-master.c | 1019 ++++++++++++++++++++
5 files changed, 1109 insertions(+)
---
base-commit: 51963783b876a2f493a3eac0ea9eba271cb6809a
change-id: 20250604-adi-i3c-master-2a5148c58c47
prerequisite-message-id: <20250622-i3c-writesl-readsl-v2-0-2afd34ec6306@analog.com>
prerequisite-patch-id: 5443f14ca82fc08593960fafdb43488cce56f7d9
prerequisite-patch-id: 647084f5fe09f4887f633b0b02b306912a156672
prerequisite-patch-id: 6f582bb2ef1aafb66f26c515a19d5efa06ab8968
Best regards,
--
Jorge Marques <jorge.marques@analog.com>
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v7 1/2] dt-bindings: i3c: Add adi-i3c-master
2025-08-18 11:51 [PATCH v7 0/2] Add ADI I3C Controller Jorge Marques
@ 2025-08-18 11:51 ` Jorge Marques
2025-08-18 17:01 ` Conor Dooley
2025-08-26 15:47 ` Frank Li
2025-08-18 11:51 ` [PATCH v7 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP Jorge Marques
2025-08-26 15:56 ` [PATCH v7 0/2] Add ADI I3C Controller Frank Li
2 siblings, 2 replies; 9+ messages in thread
From: Jorge Marques @ 2025-08-18 11:51 UTC (permalink / raw)
To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Kees Cook, Gustavo A. R. Silva
Cc: linux-i3c, devicetree, linux-kernel, gastmaier, linux-hardening,
Jorge Marques
Add bindings doc for ADI I3C Controller IP core, a FPGA synthesizable IP
core that implements the MIPI I3C Basic controller specification.
The IP Core is versioned following Semantic Versioning 2.0.0 and
ADI's open-source HDL guidelines for devicetree bindings and drivers.
Signed-off-by: Jorge Marques <jorge.marques@analog.com>
---
.../devicetree/bindings/i3c/adi,i3c-master.yaml | 72 ++++++++++++++++++++++
MAINTAINERS | 5 ++
2 files changed, 77 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..fda240f9ee0c73bcbea97f775d6e081fe0c089d9
--- /dev/null
+++ b/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
@@ -0,0 +1,72 @@
+# 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. The IP core is tested
+ on arm, microblaze, and arm64 architectures.
+
+ https://analogdevicesinc.github.io/hdl/library/i3c_controller
+
+maintainers:
+ - Jorge Marques <jorge.marques@analog.com>
+
+properties:
+ compatible:
+ const: adi,i3c-master-v1
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ minItems: 1
+ items:
+ - description: The AXI interconnect clock, drives the register map.
+ - description: |
+ The secondary clock, drives the internal logic asynchronously to the
+ register map. The presence of this entry states that the IP Core was
+ synthesized with a second clock input, and the absence of this entry
+ indicates a topology where a single clock input drives all the
+ internal logic.
+
+ clock-names:
+ minItems: 1
+ items:
+ - const: axi
+ - const: i3c
+
+ interrupts:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - interrupts
+
+allOf:
+ - $ref: i3c.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i3c@44a00000 {
+ compatible = "adi,i3c-master-v1";
+ reg = <0x44a00000 0x1000>;
+ interrupts = <3 IRQ_TYPE_LEVEL_HIGH>;
+ 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 35ed8498ab1e9b92b4bce5db9bb64004d80e4b1a..faa50535b514037ddf97309874ec64aac2013cb6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11452,6 +11452,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] 9+ messages in thread
* [PATCH v7 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP
2025-08-18 11:51 [PATCH v7 0/2] Add ADI I3C Controller Jorge Marques
2025-08-18 11:51 ` [PATCH v7 1/2] dt-bindings: i3c: Add adi-i3c-master Jorge Marques
@ 2025-08-18 11:51 ` Jorge Marques
2025-08-26 15:57 ` Frank Li
2025-08-26 15:56 ` [PATCH v7 0/2] Add ADI I3C Controller Frank Li
2 siblings, 1 reply; 9+ messages in thread
From: Jorge Marques @ 2025-08-18 11:51 UTC (permalink / raw)
To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Kees Cook, Gustavo A. R. Silva
Cc: linux-i3c, devicetree, linux-kernel, gastmaier, linux-hardening,
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 | 1019 +++++++++++++++++++++++++++++++++++
4 files changed, 1032 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index faa50535b514037ddf97309874ec64aac2013cb6..aeb39657c55a46b89d17c6e4039a2642b62634d0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11456,6 +11456,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 13df2944f2ec9a0aa7e4dfa46f9a52ad19ba9bb2..82cf330778d5a789c0df7c10bd527a45549c1594 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 aac74f3e3851441fd2974f6b1f77c569a9ed1cc6..816a227b6f7acf0a99971e385fdef6a405e20040 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..162f9eed39aa5ef6f656239dcd8bfc5fe6281264
--- /dev/null
+++ b/drivers/i3c/master/adi-i3c-master.c
@@ -0,0 +1,1019 @@
+// 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/fpga/adi-axi-common.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>
+
+#include "../internals.h"
+
+#define ADI_MAX_DEVS 16
+#define ADI_HAS_MDB_FROM_BCR(x) (FIELD_GET(BIT(2), (x)))
+
+#define REG_ENABLE 0x040
+
+#define REG_PID_L 0x054
+#define REG_PID_H 0x058
+#define REG_DCR_BCR_DA 0x05c
+#define REG_DCR_BCR_DA_GET_DA(x) FIELD_GET(GENMASK(22, 16), (x))
+#define REG_DCR_BCR_DA_GET_BCR(x) FIELD_GET(GENMASK(15, 8), (x))
+#define REG_DCR_BCR_DA_GET_DCR(x) FIELD_GET(GENMASK(7, 0), (x))
+
+#define REG_IRQ_MASK 0x080
+#define REG_IRQ_PENDING 0x084
+#define REG_IRQ_PENDING_DAA BIT(7)
+#define REG_IRQ_PENDING_IBI BIT(6)
+#define REG_IRQ_PENDING_CMDR BIT(5)
+
+#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_UDA_ERROR 8
+#define REG_CMDR_FIFO_NACK_RESP 6
+#define REG_CMDR_FIFO_CE2_ERROR 4
+#define REG_CMDR_FIFO_CE0_ERROR 1
+#define REG_CMDR_FIFO_NO_ERROR 0
+#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_PP_SG_MASK GENMASK(6, 5)
+#define REG_OPS_SET_SG(x) FIELD_PREP(REG_OPS_PP_SG_MASK, (x))
+
+#define REG_IBI_CONFIG 0x140
+#define REG_IBI_CONFIG_ENABLE BIT(0)
+#define REG_IBI_CONFIG_LISTEN BIT(1)
+
+#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) FIELD_PREP(GENMASK(3, 2), (x))
+#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[] __counted_by(ncmds);
+};
+
+struct adi_i3c_master {
+ struct i3c_master_controller base;
+ u32 free_rr_slots;
+ 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[ADI_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 *buf, unsigned int nbytes)
+{
+ unsigned int n, m;
+
+ n = readl(master->regs + REG_SDO_FIFO_ROOM);
+ m = min(n, nbytes);
+ i3c_writel_fifo(master->regs + REG_SDO_FIFO, buf, nbytes);
+}
+
+static void adi_i3c_master_rd_from_rx_fifo(struct adi_i3c_master *master,
+ u8 *buf, unsigned int nbytes)
+{
+ i3c_readl_fifo(master->regs + REG_SDI_FIFO, buf, nbytes);
+}
+
+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;
+
+ if (!xfer)
+ return;
+
+ 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_comp; 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 __free(kfree) = NULL;
+ 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]);
+
+ 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 __free(kfree) = NULL;
+ 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]);
+
+ return ret;
+}
+
+struct adi_i3c_i2c_dev_data {
+ struct i3c_generic_ibi_pool *ibi_pool;
+ u16 id;
+ s16 ibi;
+};
+
+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->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;
+ u32 bcr_ibi;
+ 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);
+ bcr_ibi = FIELD_GET(I3C_BCR_IBI_PAYLOAD | I3C_BCR_IBI_REQ_CAP, (i3cdev->info.bcr));
+ writel(readl(master->regs + REG_DEV_CHAR) |
+ REG_DEV_CHAR_BCR_IBI(bcr_ibi) | 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)
+{
+ u32 buf;
+
+ /* Dynamic address and PID are for identification only */
+ memset(info, 0, sizeof(*info));
+ buf = readl(master->regs + REG_DCR_BCR_DA);
+ info->dyn_addr = REG_DCR_BCR_DA_GET_DA(buf);
+ info->dcr = REG_DCR_BCR_DA_GET_DCR(buf);
+ info->bcr = REG_DCR_BCR_DA_GET_BCR(buf);
+ info->pid = readl(master->regs + REG_PID_L);
+ info->pid |= (u64)readl(master->regs + REG_PID_H) << 32;
+}
+
+static int adi_i3c_master_do_daa(struct i3c_master_controller *m)
+{
+ struct adi_i3c_master *master = to_adi_i3c_master(m);
+ int ret, addr = 0;
+ u32 irq_mask;
+
+ for (u8 i = 0; i < ADI_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 raw)
+{
+ struct adi_i3c_i2c_dev_data *data;
+ struct i3c_ibi_slot *slot;
+ struct i3c_dev_desc *dev;
+ u8 da, id, mdb, len;
+ u8 *buf;
+
+ da = FIELD_GET(GENMASK(23, 17), raw);
+ mdb = FIELD_GET(GENMASK(15, 8), raw);
+ 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];
+ len = ADI_HAS_MDB_FROM_BCR(dev->info.bcr);
+ data = i3c_dev_get_master_data(dev);
+
+ guard(spinlock)(&master->ibi.lock);
+ slot = i3c_generic_ibi_get_free_slot(data->ibi_pool);
+ if (!slot)
+ return;
+
+ slot->len = len;
+ buf = slot->data;
+ buf[0] = mdb;
+ i3c_master_queue_ibi(dev, slot);
+}
+
+static void adi_i3c_master_demux_ibis(struct adi_i3c_master *master)
+{
+ while (!(readl(master->regs + REG_FIFO_STATUS) & REG_FIFO_STATUS_IBI_EMPTY)) {
+ u32 raw = readl(master->regs + REG_IBI_FIFO);
+
+ adi_i3c_master_handle_ibi(master, raw);
+ }
+}
+
+static void adi_i3c_master_handle_da_req(struct adi_i3c_master *master)
+{
+ u8 payload0[8];
+ u32 addr;
+
+ 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) {
+ scoped_guard(spinlock_irqsave, &master->xferqueue.lock) {
+ adi_i3c_master_end_xfer_locked(master, pending);
+ }
+ }
+ 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 __free(kfree) = NULL;
+ int i;
+
+ 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);
+
+ return xfer->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 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);
+
+ scoped_guard(spinlock_irqsave, &master->ibi.lock) {
+ for (i = 0; i < master->ibi.num_slots; i++) {
+ if (!master->ibi.slots[i]) {
+ data->ibi = i;
+ master->ibi.slots[i] = dev;
+ break;
+ }
+ }
+ }
+
+ 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);
+
+ scoped_guard(spinlock_irqsave, &master->ibi.lock) {
+ master->ibi.slots[data->ibi] = NULL;
+ }
+
+ 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-v1" },
+ {}
+};
+
+static int adi_i3c_master_probe(struct platform_device *pdev)
+{
+ struct adi_i3c_master *master;
+ struct clk_bulk_data *clk;
+ 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);
+
+ 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");
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ version = readl(master->regs + ADI_AXI_REG_VERSION);
+ if (ADI_AXI_PCORE_VER_MAJOR(version) != 1)
+ dev_err_probe(&pdev->dev, -ENODEV, "Unsupported peripheral version %u.%u.%u\n",
+ ADI_AXI_PCORE_VER_MAJOR(version),
+ ADI_AXI_PCORE_VER_MINOR(version),
+ ADI_AXI_PCORE_VER_PATCH(version));
+
+ 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->free_rr_slots = GENMASK(ADI_MAX_DEVS, 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;
+
+ spin_lock_init(&master->xferqueue.lock);
+ INIT_LIST_HEAD(&master->xferqueue.list);
+
+ 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] 9+ messages in thread
* Re: [PATCH v7 1/2] dt-bindings: i3c: Add adi-i3c-master
2025-08-18 11:51 ` [PATCH v7 1/2] dt-bindings: i3c: Add adi-i3c-master Jorge Marques
@ 2025-08-18 17:01 ` Conor Dooley
2025-08-26 15:47 ` Frank Li
1 sibling, 0 replies; 9+ messages in thread
From: Conor Dooley @ 2025-08-18 17:01 UTC (permalink / raw)
To: Jorge Marques
Cc: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Kees Cook, Gustavo A. R. Silva, linux-i3c,
devicetree, linux-kernel, gastmaier, linux-hardening
[-- Attachment #1: Type: text/plain, Size: 471 bytes --]
On Mon, Aug 18, 2025 at 01:51:13PM +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.
> The IP Core is versioned following Semantic Versioning 2.0.0 and
> ADI's open-source HDL guidelines for devicetree bindings and drivers.
>
> Signed-off-by: Jorge Marques <jorge.marques@analog.com>
Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v7 1/2] dt-bindings: i3c: Add adi-i3c-master
2025-08-18 11:51 ` [PATCH v7 1/2] dt-bindings: i3c: Add adi-i3c-master Jorge Marques
2025-08-18 17:01 ` Conor Dooley
@ 2025-08-26 15:47 ` Frank Li
2025-08-27 12:12 ` Jorge Marques
1 sibling, 1 reply; 9+ messages in thread
From: Frank Li @ 2025-08-26 15:47 UTC (permalink / raw)
To: Jorge Marques
Cc: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kees Cook, Gustavo A. R. Silva, linux-i3c, devicetree,
linux-kernel, gastmaier, linux-hardening
On Mon, Aug 18, 2025 at 01:51:13PM +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.
> The IP Core is versioned following Semantic Versioning 2.0.0 and
> ADI's open-source HDL guidelines for devicetree bindings and drivers.
>
> Signed-off-by: Jorge Marques <jorge.marques@analog.com>
> ---
> .../devicetree/bindings/i3c/adi,i3c-master.yaml | 72 ++++++++++++++++++++++
> MAINTAINERS | 5 ++
> 2 files changed, 77 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..fda240f9ee0c73bcbea97f775d6e081fe0c089d9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
> @@ -0,0 +1,72 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
...
> +
> + clocks:
> + minItems: 1
> + items:
> + - description: The AXI interconnect clock, drives the register map.
> + - description: |
Needn't | here.
Frank
> + The secondary clock, drives the internal logic asynchronously to the
> + register map. The presence of this entry states that the IP Core was
> + synthesized with a second clock input, and the absence of this entry
> + indicates a topology where a single clock input drives all the
> + internal logic.
> +
> + clock-names:
> + minItems: 1
> + items:
> + - const: axi
> + - const: i3c
> +
> + interrupts:
> + maxItems: 1
> +
> +required:
> + - compatible
> + - reg
> + - clocks
> + - clock-names
> + - interrupts
> +
> +allOf:
> + - $ref: i3c.yaml#
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + i3c@44a00000 {
> + compatible = "adi,i3c-master-v1";
> + reg = <0x44a00000 0x1000>;
> + interrupts = <3 IRQ_TYPE_LEVEL_HIGH>;
> + 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 35ed8498ab1e9b92b4bce5db9bb64004d80e4b1a..faa50535b514037ddf97309874ec64aac2013cb6 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -11452,6 +11452,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 [flat|nested] 9+ messages in thread
* Re: [PATCH v7 0/2] Add ADI I3C Controller
2025-08-18 11:51 [PATCH v7 0/2] Add ADI I3C Controller Jorge Marques
2025-08-18 11:51 ` [PATCH v7 1/2] dt-bindings: i3c: Add adi-i3c-master Jorge Marques
2025-08-18 11:51 ` [PATCH v7 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP Jorge Marques
@ 2025-08-26 15:56 ` Frank Li
2025-08-27 12:15 ` Jorge Marques
2 siblings, 1 reply; 9+ messages in thread
From: Frank Li @ 2025-08-26 15:56 UTC (permalink / raw)
To: Jorge Marques
Cc: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kees Cook, Gustavo A. R. Silva, linux-i3c, devicetree,
linux-kernel, gastmaier, linux-hardening
On Mon, Aug 18, 2025 at 01:51:12PM +0200, Jorge Marques wrote:
> 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.
>
> The IP Core is versioned following ADI's open source HDL guidelines
> for devicetree bindings and drivers described at
> https://analogdevicesinc.github.io/hdl/user_guide/contributing.html#devicetree-bindings-drivers
> in summary, follows Semantic Versioning 2.0.0, with the dt-binding suffixed
> by -v<major>.
>
> If necessary, the contents of
> https://analogdevicesinc.github.io/hdl/user_guide/contributing.html#devicetree-bindings-drivers
> can be replicated to a file in a different series, similar to AMD Xilinx
> at Documentation/devicetree/bindings/xilinx.txt, but as adi.txt or
> similar.
>
> Depends on https://lore.kernel.org/linux-i3c/20250622-i3c-writesl-readsl-v2-0-2afd34ec6306@analog.com/T/#t
It is already in v6.17-rc1. You can remove this depends.
commit 733b439375b494e8a6950ab47d18a4b615b73cb3
Author: Jorge Marques <jorge.marques@analog.com>
Date: Tue Jun 24 11:06:04 2025 +0200
i3c: master: Add inline i3c_readl_fifo() and i3c_writel_fifo()
The I3C abstraction expects u8 buffers, but some controllers operate with
a 32-bit bus width FIFO and cannot flag valid bytes individually. To avoid
reading or writing outside the buffer bounds, use 32-bit accesses where
possible and apply memcpy for any remaining bytes
Signed-off-by: Jorge Marques <jorge.marques@analog.com>
Suggested-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Reviewed-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Tested-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Link: https://lore.kernel.org/r/20250624-i3c-writesl-readsl-v3-1-63ccf0870f01@analog.com
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
>
> Signed-off-by: Jorge Marques <jorge.marques@analog.com>
> ---
> Changes in v7:
> - Edit cover linking guidelines to ADI IP Core versioning.
> Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml:
> - Extend second clock description to explain relation to synthesized topology.
> - Link to v6: https://lore.kernel.org/r/20250717-adi-i3c-master-v6-0-6c687ed71bed@analog.com
>
> Changes in v6:
> - Format 0x05C undercase
> - Link to v5: https://lore.kernel.org/r/20250715-adi-i3c-master-v5-0-c89434cbaf5e@analog.com
>
> Changes in v5:
> Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml:
> - Use semantic versioning major field for dt-binding compatible, with
> the format adi,<ip-name>-v<major>
>
> adi-i3c-master.c:
> - Rename MAX_DEVS to ADI_MAX_DEVS
> - Encapsulate REG_CMD_FIFO_0_DEV_ADDR var
> - Reorder struct adi_i3c_i2c_dev_data fields
> - Start addr at 0 instead of 8 at adi_i3c_master_get_rr_slot
> - Minor rework on adi_i3c_master_handle_ibi to most logic out of the
> lock. Even if length is 0 (BCR[2]=0), the mdb field is extracted and
> written to the slot buffer. Since the length is 0, the written data
> doesn't matter.
> In a future update with additional bytes support (e.g., bits 31-23),
> len would be incremented and an IBI FIFO would be read.
> - Version check against first stable release, major v1.
> Driver+RTL features updates affect the minor field, therefore check
> for major == 1.
> - Link to v4: https://lore.kernel.org/r/20250626-adi-i3c-master-v4-0-3846a1f66d5e@analog.com
>
> Changes in v4:
> Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml:
> - Add -1.00.a suffix where missing
> - Extend clocks descriptions
> - Add minItems to clock-names, to match clocks
> - Use header in example
>
> adi-i3c-master.c:
> - Regmap:
> - Add new controller info registers (dyn_addr, dcr, bcr, pid)
> - Always decreasing fields
> - Add line break between registers
> - Reformat REG_DEV_CHAR_BSCR_IBI to use easier to read FIELD_GET,
> FIELD_PREP
> - Read controller info from regmap with explanation comment
> - Use linux/fpga/adi-axi-common.h macros
> - Use __counter_by macro on ncmds
> - Use __free macro
> - Use new i3c_writel_fifo and i3c_readl_fifo macros
> - Rename bytes to buf when nbytes is present
> - Use scoped_guard instead of spin_lock, spin_unlock
> - Reformat loops to read fifo status, use while single line alternative
> - Drop adi_i3c_master.max_devs, use MAX_DEVS directly
> - Use devm_clk_bulk_get_all_enabled, dropping clock name match (CHECK_DTB does it)
> - Init spin_lock
> - Init list head
> - Link to v3: https://lore.kernel.org/r/20250618-adi-i3c-master-v3-0-e66170a6cb95@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 | 72 ++
> MAINTAINERS | 6 +
> drivers/i3c/master/Kconfig | 11 +
> drivers/i3c/master/Makefile | 1 +
> drivers/i3c/master/adi-i3c-master.c | 1019 ++++++++++++++++++++
> 5 files changed, 1109 insertions(+)
> ---
> base-commit: 51963783b876a2f493a3eac0ea9eba271cb6809a
> change-id: 20250604-adi-i3c-master-2a5148c58c47
> prerequisite-message-id: <20250622-i3c-writesl-readsl-v2-0-2afd34ec6306@analog.com>
> prerequisite-patch-id: 5443f14ca82fc08593960fafdb43488cce56f7d9
> prerequisite-patch-id: 647084f5fe09f4887f633b0b02b306912a156672
> prerequisite-patch-id: 6f582bb2ef1aafb66f26c515a19d5efa06ab8968
>
> Best regards,
> --
> Jorge Marques <jorge.marques@analog.com>
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v7 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP
2025-08-18 11:51 ` [PATCH v7 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP Jorge Marques
@ 2025-08-26 15:57 ` Frank Li
0 siblings, 0 replies; 9+ messages in thread
From: Frank Li @ 2025-08-26 15:57 UTC (permalink / raw)
To: Jorge Marques
Cc: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kees Cook, Gustavo A. R. Silva, linux-i3c, devicetree,
linux-kernel, gastmaier, linux-hardening
On Mon, Aug 18, 2025 at 01:51:14PM +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>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> ---
> MAINTAINERS | 1 +
> drivers/i3c/master/Kconfig | 11 +
> drivers/i3c/master/Makefile | 1 +
> drivers/i3c/master/adi-i3c-master.c | 1019 +++++++++++++++++++++++++++++++++++
> 4 files changed, 1032 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index faa50535b514037ddf97309874ec64aac2013cb6..aeb39657c55a46b89d17c6e4039a2642b62634d0 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -11456,6 +11456,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 13df2944f2ec9a0aa7e4dfa46f9a52ad19ba9bb2..82cf330778d5a789c0df7c10bd527a45549c1594 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 aac74f3e3851441fd2974f6b1f77c569a9ed1cc6..816a227b6f7acf0a99971e385fdef6a405e20040 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..162f9eed39aa5ef6f656239dcd8bfc5fe6281264
> --- /dev/null
> +++ b/drivers/i3c/master/adi-i3c-master.c
> @@ -0,0 +1,1019 @@
> +// 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/fpga/adi-axi-common.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>
> +
> +#include "../internals.h"
> +
> +#define ADI_MAX_DEVS 16
> +#define ADI_HAS_MDB_FROM_BCR(x) (FIELD_GET(BIT(2), (x)))
> +
> +#define REG_ENABLE 0x040
> +
> +#define REG_PID_L 0x054
> +#define REG_PID_H 0x058
> +#define REG_DCR_BCR_DA 0x05c
> +#define REG_DCR_BCR_DA_GET_DA(x) FIELD_GET(GENMASK(22, 16), (x))
> +#define REG_DCR_BCR_DA_GET_BCR(x) FIELD_GET(GENMASK(15, 8), (x))
> +#define REG_DCR_BCR_DA_GET_DCR(x) FIELD_GET(GENMASK(7, 0), (x))
> +
> +#define REG_IRQ_MASK 0x080
> +#define REG_IRQ_PENDING 0x084
> +#define REG_IRQ_PENDING_DAA BIT(7)
> +#define REG_IRQ_PENDING_IBI BIT(6)
> +#define REG_IRQ_PENDING_CMDR BIT(5)
> +
> +#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_UDA_ERROR 8
> +#define REG_CMDR_FIFO_NACK_RESP 6
> +#define REG_CMDR_FIFO_CE2_ERROR 4
> +#define REG_CMDR_FIFO_CE0_ERROR 1
> +#define REG_CMDR_FIFO_NO_ERROR 0
> +#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_PP_SG_MASK GENMASK(6, 5)
> +#define REG_OPS_SET_SG(x) FIELD_PREP(REG_OPS_PP_SG_MASK, (x))
> +
> +#define REG_IBI_CONFIG 0x140
> +#define REG_IBI_CONFIG_ENABLE BIT(0)
> +#define REG_IBI_CONFIG_LISTEN BIT(1)
> +
> +#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) FIELD_PREP(GENMASK(3, 2), (x))
> +#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[] __counted_by(ncmds);
> +};
> +
> +struct adi_i3c_master {
> + struct i3c_master_controller base;
> + u32 free_rr_slots;
> + 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[ADI_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 *buf, unsigned int nbytes)
> +{
> + unsigned int n, m;
> +
> + n = readl(master->regs + REG_SDO_FIFO_ROOM);
> + m = min(n, nbytes);
> + i3c_writel_fifo(master->regs + REG_SDO_FIFO, buf, nbytes);
> +}
> +
> +static void adi_i3c_master_rd_from_rx_fifo(struct adi_i3c_master *master,
> + u8 *buf, unsigned int nbytes)
> +{
> + i3c_readl_fifo(master->regs + REG_SDI_FIFO, buf, nbytes);
> +}
> +
> +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;
> +
> + if (!xfer)
> + return;
> +
> + 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_comp; 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 __free(kfree) = NULL;
> + 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]);
> +
> + 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 __free(kfree) = NULL;
> + 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]);
> +
> + return ret;
> +}
> +
> +struct adi_i3c_i2c_dev_data {
> + struct i3c_generic_ibi_pool *ibi_pool;
> + u16 id;
> + s16 ibi;
> +};
> +
> +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->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;
> + u32 bcr_ibi;
> + 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);
> + bcr_ibi = FIELD_GET(I3C_BCR_IBI_PAYLOAD | I3C_BCR_IBI_REQ_CAP, (i3cdev->info.bcr));
> + writel(readl(master->regs + REG_DEV_CHAR) |
> + REG_DEV_CHAR_BCR_IBI(bcr_ibi) | 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)
> +{
> + u32 buf;
> +
> + /* Dynamic address and PID are for identification only */
> + memset(info, 0, sizeof(*info));
> + buf = readl(master->regs + REG_DCR_BCR_DA);
> + info->dyn_addr = REG_DCR_BCR_DA_GET_DA(buf);
> + info->dcr = REG_DCR_BCR_DA_GET_DCR(buf);
> + info->bcr = REG_DCR_BCR_DA_GET_BCR(buf);
> + info->pid = readl(master->regs + REG_PID_L);
> + info->pid |= (u64)readl(master->regs + REG_PID_H) << 32;
> +}
> +
> +static int adi_i3c_master_do_daa(struct i3c_master_controller *m)
> +{
> + struct adi_i3c_master *master = to_adi_i3c_master(m);
> + int ret, addr = 0;
> + u32 irq_mask;
> +
> + for (u8 i = 0; i < ADI_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 raw)
> +{
> + struct adi_i3c_i2c_dev_data *data;
> + struct i3c_ibi_slot *slot;
> + struct i3c_dev_desc *dev;
> + u8 da, id, mdb, len;
> + u8 *buf;
> +
> + da = FIELD_GET(GENMASK(23, 17), raw);
> + mdb = FIELD_GET(GENMASK(15, 8), raw);
> + 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];
> + len = ADI_HAS_MDB_FROM_BCR(dev->info.bcr);
> + data = i3c_dev_get_master_data(dev);
> +
> + guard(spinlock)(&master->ibi.lock);
> + slot = i3c_generic_ibi_get_free_slot(data->ibi_pool);
> + if (!slot)
> + return;
> +
> + slot->len = len;
> + buf = slot->data;
> + buf[0] = mdb;
> + i3c_master_queue_ibi(dev, slot);
> +}
> +
> +static void adi_i3c_master_demux_ibis(struct adi_i3c_master *master)
> +{
> + while (!(readl(master->regs + REG_FIFO_STATUS) & REG_FIFO_STATUS_IBI_EMPTY)) {
> + u32 raw = readl(master->regs + REG_IBI_FIFO);
> +
> + adi_i3c_master_handle_ibi(master, raw);
> + }
> +}
> +
> +static void adi_i3c_master_handle_da_req(struct adi_i3c_master *master)
> +{
> + u8 payload0[8];
> + u32 addr;
> +
> + 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) {
> + scoped_guard(spinlock_irqsave, &master->xferqueue.lock) {
> + adi_i3c_master_end_xfer_locked(master, pending);
> + }
> + }
> + 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 __free(kfree) = NULL;
> + int i;
> +
> + 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);
> +
> + return xfer->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 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);
> +
> + scoped_guard(spinlock_irqsave, &master->ibi.lock) {
> + for (i = 0; i < master->ibi.num_slots; i++) {
> + if (!master->ibi.slots[i]) {
> + data->ibi = i;
> + master->ibi.slots[i] = dev;
> + break;
> + }
> + }
> + }
> +
> + 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);
> +
> + scoped_guard(spinlock_irqsave, &master->ibi.lock) {
> + master->ibi.slots[data->ibi] = NULL;
> + }
> +
> + 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-v1" },
> + {}
> +};
> +
> +static int adi_i3c_master_probe(struct platform_device *pdev)
> +{
> + struct adi_i3c_master *master;
> + struct clk_bulk_data *clk;
> + 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);
> +
> + 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");
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0)
> + return irq;
> +
> + version = readl(master->regs + ADI_AXI_REG_VERSION);
> + if (ADI_AXI_PCORE_VER_MAJOR(version) != 1)
> + dev_err_probe(&pdev->dev, -ENODEV, "Unsupported peripheral version %u.%u.%u\n",
> + ADI_AXI_PCORE_VER_MAJOR(version),
> + ADI_AXI_PCORE_VER_MINOR(version),
> + ADI_AXI_PCORE_VER_PATCH(version));
> +
> + 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->free_rr_slots = GENMASK(ADI_MAX_DEVS, 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;
> +
> + spin_lock_init(&master->xferqueue.lock);
> + INIT_LIST_HEAD(&master->xferqueue.list);
> +
> + 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] 9+ messages in thread
* Re: [PATCH v7 1/2] dt-bindings: i3c: Add adi-i3c-master
2025-08-26 15:47 ` Frank Li
@ 2025-08-27 12:12 ` Jorge Marques
0 siblings, 0 replies; 9+ messages in thread
From: Jorge Marques @ 2025-08-27 12:12 UTC (permalink / raw)
To: Frank Li
Cc: Jorge Marques, Alexandre Belloni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Kees Cook, Gustavo A. R. Silva,
linux-i3c, devicetree, linux-kernel, linux-hardening
On Tue, Aug 26, 2025 at 11:47:45AM -0400, Frank Li wrote:
> On Mon, Aug 18, 2025 at 01:51:13PM +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.
> > The IP Core is versioned following Semantic Versioning 2.0.0 and
> > ADI's open-source HDL guidelines for devicetree bindings and drivers.
> >
> > Signed-off-by: Jorge Marques <jorge.marques@analog.com>
> > ---
> > .../devicetree/bindings/i3c/adi,i3c-master.yaml | 72 ++++++++++++++++++++++
> > MAINTAINERS | 5 ++
> > 2 files changed, 77 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..fda240f9ee0c73bcbea97f775d6e081fe0c089d9
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml
> > @@ -0,0 +1,72 @@
> > +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> > +%YAML 1.2
> > +---
> ...
> > +
> > + clocks:
> > + minItems: 1
> > + items:
> > + - description: The AXI interconnect clock, drives the register map.
> > + - description: |
>
> Needn't | here.
>
> Frank
Ack
Best regards,
Jorge
> > + The secondary clock, drives the internal logic asynchronously to the
> > + register map. The presence of this entry states that the IP Core was
> > + synthesized with a second clock input, and the absence of this entry
> > + indicates a topology where a single clock input drives all the
> > + internal logic.
> > +
> > + clock-names:
> > + minItems: 1
> > + items:
> > + - const: axi
> > + - const: i3c
> > +
> > + interrupts:
> > + maxItems: 1
> > +
> > +required:
> > + - compatible
> > + - reg
> > + - clocks
> > + - clock-names
> > + - interrupts
> > +
> > +allOf:
> > + - $ref: i3c.yaml#
> > +
> > +unevaluatedProperties: false
> > +
> > +examples:
> > + - |
> > + #include <dt-bindings/interrupt-controller/irq.h>
> > +
> > + i3c@44a00000 {
> > + compatible = "adi,i3c-master-v1";
> > + reg = <0x44a00000 0x1000>;
> > + interrupts = <3 IRQ_TYPE_LEVEL_HIGH>;
> > + 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 35ed8498ab1e9b92b4bce5db9bb64004d80e4b1a..faa50535b514037ddf97309874ec64aac2013cb6 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -11452,6 +11452,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 [flat|nested] 9+ messages in thread
* Re: [PATCH v7 0/2] Add ADI I3C Controller
2025-08-26 15:56 ` [PATCH v7 0/2] Add ADI I3C Controller Frank Li
@ 2025-08-27 12:15 ` Jorge Marques
0 siblings, 0 replies; 9+ messages in thread
From: Jorge Marques @ 2025-08-27 12:15 UTC (permalink / raw)
To: Frank Li
Cc: Jorge Marques, Alexandre Belloni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Kees Cook, Gustavo A. R. Silva,
linux-i3c, devicetree, linux-kernel, linux-hardening
On Tue, Aug 26, 2025 at 11:56:05AM -0400, Frank Li wrote:
> On Mon, Aug 18, 2025 at 01:51:12PM +0200, Jorge Marques wrote:
> > 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.
> >
> > The IP Core is versioned following ADI's open source HDL guidelines
> > for devicetree bindings and drivers described at
> > https://analogdevicesinc.github.io/hdl/user_guide/contributing.html#devicetree-bindings-drivers
> > in summary, follows Semantic Versioning 2.0.0, with the dt-binding suffixed
> > by -v<major>.
> >
> > If necessary, the contents of
> > https://analogdevicesinc.github.io/hdl/user_guide/contributing.html#devicetree-bindings-drivers
> > can be replicated to a file in a different series, similar to AMD Xilinx
> > at Documentation/devicetree/bindings/xilinx.txt, but as adi.txt or
> > similar.
> >
> > Depends on https://lore.kernel.org/linux-i3c/20250622-i3c-writesl-readsl-v2-0-2afd34ec6306@analog.com/T/#t
>
> It is already in v6.17-rc1. You can remove this depends.
Ack
Best regards,
Jorge
>
> commit 733b439375b494e8a6950ab47d18a4b615b73cb3
> Author: Jorge Marques <jorge.marques@analog.com>
> Date: Tue Jun 24 11:06:04 2025 +0200
>
> i3c: master: Add inline i3c_readl_fifo() and i3c_writel_fifo()
>
> The I3C abstraction expects u8 buffers, but some controllers operate with
> a 32-bit bus width FIFO and cannot flag valid bytes individually. To avoid
> reading or writing outside the buffer bounds, use 32-bit accesses where
> possible and apply memcpy for any remaining bytes
>
> Signed-off-by: Jorge Marques <jorge.marques@analog.com>
> Suggested-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
> Reviewed-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
> Tested-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
> Reviewed-by: Frank Li <Frank.Li@nxp.com>
> Link: https://lore.kernel.org/r/20250624-i3c-writesl-readsl-v3-1-63ccf0870f01@analog.com
> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
>
> >
> > Signed-off-by: Jorge Marques <jorge.marques@analog.com>
> > ---
> > Changes in v7:
> > - Edit cover linking guidelines to ADI IP Core versioning.
> > Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml:
> > - Extend second clock description to explain relation to synthesized topology.
> > - Link to v6: https://lore.kernel.org/r/20250717-adi-i3c-master-v6-0-6c687ed71bed@analog.com
> >
> > Changes in v6:
> > - Format 0x05C undercase
> > - Link to v5: https://lore.kernel.org/r/20250715-adi-i3c-master-v5-0-c89434cbaf5e@analog.com
> >
> > Changes in v5:
> > Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml:
> > - Use semantic versioning major field for dt-binding compatible, with
> > the format adi,<ip-name>-v<major>
> >
> > adi-i3c-master.c:
> > - Rename MAX_DEVS to ADI_MAX_DEVS
> > - Encapsulate REG_CMD_FIFO_0_DEV_ADDR var
> > - Reorder struct adi_i3c_i2c_dev_data fields
> > - Start addr at 0 instead of 8 at adi_i3c_master_get_rr_slot
> > - Minor rework on adi_i3c_master_handle_ibi to most logic out of the
> > lock. Even if length is 0 (BCR[2]=0), the mdb field is extracted and
> > written to the slot buffer. Since the length is 0, the written data
> > doesn't matter.
> > In a future update with additional bytes support (e.g., bits 31-23),
> > len would be incremented and an IBI FIFO would be read.
> > - Version check against first stable release, major v1.
> > Driver+RTL features updates affect the minor field, therefore check
> > for major == 1.
> > - Link to v4: https://lore.kernel.org/r/20250626-adi-i3c-master-v4-0-3846a1f66d5e@analog.com
> >
> > Changes in v4:
> > Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml:
> > - Add -1.00.a suffix where missing
> > - Extend clocks descriptions
> > - Add minItems to clock-names, to match clocks
> > - Use header in example
> >
> > adi-i3c-master.c:
> > - Regmap:
> > - Add new controller info registers (dyn_addr, dcr, bcr, pid)
> > - Always decreasing fields
> > - Add line break between registers
> > - Reformat REG_DEV_CHAR_BSCR_IBI to use easier to read FIELD_GET,
> > FIELD_PREP
> > - Read controller info from regmap with explanation comment
> > - Use linux/fpga/adi-axi-common.h macros
> > - Use __counter_by macro on ncmds
> > - Use __free macro
> > - Use new i3c_writel_fifo and i3c_readl_fifo macros
> > - Rename bytes to buf when nbytes is present
> > - Use scoped_guard instead of spin_lock, spin_unlock
> > - Reformat loops to read fifo status, use while single line alternative
> > - Drop adi_i3c_master.max_devs, use MAX_DEVS directly
> > - Use devm_clk_bulk_get_all_enabled, dropping clock name match (CHECK_DTB does it)
> > - Init spin_lock
> > - Init list head
> > - Link to v3: https://lore.kernel.org/r/20250618-adi-i3c-master-v3-0-e66170a6cb95@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 | 72 ++
> > MAINTAINERS | 6 +
> > drivers/i3c/master/Kconfig | 11 +
> > drivers/i3c/master/Makefile | 1 +
> > drivers/i3c/master/adi-i3c-master.c | 1019 ++++++++++++++++++++
> > 5 files changed, 1109 insertions(+)
> > ---
> > base-commit: 51963783b876a2f493a3eac0ea9eba271cb6809a
> > change-id: 20250604-adi-i3c-master-2a5148c58c47
> > prerequisite-message-id: <20250622-i3c-writesl-readsl-v2-0-2afd34ec6306@analog.com>
> > prerequisite-patch-id: 5443f14ca82fc08593960fafdb43488cce56f7d9
> > prerequisite-patch-id: 647084f5fe09f4887f633b0b02b306912a156672
> > prerequisite-patch-id: 6f582bb2ef1aafb66f26c515a19d5efa06ab8968
> >
> > Best regards,
> > --
> > Jorge Marques <jorge.marques@analog.com>
> >
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2025-08-27 12:15 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-18 11:51 [PATCH v7 0/2] Add ADI I3C Controller Jorge Marques
2025-08-18 11:51 ` [PATCH v7 1/2] dt-bindings: i3c: Add adi-i3c-master Jorge Marques
2025-08-18 17:01 ` Conor Dooley
2025-08-26 15:47 ` Frank Li
2025-08-27 12:12 ` Jorge Marques
2025-08-18 11:51 ` [PATCH v7 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP Jorge Marques
2025-08-26 15:57 ` Frank Li
2025-08-26 15:56 ` [PATCH v7 0/2] Add ADI I3C Controller Frank Li
2025-08-27 12:15 ` Jorge Marques
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).