[PATCH v7 13/18] ASoC: rsnd: adg: Add per-SSI ADG and SSIF supply clock management

From: John Madieu

Date: Mon May 25 2026 - 07:05:09 EST


RZ/G3E's ADG module requires explicit clock management for SSI audio
interfaces that differs from R-Car Gen2/Gen3/Gen4:

- Per-SSI ADG clocks (adg-ssi-N, or adg.ssi.N in legacy bindings)
for each SSI module
- A shared SSIF supply clock for the SSI subsystem

These clocks are acquired using optional APIs, making them transparent
to platforms that do not require them.

Signed-off-by: John Madieu <john.madieu.xa@xxxxxxxxxxxxxx>
---

Changes:

v7:
- Replace the inline "ret |= clk_prepare(...)" accumulation in
rsnd_adg_clk_control() with dedicated rsnd_adg_ssi_clk_prepare()
and rsnd_adg_ssi_clk_unprepare() helpers. The prepare helper
stops at the first failure and unwinds only the clocks it
already prepared, instead of unconditionally unpreparing every
clock (which would underflow the state of any clock that never
prepared).
- Track prepared state with a new bool ssi_clk_prepared in
struct rsnd_adg so prepare and unprepare are idempotent.
- Preserve the first error from the surrounding for_each_rsnd_clkin
loop: adopt the SSI-clock helper's return value only when ret was
still zero, instead of OR-ing error codes together (sashiko-bot).

v6:
- Rename the per-SSI clock lookup string in code from
"adg.ssi.%d" to "adg-ssi-%d", matching the new DT binding. The
clock is RZ/G3E-only and has no legacy dotted form, so the
indexed helper from patch 04/16 is not needed here.
- Simplify rsnd_adg_clk_control(): collapse the per-SSI ADG and
SSIF supply prepare/unprepare path into the accumulating
"ret |= clk_prepare(...)" style already used for the regular
clkin loop above, restructured as a single if (enable) / else
block.
- Update the in-driver comment to refer to the hyphenated name.

v5: No changes

v4:
- Move clk_prepare/unprepare for per-SSI ADG and SSIF supply
clocks into rsnd_adg_clk_control() instead of separate
prepare/unprepare functions, centralising clock lifecycle
management.
- Return proper errors on clk_enable() failure instead of
dev_warn().
- Eliminates hw_params prepare leak concern since prepare now
happens once at probe/resume.

v3: No changes
v2:
- Split clock handling into prepare/enable phases for atomic
context safety.

sound/soc/renesas/rcar/adg.c | 124 ++++++++++++++++++++++++++++++++++-
1 file changed, 123 insertions(+), 1 deletion(-)

diff --git a/sound/soc/renesas/rcar/adg.c b/sound/soc/renesas/rcar/adg.c
index 813ad5eabba6..5dce62287d20 100644
--- a/sound/soc/renesas/rcar/adg.c
+++ b/sound/soc/renesas/rcar/adg.c
@@ -19,6 +19,9 @@
#define CLKOUT3 3
#define CLKOUTMAX 4

+/* Maximum SSI count for per-SSI clocks */
+#define ADG_SSI_MAX 10
+
#define BRGCKR_31 (1 << 31)
#define BRRx_MASK(x) (0x3FF & x)

@@ -34,10 +37,14 @@ struct rsnd_adg {
struct clk *adg;
struct clk *clkin[CLKINMAX];
struct clk *clkout[CLKOUTMAX];
+ /* RZ/G3E: per-SSI ADG clocks (adg-ssi-0 through adg-ssi-9) */
+ struct clk *clk_adg_ssi[ADG_SSI_MAX];
+ struct clk *clk_ssif_supply;
struct clk *null_clk;
struct clk_onecell_data onecell;
struct rsnd_mod mod;
int clkin_rate[CLKINMAX];
+ bool ssi_clk_prepared;
int clkin_size;
int clkout_size;
u32 ckr;
@@ -343,8 +350,16 @@ int rsnd_adg_clk_query(struct rsnd_priv *priv, unsigned int rate)

int rsnd_adg_ssi_clk_stop(struct rsnd_mod *ssi_mod)
{
+ struct rsnd_priv *priv = rsnd_mod_to_priv(ssi_mod);
+ struct rsnd_adg *adg = rsnd_priv_to_adg(priv);
+ int id = rsnd_mod_id(ssi_mod);
+
rsnd_adg_set_ssi_clk(ssi_mod, 0);

+ /* RZ/G3E: only disable here, unprepare is done in hw_free */
+ clk_disable(adg->clk_adg_ssi[id]);
+ clk_disable(adg->clk_ssif_supply);
+
return 0;
}

@@ -354,7 +369,8 @@ int rsnd_adg_ssi_clk_try_start(struct rsnd_mod *ssi_mod, unsigned int rate)
struct rsnd_adg *adg = rsnd_priv_to_adg(priv);
struct device *dev = rsnd_priv_to_dev(priv);
struct rsnd_mod *adg_mod = rsnd_mod_get(adg);
- int data;
+ int id = rsnd_mod_id(ssi_mod);
+ int ret, data;
u32 ckr = 0;

data = rsnd_adg_clk_query(priv, rate);
@@ -376,9 +392,63 @@ int rsnd_adg_ssi_clk_try_start(struct rsnd_mod *ssi_mod, unsigned int rate)
(ckr) ? adg->brg_rate[ADG_HZ_48] :
adg->brg_rate[ADG_HZ_441]);

+ /*
+ * RZ/G3E: enable per-SSI and supply clocks
+ */
+ ret = clk_enable(adg->clk_adg_ssi[id]);
+ if (ret) {
+ dev_err(dev, "Cannot enable adg-ssi-%d ADG clock\n", id);
+ return ret;
+ }
+
+ ret = clk_enable(adg->clk_ssif_supply);
+ if (ret) {
+ dev_err(dev, "Cannot enable SSIF supply clock\n");
+ clk_disable(adg->clk_adg_ssi[id]);
+ return ret;
+ }
+
return 0;
}

+static int rsnd_adg_ssi_clk_prepare(struct rsnd_adg *adg)
+{
+ int i, ret;
+
+ if (adg->ssi_clk_prepared)
+ return 0;
+
+ for (i = 0; i < ADG_SSI_MAX; i++) {
+ ret = clk_prepare(adg->clk_adg_ssi[i]);
+ if (ret)
+ goto unwind;
+ }
+ ret = clk_prepare(adg->clk_ssif_supply);
+ if (ret)
+ goto unwind;
+
+ adg->ssi_clk_prepared = true;
+ return 0;
+
+unwind:
+ while (i--)
+ clk_unprepare(adg->clk_adg_ssi[i]);
+ return ret;
+}
+
+static void rsnd_adg_ssi_clk_unprepare(struct rsnd_adg *adg)
+{
+ int i;
+
+ if (!adg->ssi_clk_prepared)
+ return;
+ adg->ssi_clk_prepared = false;
+
+ clk_unprepare(adg->clk_ssif_supply);
+ for (i = 0; i < ADG_SSI_MAX; i++)
+ clk_unprepare(adg->clk_adg_ssi[i]);
+}
+
int rsnd_adg_clk_control(struct rsnd_priv *priv, int enable)
{
struct rsnd_adg *adg = rsnd_priv_to_adg(priv);
@@ -417,6 +487,28 @@ int rsnd_adg_clk_control(struct rsnd_priv *priv, int enable)
}
}

+ /*
+ * rsnd_adg_clk_enable() might return error (_disable() will not).
+ * We need to rollback in such case
+ */
+ /*
+ * RZ/G3E per-SSI ADG and SSIF supply clocks.
+ *
+ * Follow the same style as for_each_rsnd_clkin() above: on enable,
+ * try to prepare every clock and accumulate the error. On disable,
+ * unprepare every clock. Absent optional clocks are NULL, for
+ * which clk_prepare() and clk_unprepare() are no-ops.
+ */
+ if (enable) {
+ int sub_ret = rsnd_adg_ssi_clk_prepare(adg);
+
+ /* Preserve the first error from the clkin loop above. */
+ if (sub_ret && !ret)
+ ret = sub_ret;
+ } else {
+ rsnd_adg_ssi_clk_unprepare(adg);
+ }
+
/*
* rsnd_adg_clk_enable() might return error (_disable() will not).
* We need to rollback in such case
@@ -769,6 +861,31 @@ void rsnd_adg_clk_dbg_info(struct rsnd_priv *priv, struct seq_file *m)
#define rsnd_adg_clk_dbg_info(priv, m)
#endif

+static int rsnd_adg_get_ssi_clks(struct rsnd_priv *priv)
+{
+ struct rsnd_adg *adg = rsnd_priv_to_adg(priv);
+ struct device *dev = rsnd_priv_to_dev(priv);
+ char name[16];
+ int i;
+
+ /* SSIF supply clock */
+ adg->clk_ssif_supply = devm_clk_get_optional(dev, "ssif_supply");
+ if (IS_ERR(adg->clk_ssif_supply))
+ return dev_err_probe(dev, PTR_ERR(adg->clk_ssif_supply),
+ "failed to get ssif_supply clock\n");
+
+ /* Per-SSI ADG clocks (RZ/G3E-only; no legacy dotted form exists) */
+ for (i = 0; i < ADG_SSI_MAX; i++) {
+ snprintf(name, sizeof(name), "adg-ssi-%d", i);
+ adg->clk_adg_ssi[i] = devm_clk_get_optional(dev, name);
+ if (IS_ERR(adg->clk_adg_ssi[i]))
+ return dev_err_probe(dev, PTR_ERR(adg->clk_adg_ssi[i]),
+ "failed to get %s clock\n", name);
+ }
+
+ return 0;
+}
+
int rsnd_adg_probe(struct rsnd_priv *priv)
{
struct reset_control *rstc;
@@ -798,6 +915,11 @@ int rsnd_adg_probe(struct rsnd_priv *priv)
if (ret)
return ret;

+ /* RZ/G3E-specific: per-SSI ADG and SSIF supply clocks */
+ ret = rsnd_adg_get_ssi_clks(priv);
+ if (ret)
+ return ret;
+
ret = rsnd_adg_clk_enable(priv);
if (ret)
return ret;
--
2.25.1