* [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>
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ 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 7:32 ` sashiko-bot 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, 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 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 _______________________________________________ linux-amlogic mailing list linux-amlogic@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-amlogic ^ 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 7:32 ` sashiko-bot 2026-11-06 17:15 ` Frank Li 1 sibling, 0 replies; 6+ messages in thread From: sashiko-bot @ 2026-11-06 7:32 UTC (permalink / raw) To: Junyi Zhao Cc: linux-i3c, devicetree, linux-amlogic, imx, robh, conor+dt, Frank.Li, neil.armstrong, Alexandre Belloni Thank you for your contribution! Sashiko AI review found 4 potential issues to consider: - [Medium] i3c: master: svc: unbalanced fclk disable in aml_ao_i3c_tail_clocks() - [Medium] i3c: master: svc: NULL dereference of clks[0].id in aml_i3c_init() - [Medium] i3c: master: svc: unclocked MMIO read in aml_ao_i3c_tail_clocks() - [Low] i3c: master: svc: duplicate fast_clk lookup in aml_i3c_init() -- commit 13e8b1ef64cdb3b2e6850ddcbaa3e5a7ffee2a56 Author: Junyi Zhao <junyi.zhao@amlogic.com> i3c: master: svc: Add amlogic a9 support This commit adds Amlogic A9 and A9 AO I3C controller support to the Silvaco I3C master driver, including a vendor_init callback that registers the AO variant's internal clock divider chain and configures the fast_clk rate. > diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c > index e700392f0801f..aef56286ecfa8 100644 > --- a/drivers/i3c/master/svc-i3c-master.c > +++ b/drivers/i3c/master/svc-i3c-master.c [ ... ] > @@ -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; > + } [ ... ] > @@ -2163,9 +2209,213 @@ static const struct svc_i3c_drvdata npcm845_drvdata = { [ ... ] > +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); [Severity: Medium] Can the clock framework read AML_AO_I3C_FCLK_DIV_CTRL while pclk is disabled during runtime suspend? svc_i3c_master_probe() invokes vendor_init() before calling pm_runtime_enable(), so clk_pm_runtime_init() sees pm_runtime_enabled(dev) == false and leaves core->rpm_enabled unset for div_en, div, and gate, and none of the three clocks sets CLK_OPS_PARENT_ENABLE. Once svc_i3c_runtime_suspend() disables pclk, any clock query (such as reading /sys/kernel/debug/clk/clk_summary) calls clk_gate_ops.is_enabled or clk_divider_ops.recalc_rate and performs an unclocked MMIO read of AML_AO_I3C_FCLK_DIV_CTRL. > + 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; > +} [Severity: Medium] If clk_prepare_enable(fclk) fails here, does the err_disable_clks path in svc_i3c_master_probe() call clk_bulk_disable_unprepare() on an unprepared and disabled fclk? master->clks and master->num_clks are updated to include fclk before clk_prepare_enable(fclk) succeeds, which will trigger WARN() assertions in clk_core_disable() and clk_core_unprepare(). > +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"); [Severity: Medium] Can this dereference a NULL pointer if the DT node omits clocks or clock-names? devm_clk_bulk_get_all() returns 0 with master->clks == NULL when no clocks are present in DT (passing the master->num_clks < 0 check in svc_i3c_master_probe()), and leaves master->clks[0].id as NULL if clock-names is missing. > + > + 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); [Severity: Low] This isn't a bug, but this fast_clk lookup and IS_ERR() check duplicate the identical loop that runs in svc_i3c_master_probe() right after vendor_init() returns. Could master->fclk be resolved once in a helper or before setting the clock rate? -- Sashiko AI review · https://sashiko.dev/#/patchset/20261106-a9_i3c-v3-0-804b69b9095b@amlogic.com?part=1 _______________________________________________ linux-amlogic mailing list linux-amlogic@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-amlogic ^ 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 7:32 ` sashiko-bot @ 2026-11-06 17:15 ` Frank Li 1 sibling, 0 replies; 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 > _______________________________________________ linux-amlogic mailing list linux-amlogic@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-amlogic ^ 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-06 7:22 ` sashiko-bot 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 _______________________________________________ linux-amlogic mailing list linux-amlogic@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-amlogic ^ 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-06 7:22 ` sashiko-bot 0 siblings, 0 replies; 6+ messages in thread From: sashiko-bot @ 2026-11-06 7:22 UTC (permalink / raw) To: Junyi Zhao Cc: linux-i3c, devicetree, linux-amlogic, imx, robh, conor+dt, Frank.Li, neil.armstrong, Alexandre Belloni Thank you for your contribution! Sashiko AI review found 2 potential issues to consider: - [Low] dt-bindings: i3c: silvaco: unwieldy per-compatible clock conditionals - [Low] dt-bindings: i3c: silvaco: single-item enum and description in if/then -- commit dbac4436ecc3f857ef0fa7dbe10d0fd5116cdb2d Author: Junyi Zhao <junyi.zhao@amlogic.com> dt-bindings: i3c: silvaco,i3c-master: Add Amlogic A9 support This commit adds the amlogic,a9-i3c and amlogic,a9-ao-i3c compatible strings and their per-variant clock constraints to the Silvaco I3C master Device Tree binding schema. > diff --git a/Documentation/devicetree/bindings/i3c/silvaco,i3c-master.yaml b/Documentation/devicetree/bindings/i3c/silvaco,i3c-master.yaml > index 853092f7522d3..d892d42bee65c 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 [Severity: Low] Should the Amlogic A9 variants be split into a separate binding schema instead of adding more per-compatible conditionals here? Neither amlogic,a9-i3c nor amlogic,a9-ao-i3c uses the silvaco,i3c-master-v1 fallback, and accommodating the 1-clock AO variant weakens the top-level clocks and clock-names minItems to 1 so that 3-clock, 2-clock, and 1-clock variants all have to be constrained via separate if/then blocks in allOf. > + > + - 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 [Severity: Low] Does this new if/then block pass dt_binding_check? The dtschema meta-schema requires enum arrays to have at least two items, so matching the single amlogic,a9-ao-i3c string under contains needs const rather than a single-entry enum. Also, property descriptions belong in the top-level properties block rather than inside an if/then conditional. -- Sashiko AI review · https://sashiko.dev/#/patchset/20261106-a9_i3c-v3-0-804b69b9095b@amlogic.com?part=2 _______________________________________________ linux-amlogic mailing list linux-amlogic@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-amlogic ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-11-06 17:16 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 7:32 ` sashiko-bot 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 2026-11-06 7:22 ` sashiko-bot
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®