Devicetree
 help / color / mirror / Atom feed
* [PATCH 0/3] Add vendor glue layer support and Amlogic A9 implementation
@ 2026-10-19  5:34 Junyi Zhao via B4 Relay
  2026-10-19  5:34 ` [PATCH 1/3] i3c: master: Rework SVC I3C to support vendor glue layers Junyi Zhao via B4 Relay
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Junyi Zhao via B4 Relay @ 2026-10-19  5:34 UTC (permalink / raw)
  To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Neil Armstrong, Kevin Hilman, Jerome Brunet,
	Martin Blumenstingl, Miquel Raynal, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-i3c, devicetree, linux-kernel, linux-arm-kernel,
	linux-amlogic, imx, linux-hardening, xianwei.zhao, Kelvin.Zhang,
	Junyi Zhao

The SVC I3C controller IP is used by multiple vendors, which may
require platform-specific handling for controller resources and
integration details.

Rework the SVC I3C driver to introduce a vendor glue layer that
separates common controller logic from vendor-specific extensions.
This allows different implementations to reuse the shared SVC I3C
framework while providing their own platform-dependent operations.

On top of the glue layer framework, add support for the Amlogic A9
implementation of the SVC I3C controller, including Device Tree
bindings and the A9-specific glue layer driver.

This series consists of:

  - Rework the SVC I3C driver to support vendor glue layers.
  - Add Device Tree bindings for the Amlogic A9 I3C controller.
  - Add the Amlogic A9 SVC I3C glue layer driver.

Signed-off-by: Junyi Zhao <junyi.zhao@amlogic.com>
---
Junyi Zhao (3):
      i3c: master: Rework SVC I3C to support vendor glue layers
      dt-bindings: i3c: Add Amlogic A9 I3C controller
      i3c: master: svc: Add Amlogic A9 glue layer

 .../devicetree/bindings/i3c/amlogic,i3c-a9.yaml    | 102 +++++
 MAINTAINERS                                        |  12 +-
 drivers/i3c/master/Kconfig                         |   9 +-
 drivers/i3c/master/Makefile                        |   2 +-
 drivers/i3c/master/svc/Kconfig                     |  28 ++
 drivers/i3c/master/svc/Makefile                    |   6 +
 drivers/i3c/master/svc/i3c-amlogic.c               | 250 +++++++++++
 .../master/{svc-i3c-master.c => svc/i3c-main.c}    | 467 ++-------------------
 drivers/i3c/master/svc/i3c-main.h                  | 297 +++++++++++++
 drivers/i3c/master/svc/i3c-silvaco.c               | 173 ++++++++
 10 files changed, 909 insertions(+), 437 deletions(-)
---
base-commit: aac26bee2287c88af5be5a5ff96d783b19a28790
change-id: 20261010-a9_i3c-9d5245d094c8

Best regards,
--  
Junyi Zhao <junyi.zhao@amlogic.com>



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

* [PATCH 1/3] i3c: master: Rework SVC I3C to support vendor glue layers
  2026-10-19  5:34 [PATCH 0/3] Add vendor glue layer support and Amlogic A9 implementation Junyi Zhao via B4 Relay
@ 2026-10-19  5:34 ` Junyi Zhao via B4 Relay
  2026-10-19  5:34 ` [PATCH 2/3] dt-bindings: i3c: Add Amlogic A9 I3C controller Junyi Zhao via B4 Relay
  2026-10-19  5:34 ` [PATCH 3/3] i3c: master: svc: Add Amlogic A9 glue layer Junyi Zhao via B4 Relay
  2 siblings, 0 replies; 8+ messages in thread
From: Junyi Zhao via B4 Relay @ 2026-10-19  5:34 UTC (permalink / raw)
  To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Neil Armstrong, Kevin Hilman, Jerome Brunet,
	Martin Blumenstingl, Miquel Raynal, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-i3c, devicetree, linux-kernel, linux-arm-kernel,
	linux-amlogic, imx, linux-hardening, xianwei.zhao, Kelvin.Zhang,
	Junyi Zhao

From: Junyi Zhao <junyi.zhao@amlogic.com>

The SVC I3C controller IP can be integrated into SoCs from different
vendors. Peripheral resources associated with the controller, such as
clocks, may vary between vendor implementations.

Rework the driver to separate common functionality from
vendor-specific extensions. The common logic is shared across all
implementations, while platform-dependent operations are provided
through vendor-specific hooks.

Also add a corresponding MAINTAINERS entry for the SILVACO I3C
controller.

Signed-off-by: Junyi Zhao <junyi.zhao@amlogic.com>
---
 MAINTAINERS                                        |   6 +-
 drivers/i3c/master/Kconfig                         |   9 +-
 drivers/i3c/master/Makefile                        |   2 +-
 drivers/i3c/master/svc/Kconfig                     |  21 +
 drivers/i3c/master/svc/Makefile                    |   5 +
 .../master/{svc-i3c-master.c => svc/i3c-main.c}    | 467 ++-------------------
 drivers/i3c/master/svc/i3c-main.h                  | 297 +++++++++++++
 drivers/i3c/master/svc/i3c-silvaco.c               | 173 ++++++++
 8 files changed, 543 insertions(+), 437 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 77bd64bae..dffc63af2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25566,7 +25566,11 @@ L:	linux-i3c@lists.infradead.org (moderated for non-subscribers)
 L:	imx@lists.linux.dev
 S:	Maintained
 F:	Documentation/devicetree/bindings/i3c/silvaco,i3c-master.yaml
-F:	drivers/i3c/master/svc-i3c-master.c
+F:	drivers/i3c/master/svc/Kconfig
+F:	drivers/i3c/master/svc/Makefile
+F:	drivers/i3c/master/svc/i3c-main.c
+F:	drivers/i3c/master/svc/i3c-main.h
+F:	drivers/i3c/master/svc/i3c-silvaco.c
 
 SIMPLEFB FB DRIVER
 M:	Hans de Goede <hansg@kernel.org>
diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig
index da96d2aaa..722661c2e 100644
--- a/drivers/i3c/master/Kconfig
+++ b/drivers/i3c/master/Kconfig
@@ -45,13 +45,6 @@ config AST2600_I3C_MASTER
 	  This driver can also be built as a module.  If so, the module
 	  will be called ast2600-i3c-master.
 
-config SVC_I3C_MASTER
-	tristate "Silvaco I3C Dual-Role Master driver"
-	depends on HAS_IOMEM
-	depends on !(ALPHA || PARISC)
-	help
-	  Support for Silvaco I3C Dual-Role Master Controller.
-
 config MIPI_I3C_HCI
 	tristate "MIPI I3C Host Controller Interface driver (EXPERIMENTAL)"
 	depends on HAS_IOMEM
@@ -101,3 +94,5 @@ config AMD_AXI_I3C_MASTER
 
 	  This driver can also be built as a module. If so, the module
 	  will be called amd-i3c-master.
+
+source "drivers/i3c/master/svc/Kconfig"
diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile
index 8d82196dc..a85e566ba 100644
--- a/drivers/i3c/master/Makefile
+++ b/drivers/i3c/master/Makefile
@@ -3,7 +3,7 @@ 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
-obj-$(CONFIG_SVC_I3C_MASTER)		+= svc-i3c-master.o
+obj-$(CONFIG_SVC_I3C)		+= svc/
 obj-$(CONFIG_MIPI_I3C_HCI)		+= mipi-i3c-hci/
 obj-$(CONFIG_RENESAS_I3C)		+= renesas-i3c.o
 obj-$(CONFIG_AMD_AXI_I3C_MASTER)	+= amd-i3c-master.o
diff --git a/drivers/i3c/master/svc/Kconfig b/drivers/i3c/master/svc/Kconfig
new file mode 100644
index 000000000..14fa1e542
--- /dev/null
+++ b/drivers/i3c/master/svc/Kconfig
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config SVC_I3C
+	tristate "SVC I3C Dual-Role Master driver"
+	depends on HAS_IOMEM
+	depends on !(ALPHA || PARISC)
+	help
+	  Support for SVC I3C Dual-Role Master Controller.
+
+if SVC_I3C
+
+config I3C_SILVACO
+	tristate "Silvaco I3C Master support for other vendors"
+	default y
+	help
+	  Support for Silvaco I3C Dual-Role Master Controller with the
+	  generic (upstream) probe glue. This covers the original
+	  compatible strings such as "silvaco,i3c-master-v1" and
+	  "nuvoton,npcm845-i3c".
+
+endif # SVC_I3C
diff --git a/drivers/i3c/master/svc/Makefile b/drivers/i3c/master/svc/Makefile
new file mode 100644
index 000000000..cf18c5da6
--- /dev/null
+++ b/drivers/i3c/master/svc/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+# Copyright (C) 2026 Amlogic, Inc. All rights reserved
+
+obj-$(CONFIG_SVC_I3C)	+= i3c-main.o
+obj-$(CONFIG_I3C_SILVACO)	+= i3c-silvaco.o
diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc/i3c-main.c
similarity index 79%
rename from drivers/i3c/master/svc-i3c-master.c
rename to drivers/i3c/master/svc/i3c-main.c
index e700392f0..10e2f7761 100644
--- a/drivers/i3c/master/svc-i3c-master.c
+++ b/drivers/i3c/master/svc/i3c-main.c
@@ -6,285 +6,7 @@
  * Author: Miquel RAYNAL <miquel.raynal@bootlin.com>
  * Based on a work from: Conor Culhane <conor.culhane@silvaco.com>
  */
-
-#include <linux/bitfield.h>
-#include <linux/clk.h>
-#include <linux/completion.h>
-#include <linux/errno.h>
-#include <linux/i3c/master.h>
-#include <linux/interrupt.h>
-#include <linux/iopoll.h>
-#include <linux/list.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/pinctrl/consumer.h>
-#include <linux/platform_device.h>
-#include <linux/pm_runtime.h>
-
-/* Master Mode Registers */
-#define SVC_I3C_MCONFIG      0x000
-#define   SVC_I3C_MCONFIG_MASTER_EN BIT(0)
-#define   SVC_I3C_MCONFIG_DISTO(x) FIELD_PREP(BIT(3), (x))
-#define   SVC_I3C_MCONFIG_HKEEP(x) FIELD_PREP(GENMASK(5, 4), (x))
-#define   SVC_I3C_MCONFIG_ODSTOP(x) FIELD_PREP(BIT(6), (x))
-#define   SVC_I3C_MCONFIG_PPBAUD(x) FIELD_PREP(GENMASK(11, 8), (x))
-#define   SVC_I3C_MCONFIG_PPLOW(x) FIELD_PREP(GENMASK(15, 12), (x))
-#define   SVC_I3C_MCONFIG_ODBAUD(x) FIELD_PREP(GENMASK(23, 16), (x))
-#define   SVC_I3C_MCONFIG_ODHPP(x) FIELD_PREP(BIT(24), (x))
-#define   SVC_I3C_MCONFIG_SKEW(x) FIELD_PREP(GENMASK(27, 25), (x))
-#define   SVC_I3C_MCONFIG_SKEW_MASK GENMASK(27, 25)
-#define   SVC_I3C_MCONFIG_I2CBAUD(x) FIELD_PREP(GENMASK(31, 28), (x))
-
-#define SVC_I3C_MCTRL        0x084
-#define   SVC_I3C_MCTRL_REQUEST_MASK GENMASK(2, 0)
-#define   SVC_I3C_MCTRL_REQUEST_NONE 0
-#define   SVC_I3C_MCTRL_REQUEST_START_ADDR 1
-#define   SVC_I3C_MCTRL_REQUEST_STOP 2
-#define   SVC_I3C_MCTRL_REQUEST_FORCE_EXIT 6
-#define   SVC_I3C_MCTRL_REQUEST_IBI_ACKNACK 3
-#define   SVC_I3C_MCTRL_REQUEST_PROC_DAA 4
-#define   SVC_I3C_MCTRL_REQUEST_AUTO_IBI 7
-#define   SVC_I3C_MCTRL_TYPE_I3C 0
-#define   SVC_I3C_MCTRL_TYPE_I2C BIT(4)
-#define   SVC_I3C_MCTRL_TYPE_DDR BIT(5)
-#define   SVC_I3C_MCTRL_IBIRESP_AUTO 0
-#define   SVC_I3C_MCTRL_IBIRESP_ACK_WITHOUT_BYTE 0
-#define   SVC_I3C_MCTRL_IBIRESP_ACK_WITH_BYTE BIT(7)
-#define   SVC_I3C_MCTRL_IBIRESP_NACK BIT(6)
-#define   SVC_I3C_MCTRL_IBIRESP_MANUAL GENMASK(7, 6)
-#define   SVC_I3C_MCTRL_DIR(x) FIELD_PREP(BIT(8), (x))
-#define   SVC_I3C_MCTRL_DIR_WRITE 0
-#define   SVC_I3C_MCTRL_DIR_READ 1
-#define   SVC_I3C_MCTRL_ADDR(x) FIELD_PREP(GENMASK(15, 9), (x))
-#define   SVC_I3C_MCTRL_RDTERM(x) FIELD_PREP(GENMASK(23, 16), (x))
-
-#define SVC_I3C_MSTATUS      0x088
-#define   SVC_I3C_MSTATUS_STATE(x) FIELD_GET(GENMASK(2, 0), (x))
-#define   SVC_I3C_MSTATUS_STATE_DAA(x) (SVC_I3C_MSTATUS_STATE(x) == 5)
-#define   SVC_I3C_MSTATUS_STATE_SLVREQ(x) (SVC_I3C_MSTATUS_STATE(x) == 1)
-#define   SVC_I3C_MSTATUS_STATE_IDLE(x) (SVC_I3C_MSTATUS_STATE(x) == 0)
-#define   SVC_I3C_MSTATUS_BETWEEN(x) FIELD_GET(BIT(4), (x))
-#define   SVC_I3C_MSTATUS_NACKED(x) FIELD_GET(BIT(5), (x))
-#define   SVC_I3C_MSTATUS_IBITYPE(x) FIELD_GET(GENMASK(7, 6), (x))
-#define   SVC_I3C_MSTATUS_IBITYPE_IBI 1
-#define   SVC_I3C_MSTATUS_IBITYPE_MASTER_REQUEST 2
-#define   SVC_I3C_MSTATUS_IBITYPE_HOT_JOIN 3
-#define   SVC_I3C_MINT_SLVSTART BIT(8)
-#define   SVC_I3C_MINT_MCTRLDONE BIT(9)
-#define   SVC_I3C_MINT_COMPLETE BIT(10)
-#define   SVC_I3C_MINT_RXPEND BIT(11)
-#define   SVC_I3C_MINT_TXNOTFULL BIT(12)
-#define   SVC_I3C_MINT_IBIWON BIT(13)
-#define   SVC_I3C_MINT_ERRWARN BIT(15)
-#define   SVC_I3C_MSTATUS_SLVSTART(x) FIELD_GET(SVC_I3C_MINT_SLVSTART, (x))
-#define   SVC_I3C_MSTATUS_MCTRLDONE(x) FIELD_GET(SVC_I3C_MINT_MCTRLDONE, (x))
-#define   SVC_I3C_MSTATUS_COMPLETE(x) FIELD_GET(SVC_I3C_MINT_COMPLETE, (x))
-#define   SVC_I3C_MSTATUS_RXPEND(x) FIELD_GET(SVC_I3C_MINT_RXPEND, (x))
-#define   SVC_I3C_MSTATUS_TXNOTFULL(x) FIELD_GET(SVC_I3C_MINT_TXNOTFULL, (x))
-#define   SVC_I3C_MSTATUS_IBIWON(x) FIELD_GET(SVC_I3C_MINT_IBIWON, (x))
-#define   SVC_I3C_MSTATUS_ERRWARN(x) FIELD_GET(SVC_I3C_MINT_ERRWARN, (x))
-#define   SVC_I3C_MSTATUS_IBIADDR(x) FIELD_GET(GENMASK(30, 24), (x))
-
-#define SVC_I3C_IBIRULES     0x08C
-#define   SVC_I3C_IBIRULES_ADDR(slot, addr) FIELD_PREP(GENMASK(29, 0), \
-						       ((addr) & 0x3F) << ((slot) * 6))
-#define   SVC_I3C_IBIRULES_ADDRS 5
-#define   SVC_I3C_IBIRULES_MSB0 BIT(30)
-#define   SVC_I3C_IBIRULES_NOBYTE BIT(31)
-#define   SVC_I3C_IBIRULES_MANDBYTE 0
-#define SVC_I3C_MINTSET      0x090
-#define SVC_I3C_MINTCLR      0x094
-#define SVC_I3C_MINTMASKED   0x098
-#define SVC_I3C_MERRWARN     0x09C
-#define   SVC_I3C_MERRWARN_NACK BIT(2)
-#define   SVC_I3C_MERRWARN_CRC	BIT(10)
-#define   SVC_I3C_MERRWARN_TIMEOUT BIT(20)
-#define SVC_I3C_MDMACTRL     0x0A0
-#define SVC_I3C_MDATACTRL    0x0AC
-#define   SVC_I3C_MDATACTRL_FLUSHTB BIT(0)
-#define   SVC_I3C_MDATACTRL_FLUSHRB BIT(1)
-#define   SVC_I3C_MDATACTRL_UNLOCK_TRIG BIT(3)
-#define   SVC_I3C_MDATACTRL_TXTRIG_FIFO_NOT_FULL GENMASK(5, 4)
-#define   SVC_I3C_MDATACTRL_RXTRIG_FIFO_NOT_EMPTY 0
-#define   SVC_I3C_MDATACTRL_RXCOUNT(x) FIELD_GET(GENMASK(28, 24), (x))
-#define   SVC_I3C_MDATACTRL_TXCOUNT(x) FIELD_GET(GENMASK(20, 16), (x))
-#define   SVC_I3C_MDATACTRL_TXFULL BIT(30)
-#define   SVC_I3C_MDATACTRL_RXEMPTY BIT(31)
-
-#define SVC_I3C_MWDATAB      0x0B0
-#define   SVC_I3C_MWDATAB_END BIT(8)
-
-#define SVC_I3C_MWDATABE     0x0B4
-#define SVC_I3C_MWDATAH      0x0B8
-#define SVC_I3C_MWDATAHE     0x0BC
-#define SVC_I3C_MRDATAB      0x0C0
-#define SVC_I3C_MRDATAH      0x0C8
-#define SVC_I3C_MWDATAB1     0x0CC
-#define SVC_I3C_MWMSG_SDR    0x0D0
-#define SVC_I3C_MRMSG_SDR    0x0D4
-#define SVC_I3C_MWMSG_DDR    0x0D8
-#define SVC_I3C_MRMSG_DDR    0x0DC
-
-#define SVC_I3C_MDYNADDR     0x0E4
-#define   SVC_MDYNADDR_VALID BIT(0)
-#define   SVC_MDYNADDR_ADDR(x) FIELD_PREP(GENMASK(7, 1), (x))
-
-#define SVC_I3C_MAX_DEVS 32
-#define SVC_I3C_PM_TIMEOUT_MS 1000
-
-/* This parameter depends on the implementation and may be tuned */
-#define SVC_I3C_FIFO_SIZE 16
-#define SVC_I3C_PPBAUD_MAX 15
-#define SVC_I3C_QUICK_I2C_CLK 4170000
-
-#define SVC_I3C_EVENT_IBI	GENMASK(7, 0)
-#define SVC_I3C_EVENT_HOTJOIN	BIT(31)
-
-/*
- * SVC_I3C_QUIRK_FIFO_EMPTY:
- * I3C HW stalls the write transfer if the transmit FIFO becomes empty,
- * when new data is written to FIFO, I3C HW resumes the transfer but
- * the first transmitted data bit may have the wrong value.
- * Workaround:
- * Fill the FIFO in advance to prevent FIFO from becoming empty.
- */
-#define SVC_I3C_QUIRK_FIFO_EMPTY	BIT(0)
-/*
- * SVC_I3C_QUIRK_FLASE_SLVSTART:
- * I3C HW may generate an invalid SlvStart event when emitting a STOP.
- * If it is a true SlvStart, the MSTATUS state is SLVREQ.
- */
-#define SVC_I3C_QUIRK_FALSE_SLVSTART	BIT(1)
-/*
- * SVC_I3C_QUIRK_DAA_CORRUPT:
- * When MCONFIG.SKEW=0 and MCONFIG.ODHPP=0, the ENTDAA transaction gets
- * corrupted and results in a no repeated-start condition at the end of
- * address assignment.
- * Workaround:
- * Set MCONFIG.SKEW to 1 before initiating the DAA process. After the DAA
- * process is completed, return MCONFIG.SKEW to its previous value.
- */
-#define SVC_I3C_QUIRK_DAA_CORRUPT	BIT(2)
-
-struct svc_i3c_cmd {
-	u8 addr;
-	union {
-		bool rnw;
-		u8 cmd;
-		u32 rnw_cmd;
-	};
-	u8 *in;
-	const void *out;
-	unsigned int len;
-	unsigned int actual_len;
-	struct i3c_xfer *xfer;
-	bool continued;
-};
-
-struct svc_i3c_xfer {
-	struct list_head node;
-	struct completion comp;
-	int ret;
-	unsigned int type;
-	unsigned int ncmds;
-	struct svc_i3c_cmd cmds[] __counted_by(ncmds);
-};
-
-struct svc_i3c_regs_save {
-	u32 mconfig;
-	u32 mdynaddr;
-};
-
-struct svc_i3c_drvdata {
-	u32 quirks;
-};
-
-/**
- * struct svc_i3c_master - Silvaco I3C Master structure
- * @base: I3C master controller
- * @dev: Corresponding device
- * @regs: Memory mapping
- * @saved_regs: Volatile values for PM operations
- * @free_slots: Bit array of available slots
- * @addrs: Array containing the dynamic addresses of each attached device
- * @descs: Array of descriptors, one per attached device
- * @irq: Main interrupt
- * @num_clks: I3C clock number
- * @fclk: Fast clock (bus)
- * @clks: I3C clock array
- * @xferqueue: Transfer queue structure
- * @xferqueue.list: List member
- * @xferqueue.cur: Current ongoing transfer
- * @xferqueue.lock: Queue lock
- * @ibi: IBI structure
- * @ibi.num_slots: Number of slots available in @ibi.slots
- * @ibi.slots: Available IBI slots
- * @ibi.tbq_slot: To be queued IBI slot
- * @ibi.lock: IBI lock
- * @lock: Transfer lock, protect between IBI work thread and callbacks from master
- * @drvdata: Driver data
- * @enabled_events: Bit masks for enable events (IBI, HotJoin).
- * @mctrl_config: Configuration value in SVC_I3C_MCTRL for setting speed back.
- */
-struct svc_i3c_master {
-	struct i3c_master_controller base;
-	struct device *dev;
-	void __iomem *regs;
-	struct svc_i3c_regs_save saved_regs;
-	u32 free_slots;
-	u8 addrs[SVC_I3C_MAX_DEVS];
-	struct i3c_dev_desc *descs[SVC_I3C_MAX_DEVS];
-	int irq;
-	int num_clks;
-	struct clk *fclk;
-	struct clk_bulk_data *clks;
-	struct {
-		struct list_head list;
-		struct svc_i3c_xfer *cur;
-		/* Prevent races between transfers */
-		spinlock_t lock;
-	} xferqueue;
-	struct {
-		unsigned int num_slots;
-		struct i3c_dev_desc **slots;
-		struct i3c_ibi_slot *tbq_slot;
-		/* Prevent races within IBI handlers */
-		spinlock_t lock;
-	} ibi;
-	struct mutex lock;
-	const struct svc_i3c_drvdata *drvdata;
-	u32 enabled_events;
-	u32 mctrl_config;
-};
-
-/**
- * struct svc_i3c_i2c_dev_data - Device specific data
- * @index: Index in the master tables corresponding to this device
- * @ibi: IBI slot index in the master structure
- * @ibi_pool: IBI pool associated to this device
- */
-struct svc_i3c_i2c_dev_data {
-	u8 index;
-	int ibi;
-	struct i3c_generic_ibi_pool *ibi_pool;
-};
-
-static inline bool svc_has_quirk(struct svc_i3c_master *master, u32 quirk)
-{
-	return (master->drvdata->quirks & quirk);
-}
-
-static inline bool svc_has_daa_corrupt(struct svc_i3c_master *master)
-{
-	return ((master->drvdata->quirks & SVC_I3C_QUIRK_DAA_CORRUPT) &&
-		!(master->mctrl_config &
-		(SVC_I3C_MCONFIG_SKEW_MASK | SVC_I3C_MCONFIG_ODHPP(1))));
-}
-
-static inline bool is_events_enabled(struct svc_i3c_master *master, u32 mask)
-{
-	return !!(master->enabled_events & mask);
-}
+#include "i3c-main.h"
 
 static bool svc_i3c_master_error(struct svc_i3c_master *master)
 {
@@ -719,7 +441,7 @@ static int svc_i3c_master_set_speed(struct i3c_master_controller *m,
 
 	switch (speed) {
 	case I3C_OPEN_DRAIN_SLOW_SPEED:
-		fclk_rate = clk_get_rate(master->fclk);
+		fclk_rate = master->fclk_rate;
 		if (!fclk_rate) {
 			ret = -EINVAL;
 			goto rpm_out;
@@ -769,7 +491,7 @@ static int svc_i3c_master_bus_init(struct i3c_master_controller *m)
 	}
 
 	/* Timings derivation */
-	fclk_rate = clk_get_rate(master->fclk);
+	fclk_rate = master->fclk_rate;
 	if (!fclk_rate) {
 		ret = -EINVAL;
 		goto rpm_out;
@@ -1997,56 +1719,55 @@ static const struct i3c_master_controller_ops svc_i3c_master_ops = {
 	.set_speed = svc_i3c_master_set_speed,
 };
 
-static int svc_i3c_master_probe(struct platform_device *pdev)
+void svc_i3c_master_remove(struct svc_i3c_master *master)
 {
-	struct device *dev = &pdev->dev;
-	struct svc_i3c_master *master;
-	int ret, i;
+	i3c_master_unregister(&master->base);
+}
+EXPORT_SYMBOL_GPL(svc_i3c_master_remove);
 
-	master = devm_kzalloc(dev, sizeof(*master), GFP_KERNEL);
-	if (!master)
-		return -ENOMEM;
+void svc_i3c_save_regs(struct svc_i3c_master *master)
+{
+	master->saved_regs.mconfig = readl(master->regs + SVC_I3C_MCONFIG);
+	master->saved_regs.mdynaddr = readl(master->regs + SVC_I3C_MDYNADDR);
+}
+EXPORT_SYMBOL_GPL(svc_i3c_save_regs);
 
-	master->drvdata = of_device_get_match_data(dev);
-	if (!master->drvdata)
-		return -EINVAL;
+void svc_i3c_restore_regs(struct svc_i3c_master *master)
+{
+	if (readl(master->regs + SVC_I3C_MDYNADDR) !=
+	    master->saved_regs.mdynaddr) {
+		writel(master->saved_regs.mconfig,
+		       master->regs + SVC_I3C_MCONFIG);
+		writel(master->saved_regs.mdynaddr,
+		       master->regs + SVC_I3C_MDYNADDR);
+	}
+}
+EXPORT_SYMBOL_GPL(svc_i3c_restore_regs);
 
-	master->regs = devm_platform_ioremap_resource(pdev, 0);
-	if (IS_ERR(master->regs))
-		return PTR_ERR(master->regs);
+struct svc_i3c_master *svc_i3c_master_alloc(struct device *dev)
+{
+	struct svc_i3c_master *master;
 
-	master->num_clks = devm_clk_bulk_get_all(dev, &master->clks);
-	if (master->num_clks < 0)
-		return dev_err_probe(dev, -EINVAL, "can't get I3C clocks\n");
+	master = devm_kzalloc(dev, sizeof(*master), GFP_KERNEL);
 
-	for (i = 0; i < master->num_clks; i++) {
-		if (!strcmp(master->clks[i].id, "fast_clk"))
-			break;
-	}
+	return master;
+}
+EXPORT_SYMBOL_GPL(svc_i3c_master_alloc);
 
-	if (i == master->num_clks)
-		return dev_err_probe(dev, -EINVAL,
-				     "can't get I3C peripheral clock\n");
+int svc_i3c_master_probe(struct platform_device *pdev, struct svc_i3c_master *master)
+{
+	struct device *dev = &pdev->dev;
 
-	master->fclk = master->clks[i].clk;
-	if (IS_ERR(master->fclk))
-		return PTR_ERR(master->fclk);
+	int ret;
 
 	master->irq = platform_get_irq(pdev, 0);
 	if (master->irq < 0)
 		return master->irq;
-
-	master->dev = dev;
-	ret = clk_bulk_prepare_enable(master->num_clks, master->clks);
-	if (ret)
-		return dev_err_probe(dev, ret, "can't enable I3C clocks\n");
-
 	mutex_init(&master->lock);
-
 	ret = devm_request_irq(dev, master->irq, svc_i3c_master_irq_handler,
 			       IRQF_NO_SUSPEND, "svc-i3c-irq", master);
 	if (ret)
-		goto err_disable_clks;
+		return ret;
 
 	master->free_slots = GENMASK(SVC_I3C_MAX_DEVS - 1, 0);
 
@@ -2058,128 +1779,18 @@ static int svc_i3c_master_probe(struct platform_device *pdev)
 	master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots,
 					 sizeof(*master->ibi.slots),
 					 GFP_KERNEL);
-	if (!master->ibi.slots) {
-		ret = -ENOMEM;
-		goto err_disable_clks;
-	}
-
-	platform_set_drvdata(pdev, master);
-
-	pm_runtime_set_autosuspend_delay(&pdev->dev, SVC_I3C_PM_TIMEOUT_MS);
-	pm_runtime_use_autosuspend(&pdev->dev);
-	pm_runtime_get_noresume(&pdev->dev);
-	pm_runtime_set_active(&pdev->dev);
-	pm_runtime_enable(&pdev->dev);
+	if (!master->ibi.slots)
+		return -ENOMEM;
 
 	svc_i3c_master_reset(master);
 
 	/* Register the master */
 	ret = i3c_master_register(&master->base, &pdev->dev,
 				  &svc_i3c_master_ops, false);
-	if (ret)
-		goto rpm_disable;
-
-	pm_runtime_put_autosuspend(&pdev->dev);
-
-	return 0;
-
-rpm_disable:
-	pm_runtime_dont_use_autosuspend(&pdev->dev);
-	pm_runtime_put_noidle(&pdev->dev);
-	pm_runtime_disable(&pdev->dev);
-	pm_runtime_set_suspended(&pdev->dev);
-
-err_disable_clks:
-	clk_bulk_disable_unprepare(master->num_clks, master->clks);
 
 	return ret;
 }
-
-static void svc_i3c_master_remove(struct platform_device *pdev)
-{
-	struct svc_i3c_master *master = platform_get_drvdata(pdev);
-
-	i3c_master_unregister(&master->base);
-
-	pm_runtime_dont_use_autosuspend(&pdev->dev);
-	pm_runtime_disable(&pdev->dev);
-}
-
-static void svc_i3c_save_regs(struct svc_i3c_master *master)
-{
-	master->saved_regs.mconfig = readl(master->regs + SVC_I3C_MCONFIG);
-	master->saved_regs.mdynaddr = readl(master->regs + SVC_I3C_MDYNADDR);
-}
-
-static void svc_i3c_restore_regs(struct svc_i3c_master *master)
-{
-	if (readl(master->regs + SVC_I3C_MDYNADDR) !=
-	    master->saved_regs.mdynaddr) {
-		writel(master->saved_regs.mconfig,
-		       master->regs + SVC_I3C_MCONFIG);
-		writel(master->saved_regs.mdynaddr,
-		       master->regs + SVC_I3C_MDYNADDR);
-	}
-}
-
-static int __maybe_unused svc_i3c_runtime_suspend(struct device *dev)
-{
-	struct svc_i3c_master *master = dev_get_drvdata(dev);
-
-	svc_i3c_save_regs(master);
-	clk_bulk_disable_unprepare(master->num_clks, master->clks);
-	pinctrl_pm_select_sleep_state(dev);
-
-	return 0;
-}
-
-static int __maybe_unused svc_i3c_runtime_resume(struct device *dev)
-{
-	struct svc_i3c_master *master = dev_get_drvdata(dev);
-	int ret;
-
-	pinctrl_pm_select_default_state(dev);
-	ret = clk_bulk_prepare_enable(master->num_clks, master->clks);
-	if (ret)
-		return ret;
-
-	svc_i3c_restore_regs(master);
-
-	return 0;
-}
-
-static const struct dev_pm_ops svc_i3c_pm_ops = {
-	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
-				      pm_runtime_force_resume)
-	SET_RUNTIME_PM_OPS(svc_i3c_runtime_suspend,
-			   svc_i3c_runtime_resume, NULL)
-};
-
-static const struct svc_i3c_drvdata npcm845_drvdata = {
-	.quirks = SVC_I3C_QUIRK_FIFO_EMPTY |
-		SVC_I3C_QUIRK_FALSE_SLVSTART |
-		SVC_I3C_QUIRK_DAA_CORRUPT,
-};
-
-static const struct svc_i3c_drvdata svc_default_drvdata = {};
-
-static const struct of_device_id svc_i3c_master_of_match_tbl[] = {
-	{ .compatible = "nuvoton,npcm845-i3c", .data = &npcm845_drvdata },
-	{ .compatible = "silvaco,i3c-master-v1", .data = &svc_default_drvdata },
-	{ /* sentinel */ },
-};
-MODULE_DEVICE_TABLE(of, svc_i3c_master_of_match_tbl);
-
-static struct platform_driver svc_i3c_master = {
-	.probe = svc_i3c_master_probe,
-	.remove = svc_i3c_master_remove,
-	.driver = {
-		.name = "silvaco-i3c-master",
-		.of_match_table = svc_i3c_master_of_match_tbl,
-		.pm = &svc_i3c_pm_ops,
-	},
-};
-module_platform_driver(svc_i3c_master);
+EXPORT_SYMBOL_GPL(svc_i3c_master_probe);
 
 MODULE_AUTHOR("Conor Culhane <conor.culhane@silvaco.com>");
 MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
diff --git a/drivers/i3c/master/svc/i3c-main.h b/drivers/i3c/master/svc/i3c-main.h
new file mode 100644
index 000000000..f72d9c26b
--- /dev/null
+++ b/drivers/i3c/master/svc/i3c-main.h
@@ -0,0 +1,297 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Silvaco dual-role I3C master driver
+ *
+ * Copyright (C) 2020 Silvaco
+ * Author: Miquel RAYNAL <miquel.raynal@bootlin.com>
+ * Based on a work from: Conor Culhane <conor.culhane@silvaco.com>
+ */
+
+#ifndef _SVC_I3C_MASTER_H_
+#define _SVC_I3C_MASTER_H_
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/errno.h>
+#include <linux/i3c/master.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+/* Master Mode Registers */
+#define SVC_I3C_MCONFIG      0x000
+#define   SVC_I3C_MCONFIG_MASTER_EN BIT(0)
+#define   SVC_I3C_MCONFIG_DISTO(x) FIELD_PREP(BIT(3), (x))
+#define   SVC_I3C_MCONFIG_HKEEP(x) FIELD_PREP(GENMASK(5, 4), (x))
+#define   SVC_I3C_MCONFIG_ODSTOP(x) FIELD_PREP(BIT(6), (x))
+#define   SVC_I3C_MCONFIG_PPBAUD(x) FIELD_PREP(GENMASK(11, 8), (x))
+#define   SVC_I3C_MCONFIG_PPLOW(x) FIELD_PREP(GENMASK(15, 12), (x))
+#define   SVC_I3C_MCONFIG_ODBAUD(x) FIELD_PREP(GENMASK(23, 16), (x))
+#define   SVC_I3C_MCONFIG_ODHPP(x) FIELD_PREP(BIT(24), (x))
+#define   SVC_I3C_MCONFIG_SKEW(x) FIELD_PREP(GENMASK(27, 25), (x))
+#define   SVC_I3C_MCONFIG_SKEW_MASK GENMASK(27, 25)
+#define   SVC_I3C_MCONFIG_I2CBAUD(x) FIELD_PREP(GENMASK(31, 28), (x))
+
+#define SVC_I3C_MCTRL        0x084
+#define   SVC_I3C_MCTRL_REQUEST_MASK GENMASK(2, 0)
+#define   SVC_I3C_MCTRL_REQUEST_NONE 0
+#define   SVC_I3C_MCTRL_REQUEST_START_ADDR 1
+#define   SVC_I3C_MCTRL_REQUEST_STOP 2
+#define   SVC_I3C_MCTRL_REQUEST_FORCE_EXIT 6
+#define   SVC_I3C_MCTRL_REQUEST_IBI_ACKNACK 3
+#define   SVC_I3C_MCTRL_REQUEST_PROC_DAA 4
+#define   SVC_I3C_MCTRL_REQUEST_AUTO_IBI 7
+#define   SVC_I3C_MCTRL_TYPE_I3C 0
+#define   SVC_I3C_MCTRL_TYPE_I2C BIT(4)
+#define   SVC_I3C_MCTRL_TYPE_DDR BIT(5)
+#define   SVC_I3C_MCTRL_IBIRESP_AUTO 0
+#define   SVC_I3C_MCTRL_IBIRESP_ACK_WITHOUT_BYTE 0
+#define   SVC_I3C_MCTRL_IBIRESP_ACK_WITH_BYTE BIT(7)
+#define   SVC_I3C_MCTRL_IBIRESP_NACK BIT(6)
+#define   SVC_I3C_MCTRL_IBIRESP_MANUAL GENMASK(7, 6)
+#define   SVC_I3C_MCTRL_DIR(x) FIELD_PREP(BIT(8), (x))
+#define   SVC_I3C_MCTRL_DIR_WRITE 0
+#define   SVC_I3C_MCTRL_DIR_READ 1
+#define   SVC_I3C_MCTRL_ADDR(x) FIELD_PREP(GENMASK(15, 9), (x))
+#define   SVC_I3C_MCTRL_RDTERM(x) FIELD_PREP(GENMASK(23, 16), (x))
+
+#define SVC_I3C_MSTATUS      0x088
+#define   SVC_I3C_MSTATUS_STATE(x) FIELD_GET(GENMASK(2, 0), (x))
+#define   SVC_I3C_MSTATUS_STATE_DAA(x) (SVC_I3C_MSTATUS_STATE(x) == 5)
+#define   SVC_I3C_MSTATUS_STATE_SLVREQ(x) (SVC_I3C_MSTATUS_STATE(x) == 1)
+#define   SVC_I3C_MSTATUS_STATE_IDLE(x) (SVC_I3C_MSTATUS_STATE(x) == 0)
+#define   SVC_I3C_MSTATUS_BETWEEN(x) FIELD_GET(BIT(4), (x))
+#define   SVC_I3C_MSTATUS_NACKED(x) FIELD_GET(BIT(5), (x))
+#define   SVC_I3C_MSTATUS_IBITYPE(x) FIELD_GET(GENMASK(7, 6), (x))
+#define   SVC_I3C_MSTATUS_IBITYPE_IBI 1
+#define   SVC_I3C_MSTATUS_IBITYPE_MASTER_REQUEST 2
+#define   SVC_I3C_MSTATUS_IBITYPE_HOT_JOIN 3
+#define   SVC_I3C_MINT_SLVSTART BIT(8)
+#define   SVC_I3C_MINT_MCTRLDONE BIT(9)
+#define   SVC_I3C_MINT_COMPLETE BIT(10)
+#define   SVC_I3C_MINT_RXPEND BIT(11)
+#define   SVC_I3C_MINT_TXNOTFULL BIT(12)
+#define   SVC_I3C_MINT_IBIWON BIT(13)
+#define   SVC_I3C_MINT_ERRWARN BIT(15)
+#define   SVC_I3C_MSTATUS_SLVSTART(x) FIELD_GET(SVC_I3C_MINT_SLVSTART, (x))
+#define   SVC_I3C_MSTATUS_MCTRLDONE(x) FIELD_GET(SVC_I3C_MINT_MCTRLDONE, (x))
+#define   SVC_I3C_MSTATUS_COMPLETE(x) FIELD_GET(SVC_I3C_MINT_COMPLETE, (x))
+#define   SVC_I3C_MSTATUS_RXPEND(x) FIELD_GET(SVC_I3C_MINT_RXPEND, (x))
+#define   SVC_I3C_MSTATUS_TXNOTFULL(x) FIELD_GET(SVC_I3C_MINT_TXNOTFULL, (x))
+#define   SVC_I3C_MSTATUS_IBIWON(x) FIELD_GET(SVC_I3C_MINT_IBIWON, (x))
+#define   SVC_I3C_MSTATUS_ERRWARN(x) FIELD_GET(SVC_I3C_MINT_ERRWARN, (x))
+#define   SVC_I3C_MSTATUS_IBIADDR(x) FIELD_GET(GENMASK(30, 24), (x))
+
+#define SVC_I3C_IBIRULES     0x08C
+#define   SVC_I3C_IBIRULES_ADDR(slot, addr) FIELD_PREP(GENMASK(29, 0), \
+						       ((addr) & 0x3F) << ((slot) * 6))
+#define   SVC_I3C_IBIRULES_ADDRS 5
+#define   SVC_I3C_IBIRULES_MSB0 BIT(30)
+#define   SVC_I3C_IBIRULES_NOBYTE BIT(31)
+#define   SVC_I3C_IBIRULES_MANDBYTE 0
+#define SVC_I3C_MINTSET      0x090
+#define SVC_I3C_MINTCLR      0x094
+#define SVC_I3C_MINTMASKED   0x098
+#define SVC_I3C_MERRWARN     0x09C
+#define   SVC_I3C_MERRWARN_NACK BIT(2)
+#define   SVC_I3C_MERRWARN_CRC	BIT(10)
+#define   SVC_I3C_MERRWARN_TIMEOUT BIT(20)
+#define SVC_I3C_MDMACTRL     0x0A0
+#define SVC_I3C_MDATACTRL    0x0AC
+#define   SVC_I3C_MDATACTRL_FLUSHTB BIT(0)
+#define   SVC_I3C_MDATACTRL_FLUSHRB BIT(1)
+#define   SVC_I3C_MDATACTRL_UNLOCK_TRIG BIT(3)
+#define   SVC_I3C_MDATACTRL_TXTRIG_FIFO_NOT_FULL GENMASK(5, 4)
+#define   SVC_I3C_MDATACTRL_RXTRIG_FIFO_NOT_EMPTY 0
+#define   SVC_I3C_MDATACTRL_RXCOUNT(x) FIELD_GET(GENMASK(28, 24), (x))
+#define   SVC_I3C_MDATACTRL_TXCOUNT(x) FIELD_GET(GENMASK(20, 16), (x))
+#define   SVC_I3C_MDATACTRL_TXFULL BIT(30)
+#define   SVC_I3C_MDATACTRL_RXEMPTY BIT(31)
+
+#define SVC_I3C_MWDATAB      0x0B0
+#define   SVC_I3C_MWDATAB_END BIT(8)
+
+#define SVC_I3C_MWDATABE     0x0B4
+#define SVC_I3C_MWDATAH      0x0B8
+#define SVC_I3C_MWDATAHE     0x0BC
+#define SVC_I3C_MRDATAB      0x0C0
+#define SVC_I3C_MRDATAH      0x0C8
+#define SVC_I3C_MWDATAB1     0x0CC
+#define SVC_I3C_MWMSG_SDR    0x0D0
+#define SVC_I3C_MRMSG_SDR    0x0D4
+#define SVC_I3C_MWMSG_DDR    0x0D8
+#define SVC_I3C_MRMSG_DDR    0x0DC
+
+#define SVC_I3C_MDYNADDR     0x0E4
+#define   SVC_MDYNADDR_VALID BIT(0)
+#define   SVC_MDYNADDR_ADDR(x) FIELD_PREP(GENMASK(7, 1), (x))
+
+#define SVC_I3C_MAX_DEVS 32
+#define SVC_I3C_PM_TIMEOUT_MS 1000
+
+/* This parameter depends on the implementation and may be tuned */
+#define SVC_I3C_FIFO_SIZE 16
+#define SVC_I3C_PPBAUD_MAX 15
+#define SVC_I3C_QUICK_I2C_CLK 4170000
+
+#define SVC_I3C_EVENT_IBI	GENMASK(7, 0)
+#define SVC_I3C_EVENT_HOTJOIN	BIT(31)
+
+/*
+ * SVC_I3C_QUIRK_FIFO_EMPTY:
+ * I3C HW stalls the write transfer if the transmit FIFO becomes empty,
+ * when new data is written to FIFO, I3C HW resumes the transfer but
+ * the first transmitted data bit may have the wrong value.
+ * Workaround:
+ * Fill the FIFO in advance to prevent FIFO from becoming empty.
+ */
+#define SVC_I3C_QUIRK_FIFO_EMPTY	BIT(0)
+/*
+ * SVC_I3C_QUIRK_FLASE_SLVSTART:
+ * I3C HW may generate an invalid SlvStart event when emitting a STOP.
+ * If it is a true SlvStart, the MSTATUS state is SLVREQ.
+ */
+#define SVC_I3C_QUIRK_FALSE_SLVSTART	BIT(1)
+/*
+ * SVC_I3C_QUIRK_DAA_CORRUPT:
+ * When MCONFIG.SKEW=0 and MCONFIG.ODHPP=0, the ENTDAA transaction gets
+ * corrupted and results in a no repeated-start condition at the end of
+ * address assignment.
+ * Workaround:
+ * Set MCONFIG.SKEW to 1 before initiating the DAA process. After the DAA
+ * process is completed, return MCONFIG.SKEW to its previous value.
+ */
+#define SVC_I3C_QUIRK_DAA_CORRUPT	BIT(2)
+
+struct svc_i3c_cmd {
+	u8 addr;
+	union {
+		bool rnw;
+		u8 cmd;
+		u32 rnw_cmd;
+	};
+	u8 *in;
+	const void *out;
+	unsigned int len;
+	unsigned int actual_len;
+	struct i3c_xfer *xfer;
+	bool continued;
+};
+
+struct svc_i3c_xfer {
+	struct list_head node;
+	struct completion comp;
+	int ret;
+	unsigned int type;
+	unsigned int ncmds;
+	struct svc_i3c_cmd cmds[] __counted_by(ncmds);
+};
+
+struct svc_i3c_regs_save {
+	u32 mconfig;
+	u32 mdynaddr;
+};
+
+struct svc_i3c_drvdata {
+	u32 quirks;
+	const void *bsp_data;
+};
+
+/**
+ * struct svc_i3c_master - Silvaco I3C Master structure
+ * @base: I3C master controller
+ * @dev: Corresponding device
+ * @regs: Memory mapping
+ * @saved_regs: Volatile values for PM operations
+ * @free_slots: Bit array of available slots
+ * @addrs: Array containing the dynamic addresses of each attached device
+ * @descs: Array of descriptors, one per attached device
+ * @irq: Main interrupt
+ * @fclk_rate: Fast clock (bus) rate, in Hz, set by the glue layer
+ * @bsp_priv: Private data owned by the glue layer
+ * @xferqueue: Transfer queue structure
+ * @xferqueue.list: List member
+ * @xferqueue.cur: Current ongoing transfer
+ * @xferqueue.lock: Queue lock
+ * @ibi: IBI structure
+ * @ibi.num_slots: Number of slots available in @ibi.slots
+ * @ibi.slots: Available IBI slots
+ * @ibi.tbq_slot: To be queued IBI slot
+ * @ibi.lock: IBI lock
+ * @lock: Transfer lock, protect between IBI work thread and callbacks from master
+ * @drvdata: Driver data
+ * @enabled_events: Bit masks for enable events (IBI, HotJoin).
+ * @mctrl_config: Configuration value in SVC_I3C_MCTRL for setting speed back.
+ */
+struct svc_i3c_master {
+	struct i3c_master_controller base;
+	struct device *dev;
+	void __iomem *regs;
+	struct svc_i3c_regs_save saved_regs;
+	u32 free_slots;
+	u8 addrs[SVC_I3C_MAX_DEVS];
+	struct i3c_dev_desc *descs[SVC_I3C_MAX_DEVS];
+	int irq;
+	unsigned long fclk_rate;
+	void *bsp_priv;
+	struct {
+		struct list_head list;
+		struct svc_i3c_xfer *cur;
+		/* Prevent races between transfers */
+		spinlock_t lock;
+	} xferqueue;
+	struct {
+		unsigned int num_slots;
+		struct i3c_dev_desc **slots;
+		struct i3c_ibi_slot *tbq_slot;
+		/* Prevent races within IBI handlers */
+		spinlock_t lock;
+	} ibi;
+	struct mutex lock;
+	const struct svc_i3c_drvdata *drvdata;
+	u32 enabled_events;
+	u32 mctrl_config;
+};
+
+/**
+ * struct svc_i3c_i2c_dev_data - Device specific data
+ * @index: Index in the master tables corresponding to this device
+ * @ibi: IBI slot index in the master structure
+ * @ibi_pool: IBI pool associated to this device
+ */
+struct svc_i3c_i2c_dev_data {
+	u8 index;
+	int ibi;
+	struct i3c_generic_ibi_pool *ibi_pool;
+};
+
+static inline bool svc_has_quirk(struct svc_i3c_master *master, u32 quirk)
+{
+	return (master->drvdata->quirks & quirk);
+}
+
+static inline bool svc_has_daa_corrupt(struct svc_i3c_master *master)
+{
+	return ((master->drvdata->quirks & SVC_I3C_QUIRK_DAA_CORRUPT) &&
+		!(master->mctrl_config &
+		(SVC_I3C_MCONFIG_SKEW_MASK | SVC_I3C_MCONFIG_ODHPP(1))));
+}
+
+static inline bool is_events_enabled(struct svc_i3c_master *master, u32 mask)
+{
+	return !!(master->enabled_events & mask);
+}
+
+void svc_i3c_save_regs(struct svc_i3c_master *master);
+void svc_i3c_restore_regs(struct svc_i3c_master *master);
+struct svc_i3c_master *svc_i3c_master_alloc(struct device *dev);
+void svc_i3c_master_remove(struct svc_i3c_master *master);
+int svc_i3c_master_probe(struct platform_device *pdev, struct svc_i3c_master *master);
+
+#endif /* _SVC_I3C_MASTER_H_ */
diff --git a/drivers/i3c/master/svc/i3c-silvaco.c b/drivers/i3c/master/svc/i3c-silvaco.c
new file mode 100644
index 000000000..da2fc1560
--- /dev/null
+++ b/drivers/i3c/master/svc/i3c-silvaco.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Silvaco dual-role I3C master driver
+ *
+ * Copyright (C) 2020 Silvaco
+ * Author: Miquel RAYNAL <miquel.raynal@bootlin.com>
+ * Based on a work from: Conor Culhane <conor.culhane@silvaco.com>
+ */
+#include "i3c-main.h"
+
+struct svc_origin_priv {
+	int num_clks;
+	struct clk_bulk_data *clks;
+};
+
+static int silvaco_i3c_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct svc_i3c_master *master;
+	struct svc_origin_priv *priv;
+	int ret, i;
+
+	master = svc_i3c_master_alloc(dev);
+	if (!master)
+		return -ENOMEM;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	master->drvdata = of_device_get_match_data(dev);
+	if (!master->drvdata)
+		return -EINVAL;
+
+	master->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(master->regs))
+		return PTR_ERR(master->regs);
+
+	master->irq = platform_get_irq(pdev, 0);
+	if (master->irq < 0)
+		return master->irq;
+
+	priv->num_clks = devm_clk_bulk_get_all(dev, &priv->clks);
+	if (priv->num_clks < 0)
+		return dev_err_probe(dev, -EINVAL, "can't get I3C clocks\n");
+
+	for (i = 0; i < priv->num_clks; i++) {
+		if (!strcmp(priv->clks[i].id, "fast_clk"))
+			break;
+	}
+
+	if (i == priv->num_clks)
+		return dev_err_probe(dev, -EINVAL,
+				     "can't get I3C peripheral clock\n");
+
+	if (IS_ERR(priv->clks[i].clk))
+		return PTR_ERR(priv->clks[i].clk);
+
+	ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
+	if (ret)
+		return dev_err_probe(dev, ret, "can't enable I3C clocks\n");
+
+	master->dev = dev;
+	master->bsp_priv = priv;
+	master->fclk_rate = clk_get_rate(priv->clks[i].clk);
+	if (!master->fclk_rate) {
+		dev_err(dev, "can't get I3C fast clock rate\n");
+		ret = -EINVAL;
+		goto err_disable_clks;
+	}
+
+	pm_runtime_set_autosuspend_delay(dev, SVC_I3C_PM_TIMEOUT_MS);
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_get_noresume(dev);
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+
+	ret = svc_i3c_master_probe(pdev, master);
+	if (ret)
+		goto rpm_disable;
+
+	platform_set_drvdata(pdev, master);
+	pm_runtime_put_autosuspend(dev);
+
+	return 0;
+
+rpm_disable:
+	pm_runtime_dont_use_autosuspend(dev);
+	pm_runtime_put_noidle(dev);
+	pm_runtime_disable(dev);
+	pm_runtime_set_suspended(dev);
+
+err_disable_clks:
+	clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
+
+	return ret;
+}
+
+static void silvaco_i3c_remove(struct platform_device *pdev)
+{
+	struct svc_i3c_master *master = platform_get_drvdata(pdev);
+
+	svc_i3c_master_remove(master);
+
+	pm_runtime_dont_use_autosuspend(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+}
+
+static int __maybe_unused silvaco_i3c_runtime_suspend(struct device *dev)
+{
+	struct svc_i3c_master *master = dev_get_drvdata(dev);
+	struct svc_origin_priv *priv = master->bsp_priv;
+
+	svc_i3c_save_regs(master);
+	clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
+	pinctrl_pm_select_sleep_state(dev);
+
+	return 0;
+}
+
+static int __maybe_unused silvaco_i3c_runtime_resume(struct device *dev)
+{
+	struct svc_i3c_master *master = dev_get_drvdata(dev);
+	struct svc_origin_priv *priv = master->bsp_priv;
+	int ret;
+
+	pinctrl_pm_select_default_state(dev);
+	ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
+	if (ret)
+		return ret;
+
+	svc_i3c_restore_regs(master);
+
+	return 0;
+}
+
+static const struct dev_pm_ops silvaco_i3c_pm_ops = {
+	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				      pm_runtime_force_resume)
+	SET_RUNTIME_PM_OPS(silvaco_i3c_runtime_suspend,
+			   silvaco_i3c_runtime_resume, NULL)
+};
+
+static const struct svc_i3c_drvdata npcm845_drvdata = {
+	.quirks = SVC_I3C_QUIRK_FIFO_EMPTY |
+		SVC_I3C_QUIRK_FALSE_SLVSTART |
+		SVC_I3C_QUIRK_DAA_CORRUPT,
+};
+
+static const struct svc_i3c_drvdata svc_default_drvdata = {};
+
+static const struct of_device_id svc_i3c_master_of_match_tbl[] = {
+	{ .compatible = "nuvoton,npcm845-i3c", .data = &npcm845_drvdata },
+	{ .compatible = "silvaco,i3c-master-v1", .data = &svc_default_drvdata },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, svc_i3c_master_of_match_tbl);
+
+static struct platform_driver silvaco_i3c_driver = {
+	.probe = silvaco_i3c_probe,
+	.remove = silvaco_i3c_remove,
+	.driver = {
+		.name = "silvaco-i3c-master",
+		.of_match_table = svc_i3c_master_of_match_tbl,
+		.pm = &silvaco_i3c_pm_ops,
+	},
+};
+module_platform_driver(silvaco_i3c_driver);
+
+MODULE_AUTHOR("Conor Culhane <conor.culhane@silvaco.com>");
+MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
+MODULE_DESCRIPTION("Silvaco dual-role I3C master driver");
+MODULE_LICENSE("GPL v2");

-- 
2.42.0



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

* [PATCH 2/3] dt-bindings: i3c: Add Amlogic A9 I3C controller
  2026-10-19  5:34 [PATCH 0/3] Add vendor glue layer support and Amlogic A9 implementation Junyi Zhao via B4 Relay
  2026-10-19  5:34 ` [PATCH 1/3] i3c: master: Rework SVC I3C to support vendor glue layers Junyi Zhao via B4 Relay
@ 2026-10-19  5:34 ` Junyi Zhao via B4 Relay
  2026-10-19  6:00   ` Krzysztof Kozlowski
  2026-10-19  6:01   ` Krzysztof Kozlowski
  2026-10-19  5:34 ` [PATCH 3/3] i3c: master: svc: Add Amlogic A9 glue layer Junyi Zhao via B4 Relay
  2 siblings, 2 replies; 8+ messages in thread
From: Junyi Zhao via B4 Relay @ 2026-10-19  5:34 UTC (permalink / raw)
  To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Neil Armstrong, Kevin Hilman, Jerome Brunet,
	Martin Blumenstingl, Miquel Raynal, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-i3c, devicetree, linux-kernel, linux-arm-kernel,
	linux-amlogic, imx, linux-hardening, xianwei.zhao, Kelvin.Zhang,
	Junyi Zhao

From: Junyi Zhao <junyi.zhao@amlogic.com>

Add a devicetree binding schema for the A9 I3C controller.

The binding describes the controller instance and its bus-specific
properties required for driver probing.

Signed-off-by: Junyi Zhao <junyi.zhao@amlogic.com>
---
 .../devicetree/bindings/i3c/amlogic,i3c-a9.yaml    | 102 +++++++++++++++++++++
 1 file changed, 102 insertions(+)

diff --git a/Documentation/devicetree/bindings/i3c/amlogic,i3c-a9.yaml b/Documentation/devicetree/bindings/i3c/amlogic,i3c-a9.yaml
new file mode 100644
index 000000000..8e25122c6
--- /dev/null
+++ b/Documentation/devicetree/bindings/i3c/amlogic,i3c-a9.yaml
@@ -0,0 +1,102 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2026 Amlogic, Inc. All rights reserved
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/i3c/amlogic,i3c-a9.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Amlogic A9 I3C master controller
+
+maintainers:
+  - Junyi Zhao <junyi.zhao@amlogic.com>
+
+description:
+  The Amlogic A9 SoC family embeds an I3C master controller based on the
+  Silvaco I3C Dual-Role Master IP, with Amlogic specific glue logic.
+
+allOf:
+  - $ref: /schemas/i3c/i3c.yaml#
+
+properties:
+  compatible:
+    enum:
+      - amlogic,i3c-a9
+      - amlogic,i3c-ao-a9
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    minItems: 1
+    maxItems: 2
+
+  clock-names:
+    minItems: 1
+    maxItems: 2
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - clock-names
+
+unevaluatedProperties: false
+
+if:
+  properties:
+    compatible:
+      const: amlogic,i3c-a9
+then:
+  properties:
+    clocks:
+      items:
+        - description: APB peripheral clock
+        - description: I3C fast clock
+    clock-names:
+      items:
+        - const: pclk
+        - const: fast_clk
+else:
+  properties:
+    clocks:
+      maxItems: 1
+    clock-names:
+      items:
+        - const: fast_clk
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    i3c@3a7000 {
+        compatible = "amlogic,i3c-a9";
+        reg = <0x3a7000 0x10000>;
+        interrupts = <GIC_SPI 153 IRQ_TYPE_LEVEL_HIGH>;
+        #address-cells = <3>;
+        #size-cells = <0>;
+        clocks = <&clkc 15>,
+                 <&clkc 16>;
+        clock-names = "pclk", "fast_clk";
+        status = "disabled";
+    };
+
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    i3c@22000 {
+        compatible = "amlogic,i3c-ao-a9";
+        reg = <0x22000 0x10000>;
+        interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
+        #address-cells = <3>;
+        #size-cells = <0>;
+        clocks = <&clkc 17>;
+        clock-names = "fast_clk";
+        status = "disabled";
+    };
+...

-- 
2.42.0



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

* [PATCH 3/3] i3c: master: svc: Add Amlogic A9 glue layer
  2026-10-19  5:34 [PATCH 0/3] Add vendor glue layer support and Amlogic A9 implementation Junyi Zhao via B4 Relay
  2026-10-19  5:34 ` [PATCH 1/3] i3c: master: Rework SVC I3C to support vendor glue layers Junyi Zhao via B4 Relay
  2026-10-19  5:34 ` [PATCH 2/3] dt-bindings: i3c: Add Amlogic A9 I3C controller Junyi Zhao via B4 Relay
@ 2026-10-19  5:34 ` Junyi Zhao via B4 Relay
  2026-10-19  5:52   ` sashiko-bot
  2026-10-19  7:40   ` Neil Armstrong
  2 siblings, 2 replies; 8+ messages in thread
From: Junyi Zhao via B4 Relay @ 2026-10-19  5:34 UTC (permalink / raw)
  To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Neil Armstrong, Kevin Hilman, Jerome Brunet,
	Martin Blumenstingl, Miquel Raynal, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-i3c, devicetree, linux-kernel, linux-arm-kernel,
	linux-amlogic, imx, linux-hardening, xianwei.zhao, Kelvin.Zhang,
	Junyi Zhao

From: Junyi Zhao <junyi.zhao@amlogic.com>

Add support for the Amlogic A9 implementation of the Silvaco
SVC I3C controller.

The glue layer provides Amlogic-specific operations and resource
handling while reusing the common SVC I3C core driver.

Add a corresponding MAINTAINERS entry for the Amlogic I3C
controller.

Signed-off-by: Junyi Zhao <junyi.zhao@amlogic.com>
---
 MAINTAINERS                          |   6 +
 drivers/i3c/master/svc/Kconfig       |   7 +
 drivers/i3c/master/svc/Makefile      |   1 +
 drivers/i3c/master/svc/i3c-amlogic.c | 250 +++++++++++++++++++++++++++++++++++
 4 files changed, 264 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index dffc63af2..478979446 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1398,6 +1398,12 @@ F:	Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst
 F:	drivers/media/platform/amlogic/c3/isp/
 F:	include/uapi/linux/media/amlogic/
 
+AMLOGIC I3C DRIVER
+M:	Junyi Zhao <junyi.zhao@amlogic.com>
+S:	Maintained
+F:	Documentation/devicetree/bindings/i3c/amlogic,i3c-a9.yaml
+F:	drivers/i3c/master/svc/i3c-amlogic.c
+
 AMLOGIC MIPI ADAPTER DRIVER
 M:	Keke Li <keke.li@amlogic.com>
 L:	linux-media@vger.kernel.org
diff --git a/drivers/i3c/master/svc/Kconfig b/drivers/i3c/master/svc/Kconfig
index 14fa1e542..df3528bc1 100644
--- a/drivers/i3c/master/svc/Kconfig
+++ b/drivers/i3c/master/svc/Kconfig
@@ -18,4 +18,11 @@ config I3C_SILVACO
 	  compatible strings such as "silvaco,i3c-master-v1" and
 	  "nuvoton,npcm845-i3c".
 
+config I3C_AMLOGIC
+	tristate "Amlogic I3C support"
+	default y
+	help
+	  This selects the Amlogic SoC glue layer support for
+	  the Silvaco device driver. This driver is used for A9, C5 SoCs.
+
 endif # SVC_I3C
diff --git a/drivers/i3c/master/svc/Makefile b/drivers/i3c/master/svc/Makefile
index cf18c5da6..695863d1f 100644
--- a/drivers/i3c/master/svc/Makefile
+++ b/drivers/i3c/master/svc/Makefile
@@ -3,3 +3,4 @@
 
 obj-$(CONFIG_SVC_I3C)	+= i3c-main.o
 obj-$(CONFIG_I3C_SILVACO)	+= i3c-silvaco.o
+obj-$(CONFIG_I3C_AMLOGIC)	+= i3c-amlogic.o
diff --git a/drivers/i3c/master/svc/i3c-amlogic.c b/drivers/i3c/master/svc/i3c-amlogic.c
new file mode 100644
index 000000000..82300d6ae
--- /dev/null
+++ b/drivers/i3c/master/svc/i3c-amlogic.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2026 Amlogic, Inc. All rights reserved
+ */
+#include "i3c-main.h"
+#include <linux/math64.h>
+
+/* Amlogic specific registers */
+#define I3C_BG_RD_ADDR			0x200
+#define I3C_BG_WR_ADDR			0x204
+#define I3C_BG_WR_CTRL			0x208
+#define I3C_BG_R_TT_LEN			0x20c
+#define I3C_BG_X_CNT			0x210
+#define I3C_BUS_MON_CTRL		0x214
+#define I3C_BUS_MON_ADDR		0x218
+#define I3C_BUS_MON_DATA		0x21c
+#define I3C_BUS_MON_DATA_MAK		0x220
+#define I3C_CLK_SLOW_DIV_CTRL		0x224
+#define I3C_BG_R_WCNT_CLR		0x228
+#define I3C_DEBUG_MODIFY		0x22c
+
+#define I3C_AO_CLK_SLOW_DIV_CTRL	0x300
+#define I3C_AO_FCLK_DIV_CTRL		0x304
+#define I3C_AO_FCLK_DIV_MASK		GENMASK(7, 0)
+#define I3C_AO_FCLK_DIV_EN		BIT(8)
+#define I3C_AO_FCLK_EN			BIT(9)
+#define I3C_AO_DEBUG_MODIFY		0x308
+
+#define I3C_DEFAULT_FCLK_RATE		25000000UL
+
+struct amlogic_i3c_priv {
+	int num_clks;
+	struct clk_bulk_data *clks;
+};
+
+struct amlogic_i3c_drvdata {
+	int (*fclk_rate_init)(struct svc_i3c_master *master,
+			      struct clk *fclk, unsigned long rate);
+};
+
+static int default_i3c_fclk_rate_init(struct svc_i3c_master *master,
+				      struct clk *fclk, unsigned long rate)
+{
+	int err;
+
+	err = clk_set_rate(fclk, rate);
+	if (err)
+		return dev_err_probe(master->dev, err,
+				     "setting fast clock rate failed\n");
+
+	return 0;
+}
+
+static int ao_i3c_fclk_rate_init(struct svc_i3c_master *master,
+				 struct clk *fclk, unsigned long rate)
+{
+	unsigned int div_cnt, reg_val;
+	unsigned long fin_freq = clk_get_rate(fclk);
+
+	if (!fin_freq)
+		return dev_err_probe(master->dev, -EINVAL,
+				     "can't get I3C fast clock rate\n");
+
+	div_cnt = div64_u64(fin_freq, rate);
+	if (!div_cnt || div_cnt > I3C_AO_FCLK_DIV_MASK)
+		return dev_err_probe(master->dev, -EINVAL,
+				     "requested fast clock frequency too low\n");
+
+	reg_val = I3C_AO_FCLK_DIV_EN | I3C_AO_FCLK_EN | (div_cnt - 1);
+	writel(reg_val, master->regs + I3C_AO_FCLK_DIV_CTRL);
+
+	return 0;
+}
+
+static int amlogic_i3c_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const struct amlogic_i3c_drvdata *data;
+	struct svc_i3c_master *master;
+	struct amlogic_i3c_priv *priv;
+	struct clk *fclk;
+	int ret, i;
+
+	master = svc_i3c_master_alloc(dev);
+	if (!master)
+		return -ENOMEM;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	master->drvdata = of_device_get_match_data(dev);
+	if (!master->drvdata)
+		return -EINVAL;
+
+	data = master->drvdata->bsp_data;
+	if (!data)
+		return -EINVAL;
+
+	master->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(master->regs))
+		return PTR_ERR(master->regs);
+
+	master->irq = platform_get_irq(pdev, 0);
+	if (master->irq < 0)
+		return master->irq;
+
+	priv->num_clks = devm_clk_bulk_get_all(dev, &priv->clks);
+	if (priv->num_clks < 0)
+		return dev_err_probe(dev, -EINVAL, "can't get I3C clocks\n");
+
+	for (i = 0; i < priv->num_clks; i++) {
+		if (!strcmp(priv->clks[i].id, "fast_clk"))
+			break;
+	}
+
+	if (i == priv->num_clks)
+		return dev_err_probe(dev, -EINVAL,
+				     "can't get I3C peripheral clock\n");
+
+	if (IS_ERR(priv->clks[i].clk))
+		return PTR_ERR(priv->clks[i].clk);
+	fclk = priv->clks[i].clk;
+
+	ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
+	if (ret)
+		return dev_err_probe(dev, ret, "can't enable I3C clocks\n");
+
+	master->dev = dev;
+	master->bsp_priv = priv;
+	ret = data->fclk_rate_init(master, fclk, I3C_DEFAULT_FCLK_RATE);
+	if (ret)
+		goto err_disable_clks;
+	master->fclk_rate = I3C_DEFAULT_FCLK_RATE;
+
+	pm_runtime_set_autosuspend_delay(dev, SVC_I3C_PM_TIMEOUT_MS);
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_get_noresume(dev);
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+
+	ret = svc_i3c_master_probe(pdev, master);
+	if (ret)
+		goto rpm_disable;
+
+	platform_set_drvdata(pdev, master);
+	pm_runtime_put_autosuspend(dev);
+
+	return 0;
+
+rpm_disable:
+	pm_runtime_dont_use_autosuspend(dev);
+	pm_runtime_put_noidle(dev);
+	pm_runtime_disable(dev);
+	pm_runtime_set_suspended(dev);
+
+err_disable_clks:
+	clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
+
+	return ret;
+}
+
+static void amlogic_i3c_remove(struct platform_device *pdev)
+{
+	struct svc_i3c_master *master = platform_get_drvdata(pdev);
+
+	svc_i3c_master_remove(master);
+
+	pm_runtime_dont_use_autosuspend(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+}
+
+static int __maybe_unused amlogic_i3c_runtime_suspend(struct device *dev)
+{
+	struct svc_i3c_master *master = dev_get_drvdata(dev);
+	struct amlogic_i3c_priv *priv = master->bsp_priv;
+
+	svc_i3c_save_regs(master);
+	clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
+	pinctrl_pm_select_sleep_state(dev);
+
+	return 0;
+}
+
+static int __maybe_unused amlogic_i3c_runtime_resume(struct device *dev)
+{
+	struct svc_i3c_master *master = dev_get_drvdata(dev);
+	struct amlogic_i3c_priv *priv = master->bsp_priv;
+	int ret;
+
+	pinctrl_pm_select_default_state(dev);
+	ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
+	if (ret)
+		return ret;
+
+	svc_i3c_restore_regs(master);
+
+	return 0;
+}
+
+static const struct dev_pm_ops amlogic_i3c_pm_ops = {
+	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				      pm_runtime_force_resume)
+	SET_RUNTIME_PM_OPS(amlogic_i3c_runtime_suspend,
+			   amlogic_i3c_runtime_resume, NULL)
+};
+
+static const struct amlogic_i3c_drvdata amlogic_i3c_data = {
+	.fclk_rate_init = default_i3c_fclk_rate_init,
+};
+
+static const struct amlogic_i3c_drvdata amlogic_i3c_ao_data = {
+	.fclk_rate_init = ao_i3c_fclk_rate_init,
+};
+
+static const struct svc_i3c_drvdata amlogic_a9_drvdata = {
+	.quirks = SVC_I3C_QUIRK_FIFO_EMPTY |
+		SVC_I3C_QUIRK_FALSE_SLVSTART |
+		SVC_I3C_QUIRK_DAA_CORRUPT,
+	.bsp_data = &amlogic_i3c_data,
+};
+
+static const struct svc_i3c_drvdata amlogic_a9_ao_drvdata = {
+	.quirks = SVC_I3C_QUIRK_FIFO_EMPTY |
+		SVC_I3C_QUIRK_FALSE_SLVSTART |
+		SVC_I3C_QUIRK_DAA_CORRUPT,
+	.bsp_data = &amlogic_i3c_ao_data,
+};
+
+static const struct of_device_id amlogic_i3c_of_match_tbl[] = {
+	{ .compatible = "amlogic,i3c-a9", .data = &amlogic_a9_drvdata },
+	{ .compatible = "amlogic,i3c-ao-a9", .data = &amlogic_a9_ao_drvdata },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, amlogic_i3c_of_match_tbl);
+
+static struct platform_driver amlogic_i3c_master = {
+	.probe = amlogic_i3c_probe,
+	.remove = amlogic_i3c_remove,
+	.driver = {
+		.name = "amlogic-i3c-master",
+		.of_match_table = amlogic_i3c_of_match_tbl,
+		.pm = &amlogic_i3c_pm_ops,
+	},
+};
+module_platform_driver(amlogic_i3c_master);
+
+MODULE_AUTHOR("Junyi Zhao <junyi.zhao@amlogic.com>");
+MODULE_DESCRIPTION("Amlogic I3C master driver");
+MODULE_LICENSE("GPL");

-- 
2.42.0



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

* Re: [PATCH 3/3] i3c: master: svc: Add Amlogic A9 glue layer
  2026-10-19  5:34 ` [PATCH 3/3] i3c: master: svc: Add Amlogic A9 glue layer Junyi Zhao via B4 Relay
@ 2026-10-19  5:52   ` sashiko-bot
  2026-10-19  7:40   ` Neil Armstrong
  1 sibling, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-10-19  5:52 UTC (permalink / raw)
  To: Junyi Zhao
  Cc: linux-i3c, devicetree, linux-amlogic, imx, robh, conor+dt,
	Frank.Li, neil.armstrong, Alexandre Belloni

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] i3c: master: svc: broken probe deferral in amlogic_i3c_probe()
- [Medium] i3c: master: svc: clock enable leak in amlogic_i3c_remove()
- [Medium] i3c: master: svc: i3c-amlogic: missing I3C_AO_FCLK_DIV_CTRL restore on resume
- [Medium] i3c: master: svc: incorrect fclk rate and divider math in amlogic_i3c_probe()
--

commit cf70cd7b59395de84561d30a5e3b3862aa275ac4
Author: Junyi Zhao <junyi.zhao@amlogic.com>

i3c: master: svc: Add Amlogic A9 glue layer

This commit adds an Amlogic A9 glue driver for the Silvaco SVC I3C
controller, providing Amlogic-specific clock and runtime PM handling while
reusing the common SVC I3C core driver.

> diff --git a/drivers/i3c/master/svc/i3c-amlogic.c b/drivers/i3c/master/svc/i3c-amlogic.c
> new file mode 100644
> index 0000000000000..82300d6ae5a31
> --- /dev/null
> +++ b/drivers/i3c/master/svc/i3c-amlogic.c
> @@ -0,0 +1,250 @@

[ ... ]

> +static int default_i3c_fclk_rate_init(struct svc_i3c_master *master,
> +				      struct clk *fclk, unsigned long rate)
> +{
> +	int err;
> +
> +	err = clk_set_rate(fclk, rate);
> +	if (err)
> +		return dev_err_probe(master->dev, err,
> +				     "setting fast clock rate failed\n");
> +
> +	return 0;
> +}
> +
> +static int ao_i3c_fclk_rate_init(struct svc_i3c_master *master,
> +				 struct clk *fclk, unsigned long rate)
> +{
> +	unsigned int div_cnt, reg_val;
> +	unsigned long fin_freq = clk_get_rate(fclk);
> +
> +	if (!fin_freq)
> +		return dev_err_probe(master->dev, -EINVAL,
> +				     "can't get I3C fast clock rate\n");
> +
> +	div_cnt = div64_u64(fin_freq, rate);
> +	if (!div_cnt || div_cnt > I3C_AO_FCLK_DIV_MASK)
> +		return dev_err_probe(master->dev, -EINVAL,
> +				     "requested fast clock frequency too low\n");
> +
> +	reg_val = I3C_AO_FCLK_DIV_EN | I3C_AO_FCLK_EN | (div_cnt - 1);
> +	writel(reg_val, master->regs + I3C_AO_FCLK_DIV_CTRL);
> +
> +	return 0;
> +}
> +
> +static int amlogic_i3c_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	const struct amlogic_i3c_drvdata *data;
> +	struct svc_i3c_master *master;
> +	struct amlogic_i3c_priv *priv;
> +	struct clk *fclk;
> +	int ret, i;

[ ... ]

> +	priv->num_clks = devm_clk_bulk_get_all(dev, &priv->clks);
> +	if (priv->num_clks < 0)
> +		return dev_err_probe(dev, -EINVAL, "can't get I3C clocks\n");

[Severity: High]
Should amlogic_i3c_probe() pass priv->num_clks instead of -EINVAL to
dev_err_probe() here?

When devm_clk_bulk_get_all() returns -EPROBE_DEFER because the clock
controller has not probed yet, passing a hardcoded -EINVAL causes
dev_err_probe() to log an error and abort probing permanently instead of
deferring.

> +
> +	for (i = 0; i < priv->num_clks; i++) {
> +		if (!strcmp(priv->clks[i].id, "fast_clk"))
> +			break;
> +	}
> +
> +	if (i == priv->num_clks)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "can't get I3C peripheral clock\n");
> +
> +	if (IS_ERR(priv->clks[i].clk))
> +		return PTR_ERR(priv->clks[i].clk);
> +	fclk = priv->clks[i].clk;
> +
> +	ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "can't enable I3C clocks\n");
> +
> +	master->dev = dev;
> +	master->bsp_priv = priv;
> +	ret = data->fclk_rate_init(master, fclk, I3C_DEFAULT_FCLK_RATE);
> +	if (ret)
> +		goto err_disable_clks;
> +	master->fclk_rate = I3C_DEFAULT_FCLK_RATE;

[Severity: Medium]
Can the divider math in ao_i3c_fclk_rate_init() and the hardcoded
master->fclk_rate assignment in amlogic_i3c_probe() fail probe on a 24 MHz
input clock or produce an inaccurate fclk_rate?

In ao_i3c_fclk_rate_init(), div_cnt = div64_u64(fin_freq, rate) truncates via
integer division:

1. For the Always-On I3C controller, fast_clk is a9_ao_sys_i3c, which is
   gated from a9_ao_sys (muxed between the 24 MHz ao_xtal and sys). When
   fin_freq is 24 MHz, div64_u64(24000000, 25000000) evaluates to 0, causing
   ao_i3c_fclk_rate_init() to fail probe with -EINVAL.

2. When fin_freq is not an exact multiple of 25 MHz (for example, a
   166.67 MHz sys clock), div64_u64(fin_freq, rate) truncates down to 6,
   configuring I3C_AO_FCLK_DIV_CTRL for 27.78 MHz while master->fclk_rate is
   hardcoded to I3C_DEFAULT_FCLK_RATE (25 MHz). Similarly, in
   default_i3c_fclk_rate_init(), clk_set_rate() rounds to the closest rate
   supported by the clock tree rather than guaranteeing 25 MHz. When the
   actual clock rate exceeds 25 MHz, svc_i3c_master_bus_init() and
   svc_i3c_master_set_speed() compute ppbaud, odbaud, and i2cbaud assuming
   25 MHz, which can drive push-pull SCL above the 12.5 MHz MIPI I3C limit.

3. Because (div_cnt - 1) is written into the 8-bit I3C_AO_FCLK_DIV_MASK field
   (GENMASK(7, 0)), the maximum supported divider is I3C_AO_FCLK_DIV_MASK + 1
   (256), so checking div_cnt > I3C_AO_FCLK_DIV_MASK is off by one.

Should ao_i3c_fclk_rate_init() round the divider up (for example with
DIV_ROUND_UP_ULL) and update master->fclk_rate to the actual divided or
queried clock rate?

> +
> +	pm_runtime_set_autosuspend_delay(dev, SVC_I3C_PM_TIMEOUT_MS);
> +	pm_runtime_use_autosuspend(dev);
> +	pm_runtime_get_noresume(dev);
> +	pm_runtime_set_active(dev);
> +	pm_runtime_enable(dev);

[ ... ]

> +static void amlogic_i3c_remove(struct platform_device *pdev)
> +{
> +	struct svc_i3c_master *master = platform_get_drvdata(pdev);
> +
> +	svc_i3c_master_remove(master);
> +
> +	pm_runtime_dont_use_autosuspend(&pdev->dev);
> +	pm_runtime_disable(&pdev->dev);
> +}

[Severity: Medium]
Does amlogic_i3c_remove() leak clock prepare and enable references on
priv->clks when the device is still active at removal time?

In amlogic_i3c_probe(), clocks are acquired via devm_clk_bulk_get_all() and
manually enabled with clk_bulk_prepare_enable(priv->num_clks, priv->clks).

During removal, svc_i3c_master_remove() calls
i3c_master_unregister()->svc_i3c_master_bus_cleanup(), which resumes the
device via pm_runtime_resume_and_get() and calls
pm_runtime_put_autosuspend().

If CONFIG_PM is disabled (where runtime PM helpers are no-ops), or if runtime
suspend is prevented at removal time (for example via sysfs power/control or
because svc_i3c_master_enable_hotjoin() incremented master->dev's runtime PM
usage count without a matching put in svc_i3c_master_remove()),
pm_runtime_dont_use_autosuspend() will not invoke
amlogic_i3c_runtime_suspend().

Should amlogic_i3c_remove() check !pm_runtime_status_suspended(&pdev->dev)
after pm_runtime_disable(&pdev->dev) and call clk_bulk_disable_unprepare()
and pm_runtime_set_suspended(&pdev->dev)?

> +
> +static int __maybe_unused amlogic_i3c_runtime_suspend(struct device *dev)
> +{
> +	struct svc_i3c_master *master = dev_get_drvdata(dev);
> +	struct amlogic_i3c_priv *priv = master->bsp_priv;
> +
> +	svc_i3c_save_regs(master);
> +	clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
> +	pinctrl_pm_select_sleep_state(dev);
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused amlogic_i3c_runtime_resume(struct device *dev)
> +{
> +	struct svc_i3c_master *master = dev_get_drvdata(dev);
> +	struct amlogic_i3c_priv *priv = master->bsp_priv;
> +	int ret;
> +
> +	pinctrl_pm_select_default_state(dev);
> +	ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
> +	if (ret)
> +		return ret;
> +
> +	svc_i3c_restore_regs(master);
> +
> +	return 0;
> +}

[Severity: Medium]
On "amlogic,i3c-ao-a9" devices, can I3C_AO_FCLK_DIV_CTRL lose its
configuration across suspend and resume?

During probe, ao_i3c_fclk_rate_init() writes I3C_AO_FCLK_DIV_EN,
I3C_AO_FCLK_EN, and the divider into master->regs + I3C_AO_FCLK_DIV_CTRL.

When suspending and resuming via amlogic_i3c_runtime_suspend() and
amlogic_i3c_runtime_resume() (which are also used for system sleep via
pm_runtime_force_suspend() and pm_runtime_force_resume()),
svc_i3c_save_regs() and svc_i3c_restore_regs() only save and restore
SVC_I3C_MCONFIG and SVC_I3C_MDYNADDR.

If the controller loses register state during suspend or hibernation,
wouldn't I3C_AO_FCLK_DIV_CTRL remain unconfigured on resume unless it is
saved and restored or re-initialized?

[ ... ]

> -- 
> 2.47.3

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261019-a9_i3c-v1-0-329d819d621f@amlogic.com?part=3

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

* Re: [PATCH 2/3] dt-bindings: i3c: Add Amlogic A9 I3C controller
  2026-10-19  5:34 ` [PATCH 2/3] dt-bindings: i3c: Add Amlogic A9 I3C controller Junyi Zhao via B4 Relay
@ 2026-10-19  6:00   ` Krzysztof Kozlowski
  2026-10-19  6:01   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 8+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-19  6:00 UTC (permalink / raw)
  To: junyi.zhao, Alexandre Belloni, Frank Li, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Kevin Hilman,
	Jerome Brunet, Martin Blumenstingl, Miquel Raynal, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-i3c, devicetree, linux-kernel, linux-arm-kernel,
	linux-amlogic, imx, linux-hardening, xianwei.zhao, Kelvin.Zhang

On 19/10/2026 07:34, Junyi Zhao via B4 Relay wrote:
> +properties:
> +  compatible:
> +    enum:
> +      - amlogic,i3c-a9
> +      - amlogic,i3c-ao-a9

What is ao?

> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  clocks:
> +    minItems: 1
> +    maxItems: 2
> +
> +  clock-names:
> +    minItems: 1
> +    maxItems: 2

Names go here

> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - clocks
> +  - clock-names
> +
> +unevaluatedProperties: false

This goes before example

> +
> +if:

And this in allOf, see example schema or other bindings.

> +  properties:
> +    compatible:
> +      const: amlogic,i3c-a9
> +then:
> +  properties:
> +    clocks:
> +      items:
> +        - description: APB peripheral clock
> +        - description: I3C fast clock
> +    clock-names:
> +      items:
> +        - const: pclk
> +        - const: fast_clk

That's just fast

minItems: 1

> +else:
> +  properties:
> +    clocks:
> +      maxItems: 1
> +    clock-names:
> +      items:
> +        - const: fast_clk

maxItems: 1

> +
> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> +    i3c@3a7000 {
> +        compatible = "amlogic,i3c-a9";
> +        reg = <0x3a7000 0x10000>;
> +        interrupts = <GIC_SPI 153 IRQ_TYPE_LEVEL_HIGH>;
> +        #address-cells = <3>;
> +        #size-cells = <0>;
> +        clocks = <&clkc 15>,
> +                 <&clkc 16>;
> +        clock-names = "pclk", "fast_clk";
> +        status = "disabled";
> +    };
> +
> +  - |
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> +    i3c@22000 {

Drop, one example is enough.

> +        compatible = "amlogic,i3c-ao-a9";
> +        reg = <0x22000 0x10000>;
> +        interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
> +        #address-cells = <3>;
> +        #size-cells = <0>;
> +        clocks = <&clkc 17>;
> +        clock-names = "fast_clk";
> +        status = "disabled";
> +    };
> +...
> 


Best regards,
Krzysztof

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

* Re: [PATCH 2/3] dt-bindings: i3c: Add Amlogic A9 I3C controller
  2026-10-19  5:34 ` [PATCH 2/3] dt-bindings: i3c: Add Amlogic A9 I3C controller Junyi Zhao via B4 Relay
  2026-10-19  6:00   ` Krzysztof Kozlowski
@ 2026-10-19  6:01   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 8+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-19  6:01 UTC (permalink / raw)
  To: junyi.zhao, Alexandre Belloni, Frank Li, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Kevin Hilman,
	Jerome Brunet, Martin Blumenstingl, Miquel Raynal, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-i3c, devicetree, linux-kernel, linux-arm-kernel,
	linux-amlogic, imx, linux-hardening, xianwei.zhao, Kelvin.Zhang

On 19/10/2026 07:34, Junyi Zhao via B4 Relay wrote:
> +    i3c@3a7000 {
> +        compatible = "amlogic,i3c-a9";
> +        reg = <0x3a7000 0x10000>;
> +        interrupts = <GIC_SPI 153 IRQ_TYPE_LEVEL_HIGH>;
> +        #address-cells = <3>;
> +        #size-cells = <0>;
> +        clocks = <&clkc 15>,
> +                 <&clkc 16>;
> +        clock-names = "pclk", "fast_clk";
> +        status = "disabled";


Drop, your binding was not actually tested.

Best regards,
Krzysztof

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

* Re: [PATCH 3/3] i3c: master: svc: Add Amlogic A9 glue layer
  2026-10-19  5:34 ` [PATCH 3/3] i3c: master: svc: Add Amlogic A9 glue layer Junyi Zhao via B4 Relay
  2026-10-19  5:52   ` sashiko-bot
@ 2026-10-19  7:40   ` Neil Armstrong
  1 sibling, 0 replies; 8+ messages in thread
From: Neil Armstrong @ 2026-10-19  7:40 UTC (permalink / raw)
  To: junyi.zhao, Alexandre Belloni, Frank Li, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Kevin Hilman, Jerome Brunet,
	Martin Blumenstingl, Miquel Raynal, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-i3c, devicetree, linux-kernel, linux-arm-kernel,
	linux-amlogic, imx, linux-hardening, xianwei.zhao, Kelvin.Zhang

On 10/19/26 07:34, Junyi Zhao via B4 Relay wrote:
> From: Junyi Zhao <junyi.zhao@amlogic.com>
> 
> Add support for the Amlogic A9 implementation of the Silvaco
> SVC I3C controller.
> 
> The glue layer provides Amlogic-specific operations and resource
> handling while reusing the common SVC I3C core driver.
> 
> Add a corresponding MAINTAINERS entry for the Amlogic I3C
> controller.
> 
> Signed-off-by: Junyi Zhao <junyi.zhao@amlogic.com>
> ---
>   MAINTAINERS                          |   6 +
>   drivers/i3c/master/svc/Kconfig       |   7 +
>   drivers/i3c/master/svc/Makefile      |   1 +
>   drivers/i3c/master/svc/i3c-amlogic.c | 250 +++++++++++++++++++++++++++++++++++
>   4 files changed, 264 insertions(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index dffc63af2..478979446 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1398,6 +1398,12 @@ F:	Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst
>   F:	drivers/media/platform/amlogic/c3/isp/
>   F:	include/uapi/linux/media/amlogic/
>   
> +AMLOGIC I3C DRIVER
> +M:	Junyi Zhao <junyi.zhao@amlogic.com>
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/i3c/amlogic,i3c-a9.yaml
> +F:	drivers/i3c/master/svc/i3c-amlogic.c
> +
>   AMLOGIC MIPI ADAPTER DRIVER
>   M:	Keke Li <keke.li@amlogic.com>
>   L:	linux-media@vger.kernel.org
> diff --git a/drivers/i3c/master/svc/Kconfig b/drivers/i3c/master/svc/Kconfig
> index 14fa1e542..df3528bc1 100644
> --- a/drivers/i3c/master/svc/Kconfig
> +++ b/drivers/i3c/master/svc/Kconfig
> @@ -18,4 +18,11 @@ config I3C_SILVACO
>   	  compatible strings such as "silvaco,i3c-master-v1" and
>   	  "nuvoton,npcm845-i3c".
>   
> +config I3C_AMLOGIC
> +	tristate "Amlogic I3C support"
> +	default y
> +	help
> +	  This selects the Amlogic SoC glue layer support for
> +	  the Silvaco device driver. This driver is used for A9, C5 SoCs.
> +
>   endif # SVC_I3C
> diff --git a/drivers/i3c/master/svc/Makefile b/drivers/i3c/master/svc/Makefile
> index cf18c5da6..695863d1f 100644
> --- a/drivers/i3c/master/svc/Makefile
> +++ b/drivers/i3c/master/svc/Makefile
> @@ -3,3 +3,4 @@
>   
>   obj-$(CONFIG_SVC_I3C)	+= i3c-main.o
>   obj-$(CONFIG_I3C_SILVACO)	+= i3c-silvaco.o
> +obj-$(CONFIG_I3C_AMLOGIC)	+= i3c-amlogic.o
> diff --git a/drivers/i3c/master/svc/i3c-amlogic.c b/drivers/i3c/master/svc/i3c-amlogic.c
> new file mode 100644
> index 000000000..82300d6ae
> --- /dev/null
> +++ b/drivers/i3c/master/svc/i3c-amlogic.c
> @@ -0,0 +1,250 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2026 Amlogic, Inc. All rights reserved
> + */
> +#include "i3c-main.h"
> +#include <linux/math64.h>
> +
> +/* Amlogic specific registers */
> +#define I3C_BG_RD_ADDR			0x200
> +#define I3C_BG_WR_ADDR			0x204
> +#define I3C_BG_WR_CTRL			0x208
> +#define I3C_BG_R_TT_LEN			0x20c
> +#define I3C_BG_X_CNT			0x210
> +#define I3C_BUS_MON_CTRL		0x214
> +#define I3C_BUS_MON_ADDR		0x218
> +#define I3C_BUS_MON_DATA		0x21c
> +#define I3C_BUS_MON_DATA_MAK		0x220
> +#define I3C_CLK_SLOW_DIV_CTRL		0x224
> +#define I3C_BG_R_WCNT_CLR		0x228
> +#define I3C_DEBUG_MODIFY		0x22c
> +
> +#define I3C_AO_CLK_SLOW_DIV_CTRL	0x300
> +#define I3C_AO_FCLK_DIV_CTRL		0x304
> +#define I3C_AO_FCLK_DIV_MASK		GENMASK(7, 0)
> +#define I3C_AO_FCLK_DIV_EN		BIT(8)
> +#define I3C_AO_FCLK_EN			BIT(9)
> +#define I3C_AO_DEBUG_MODIFY		0x308
> +
> +#define I3C_DEFAULT_FCLK_RATE		25000000UL
> +
> +struct amlogic_i3c_priv {
> +	int num_clks;
> +	struct clk_bulk_data *clks;
> +};
> +
> +struct amlogic_i3c_drvdata {
> +	int (*fclk_rate_init)(struct svc_i3c_master *master,
> +			      struct clk *fclk, unsigned long rate);
> +};
> +
> +static int default_i3c_fclk_rate_init(struct svc_i3c_master *master,
> +				      struct clk *fclk, unsigned long rate)
> +{
> +	int err;
> +
> +	err = clk_set_rate(fclk, rate);
> +	if (err)
> +		return dev_err_probe(master->dev, err,
> +				     "setting fast clock rate failed\n");
> +
> +	return 0;
> +}
> +
> +static int ao_i3c_fclk_rate_init(struct svc_i3c_master *master,
> +				 struct clk *fclk, unsigned long rate)
> +{
> +	unsigned int div_cnt, reg_val;
> +	unsigned long fin_freq = clk_get_rate(fclk);
> +
> +	if (!fin_freq)
> +		return dev_err_probe(master->dev, -EINVAL,
> +				     "can't get I3C fast clock rate\n");
> +
> +	div_cnt = div64_u64(fin_freq, rate);
> +	if (!div_cnt || div_cnt > I3C_AO_FCLK_DIV_MASK)
> +		return dev_err_probe(master->dev, -EINVAL,
> +				     "requested fast clock frequency too low\n");
> +
> +	reg_val = I3C_AO_FCLK_DIV_EN | I3C_AO_FCLK_EN | (div_cnt - 1);
> +	writel(reg_val, master->regs + I3C_AO_FCLK_DIV_CTRL);
> +
> +	return 0;
> +}

You could also create a clock divider, register it and use a common clk_set_rate()
for both setups.

Neil

> +
> +static int amlogic_i3c_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	const struct amlogic_i3c_drvdata *data;
> +	struct svc_i3c_master *master;
> +	struct amlogic_i3c_priv *priv;
> +	struct clk *fclk;
> +	int ret, i;
> +
> +	master = svc_i3c_master_alloc(dev);
> +	if (!master)
> +		return -ENOMEM;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	master->drvdata = of_device_get_match_data(dev);
> +	if (!master->drvdata)
> +		return -EINVAL;
> +
> +	data = master->drvdata->bsp_data;
> +	if (!data)
> +		return -EINVAL;
> +
> +	master->regs = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(master->regs))
> +		return PTR_ERR(master->regs);
> +
> +	master->irq = platform_get_irq(pdev, 0);
> +	if (master->irq < 0)
> +		return master->irq;
> +
> +	priv->num_clks = devm_clk_bulk_get_all(dev, &priv->clks);
> +	if (priv->num_clks < 0)
> +		return dev_err_probe(dev, -EINVAL, "can't get I3C clocks\n");
> +
> +	for (i = 0; i < priv->num_clks; i++) {
> +		if (!strcmp(priv->clks[i].id, "fast_clk"))
> +			break;
> +	}
> +
> +	if (i == priv->num_clks)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "can't get I3C peripheral clock\n");
> +
> +	if (IS_ERR(priv->clks[i].clk))
> +		return PTR_ERR(priv->clks[i].clk);
> +	fclk = priv->clks[i].clk;
> +
> +	ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "can't enable I3C clocks\n");
> +
> +	master->dev = dev;
> +	master->bsp_priv = priv;
> +	ret = data->fclk_rate_init(master, fclk, I3C_DEFAULT_FCLK_RATE);
> +	if (ret)
> +		goto err_disable_clks;
> +	master->fclk_rate = I3C_DEFAULT_FCLK_RATE;
> +
> +	pm_runtime_set_autosuspend_delay(dev, SVC_I3C_PM_TIMEOUT_MS);
> +	pm_runtime_use_autosuspend(dev);
> +	pm_runtime_get_noresume(dev);
> +	pm_runtime_set_active(dev);
> +	pm_runtime_enable(dev);
> +
> +	ret = svc_i3c_master_probe(pdev, master);
> +	if (ret)
> +		goto rpm_disable;
> +
> +	platform_set_drvdata(pdev, master);
> +	pm_runtime_put_autosuspend(dev);
> +
> +	return 0;
> +
> +rpm_disable:
> +	pm_runtime_dont_use_autosuspend(dev);
> +	pm_runtime_put_noidle(dev);
> +	pm_runtime_disable(dev);
> +	pm_runtime_set_suspended(dev);
> +
> +err_disable_clks:
> +	clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
> +
> +	return ret;
> +}
> +
> +static void amlogic_i3c_remove(struct platform_device *pdev)
> +{
> +	struct svc_i3c_master *master = platform_get_drvdata(pdev);
> +
> +	svc_i3c_master_remove(master);
> +
> +	pm_runtime_dont_use_autosuspend(&pdev->dev);
> +	pm_runtime_disable(&pdev->dev);
> +}
> +
> +static int __maybe_unused amlogic_i3c_runtime_suspend(struct device *dev)
> +{
> +	struct svc_i3c_master *master = dev_get_drvdata(dev);
> +	struct amlogic_i3c_priv *priv = master->bsp_priv;
> +
> +	svc_i3c_save_regs(master);
> +	clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
> +	pinctrl_pm_select_sleep_state(dev);
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused amlogic_i3c_runtime_resume(struct device *dev)
> +{
> +	struct svc_i3c_master *master = dev_get_drvdata(dev);
> +	struct amlogic_i3c_priv *priv = master->bsp_priv;
> +	int ret;
> +
> +	pinctrl_pm_select_default_state(dev);
> +	ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
> +	if (ret)
> +		return ret;
> +
> +	svc_i3c_restore_regs(master);
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops amlogic_i3c_pm_ops = {
> +	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> +				      pm_runtime_force_resume)
> +	SET_RUNTIME_PM_OPS(amlogic_i3c_runtime_suspend,
> +			   amlogic_i3c_runtime_resume, NULL)
> +};
> +
> +static const struct amlogic_i3c_drvdata amlogic_i3c_data = {
> +	.fclk_rate_init = default_i3c_fclk_rate_init,
> +};
> +
> +static const struct amlogic_i3c_drvdata amlogic_i3c_ao_data = {
> +	.fclk_rate_init = ao_i3c_fclk_rate_init,
> +};
> +
> +static const struct svc_i3c_drvdata amlogic_a9_drvdata = {
> +	.quirks = SVC_I3C_QUIRK_FIFO_EMPTY |
> +		SVC_I3C_QUIRK_FALSE_SLVSTART |
> +		SVC_I3C_QUIRK_DAA_CORRUPT,
> +	.bsp_data = &amlogic_i3c_data,
> +};
> +
> +static const struct svc_i3c_drvdata amlogic_a9_ao_drvdata = {
> +	.quirks = SVC_I3C_QUIRK_FIFO_EMPTY |
> +		SVC_I3C_QUIRK_FALSE_SLVSTART |
> +		SVC_I3C_QUIRK_DAA_CORRUPT,
> +	.bsp_data = &amlogic_i3c_ao_data,
> +};
> +
> +static const struct of_device_id amlogic_i3c_of_match_tbl[] = {
> +	{ .compatible = "amlogic,i3c-a9", .data = &amlogic_a9_drvdata },
> +	{ .compatible = "amlogic,i3c-ao-a9", .data = &amlogic_a9_ao_drvdata },
> +	{ /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, amlogic_i3c_of_match_tbl);
> +
> +static struct platform_driver amlogic_i3c_master = {
> +	.probe = amlogic_i3c_probe,
> +	.remove = amlogic_i3c_remove,
> +	.driver = {
> +		.name = "amlogic-i3c-master",
> +		.of_match_table = amlogic_i3c_of_match_tbl,
> +		.pm = &amlogic_i3c_pm_ops,
> +	},
> +};
> +module_platform_driver(amlogic_i3c_master);
> +
> +MODULE_AUTHOR("Junyi Zhao <junyi.zhao@amlogic.com>");
> +MODULE_DESCRIPTION("Amlogic I3C master driver");
> +MODULE_LICENSE("GPL");
> 


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

end of thread, other threads:[~2026-10-19  7:40 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-19  5:34 [PATCH 0/3] Add vendor glue layer support and Amlogic A9 implementation Junyi Zhao via B4 Relay
2026-10-19  5:34 ` [PATCH 1/3] i3c: master: Rework SVC I3C to support vendor glue layers Junyi Zhao via B4 Relay
2026-10-19  5:34 ` [PATCH 2/3] dt-bindings: i3c: Add Amlogic A9 I3C controller Junyi Zhao via B4 Relay
2026-10-19  6:00   ` Krzysztof Kozlowski
2026-10-19  6:01   ` Krzysztof Kozlowski
2026-10-19  5:34 ` [PATCH 3/3] i3c: master: svc: Add Amlogic A9 glue layer Junyi Zhao via B4 Relay
2026-10-19  5:52   ` sashiko-bot
2026-10-19  7:40   ` Neil Armstrong

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox