* [PATCH v5 0/2] Add ADI I3C Controller
@ 2025-07-15 8:56 Jorge Marques
2025-07-15 8:56 ` [PATCH v5 1/2] dt-bindings: i3c: Add adi-i3c-master Jorge Marques
2025-07-15 8:56 ` [PATCH v5 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP Jorge Marques
0 siblings, 2 replies; 6+ messages in thread
From: Jorge Marques @ 2025-07-15 8:56 UTC (permalink / raw)
To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-i3c, devicetree, linux-kernel, gastmaier, Jorge Marques
I3C Controller is subset of the I3C-basic specification to interface
peripherals through I3C and I2C. The controller RTL is FPGA
synthesizable and documentation is provided at
https://analogdevicesinc.github.io/hdl/library/i3c_controller
The main target for the I3C Controller IP is low-cost FPGAs.
In this version the driver supports IBI (only the MDB), I3C and I2C
transfers.
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 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 | 67 ++
MAINTAINERS | 6 +
drivers/i3c/master/Kconfig | 11 +
drivers/i3c/master/Makefile | 1 +
drivers/i3c/master/adi-i3c-master.c | 1019 ++++++++++++++++++++
5 files changed, 1104 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>
--
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH v5 1/2] dt-bindings: i3c: Add adi-i3c-master 2025-07-15 8:56 [PATCH v5 0/2] Add ADI I3C Controller Jorge Marques @ 2025-07-15 8:56 ` Jorge Marques 2025-07-15 8:56 ` [PATCH v5 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP Jorge Marques 1 sibling, 0 replies; 6+ messages in thread From: Jorge Marques @ 2025-07-15 8:56 UTC (permalink / raw) To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-i3c, devicetree, linux-kernel, gastmaier, Jorge Marques Add bindings doc for ADI I3C Controller IP core, a FPGA synthesizable IP core that implements the MIPI I3C Basic controller specification. Signed-off-by: Jorge Marques <jorge.marques@analog.com> --- .../devicetree/bindings/i3c/adi,i3c-master.yaml | 67 ++++++++++++++++++++++ MAINTAINERS | 5 ++ 2 files changed, 72 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..8f532386de2c05d3d514d41ff4d6a37ec840674c --- /dev/null +++ b/Documentation/devicetree/bindings/i3c/adi,i3c-master.yaml @@ -0,0 +1,67 @@ +# 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 I3C controller clock. AXI clock drives all logic if not provided. + + 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 d5a173e987c06d75848d112339e39966f2b71cea..1f212529276f4ab28f4b7d16b4105a8cf9ff5e2e 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 -- linux-i3c mailing list linux-i3c@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-i3c ^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v5 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP 2025-07-15 8:56 [PATCH v5 0/2] Add ADI I3C Controller Jorge Marques 2025-07-15 8:56 ` [PATCH v5 1/2] dt-bindings: i3c: Add adi-i3c-master Jorge Marques @ 2025-07-15 8:56 ` Jorge Marques 2025-07-15 15:26 ` Frank Li 1 sibling, 1 reply; 6+ messages in thread From: Jorge Marques @ 2025-07-15 8:56 UTC (permalink / raw) To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-i3c, devicetree, linux-kernel, gastmaier, Jorge Marques Add support for Analog Devices I3C Controller IP, an AXI-interfaced IP core that supports I3C and I2C devices, multiple speed-grades and I3C IBIs. Signed-off-by: Jorge Marques <jorge.marques@analog.com> --- MAINTAINERS | 1 + drivers/i3c/master/Kconfig | 11 + drivers/i3c/master/Makefile | 1 + drivers/i3c/master/adi-i3c-master.c | 1019 +++++++++++++++++++++++++++++++++++ 4 files changed, 1032 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 1f212529276f4ab28f4b7d16b4105a8cf9ff5e2e..f527a1276358cc03b7c3461c2aa1aedfb0e9fbbf 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 7b30db3253af9d5c6aee6544c060e491bfbeb643..e0153362de9b6fe4eb11841fdc7e0b17a858e4f8 100644 --- a/drivers/i3c/master/Kconfig +++ b/drivers/i3c/master/Kconfig @@ -1,4 +1,15 @@ # SPDX-License-Identifier: GPL-2.0-only +config ADI_I3C_MASTER + tristate "Analog Devices I3C master driver" + depends on HAS_IOMEM + help + Support for Analog Devices I3C Controller IP, an AXI-interfaced IP + core that supports I3C and I2C devices, multiple speed-grades and I3C + IBIs. + + This driver can also be built as a module. If so, the module will be + called adi-i3c-master. + config CDNS_I3C_MASTER tristate "Cadence I3C master driver" depends on HAS_IOMEM diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile index 3e97960160bc85e5eaf2966ec0c3fae458c2711e..6cc4f4b73e7bdc206b68c750390f9c3cc2ccb199 100644 --- a/drivers/i3c/master/Makefile +++ b/drivers/i3c/master/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_ADI_I3C_MASTER) += adi-i3c-master.o obj-$(CONFIG_CDNS_I3C_MASTER) += i3c-master-cdns.o obj-$(CONFIG_DW_I3C_MASTER) += dw-i3c-master.o obj-$(CONFIG_AST2600_I3C_MASTER) += ast2600-i3c-master.o diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c new file mode 100644 index 0000000000000000000000000000000000000000..771e46fd6c2b9d5349a7adc490cfb6d112bf86d3 --- /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 -- linux-i3c mailing list linux-i3c@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-i3c ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v5 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP 2025-07-15 8:56 ` [PATCH v5 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP Jorge Marques @ 2025-07-15 15:26 ` Frank Li 2025-07-15 20:31 ` Wolfram Sang 2025-07-17 7:08 ` Jorge Marques 0 siblings, 2 replies; 6+ messages in thread From: Frank Li @ 2025-07-15 15:26 UTC (permalink / raw) To: Jorge Marques Cc: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-i3c, devicetree, linux-kernel, gastmaier On Tue, Jul 15, 2025 at 10:56:04AM +0200, Jorge Marques wrote: > Add support for Analog Devices I3C Controller IP, an AXI-interfaced IP > core that supports I3C and I2C devices, multiple speed-grades and > I3C IBIs. > > Signed-off-by: Jorge Marques <jorge.marques@analog.com> > --- > MAINTAINERS | 1 + > drivers/i3c/master/Kconfig | 11 + > drivers/i3c/master/Makefile | 1 + > drivers/i3c/master/adi-i3c-master.c | 1019 +++++++++++++++++++++++++++++++++++ > 4 files changed, 1032 insertions(+) > > diff --git a/MAINTAINERS b/MAINTAINERS > index 1f212529276f4ab28f4b7d16b4105a8cf9ff5e2e..f527a1276358cc03b7c3461c2aa1aedfb0e9fbbf 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 Add i3c mail list? > > I3C DRIVER FOR CADENCE I3C MASTER IP > M: Przemysław Gaj <pgaj@cadence.com> > diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig > index 7b30db3253af9d5c6aee6544c060e491bfbeb643..e0153362de9b6fe4eb11841fdc7e0b17a858e4f8 100644 > --- a/drivers/i3c/master/Kconfig > +++ b/drivers/i3c/master/Kconfig > @@ -1,4 +1,15 @@ > # SPDX-License-Identifier: GPL-2.0-only > +config ADI_I3C_MASTER > + tristate "Analog Devices I3C master driver" > + depends on HAS_IOMEM > + help > + Support for Analog Devices I3C Controller IP, an AXI-interfaced IP > + core that supports I3C and I2C devices, multiple speed-grades and I3C > + IBIs. > + > + This driver can also be built as a module. If so, the module will be > + called adi-i3c-master. > + > config CDNS_I3C_MASTER > tristate "Cadence I3C master driver" > depends on HAS_IOMEM > diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile > index 3e97960160bc85e5eaf2966ec0c3fae458c2711e..6cc4f4b73e7bdc206b68c750390f9c3cc2ccb199 100644 > --- a/drivers/i3c/master/Makefile > +++ b/drivers/i3c/master/Makefile > @@ -1,4 +1,5 @@ > # SPDX-License-Identifier: GPL-2.0-only > +obj-$(CONFIG_ADI_I3C_MASTER) += adi-i3c-master.o > obj-$(CONFIG_CDNS_I3C_MASTER) += i3c-master-cdns.o > obj-$(CONFIG_DW_I3C_MASTER) += dw-i3c-master.o > obj-$(CONFIG_AST2600_I3C_MASTER) += ast2600-i3c-master.o > diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c > new file mode 100644 > index 0000000000000000000000000000000000000000..771e46fd6c2b9d5349a7adc490cfb6d112bf86d3 > --- /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 nit: 0x5c to keep consistency with other hex value. Look good overall, thank you very much. Frank > +#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 > -- linux-i3c mailing list linux-i3c@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-i3c ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v5 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP 2025-07-15 15:26 ` Frank Li @ 2025-07-15 20:31 ` Wolfram Sang 2025-07-17 7:08 ` Jorge Marques 1 sibling, 0 replies; 6+ messages in thread From: Wolfram Sang @ 2025-07-15 20:31 UTC (permalink / raw) To: Frank Li Cc: Jorge Marques, Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-i3c, devicetree, linux-kernel, gastmaier > > 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 > > Add i3c mail list? Not needed because it will be inherited from the I3C subsystem entry: $ scripts/get_maintainer.pl -f drivers/i3c/master/ast2600-i3c-master.c Jeremy Kerr Alexandre Belloni Frank Li linux-i3c@lists.infradead.org linux-kernel@vger.kernel.org -- linux-i3c mailing list linux-i3c@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-i3c ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v5 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP 2025-07-15 15:26 ` Frank Li 2025-07-15 20:31 ` Wolfram Sang @ 2025-07-17 7:08 ` Jorge Marques 1 sibling, 0 replies; 6+ messages in thread From: Jorge Marques @ 2025-07-17 7:08 UTC (permalink / raw) To: Frank Li Cc: Jorge Marques, Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-i3c, devicetree, linux-kernel On Tue, Jul 15, 2025 at 11:26:26AM -0400, Frank Li wrote: > On Tue, Jul 15, 2025 at 10:56:04AM +0200, Jorge Marques wrote: > > Add support for Analog Devices I3C Controller IP, an AXI-interfaced IP > > core that supports I3C and I2C devices, multiple speed-grades and > > I3C IBIs. > > > > Signed-off-by: Jorge Marques <jorge.marques@analog.com> > > --- > > MAINTAINERS | 1 + > > drivers/i3c/master/Kconfig | 11 + > > drivers/i3c/master/Makefile | 1 + > > drivers/i3c/master/adi-i3c-master.c | 1019 +++++++++++++++++++++++++++++++++++ > > 4 files changed, 1032 insertions(+) > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index 1f212529276f4ab28f4b7d16b4105a8cf9ff5e2e..f527a1276358cc03b7c3461c2aa1aedfb0e9fbbf 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 > > Add i3c mail list? > > > > > I3C DRIVER FOR CADENCE I3C MASTER IP > > M: Przemysław Gaj <pgaj@cadence.com> > > diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig > > index 7b30db3253af9d5c6aee6544c060e491bfbeb643..e0153362de9b6fe4eb11841fdc7e0b17a858e4f8 100644 > > --- a/drivers/i3c/master/Kconfig > > +++ b/drivers/i3c/master/Kconfig > > @@ -1,4 +1,15 @@ > > # SPDX-License-Identifier: GPL-2.0-only > > +config ADI_I3C_MASTER > > + tristate "Analog Devices I3C master driver" > > + depends on HAS_IOMEM > > + help > > + Support for Analog Devices I3C Controller IP, an AXI-interfaced IP > > + core that supports I3C and I2C devices, multiple speed-grades and I3C > > + IBIs. > > + > > + This driver can also be built as a module. If so, the module will be > > + called adi-i3c-master. > > + > > config CDNS_I3C_MASTER > > tristate "Cadence I3C master driver" > > depends on HAS_IOMEM > > diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile > > index 3e97960160bc85e5eaf2966ec0c3fae458c2711e..6cc4f4b73e7bdc206b68c750390f9c3cc2ccb199 100644 > > --- a/drivers/i3c/master/Makefile > > +++ b/drivers/i3c/master/Makefile > > @@ -1,4 +1,5 @@ > > # SPDX-License-Identifier: GPL-2.0-only > > +obj-$(CONFIG_ADI_I3C_MASTER) += adi-i3c-master.o > > obj-$(CONFIG_CDNS_I3C_MASTER) += i3c-master-cdns.o > > obj-$(CONFIG_DW_I3C_MASTER) += dw-i3c-master.o > > obj-$(CONFIG_AST2600_I3C_MASTER) += ast2600-i3c-master.o > > diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c > > new file mode 100644 > > index 0000000000000000000000000000000000000000..771e46fd6c2b9d5349a7adc490cfb6d112bf86d3 > > --- /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 > > nit: 0x5c to keep consistency with other hex value. > > Look good overall, thank you very much. > > Frank > Hi Frank, Thank you for the reviews, I will submit v6 formatting 0x05c shortly, Best regards, Jorge > > +#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 > > -- linux-i3c mailing list linux-i3c@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-i3c ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2025-07-17 7:24 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-07-15 8:56 [PATCH v5 0/2] Add ADI I3C Controller Jorge Marques 2025-07-15 8:56 ` [PATCH v5 1/2] dt-bindings: i3c: Add adi-i3c-master Jorge Marques 2025-07-15 8:56 ` [PATCH v5 2/2] i3c: master: Add driver for Analog Devices I3C Controller IP Jorge Marques 2025-07-15 15:26 ` Frank Li 2025-07-15 20:31 ` Wolfram Sang 2025-07-17 7:08 ` Jorge Marques
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox