mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v3 0/2] Add Amlogic A9 I3C support
@ 2026-11-06  7:13 Junyi Zhao via B4 Relay
  2026-11-06  7:13 ` [PATCH v3 1/2] i3c: master: svc: Add amlogic a9 support Junyi Zhao via B4 Relay
  2026-11-06  7:13 ` [PATCH v3 2/2] dt-bindings: i3c: silvaco,i3c-master: Add Amlogic A9 support Junyi Zhao via B4 Relay
  0 siblings, 2 replies; 6+ messages in thread
From: Junyi Zhao via B4 Relay @ 2026-11-06  7:13 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

Amlogic A9 SoCs integrate the Silvaco I3C IP, but their peripheral
clock setup differs from the original implementation: the EE-domain
controller (amlogic,a9-i3c) takes two clocks, "pclk" and "fast_clk",
while the AO-domain controller (amlogic,a9-ao-i3c) exposes only
"pclk" and derives its bus clock from an internal divider inside the
controller's own register region.

This series consists of:

  - Add a vendor_init callback for the clock setup and an
    Amlogic-specific bit (AML_I3C_QUIRK_AO) for AO-specific behaviour
    to the Silvaco I3C master driver, and enable the existing
    Silvaco workaround quirks for both Amlogic variants.

  - Add the Amlogic compatibles and per-variant clock
    constraints to the silvaco,i3c-master binding schema.

Verified on Amlogic A9 (BY401) with both EE and AO controller
instances. dt_binding_check and dtbs_check pass with this series.

Signed-off-by: Junyi Zhao <junyi.zhao@amlogic.com>
---
Changes in v3:
  - Merge vendor quirks into the legacy quirks bitmask and add
    AML_I3C_QUIRK_AO for the AO variant.
  - Check the devm_kcalloc() return value when allocating the new
    clk bulk array.
  - Enable the clocks before aml_ao_i3c_tail_clocks(), because the
    divider registers are accessed during registration.
  - Rework the probe error paths so the clocks are disabled when
    vendor_init or later steps fail.
  - Reorder the local variables (longest first).
  - Convert comments to kernel-doc format.
  - Depend on COMMON_CLK in Kconfig.
  - Drop the silvaco,i3c-master-v1 fallback and add a blank line
    before the new if/then block in the binding.
  - Explain the I3C peripheral integration details in the binding
    commit message.

- Link to v2: https://patch.msgid.link/20261102-a9_i3c-v2-0-0c474bfccffa@amlogic.com

Changes in v2:

  - No need rework the Silvaco I3C driver. 
  - Based on vanilla driver, adapt A9 SoCs through callback 
    and quirks to handle the variant.

- Link to v1: https://patch.msgid.link/20261019-a9_i3c-v1-0-329d819d621f@amlogic.com

---
Junyi Zhao (2):
      i3c: master: svc: Add amlogic a9 support
      dt-bindings: i3c: silvaco,i3c-master: Add Amlogic A9 support

 .../bindings/i3c/silvaco,i3c-master.yaml           |  26 +-
 drivers/i3c/master/Kconfig                         |   1 +
 drivers/i3c/master/svc-i3c-master.c                | 274 ++++++++++++++++++++-
 3 files changed, 287 insertions(+), 14 deletions(-)
---
base-commit: aac26bee2287c88af5be5a5ff96d783b19a28790
change-id: 20261010-a9_i3c-9d5245d094c8

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



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

* [PATCH v3 1/2] i3c: master: svc: Add amlogic a9 support
  2026-11-06  7:13 [PATCH v3 0/2] Add Amlogic A9 I3C support Junyi Zhao via B4 Relay
@ 2026-11-06  7:13 ` Junyi Zhao via B4 Relay
  2026-11-06 17:15   ` Frank Li
  2026-11-06  7:13 ` [PATCH v3 2/2] dt-bindings: i3c: silvaco,i3c-master: Add Amlogic A9 support Junyi Zhao via B4 Relay
  1 sibling, 1 reply; 6+ messages in thread
From: Junyi Zhao via B4 Relay @ 2026-11-06  7:13 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>

Amlogic a9 SoCs integrate the Silvaco I3C IP, and their peripheral
clocks vary from the vanilla implementation:

  - The EE-domain controller (amlogic,a9-i3c) takes two clocks,
    "pclk" and "fast_clk", and the fast clock rate must be programmed
    before the controller can be used.

  - The AO-domain controller (amlogic,a9-ao-i3c) exposes only "pclk":
    its bus clock is generated by an internal divider located in the
    controller's own register space.

Both variants need setup that the generic driver does not perform:
the fast clock rate must be rounded and set at probe time, and on the
AO variant the internal divider must be registered with the clock
framework so that "fast_clk" even exists. The divider registers sit
in the controller's own register space, so they can only be accessed
while pclk is running.

Add a vendor_init callback, invoked from the probe with the clocks
enabled, to handle this. On the AO variant it registers the internal
divider chain (div_en -> div -> gate) with the common clock framework
and appends the resulting clock to the clk bulk array as "fast_clk";
for both variants it then rounds and sets the fast clock rate
(target 25 MHz). As vendor_init now runs after
clk_bulk_prepare_enable(), the probe error paths that follow disable
the clocks on failure.

Extend the quirks bitmask with an Amlogic-specific bit
(AML_I3C_QUIRK_AO) marking the AO variant's internal-divider
behaviour. Since the driver now registers clocks through the common
clock framework, SVC_I3C_MASTER also depends on COMMON_CLK.

Signed-off-by: Junyi Zhao <junyi.zhao@amlogic.com>
---
 drivers/i3c/master/Kconfig          |   1 +
 drivers/i3c/master/svc-i3c-master.c | 274 ++++++++++++++++++++++++++++++++++--
 2 files changed, 263 insertions(+), 12 deletions(-)

diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig
index da96d2aaa..ac7d246c2 100644
--- a/drivers/i3c/master/Kconfig
+++ b/drivers/i3c/master/Kconfig
@@ -47,6 +47,7 @@ config AST2600_I3C_MASTER
 
 config SVC_I3C_MASTER
 	tristate "Silvaco I3C Dual-Role Master driver"
+	depends on COMMON_CLK
 	depends on HAS_IOMEM
 	depends on !(ALPHA || PARISC)
 	help
diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c
index e700392f0..aef56286e 100644
--- a/drivers/i3c/master/svc-i3c-master.c
+++ b/drivers/i3c/master/svc-i3c-master.c
@@ -9,6 +9,7 @@
 
 #include <linux/bitfield.h>
 #include <linux/clk.h>
+#include <linux/clk-provider.h>
 #include <linux/completion.h>
 #include <linux/errno.h>
 #include <linux/i3c/master.h>
@@ -166,6 +167,31 @@
  */
 #define SVC_I3C_QUIRK_DAA_CORRUPT	BIT(2)
 
+/*
+ * AML_I3C_QUIRK_AO:
+ * AO is the Always-On domain on Amlogic SoCs (remains powered in suspend).
+ * Its clock is generated by an internal divider located in the I3C
+ * controller's own register space.
+ */
+#define AML_I3C_QUIRK_AO	BIT(3)
+
+/*
+ * Quirks shared by all the Amlogic A9 I3C instances.
+ */
+#define AML_A9_I3C_QUIRKS	(SVC_I3C_QUIRK_FIFO_EMPTY | \
+				 SVC_I3C_QUIRK_FALSE_SLVSTART | \
+				 SVC_I3C_QUIRK_DAA_CORRUPT)
+
+/* Amlogic specific registers */
+#define AML_AO_I3C_FCLK_DIV_CTRL		0x304
+#define AML_AO_I3C_FCLK_DIV_EN_SHIFT		8
+#define AML_AO_I3C_FCLK_DIV_SHIFT		0
+#define AML_AO_I3C_FCLK_DIV_WIDTH		8
+#define AML_AO_I3C_FCLK_EN_SHIFT		9
+
+/* Target fast clock rate of the Amlogic A9 I3C controllers, in Hz */
+#define AML_I3C_FAST_CLK_RATE		25000000
+
 struct svc_i3c_cmd {
 	u8 addr;
 	union {
@@ -195,8 +221,11 @@ struct svc_i3c_regs_save {
 	u32 mdynaddr;
 };
 
+struct svc_i3c_master;
+
 struct svc_i3c_drvdata {
 	u32 quirks;
+	int (*vendor_init)(struct svc_i3c_master *master);
 };
 
 /**
@@ -2011,6 +2040,7 @@ static int svc_i3c_master_probe(struct platform_device *pdev)
 	if (!master->drvdata)
 		return -EINVAL;
 
+	master->dev = dev;
 	master->regs = devm_platform_ioremap_resource(pdev, 0);
 	if (IS_ERR(master->regs))
 		return PTR_ERR(master->regs);
@@ -2019,27 +2049,43 @@ static int svc_i3c_master_probe(struct platform_device *pdev)
 	if (master->num_clks < 0)
 		return dev_err_probe(dev, -EINVAL, "can't get I3C clocks\n");
 
+	ret = clk_bulk_prepare_enable(master->num_clks, master->clks);
+	if (ret)
+		return dev_err_probe(dev, ret, "can't enable I3C clocks\n");
+
+	/*
+	 * Vendor specific setup may register additional clocks (e.g. the
+	 * internal divider chain of the AO variant) and program clock rates,
+	 * so it must run with the clocks enabled.
+	 */
+	if (master->drvdata->vendor_init) {
+		ret = master->drvdata->vendor_init(master);
+		if (ret)
+			goto err_disable_clks;
+	}
+
 	for (i = 0; i < master->num_clks; i++) {
 		if (!strcmp(master->clks[i].id, "fast_clk"))
 			break;
 	}
 
-	if (i == master->num_clks)
-		return dev_err_probe(dev, -EINVAL,
-				     "can't get I3C peripheral clock\n");
+	if (i == master->num_clks) {
+		ret = dev_err_probe(dev, -EINVAL,
+				    "can't get I3C peripheral clock\n");
+		goto err_disable_clks;
+	}
 
 	master->fclk = master->clks[i].clk;
-	if (IS_ERR(master->fclk))
-		return PTR_ERR(master->fclk);
+	if (IS_ERR(master->fclk)) {
+		ret = PTR_ERR(master->fclk);
+		goto err_disable_clks;
+	}
 
 	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");
+	if (master->irq < 0) {
+		ret = master->irq;
+		goto err_disable_clks;
+	}
 
 	mutex_init(&master->lock);
 
@@ -2163,9 +2209,213 @@ static const struct svc_i3c_drvdata npcm845_drvdata = {
 
 static const struct svc_i3c_drvdata svc_default_drvdata = {};
 
+/**
+ * struct aml_i3c_ao_clk - container for the AO variant's internal fast clock
+ * @div_en: enables the internal divider
+ * @div: bus clock divider
+ * @gate: output gate of the generated fast clock
+ * @lock: spinlock shared by the gate and divider register operations
+ *
+ * The clock HW structures are registered with the clock framework and must
+ * stay alive for the whole driver lifetime, so they are kept along with the
+ * shared lock used by their gate/divider operations.
+ */
+struct aml_i3c_ao_clk {
+	struct clk_gate div_en;
+	struct clk_divider div;
+	struct clk_gate gate;
+	spinlock_t lock;
+};
+
+/**
+ * aml_ao_i3c_tail_clocks - register the AO variant's internal fast clock
+ * @master: I3C master controller
+ * @parent: parent clock of the internal divider chain; must be "pclk", the
+ *          only clock the AO variant exposes (see silvaco,i3c-master.yaml)
+ *
+ * Register the internal divider chain that produces the AO variant's
+ * fast_clk and append it to the clk bulk array as "fast_clk". The caller
+ * must run with the clocks enabled since the divider registers are
+ * accessed during registration. The appended clock is prepared and
+ * enabled here as well, because clk_bulk_prepare_enable() already ran
+ * in the probe with the original clock list.
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+static int aml_ao_i3c_tail_clocks(struct svc_i3c_master *master, struct clk *parent)
+{
+	struct device *dev = master->dev;
+	struct clk_bulk_data *new_clks;
+	struct clk_init_data init = {};
+	struct aml_i3c_ao_clk *ao;
+	struct clk *fclk;
+	char name[64];
+	int ret;
+
+	ao = devm_kzalloc(dev, sizeof(*ao), GFP_KERNEL);
+	if (!ao)
+		return -ENOMEM;
+
+	spin_lock_init(&ao->lock);
+
+	snprintf(name, sizeof(name), "%s#div_en", dev_name(dev));
+	init.name = name;
+	init.ops = &clk_gate_ops;
+	init.flags = CLK_IGNORE_UNUSED;
+	init.parent_data = &(struct clk_parent_data) {
+		.index = -1,
+		.hw = __clk_get_hw(parent),
+	};
+	init.num_parents = 1;
+
+	ao->div_en.reg = master->regs + AML_AO_I3C_FCLK_DIV_CTRL;
+	ao->div_en.bit_idx = AML_AO_I3C_FCLK_DIV_EN_SHIFT;
+	ao->div_en.hw.init = &init;
+	ao->div_en.lock = &ao->lock;
+
+	ret = devm_clk_hw_register(dev, &ao->div_en.hw);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to register %s\n", name);
+
+	snprintf(name, sizeof(name), "%s#div", dev_name(dev));
+	init.name = name;
+	init.ops = &clk_divider_ops;
+	init.flags = 0;
+	init.parent_data = &(struct clk_parent_data) {
+		.index = -1,
+		.hw = &ao->div_en.hw,
+	};
+	init.num_parents = 1;
+
+	ao->div.reg = master->regs + AML_AO_I3C_FCLK_DIV_CTRL;
+	ao->div.shift = AML_AO_I3C_FCLK_DIV_SHIFT;
+	ao->div.width = AML_AO_I3C_FCLK_DIV_WIDTH;
+	ao->div.hw.init = &init;
+	ao->div.lock = &ao->lock;
+
+	ret = devm_clk_hw_register(dev, &ao->div.hw);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to register %s\n", name);
+
+	snprintf(name, sizeof(name), "%s#gate", dev_name(dev));
+	init.name = name;
+	init.ops = &clk_gate_ops;
+	init.flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED;
+	init.parent_data = &(struct clk_parent_data) {
+		.index = -1,
+		.hw = &ao->div.hw,
+	};
+	init.num_parents = 1;
+
+	ao->gate.reg = master->regs + AML_AO_I3C_FCLK_DIV_CTRL;
+	ao->gate.bit_idx = AML_AO_I3C_FCLK_EN_SHIFT;
+	ao->gate.hw.init = &init;
+	ao->gate.lock = &ao->lock;
+
+	ret = devm_clk_hw_register(dev, &ao->gate.hw);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to register %s\n", name);
+
+	fclk = devm_clk_hw_get_clk(dev, &ao->gate.hw, NULL);
+	if (IS_ERR(fclk))
+		return dev_err_probe(dev, PTR_ERR(fclk),
+				     "tail i3c fast clock failed\n");
+
+	new_clks = devm_kcalloc(dev, master->num_clks + 1, sizeof(*new_clks),
+				GFP_KERNEL);
+	if (!new_clks)
+		return -ENOMEM;
+
+	memcpy(new_clks, master->clks,
+	       master->num_clks * sizeof(*master->clks));
+	new_clks[master->num_clks].id = "fast_clk";
+	new_clks[master->num_clks].clk = fclk;
+	master->clks = new_clks;
+	master->num_clks++;
+
+	/*
+	 * Keep the clk bulk state balanced for the following
+	 * clk_bulk_disable_unprepare() calls (error paths and runtime PM).
+	 */
+	ret = clk_prepare_enable(fclk);
+	if (ret)
+		return dev_err_probe(dev, ret, "can't enable fast_clk\n");
+
+	return 0;
+}
+
+/**
+ * aml_i3c_init - Amlogic A9 specific init, called with the clocks enabled
+ * @master: I3C master controller
+ *
+ * Register the AO variant's internal divider chain and round/set the fast
+ * clock rate for both variants.
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+static int aml_i3c_init(struct svc_i3c_master *master)
+{
+	struct device *dev = master->dev;
+	struct clk *fclk;
+	long fin_freq;
+	int ret, i;
+
+	if (svc_has_quirk(master, AML_I3C_QUIRK_AO)) {
+		/*
+		 * clk_bulk_get_all() preserves the DT order, and the
+		 * binding restricts the AO variant to exactly one clock,
+		 * which is always "pclk".  So clks[0] is the input of the
+		 * internal divider chain.
+		 */
+		if (strcmp(master->clks[0].id, "pclk"))
+			return dev_err_probe(dev, -EINVAL,
+					     "invalid AO input clock\n");
+
+		ret = aml_ao_i3c_tail_clocks(master, master->clks[0].clk);
+		if (ret)
+			return ret;
+	}
+
+	for (i = 0; i < master->num_clks; i++) {
+		if (!strcmp(master->clks[i].id, "fast_clk"))
+			break;
+	}
+
+	if (i == master->num_clks)
+		return dev_err_probe(dev, -EINVAL,
+				     "can't get I3C fast clock\n");
+
+	fclk = master->clks[i].clk;
+	if (IS_ERR(fclk))
+		return PTR_ERR(fclk);
+
+	fin_freq = clk_round_rate(fclk, AML_I3C_FAST_CLK_RATE);
+	if (fin_freq <= 0)
+		return dev_err_probe(dev, fin_freq ? fin_freq : -EINVAL,
+				     "invalid fast_clk\n");
+
+	ret = clk_set_rate(fclk, fin_freq);
+	if (ret)
+		return dev_err_probe(dev, ret, "setting fast_clk rate failed\n");
+
+	return 0;
+}
+
+static const struct svc_i3c_drvdata aml_a9_drvdata = {
+	.quirks = AML_A9_I3C_QUIRKS,
+	.vendor_init = aml_i3c_init,
+};
+
+static const struct svc_i3c_drvdata aml_a9_ao_drvdata = {
+	.quirks = AML_A9_I3C_QUIRKS | AML_I3C_QUIRK_AO,
+	.vendor_init = aml_i3c_init,
+};
+
 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 },
+	{ .compatible = "amlogic,a9-i3c", .data = &aml_a9_drvdata },
+	{ .compatible = "amlogic,a9-ao-i3c", .data = &aml_a9_ao_drvdata },
 	{ /* sentinel */ },
 };
 MODULE_DEVICE_TABLE(of, svc_i3c_master_of_match_tbl);

-- 
2.42.0



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

* [PATCH v3 2/2] dt-bindings: i3c: silvaco,i3c-master: Add Amlogic A9 support
  2026-11-06  7:13 [PATCH v3 0/2] Add Amlogic A9 I3C support Junyi Zhao via B4 Relay
  2026-11-06  7:13 ` [PATCH v3 1/2] i3c: master: svc: Add amlogic a9 support Junyi Zhao via B4 Relay
@ 2026-11-06  7:13 ` Junyi Zhao via B4 Relay
  2026-11-09 10:18   ` Krzysztof Kozlowski
  1 sibling, 1 reply; 6+ messages in thread
From: Junyi Zhao via B4 Relay @ 2026-11-06  7:13 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>

Amlogic A9 SoCs integrate the Silvaco I3C master IP in two domains:
the EE-domain instance (amlogic,a9-i3c) takes "pclk" and "fast_clk"
like the NXP integration, while the AO-domain instance
(amlogic,a9-ao-i3c) lives in the Always-On domain and generates its
bus clock from a divider inside the controller's own register space,
so there is no external fast clock and only "pclk" is exposed.

The two instances differ from the vanilla Silvaco/NXP integrations in
clock topology and required init, so the driver matches them by
dedicated compatibles instead of the "silvaco,i3c-master-v1"
fallback: a fallback would bind the generic driver data, which
neither programs the fast clock rate nor handles the AO internal
divider, and the variant would then fail to work.

Add both compatibles to the schema without the fallback, following
nuvoton,npcm845-i3c, and use per-compatible if/then conditionals to
enforce the clock counts: two clocks for the EE-domain instance and
one clock for the AO-domain instance.

Signed-off-by: Junyi Zhao <junyi.zhao@amlogic.com>
---
 .../bindings/i3c/silvaco,i3c-master.yaml           | 26 ++++++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/i3c/silvaco,i3c-master.yaml b/Documentation/devicetree/bindings/i3c/silvaco,i3c-master.yaml
index 853092f75..d892d42be 100644
--- a/Documentation/devicetree/bindings/i3c/silvaco,i3c-master.yaml
+++ b/Documentation/devicetree/bindings/i3c/silvaco,i3c-master.yaml
@@ -13,6 +13,8 @@ properties:
   compatible:
     oneOf:
       - enum:
+          - amlogic,a9-i3c
+          - amlogic,a9-ao-i3c
           - nuvoton,npcm845-i3c
           - silvaco,i3c-master-v1
       - items:
@@ -28,14 +30,14 @@ properties:
     maxItems: 1
 
   clocks:
-    minItems: 2
+    minItems: 1
     items:
       - description: system clock
       - description: bus clock
       - description: other (slower) events clock
 
   clock-names:
-    minItems: 2
+    minItems: 1
     items:
       - const: pclk
       - const: fast_clk
@@ -70,15 +72,35 @@ allOf:
         compatible:
           contains:
             enum:
+              - amlogic,a9-i3c
               - nxp,imx94-i3c
               - nxp,imx95-i3c
     then:
       properties:
         clocks:
+          minItems: 2
           maxItems: 2
         clock-names:
+          minItems: 2
           maxItems: 2
 
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - amlogic,a9-ao-i3c
+    then:
+      properties:
+        clocks:
+          maxItems: 1
+          description:
+            The "ao" variant is the controller instance in the Always-On (AO)
+            domain, which has no separate bus clock and derives the
+            bus clock from an internal divider.
+        clock-names:
+          maxItems: 1
+
 unevaluatedProperties: false
 
 examples:

-- 
2.42.0



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

* Re: [PATCH v3 1/2] i3c: master: svc: Add amlogic a9 support
  2026-11-06  7:13 ` [PATCH v3 1/2] i3c: master: svc: Add amlogic a9 support Junyi Zhao via B4 Relay
@ 2026-11-06 17:15   ` Frank Li
  2026-11-10  5:23     ` Junyi Zhao
  0 siblings, 1 reply; 6+ messages in thread
From: Frank Li @ 2026-11-06 17:15 UTC (permalink / raw)
  To: Junyi Zhao
  Cc: 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, linux-i3c, devicetree, linux-kernel,
	linux-arm-kernel, linux-amlogic, imx, linux-hardening,
	xianwei.zhao, Kelvin.Zhang

On Fri, Nov 06, 2026 at 03:13:08PM +0800, Junyi Zhao wrote:
> Amlogic a9 SoCs integrate the Silvaco I3C IP, and their peripheral
> clocks vary from the vanilla implementation:
>
>   - The EE-domain controller (amlogic,a9-i3c) takes two clocks,
>     "pclk" and "fast_clk", and the fast clock rate must be programmed
>     before the controller can be used.
>
>   - The AO-domain controller (amlogic,a9-ao-i3c) exposes only "pclk":
>     its bus clock is generated by an internal divider located in the
>     controller's own register space.
>
> Both variants need setup that the generic driver does not perform:
> the fast clock rate must be rounded and set at probe time, and on the
> AO variant the internal divider must be registered with the clock
> framework so that "fast_clk" even exists. The divider registers sit
> in the controller's own register space, so they can only be accessed
> while pclk is running.
>
> Add a vendor_init callback, invoked from the probe with the clocks
> enabled, to handle this. On the AO variant it registers the internal
> divider chain (div_en -> div -> gate) with the common clock framework
> and appends the resulting clock to the clk bulk array as "fast_clk";
> for both variants it then rounds and sets the fast clock rate
> (target 25 MHz). As vendor_init now runs after
> clk_bulk_prepare_enable(), the probe error paths that follow disable
> the clocks on failure.
>
> Extend the quirks bitmask with an Amlogic-specific bit
> (AML_I3C_QUIRK_AO) marking the AO variant's internal-divider
> behaviour. Since the driver now registers clocks through the common
> clock framework, SVC_I3C_MASTER also depends on COMMON_CLK.
>
> Signed-off-by: Junyi Zhao <junyi.zhao@amlogic.com>
> ---
>  drivers/i3c/master/Kconfig          |   1 +
>  drivers/i3c/master/svc-i3c-master.c | 274 ++++++++++++++++++++++++++++++++++--
>  2 files changed, 263 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig
> index da96d2aaa..ac7d246c2 100644
> --- a/drivers/i3c/master/Kconfig
> +++ b/drivers/i3c/master/Kconfig
> @@ -47,6 +47,7 @@ config AST2600_I3C_MASTER
>
>  config SVC_I3C_MASTER
>  	tristate "Silvaco I3C Dual-Role Master driver"
> +	depends on COMMON_CLK
>  	depends on HAS_IOMEM
>  	depends on !(ALPHA || PARISC)
>  	help
> diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c
> index e700392f0..aef56286e 100644
> --- a/drivers/i3c/master/svc-i3c-master.c
> +++ b/drivers/i3c/master/svc-i3c-master.c
> @@ -9,6 +9,7 @@
>
>  #include <linux/bitfield.h>
>  #include <linux/clk.h>
> +#include <linux/clk-provider.h>
>  #include <linux/completion.h>
>  #include <linux/errno.h>
>  #include <linux/i3c/master.h>
> @@ -166,6 +167,31 @@
>   */
>  #define SVC_I3C_QUIRK_DAA_CORRUPT	BIT(2)
>
> +/*
> + * AML_I3C_QUIRK_AO:
> + * AO is the Always-On domain on Amlogic SoCs (remains powered in suspend).
> + * Its clock is generated by an internal divider located in the I3C
> + * controller's own register space.
> + */
> +#define AML_I3C_QUIRK_AO	BIT(3)
> +
> +/*
> + * Quirks shared by all the Amlogic A9 I3C instances.
> + */
> +#define AML_A9_I3C_QUIRKS	(SVC_I3C_QUIRK_FIFO_EMPTY | \
> +				 SVC_I3C_QUIRK_FALSE_SLVSTART | \
> +				 SVC_I3C_QUIRK_DAA_CORRUPT)
> +
> +/* Amlogic specific registers */
> +#define AML_AO_I3C_FCLK_DIV_CTRL		0x304

Do you have other registers after 0x304? Is it possible create indivial
clock driver for it, reg space is such as 0x300..0x400.

I3C use 0..0x300

So needn't distinguish w/o AO.

Frank

> +#define AML_AO_I3C_FCLK_DIV_EN_SHIFT		8
> +#define AML_AO_I3C_FCLK_DIV_SHIFT		0
> +#define AML_AO_I3C_FCLK_DIV_WIDTH		8
> +#define AML_AO_I3C_FCLK_EN_SHIFT		9
> +
> +/* Target fast clock rate of the Amlogic A9 I3C controllers, in Hz */
> +#define AML_I3C_FAST_CLK_RATE		25000000
> +
>  struct svc_i3c_cmd {
>  	u8 addr;
>  	union {
> @@ -195,8 +221,11 @@ struct svc_i3c_regs_save {
>  	u32 mdynaddr;
>  };
>
> +struct svc_i3c_master;
> +
>  struct svc_i3c_drvdata {
>  	u32 quirks;
> +	int (*vendor_init)(struct svc_i3c_master *master);
>  };
>
>  /**
> @@ -2011,6 +2040,7 @@ static int svc_i3c_master_probe(struct platform_device *pdev)
>  	if (!master->drvdata)
>  		return -EINVAL;
>
> +	master->dev = dev;
>  	master->regs = devm_platform_ioremap_resource(pdev, 0);
>  	if (IS_ERR(master->regs))
>  		return PTR_ERR(master->regs);
> @@ -2019,27 +2049,43 @@ static int svc_i3c_master_probe(struct platform_device *pdev)
>  	if (master->num_clks < 0)
>  		return dev_err_probe(dev, -EINVAL, "can't get I3C clocks\n");
>
> +	ret = clk_bulk_prepare_enable(master->num_clks, master->clks);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "can't enable I3C clocks\n");
> +
> +	/*
> +	 * Vendor specific setup may register additional clocks (e.g. the
> +	 * internal divider chain of the AO variant) and program clock rates,
> +	 * so it must run with the clocks enabled.
> +	 */
> +	if (master->drvdata->vendor_init) {
> +		ret = master->drvdata->vendor_init(master);
> +		if (ret)
> +			goto err_disable_clks;
> +	}
> +
>  	for (i = 0; i < master->num_clks; i++) {
>  		if (!strcmp(master->clks[i].id, "fast_clk"))
>  			break;
>  	}
>
> -	if (i == master->num_clks)
> -		return dev_err_probe(dev, -EINVAL,
> -				     "can't get I3C peripheral clock\n");
> +	if (i == master->num_clks) {
> +		ret = dev_err_probe(dev, -EINVAL,
> +				    "can't get I3C peripheral clock\n");
> +		goto err_disable_clks;
> +	}
>
>  	master->fclk = master->clks[i].clk;
> -	if (IS_ERR(master->fclk))
> -		return PTR_ERR(master->fclk);
> +	if (IS_ERR(master->fclk)) {
> +		ret = PTR_ERR(master->fclk);
> +		goto err_disable_clks;
> +	}
>
>  	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");
> +	if (master->irq < 0) {
> +		ret = master->irq;
> +		goto err_disable_clks;
> +	}
>
>  	mutex_init(&master->lock);
>
> @@ -2163,9 +2209,213 @@ static const struct svc_i3c_drvdata npcm845_drvdata = {
>
>  static const struct svc_i3c_drvdata svc_default_drvdata = {};
>
> +/**
> + * struct aml_i3c_ao_clk - container for the AO variant's internal fast clock
> + * @div_en: enables the internal divider
> + * @div: bus clock divider
> + * @gate: output gate of the generated fast clock
> + * @lock: spinlock shared by the gate and divider register operations
> + *
> + * The clock HW structures are registered with the clock framework and must
> + * stay alive for the whole driver lifetime, so they are kept along with the
> + * shared lock used by their gate/divider operations.
> + */
> +struct aml_i3c_ao_clk {
> +	struct clk_gate div_en;
> +	struct clk_divider div;
> +	struct clk_gate gate;
> +	spinlock_t lock;
> +};
> +
> +/**
> + * aml_ao_i3c_tail_clocks - register the AO variant's internal fast clock
> + * @master: I3C master controller
> + * @parent: parent clock of the internal divider chain; must be "pclk", the
> + *          only clock the AO variant exposes (see silvaco,i3c-master.yaml)
> + *
> + * Register the internal divider chain that produces the AO variant's
> + * fast_clk and append it to the clk bulk array as "fast_clk". The caller
> + * must run with the clocks enabled since the divider registers are
> + * accessed during registration. The appended clock is prepared and
> + * enabled here as well, because clk_bulk_prepare_enable() already ran
> + * in the probe with the original clock list.
> + *
> + * Return: 0 on success, or a negative error code.
> + */
> +static int aml_ao_i3c_tail_clocks(struct svc_i3c_master *master, struct clk *parent)
> +{
> +	struct device *dev = master->dev;
> +	struct clk_bulk_data *new_clks;
> +	struct clk_init_data init = {};
> +	struct aml_i3c_ao_clk *ao;
> +	struct clk *fclk;
> +	char name[64];
> +	int ret;
> +
> +	ao = devm_kzalloc(dev, sizeof(*ao), GFP_KERNEL);
> +	if (!ao)
> +		return -ENOMEM;
> +
> +	spin_lock_init(&ao->lock);
> +
> +	snprintf(name, sizeof(name), "%s#div_en", dev_name(dev));
> +	init.name = name;
> +	init.ops = &clk_gate_ops;
> +	init.flags = CLK_IGNORE_UNUSED;
> +	init.parent_data = &(struct clk_parent_data) {
> +		.index = -1,
> +		.hw = __clk_get_hw(parent),
> +	};
> +	init.num_parents = 1;
> +
> +	ao->div_en.reg = master->regs + AML_AO_I3C_FCLK_DIV_CTRL;
> +	ao->div_en.bit_idx = AML_AO_I3C_FCLK_DIV_EN_SHIFT;
> +	ao->div_en.hw.init = &init;
> +	ao->div_en.lock = &ao->lock;
> +
> +	ret = devm_clk_hw_register(dev, &ao->div_en.hw);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to register %s\n", name);
> +
> +	snprintf(name, sizeof(name), "%s#div", dev_name(dev));
> +	init.name = name;
> +	init.ops = &clk_divider_ops;
> +	init.flags = 0;
> +	init.parent_data = &(struct clk_parent_data) {
> +		.index = -1,
> +		.hw = &ao->div_en.hw,
> +	};
> +	init.num_parents = 1;
> +
> +	ao->div.reg = master->regs + AML_AO_I3C_FCLK_DIV_CTRL;
> +	ao->div.shift = AML_AO_I3C_FCLK_DIV_SHIFT;
> +	ao->div.width = AML_AO_I3C_FCLK_DIV_WIDTH;
> +	ao->div.hw.init = &init;
> +	ao->div.lock = &ao->lock;
> +
> +	ret = devm_clk_hw_register(dev, &ao->div.hw);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to register %s\n", name);
> +
> +	snprintf(name, sizeof(name), "%s#gate", dev_name(dev));
> +	init.name = name;
> +	init.ops = &clk_gate_ops;
> +	init.flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED;
> +	init.parent_data = &(struct clk_parent_data) {
> +		.index = -1,
> +		.hw = &ao->div.hw,
> +	};
> +	init.num_parents = 1;
> +
> +	ao->gate.reg = master->regs + AML_AO_I3C_FCLK_DIV_CTRL;
> +	ao->gate.bit_idx = AML_AO_I3C_FCLK_EN_SHIFT;
> +	ao->gate.hw.init = &init;
> +	ao->gate.lock = &ao->lock;
> +
> +	ret = devm_clk_hw_register(dev, &ao->gate.hw);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to register %s\n", name);
> +
> +	fclk = devm_clk_hw_get_clk(dev, &ao->gate.hw, NULL);
> +	if (IS_ERR(fclk))
> +		return dev_err_probe(dev, PTR_ERR(fclk),
> +				     "tail i3c fast clock failed\n");
> +
> +	new_clks = devm_kcalloc(dev, master->num_clks + 1, sizeof(*new_clks),
> +				GFP_KERNEL);
> +	if (!new_clks)
> +		return -ENOMEM;
> +
> +	memcpy(new_clks, master->clks,
> +	       master->num_clks * sizeof(*master->clks));
> +	new_clks[master->num_clks].id = "fast_clk";
> +	new_clks[master->num_clks].clk = fclk;
> +	master->clks = new_clks;
> +	master->num_clks++;
> +
> +	/*
> +	 * Keep the clk bulk state balanced for the following
> +	 * clk_bulk_disable_unprepare() calls (error paths and runtime PM).
> +	 */
> +	ret = clk_prepare_enable(fclk);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "can't enable fast_clk\n");
> +
> +	return 0;
> +}
> +
> +/**
> + * aml_i3c_init - Amlogic A9 specific init, called with the clocks enabled
> + * @master: I3C master controller
> + *
> + * Register the AO variant's internal divider chain and round/set the fast
> + * clock rate for both variants.
> + *
> + * Return: 0 on success, or a negative error code.
> + */
> +static int aml_i3c_init(struct svc_i3c_master *master)
> +{
> +	struct device *dev = master->dev;
> +	struct clk *fclk;
> +	long fin_freq;
> +	int ret, i;
> +
> +	if (svc_has_quirk(master, AML_I3C_QUIRK_AO)) {
> +		/*
> +		 * clk_bulk_get_all() preserves the DT order, and the
> +		 * binding restricts the AO variant to exactly one clock,
> +		 * which is always "pclk".  So clks[0] is the input of the
> +		 * internal divider chain.
> +		 */
> +		if (strcmp(master->clks[0].id, "pclk"))
> +			return dev_err_probe(dev, -EINVAL,
> +					     "invalid AO input clock\n");
> +
> +		ret = aml_ao_i3c_tail_clocks(master, master->clks[0].clk);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	for (i = 0; i < master->num_clks; i++) {
> +		if (!strcmp(master->clks[i].id, "fast_clk"))
> +			break;
> +	}
> +
> +	if (i == master->num_clks)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "can't get I3C fast clock\n");
> +
> +	fclk = master->clks[i].clk;
> +	if (IS_ERR(fclk))
> +		return PTR_ERR(fclk);
> +
> +	fin_freq = clk_round_rate(fclk, AML_I3C_FAST_CLK_RATE);
> +	if (fin_freq <= 0)
> +		return dev_err_probe(dev, fin_freq ? fin_freq : -EINVAL,
> +				     "invalid fast_clk\n");
> +
> +	ret = clk_set_rate(fclk, fin_freq);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "setting fast_clk rate failed\n");
> +
> +	return 0;
> +}
> +
> +static const struct svc_i3c_drvdata aml_a9_drvdata = {
> +	.quirks = AML_A9_I3C_QUIRKS,
> +	.vendor_init = aml_i3c_init,
> +};
> +
> +static const struct svc_i3c_drvdata aml_a9_ao_drvdata = {
> +	.quirks = AML_A9_I3C_QUIRKS | AML_I3C_QUIRK_AO,
> +	.vendor_init = aml_i3c_init,
> +};
> +
>  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 },
> +	{ .compatible = "amlogic,a9-i3c", .data = &aml_a9_drvdata },
> +	{ .compatible = "amlogic,a9-ao-i3c", .data = &aml_a9_ao_drvdata },
>  	{ /* sentinel */ },
>  };
>  MODULE_DEVICE_TABLE(of, svc_i3c_master_of_match_tbl);
>
> --
> 2.42.0
>

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

* Re: [PATCH v3 2/2] dt-bindings: i3c: silvaco,i3c-master: Add Amlogic A9 support
  2026-11-06  7:13 ` [PATCH v3 2/2] dt-bindings: i3c: silvaco,i3c-master: Add Amlogic A9 support Junyi Zhao via B4 Relay
@ 2026-11-09 10:18   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 6+ messages in thread
From: Krzysztof Kozlowski @ 2026-11-09 10:18 UTC (permalink / raw)
  To: Junyi Zhao
  Cc: 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, linux-i3c, devicetree, linux-kernel,
	linux-arm-kernel, linux-amlogic, imx, linux-hardening,
	xianwei.zhao, Kelvin.Zhang

On Fri, Nov 06, 2026 at 03:13:09PM +0800, Junyi Zhao wrote:
> Amlogic A9 SoCs integrate the Silvaco I3C master IP in two domains:
> the EE-domain instance (amlogic,a9-i3c) takes "pclk" and "fast_clk"
> like the NXP integration, while the AO-domain instance
> (amlogic,a9-ao-i3c) lives in the Always-On domain and generates its
> bus clock from a divider inside the controller's own register space,
> so there is no external fast clock and only "pclk" is exposed.
> 
> The two instances differ from the vanilla Silvaco/NXP integrations in
> clock topology and required init, so the driver matches them by
> dedicated compatibles instead of the "silvaco,i3c-master-v1"
> fallback: a fallback would bind the generic driver data, which
> neither programs the fast clock rate nor handles the AO internal
> divider, and the variant would then fail to work.
> 
> Add both compatibles to the schema without the fallback, following
> nuvoton,npcm845-i3c, and use per-compatible if/then conditionals to
> enforce the clock counts: two clocks for the EE-domain instance and
> one clock for the AO-domain instance.
> 
> Signed-off-by: Junyi Zhao <junyi.zhao@amlogic.com>
> ---
>  .../bindings/i3c/silvaco,i3c-master.yaml           | 26 ++++++++++++++++++++--
>  1 file changed, 24 insertions(+), 2 deletions(-)
>

Please organize the patch documenting the compatible (DT bindings)
before the patch using that compatible.
See also: https://elixir.bootlin.com/linux/v6.14-rc6/source/Documentation/devicetree/bindings/submitting-patches.rst#L46

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>

Best regards,
Krzysztof


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

* Re: [PATCH v3 1/2] i3c: master: svc: Add amlogic a9 support
  2026-11-06 17:15   ` Frank Li
@ 2026-11-10  5:23     ` Junyi Zhao
  0 siblings, 0 replies; 6+ messages in thread
From: Junyi Zhao @ 2026-11-10  5:23 UTC (permalink / raw)
  To: Frank Li
  Cc: 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, linux-i3c, devicetree, linux-kernel,
	linux-arm-kernel, linux-amlogic, imx, linux-hardening,
	xianwei.zhao, Kelvin.Zhang


Hi frank,
	thks for your review.
On 07/11/2026 01:15, Frank Li wrote:
> [ EXTERNAL EMAIL ]
> 
> On Fri, Nov 06, 2026 at 03:13:08PM +0800, Junyi Zhao wrote:
>> Amlogic a9 SoCs integrate the Silvaco I3C IP, and their peripheral
>> clocks vary from the vanilla implementation:
>>
>>    - The EE-domain controller (amlogic,a9-i3c) takes two clocks,
>>      "pclk" and "fast_clk", and the fast clock rate must be programmed
>>      before the controller can be used.
>>
>>    - The AO-domain controller (amlogic,a9-ao-i3c) exposes only "pclk":
>>      its bus clock is generated by an internal divider located in the
>>      controller's own register space.
>>
>> Both variants need setup that the generic driver does not perform:
>> the fast clock rate must be rounded and set at probe time, and on the
>> AO variant the internal divider must be registered with the clock
>> framework so that "fast_clk" even exists. The divider registers sit
>> in the controller's own register space, so they can only be accessed
>> while pclk is running.
>>
>> Add a vendor_init callback, invoked from the probe with the clocks
>> enabled, to handle this. On the AO variant it registers the internal
>> divider chain (div_en -> div -> gate) with the common clock framework
>> and appends the resulting clock to the clk bulk array as "fast_clk";
>> for both variants it then rounds and sets the fast clock rate
>> (target 25 MHz). As vendor_init now runs after
>> clk_bulk_prepare_enable(), the probe error paths that follow disable
>> the clocks on failure.
>>
>> Extend the quirks bitmask with an Amlogic-specific bit
>> (AML_I3C_QUIRK_AO) marking the AO variant's internal-divider
>> behaviour. Since the driver now registers clocks through the common
>> clock framework, SVC_I3C_MASTER also depends on COMMON_CLK.
>>
>> Signed-off-by: Junyi Zhao <junyi.zhao@amlogic.com>
>> ---
>>   drivers/i3c/master/Kconfig          |   1 +
>>   drivers/i3c/master/svc-i3c-master.c | 274 ++++++++++++++++++++++++++++++++++--
>>   2 files changed, 263 insertions(+), 12 deletions(-)
>>
>> diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig
>> index da96d2aaa..ac7d246c2 100644
>> --- a/drivers/i3c/master/Kconfig
>> +++ b/drivers/i3c/master/Kconfig
>> @@ -47,6 +47,7 @@ config AST2600_I3C_MASTER
>>
>>   config SVC_I3C_MASTER
>>        tristate "Silvaco I3C Dual-Role Master driver"
>> +     depends on COMMON_CLK
>>        depends on HAS_IOMEM
>>        depends on !(ALPHA || PARISC)
>>        help
>> diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c
>> index e700392f0..aef56286e 100644
>> --- a/drivers/i3c/master/svc-i3c-master.c
>> +++ b/drivers/i3c/master/svc-i3c-master.c
>> @@ -9,6 +9,7 @@
>>
>>   #include <linux/bitfield.h>
>>   #include <linux/clk.h>
>> +#include <linux/clk-provider.h>
>>   #include <linux/completion.h>
>>   #include <linux/errno.h>
>>   #include <linux/i3c/master.h>
>> @@ -166,6 +167,31 @@
>>    */
>>   #define SVC_I3C_QUIRK_DAA_CORRUPT    BIT(2)
>>
>> +/*
>> + * AML_I3C_QUIRK_AO:
>> + * AO is the Always-On domain on Amlogic SoCs (remains powered in suspend).
>> + * Its clock is generated by an internal divider located in the I3C
>> + * controller's own register space.
>> + */
>> +#define AML_I3C_QUIRK_AO     BIT(3)
>> +
>> +/*
>> + * Quirks shared by all the Amlogic A9 I3C instances.
>> + */
>> +#define AML_A9_I3C_QUIRKS    (SVC_I3C_QUIRK_FIFO_EMPTY | \
>> +                              SVC_I3C_QUIRK_FALSE_SLVSTART | \
>> +                              SVC_I3C_QUIRK_DAA_CORRUPT)
>> +
>> +/* Amlogic specific registers */
>> +#define AML_AO_I3C_FCLK_DIV_CTRL             0x304
> 
> Do you have other registers after 0x304? Is it possible create indivial
> clock driver for it, reg space is such as 0x300..0x400.
> 
> I3C use 0..0x300
> 
> So needn't distinguish w/o AO.
> 

e.g. I3C_AO_DEBUG_MODIFY at 0x308.

0x300-0x400 is not a clean clock block.

Its registers are only accessible while the controller's pclk is
running, so its state is tightly coupled to the controller's own
power/clock state.


> Frank
> 
>> +#define AML_AO_I3C_FCLK_DIV_EN_SHIFT         8
>> +#define AML_AO_I3C_FCLK_DIV_SHIFT            0
>> +#define AML_AO_I3C_FCLK_DIV_WIDTH            8
>> +#define AML_AO_I3C_FCLK_EN_SHIFT             9
>> +
>> +/* Target fast clock rate of the Amlogic A9 I3C controllers, in Hz */
>> +#define AML_I3C_FAST_CLK_RATE                25000000
>> +
>>   struct svc_i3c_cmd {
>>        u8 addr;
>>        union {
>> @@ -195,8 +221,11 @@ struct svc_i3c_regs_save {
>>        u32 mdynaddr;
>>   };
>>
>> +struct svc_i3c_master;
>> +
>>   struct svc_i3c_drvdata {
>>        u32 quirks;
>> +     int (*vendor_init)(struct svc_i3c_master *master);
>>   };
>>
>>   /**
>> @@ -2011,6 +2040,7 @@ static int svc_i3c_master_probe(struct platform_device *pdev)
>>        if (!master->drvdata)
>>                return -EINVAL;
>>
>> +     master->dev = dev;
>>        master->regs = devm_platform_ioremap_resource(pdev, 0);
>>        if (IS_ERR(master->regs))
>>                return PTR_ERR(master->regs);
>> @@ -2019,27 +2049,43 @@ static int svc_i3c_master_probe(struct platform_device *pdev)
>>        if (master->num_clks < 0)
>>                return dev_err_probe(dev, -EINVAL, "can't get I3C clocks\n");
>>
>> +     ret = clk_bulk_prepare_enable(master->num_clks, master->clks);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "can't enable I3C clocks\n");
>> +
>> +     /*
>> +      * Vendor specific setup may register additional clocks (e.g. the
>> +      * internal divider chain of the AO variant) and program clock rates,
>> +      * so it must run with the clocks enabled.
>> +      */
>> +     if (master->drvdata->vendor_init) {
>> +             ret = master->drvdata->vendor_init(master);
>> +             if (ret)
>> +                     goto err_disable_clks;
>> +     }
>> +
>>        for (i = 0; i < master->num_clks; i++) {
>>                if (!strcmp(master->clks[i].id, "fast_clk"))
>>                        break;
>>        }
>>
>> -     if (i == master->num_clks)
>> -             return dev_err_probe(dev, -EINVAL,
>> -                                  "can't get I3C peripheral clock\n");
>> +     if (i == master->num_clks) {
>> +             ret = dev_err_probe(dev, -EINVAL,
>> +                                 "can't get I3C peripheral clock\n");
>> +             goto err_disable_clks;
>> +     }
>>
>>        master->fclk = master->clks[i].clk;
>> -     if (IS_ERR(master->fclk))
>> -             return PTR_ERR(master->fclk);
>> +     if (IS_ERR(master->fclk)) {
>> +             ret = PTR_ERR(master->fclk);
>> +             goto err_disable_clks;
>> +     }
>>
>>        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");
>> +     if (master->irq < 0) {
>> +             ret = master->irq;
>> +             goto err_disable_clks;
>> +     }
>>
>>        mutex_init(&master->lock);
>>
>> @@ -2163,9 +2209,213 @@ static const struct svc_i3c_drvdata npcm845_drvdata = {
>>
>>   static const struct svc_i3c_drvdata svc_default_drvdata = {};
>>
>> +/**
>> + * struct aml_i3c_ao_clk - container for the AO variant's internal fast clock
>> + * @div_en: enables the internal divider
>> + * @div: bus clock divider
>> + * @gate: output gate of the generated fast clock
>> + * @lock: spinlock shared by the gate and divider register operations
>> + *
>> + * The clock HW structures are registered with the clock framework and must
>> + * stay alive for the whole driver lifetime, so they are kept along with the
>> + * shared lock used by their gate/divider operations.
>> + */
>> +struct aml_i3c_ao_clk {
>> +     struct clk_gate div_en;
>> +     struct clk_divider div;
>> +     struct clk_gate gate;
>> +     spinlock_t lock;
>> +};
>> +
>> +/**
>> + * aml_ao_i3c_tail_clocks - register the AO variant's internal fast clock
>> + * @master: I3C master controller
>> + * @parent: parent clock of the internal divider chain; must be "pclk", the
>> + *          only clock the AO variant exposes (see silvaco,i3c-master.yaml)
>> + *
>> + * Register the internal divider chain that produces the AO variant's
>> + * fast_clk and append it to the clk bulk array as "fast_clk". The caller
>> + * must run with the clocks enabled since the divider registers are
>> + * accessed during registration. The appended clock is prepared and
>> + * enabled here as well, because clk_bulk_prepare_enable() already ran
>> + * in the probe with the original clock list.
>> + *
>> + * Return: 0 on success, or a negative error code.
>> + */
>> +static int aml_ao_i3c_tail_clocks(struct svc_i3c_master *master, struct clk *parent)
>> +{
>> +     struct device *dev = master->dev;
>> +     struct clk_bulk_data *new_clks;
>> +     struct clk_init_data init = {};
>> +     struct aml_i3c_ao_clk *ao;
>> +     struct clk *fclk;
>> +     char name[64];
>> +     int ret;
>> +
>> +     ao = devm_kzalloc(dev, sizeof(*ao), GFP_KERNEL);
>> +     if (!ao)
>> +             return -ENOMEM;
>> +
>> +     spin_lock_init(&ao->lock);
>> +
>> +     snprintf(name, sizeof(name), "%s#div_en", dev_name(dev));
>> +     init.name = name;
>> +     init.ops = &clk_gate_ops;
>> +     init.flags = CLK_IGNORE_UNUSED;
>> +     init.parent_data = &(struct clk_parent_data) {
>> +             .index = -1,
>> +             .hw = __clk_get_hw(parent),
>> +     };
>> +     init.num_parents = 1;
>> +
>> +     ao->div_en.reg = master->regs + AML_AO_I3C_FCLK_DIV_CTRL;
>> +     ao->div_en.bit_idx = AML_AO_I3C_FCLK_DIV_EN_SHIFT;
>> +     ao->div_en.hw.init = &init;
>> +     ao->div_en.lock = &ao->lock;
>> +
>> +     ret = devm_clk_hw_register(dev, &ao->div_en.hw);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "failed to register %s\n", name);
>> +
>> +     snprintf(name, sizeof(name), "%s#div", dev_name(dev));
>> +     init.name = name;
>> +     init.ops = &clk_divider_ops;
>> +     init.flags = 0;
>> +     init.parent_data = &(struct clk_parent_data) {
>> +             .index = -1,
>> +             .hw = &ao->div_en.hw,
>> +     };
>> +     init.num_parents = 1;
>> +
>> +     ao->div.reg = master->regs + AML_AO_I3C_FCLK_DIV_CTRL;
>> +     ao->div.shift = AML_AO_I3C_FCLK_DIV_SHIFT;
>> +     ao->div.width = AML_AO_I3C_FCLK_DIV_WIDTH;
>> +     ao->div.hw.init = &init;
>> +     ao->div.lock = &ao->lock;
>> +
>> +     ret = devm_clk_hw_register(dev, &ao->div.hw);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "failed to register %s\n", name);
>> +
>> +     snprintf(name, sizeof(name), "%s#gate", dev_name(dev));
>> +     init.name = name;
>> +     init.ops = &clk_gate_ops;
>> +     init.flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED;
>> +     init.parent_data = &(struct clk_parent_data) {
>> +             .index = -1,
>> +             .hw = &ao->div.hw,
>> +     };
>> +     init.num_parents = 1;
>> +
>> +     ao->gate.reg = master->regs + AML_AO_I3C_FCLK_DIV_CTRL;
>> +     ao->gate.bit_idx = AML_AO_I3C_FCLK_EN_SHIFT;
>> +     ao->gate.hw.init = &init;
>> +     ao->gate.lock = &ao->lock;
>> +
>> +     ret = devm_clk_hw_register(dev, &ao->gate.hw);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "failed to register %s\n", name);
>> +
>> +     fclk = devm_clk_hw_get_clk(dev, &ao->gate.hw, NULL);
>> +     if (IS_ERR(fclk))
>> +             return dev_err_probe(dev, PTR_ERR(fclk),
>> +                                  "tail i3c fast clock failed\n");
>> +
>> +     new_clks = devm_kcalloc(dev, master->num_clks + 1, sizeof(*new_clks),
>> +                             GFP_KERNEL);
>> +     if (!new_clks)
>> +             return -ENOMEM;
>> +
>> +     memcpy(new_clks, master->clks,
>> +            master->num_clks * sizeof(*master->clks));
>> +     new_clks[master->num_clks].id = "fast_clk";
>> +     new_clks[master->num_clks].clk = fclk;
>> +     master->clks = new_clks;
>> +     master->num_clks++;
>> +
>> +     /*
>> +      * Keep the clk bulk state balanced for the following
>> +      * clk_bulk_disable_unprepare() calls (error paths and runtime PM).
>> +      */
>> +     ret = clk_prepare_enable(fclk);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "can't enable fast_clk\n");
>> +
>> +     return 0;
>> +}
>> +
>> +/**
>> + * aml_i3c_init - Amlogic A9 specific init, called with the clocks enabled
>> + * @master: I3C master controller
>> + *
>> + * Register the AO variant's internal divider chain and round/set the fast
>> + * clock rate for both variants.
>> + *
>> + * Return: 0 on success, or a negative error code.
>> + */
>> +static int aml_i3c_init(struct svc_i3c_master *master)
>> +{
>> +     struct device *dev = master->dev;
>> +     struct clk *fclk;
>> +     long fin_freq;
>> +     int ret, i;
>> +
>> +     if (svc_has_quirk(master, AML_I3C_QUIRK_AO)) {
>> +             /*
>> +              * clk_bulk_get_all() preserves the DT order, and the
>> +              * binding restricts the AO variant to exactly one clock,
>> +              * which is always "pclk".  So clks[0] is the input of the
>> +              * internal divider chain.
>> +              */
>> +             if (strcmp(master->clks[0].id, "pclk"))
>> +                     return dev_err_probe(dev, -EINVAL,
>> +                                          "invalid AO input clock\n");
>> +
>> +             ret = aml_ao_i3c_tail_clocks(master, master->clks[0].clk);
>> +             if (ret)
>> +                     return ret;
>> +     }
>> +
>> +     for (i = 0; i < master->num_clks; i++) {
>> +             if (!strcmp(master->clks[i].id, "fast_clk"))
>> +                     break;
>> +     }
>> +
>> +     if (i == master->num_clks)
>> +             return dev_err_probe(dev, -EINVAL,
>> +                                  "can't get I3C fast clock\n");
>> +
>> +     fclk = master->clks[i].clk;
>> +     if (IS_ERR(fclk))
>> +             return PTR_ERR(fclk);
>> +
>> +     fin_freq = clk_round_rate(fclk, AML_I3C_FAST_CLK_RATE);
>> +     if (fin_freq <= 0)
>> +             return dev_err_probe(dev, fin_freq ? fin_freq : -EINVAL,
>> +                                  "invalid fast_clk\n");
>> +
>> +     ret = clk_set_rate(fclk, fin_freq);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "setting fast_clk rate failed\n");
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct svc_i3c_drvdata aml_a9_drvdata = {
>> +     .quirks = AML_A9_I3C_QUIRKS,
>> +     .vendor_init = aml_i3c_init,
>> +};
>> +
>> +static const struct svc_i3c_drvdata aml_a9_ao_drvdata = {
>> +     .quirks = AML_A9_I3C_QUIRKS | AML_I3C_QUIRK_AO,
>> +     .vendor_init = aml_i3c_init,
>> +};
>> +
>>   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 },
>> +     { .compatible = "amlogic,a9-i3c", .data = &aml_a9_drvdata },
>> +     { .compatible = "amlogic,a9-ao-i3c", .data = &aml_a9_ao_drvdata },
>>        { /* sentinel */ },
>>   };
>>   MODULE_DEVICE_TABLE(of, svc_i3c_master_of_match_tbl);
>>
>> --
>> 2.42.0
>>


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

end of thread, other threads:[~2026-11-10  5:23 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-06  7:13 [PATCH v3 0/2] Add Amlogic A9 I3C support Junyi Zhao via B4 Relay
2026-11-06  7:13 ` [PATCH v3 1/2] i3c: master: svc: Add amlogic a9 support Junyi Zhao via B4 Relay
2026-11-06 17:15   ` Frank Li
2026-11-10  5:23     ` Junyi Zhao
2026-11-06  7:13 ` [PATCH v3 2/2] dt-bindings: i3c: silvaco,i3c-master: Add Amlogic A9 support Junyi Zhao via B4 Relay
2026-11-09 10:18   ` Krzysztof Kozlowski

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®