* [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; 7+ 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] 7+ 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; 7+ 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 [flat|nested] 7+ 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; 7+ 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 [flat|nested] 7+ 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 7:40 ` Neil Armstrong
2 siblings, 1 reply; 7+ 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 [flat|nested] 7+ 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; 7+ 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] 7+ 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; 7+ 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] 7+ 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 7:40 ` Neil Armstrong
0 siblings, 0 replies; 7+ 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] 7+ messages in thread
end of thread, other threads:[~2026-10-19 7:40 UTC | newest]
Thread overview: 7+ 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 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
all inboxes | Powered by JetHome®