The I2S MCLK outputs on RK3588 are gated by bits in the SYS_GRF
register SOC_CON6 (offset 0x318). These gates control whether the
internal CRU MCLK signals reach the external IO pins connected to
audio codecs.
The kernel should explicitly manage these gates so that audio
functionality does not depend on bootloader register state. This is
analogous to what was done for RK3576 SAI MCLK outputs [1].
Register the SYS_GRF as an auxiliary GRF with grf_type_sys in the
early clock init, and add GATE_GRF entries for all four I2S MCLK
output gates:
- I2S0_8CH_MCLKOUT_TO_IO (bit 0)
- I2S1_8CH_MCLKOUT_TO_IO (bit 1)
- I2S2_2CH_MCLKOUT_TO_IO (bit 2)
- I2S3_2CH_MCLKOUT_TO_IO (bit 7)
Board DTS files that need MCLK on an IO pin can reference these
clocks, e.g.:
clocks = <&cru I2S0_8CH_MCLKOUT_TO_IO>;
Tested on the Youyeetoo YY3588 (RK3588) with an ES8388 codec on I2S0.
[1] https://lore.kernel.org/r/20250305-rk3576-sai-v1-2-64e6cf863e9a@collabora.com/
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202603191419.MH6EuPga-lkp@intel.com/
Signed-off-by: Daniele Briguglio <hello@superkali.me>
---
drivers/clk/rockchip/clk-rk3588.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/drivers/clk/rockchip/clk-rk3588.c b/drivers/clk/rockchip/clk-rk3588.c
index 1694223f4f84..4611b6ce8217 100644
--- a/drivers/clk/rockchip/clk-rk3588.c
+++ b/drivers/clk/rockchip/clk-rk3588.c
@@ -5,7 +5,9 @@
*/
#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
#include <linux/of.h>
+#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/syscore_ops.h>
@@ -892,6 +894,8 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
RK3588_CLKGATE_CON(8), 0, GFLAGS),
MUX(I2S2_2CH_MCLKOUT, "i2s2_2ch_mclkout", i2s2_2ch_mclkout_p, CLK_SET_RATE_PARENT,
RK3588_CLKSEL_CON(30), 2, 1, MFLAGS),
+ GATE_GRF(I2S2_2CH_MCLKOUT_TO_IO, "i2s2_2ch_mclkout_to_io", "i2s2_2ch_mclkout",
+ 0, 0x0318, 2, GFLAGS, grf_type_sys),
COMPOSITE(CLK_I2S3_2CH_SRC, "clk_i2s3_2ch_src", gpll_aupll_p, 0,
RK3588_CLKSEL_CON(30), 8, 1, MFLAGS, 3, 5, DFLAGS,
@@ -907,6 +911,8 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
RK3588_CLKGATE_CON(8), 4, GFLAGS),
MUX(I2S3_2CH_MCLKOUT, "i2s3_2ch_mclkout", i2s3_2ch_mclkout_p, CLK_SET_RATE_PARENT,
RK3588_CLKSEL_CON(32), 2, 1, MFLAGS),
+ GATE_GRF(I2S3_2CH_MCLKOUT_TO_IO, "i2s3_2ch_mclkout_to_io", "i2s3_2ch_mclkout",
+ 0, 0x0318, 7, GFLAGS, grf_type_sys),
GATE(PCLK_ACDCDIG, "pclk_acdcdig", "pclk_audio_root", 0,
RK3588_CLKGATE_CON(7), 11, GFLAGS),
GATE(HCLK_I2S0_8CH, "hclk_i2s0_8ch", "hclk_audio_root", 0,
@@ -935,6 +941,8 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
RK3588_CLKGATE_CON(7), 10, GFLAGS),
MUX(I2S0_8CH_MCLKOUT, "i2s0_8ch_mclkout", i2s0_8ch_mclkout_p, CLK_SET_RATE_PARENT,
RK3588_CLKSEL_CON(28), 2, 2, MFLAGS),
+ GATE_GRF(I2S0_8CH_MCLKOUT_TO_IO, "i2s0_8ch_mclkout_to_io", "i2s0_8ch_mclkout",
+ 0, 0x0318, 0, GFLAGS, grf_type_sys),
GATE(HCLK_PDM1, "hclk_pdm1", "hclk_audio_root", 0,
RK3588_CLKGATE_CON(9), 6, GFLAGS),
@@ -2220,6 +2228,8 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
RK3588_PMU_CLKGATE_CON(2), 13, GFLAGS),
MUX(I2S1_8CH_MCLKOUT, "i2s1_8ch_mclkout", i2s1_8ch_mclkout_p, CLK_SET_RATE_PARENT,
RK3588_PMU_CLKSEL_CON(9), 2, 2, MFLAGS),
+ GATE_GRF(I2S1_8CH_MCLKOUT_TO_IO, "i2s1_8ch_mclkout_to_io", "i2s1_8ch_mclkout",
+ 0, 0x0318, 1, GFLAGS, grf_type_sys),
GATE(PCLK_PMU1, "pclk_pmu1", "pclk_pmu0_root", CLK_IS_CRITICAL,
RK3588_PMU_CLKGATE_CON(1), 0, GFLAGS),
GATE(CLK_DDR_FAIL_SAFE, "clk_ddr_fail_safe", "clk_pmu0", CLK_IGNORE_UNUSED,
@@ -2439,6 +2449,8 @@ static struct rockchip_clk_branch rk3588_clk_branches[] = {
static void __init rk3588_clk_early_init(struct device_node *np)
{
struct rockchip_clk_provider *ctx;
+ struct rockchip_aux_grf *sys_grf_e;
+ struct regmap *sys_grf;
unsigned long clk_nr_clks, max_clk_id1, max_clk_id2;
void __iomem *reg_base;
@@ -2479,6 +2491,17 @@ static void __init rk3588_clk_early_init(struct device_node *np)
&rk3588_cpub1clk_data, rk3588_cpub1clk_rates,
ARRAY_SIZE(rk3588_cpub1clk_rates));
+ /* Register SYS_GRF for I2S MCLK output to IO gate clocks */
+ sys_grf = syscon_regmap_lookup_by_compatible("rockchip,rk3588-sys-grf");
+ if (!IS_ERR(sys_grf)) {
+ sys_grf_e = kzalloc_obj(*sys_grf_e);
+ if (sys_grf_e) {
+ sys_grf_e->grf = sys_grf;
+ sys_grf_e->type = grf_type_sys;
+ hash_add(ctx->aux_grf_table, &sys_grf_e->node, grf_type_sys);
+ }
+ }
+
rockchip_clk_register_branches(ctx, rk3588_early_clk_branches,
ARRAY_SIZE(rk3588_early_clk_branches));
--
2.53.0
On Thursday, 19 March 2026 08:34:01 Central European Standard Time Daniele Briguglio wrote:
> The I2S MCLK outputs on RK3588 are gated by bits in the SYS_GRF
> register SOC_CON6 (offset 0x318). These gates control whether the
> internal CRU MCLK signals reach the external IO pins connected to
> audio codecs.
>
> The kernel should explicitly manage these gates so that audio
> functionality does not depend on bootloader register state. This is
> analogous to what was done for RK3576 SAI MCLK outputs [1].
>
> Register the SYS_GRF as an auxiliary GRF with grf_type_sys in the
> early clock init, and add GATE_GRF entries for all four I2S MCLK
> output gates:
>
> - I2S0_8CH_MCLKOUT_TO_IO (bit 0)
> - I2S1_8CH_MCLKOUT_TO_IO (bit 1)
> - I2S2_2CH_MCLKOUT_TO_IO (bit 2)
> - I2S3_2CH_MCLKOUT_TO_IO (bit 7)
>
> Board DTS files that need MCLK on an IO pin can reference these
> clocks, e.g.:
>
> clocks = <&cru I2S0_8CH_MCLKOUT_TO_IO>;
>
> Tested on the Youyeetoo YY3588 (RK3588) with an ES8388 codec on I2S0.
>
> [1] https://lore.kernel.org/r/20250305-rk3576-sai-v1-2-64e6cf863e9a@collabora.com/
>
> Reported-by: kernel test robot <lkp@intel.com>
> Closes: https://lore.kernel.org/oe-kbuild-all/202603191419.MH6EuPga-lkp@intel.com/
I think you can drop these two tags, the patch wasn't merged yet.
> Signed-off-by: Daniele Briguglio <hello@superkali.me>
> ---
> drivers/clk/rockchip/clk-rk3588.c | 23 +++++++++++++++++++++++
> 1 file changed, 23 insertions(+)
>
> diff --git a/drivers/clk/rockchip/clk-rk3588.c b/drivers/clk/rockchip/clk-rk3588.c
> index 1694223f4f84..4611b6ce8217 100644
> --- a/drivers/clk/rockchip/clk-rk3588.c
> +++ b/drivers/clk/rockchip/clk-rk3588.c
> @@ -5,7 +5,9 @@
> */
>
> #include <linux/clk-provider.h>
> +#include <linux/mfd/syscon.h>
> #include <linux/of.h>
> +#include <linux/slab.h>
> #include <linux/of_address.h>
> #include <linux/platform_device.h>
> #include <linux/syscore_ops.h>
> @@ -892,6 +894,8 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
> RK3588_CLKGATE_CON(8), 0, GFLAGS),
> MUX(I2S2_2CH_MCLKOUT, "i2s2_2ch_mclkout", i2s2_2ch_mclkout_p, CLK_SET_RATE_PARENT,
> RK3588_CLKSEL_CON(30), 2, 1, MFLAGS),
> + GATE_GRF(I2S2_2CH_MCLKOUT_TO_IO, "i2s2_2ch_mclkout_to_io", "i2s2_2ch_mclkout",
> + 0, 0x0318, 2, GFLAGS, grf_type_sys),
For the register offset, please add another patch which adds a
#define RK3588_SYSGRF_SOC_CON6 0x318
to `include/soc/rockchip/rk3588_grf.h`, and then use that name
here by including that header. Alternatively, just define it
in this file (though it's up to Heiko to decide what he prefers.)
>
> COMPOSITE(CLK_I2S3_2CH_SRC, "clk_i2s3_2ch_src", gpll_aupll_p, 0,
> RK3588_CLKSEL_CON(30), 8, 1, MFLAGS, 3, 5, DFLAGS,
> @@ -907,6 +911,8 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
> RK3588_CLKGATE_CON(8), 4, GFLAGS),
> MUX(I2S3_2CH_MCLKOUT, "i2s3_2ch_mclkout", i2s3_2ch_mclkout_p, CLK_SET_RATE_PARENT,
> RK3588_CLKSEL_CON(32), 2, 1, MFLAGS),
> + GATE_GRF(I2S3_2CH_MCLKOUT_TO_IO, "i2s3_2ch_mclkout_to_io", "i2s3_2ch_mclkout",
> + 0, 0x0318, 7, GFLAGS, grf_type_sys),
> GATE(PCLK_ACDCDIG, "pclk_acdcdig", "pclk_audio_root", 0,
> RK3588_CLKGATE_CON(7), 11, GFLAGS),
> GATE(HCLK_I2S0_8CH, "hclk_i2s0_8ch", "hclk_audio_root", 0,
> @@ -935,6 +941,8 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
> RK3588_CLKGATE_CON(7), 10, GFLAGS),
> MUX(I2S0_8CH_MCLKOUT, "i2s0_8ch_mclkout", i2s0_8ch_mclkout_p, CLK_SET_RATE_PARENT,
> RK3588_CLKSEL_CON(28), 2, 2, MFLAGS),
> + GATE_GRF(I2S0_8CH_MCLKOUT_TO_IO, "i2s0_8ch_mclkout_to_io", "i2s0_8ch_mclkout",
> + 0, 0x0318, 0, GFLAGS, grf_type_sys),
>
> GATE(HCLK_PDM1, "hclk_pdm1", "hclk_audio_root", 0,
> RK3588_CLKGATE_CON(9), 6, GFLAGS),
> @@ -2220,6 +2228,8 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
> RK3588_PMU_CLKGATE_CON(2), 13, GFLAGS),
> MUX(I2S1_8CH_MCLKOUT, "i2s1_8ch_mclkout", i2s1_8ch_mclkout_p, CLK_SET_RATE_PARENT,
> RK3588_PMU_CLKSEL_CON(9), 2, 2, MFLAGS),
> + GATE_GRF(I2S1_8CH_MCLKOUT_TO_IO, "i2s1_8ch_mclkout_to_io", "i2s1_8ch_mclkout",
> + 0, 0x0318, 1, GFLAGS, grf_type_sys),
> GATE(PCLK_PMU1, "pclk_pmu1", "pclk_pmu0_root", CLK_IS_CRITICAL,
> RK3588_PMU_CLKGATE_CON(1), 0, GFLAGS),
> GATE(CLK_DDR_FAIL_SAFE, "clk_ddr_fail_safe", "clk_pmu0", CLK_IGNORE_UNUSED,
> @@ -2439,6 +2449,8 @@ static struct rockchip_clk_branch rk3588_clk_branches[] = {
> static void __init rk3588_clk_early_init(struct device_node *np)
> {
> struct rockchip_clk_provider *ctx;
> + struct rockchip_aux_grf *sys_grf_e;
> + struct regmap *sys_grf;
> unsigned long clk_nr_clks, max_clk_id1, max_clk_id2;
> void __iomem *reg_base;
>
> @@ -2479,6 +2491,17 @@ static void __init rk3588_clk_early_init(struct device_node *np)
> &rk3588_cpub1clk_data, rk3588_cpub1clk_rates,
> ARRAY_SIZE(rk3588_cpub1clk_rates));
>
> + /* Register SYS_GRF for I2S MCLK output to IO gate clocks */
> + sys_grf = syscon_regmap_lookup_by_compatible("rockchip,rk3588-sys-grf");
> + if (!IS_ERR(sys_grf)) {
> + sys_grf_e = kzalloc_obj(*sys_grf_e);
> + if (sys_grf_e) {
> + sys_grf_e->grf = sys_grf;
> + sys_grf_e->type = grf_type_sys;
> + hash_add(ctx->aux_grf_table, &sys_grf_e->node, grf_type_sys);
> + }
> + }
> +
> rockchip_clk_register_branches(ctx, rk3588_early_clk_branches,
> ARRAY_SIZE(rk3588_early_clk_branches));
>
>
>
With that addressed, you can add a
Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
Kind regards,
Nicolas Frattaroli
© 2016 - 2026 Red Hat, Inc.