[v4,9/9] clk: sunxi-ng: sun6i-rtc: add a733 support
Commit Message
Add support for the sun60i a733 CCU RTC.
Compared to the a523, this SoC has a different input oscillator divider
which auto-detects the oscillator rate and select a divider to provide
a fixed 32768Hz clock. It also provides several phy reference clocks
with dedicated clock gates.
Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
drivers/clk/sunxi-ng/ccu-sun6i-rtc.c | 73 ++++++++++++++++++++++++++++++++++--
drivers/clk/sunxi-ng/ccu-sun6i-rtc.h | 2 +-
2 files changed, 71 insertions(+), 4 deletions(-)
Comments
> Add support for the sun60i a733 CCU RTC.
>
> Compared to the a523, this SoC has a different input oscillator divider
> which auto-detects the oscillator rate and select a divider to provide
> a fixed 32768Hz clock. It also provides several phy reference clocks
> with dedicated clock gates.
>
> Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Sashiko has reviewed this patch and found no issues. It looks great!
On Mon, Jul 6, 2026 at 5:32 PM Jerome Brunet <jbrunet@baylibre.com> wrote:
>
> Add support for the sun60i a733 CCU RTC.
>
> Compared to the a523, this SoC has a different input oscillator divider
> which auto-detects the oscillator rate and select a divider to provide
> a fixed 32768Hz clock. It also provides several phy reference clocks
> with dedicated clock gates.
>
> Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
> ---
> drivers/clk/sunxi-ng/ccu-sun6i-rtc.c | 73 ++++++++++++++++++++++++++++++++++--
> drivers/clk/sunxi-ng/ccu-sun6i-rtc.h | 2 +-
> 2 files changed, 71 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c
> index 25dd87e78eb7..6b71bbd80255 100644
> --- a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c
> +++ b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c
> @@ -44,9 +44,13 @@
> #define DCXO_CTRL_REG 0x160
> #define DCXO_CTRL_CLK16M_RC_EN BIT(0)
>
> +#define DCXO_GATING_REG 0x16c
> +
> struct sun6i_rtc_match_data {
> bool have_ext_osc32k : 1;
> bool have_iosc_calibration : 1;
> + bool have_dcxo_status : 1;
> + bool have_phy_ref_gates : 1;
> bool rtc_32k_single_parent : 1;
> const struct clk_parent_data *osc32k_fanout_parents;
> u8 osc32k_fanout_nparents;
> @@ -213,7 +217,12 @@ static struct ccu_mux osc32k_clk = {
> },
> };
>
> -/* This falls back to the global name for fwnodes without a named reference. */
> +/*
> + * This falls back to the global name for fwnodes without a named reference.
> + * NOTE: osc24M name might be misleading the oscillator could also be a 26MHz
> + * or a 19.2MHz one starting with the a733. The original name is kept anyway
> + * in case anything is relying on it.
> + */
> static const struct clk_parent_data osc24M[] = {
> { .fw_name = "hosc", .name = "osc24M" }
> };
> @@ -227,8 +236,28 @@ static struct clk_fixed_factor osc24M_32k_div_clk = {
> 0),
> };
>
> -static SUNXI_CCU_GATE_HW(osc24M_32k_clk, "osc24M-32k", &osc24M_32k_div_clk.hw,
> - LOSC_OUT_GATING_REG, BIT(16), 0);
> +static struct clk_div_table osc24M_32k_div_a733_table[] = {
> + { .val = 0, .div = 732 },
> + { .val = 1, .div = 586 },
> + { .val = 2, .div = 793 },
> + { .val = 3, .div = 732 },
> + { /* Sentinel */ },
> +};
> +
> +static struct ccu_div osc24M_32k_div_a733_clk = {
> + .enable = BIT(1),
> + .div = _SUNXI_CCU_DIV_TABLE(14, 2, osc24M_32k_div_a733_table),
> + .common = {
> + .reg = DCXO_CTRL_REG,
> + .hw.init = CLK_HW_INIT_PARENTS_DATA("osc24M-32k-div",
> + osc24M,
> + &ccu_rodiv_ops,
> + 0),
> + },
> +};
> +
> +static SUNXI_CCU_GATE(osc24M_32k_clk, "osc24M-32k", "osc24M-32k-div",
I'm not a big fan of using global clock parent names, especially when we
can have proper struct clk_hw pointer references. However in this case
it seems unavoidable without making a huge mess.
> + LOSC_OUT_GATING_REG, BIT(16), 0);
>
> static const struct clk_hw *rtc_32k_parents[] = {
> &osc32k_clk.common.hw,
> @@ -267,6 +296,15 @@ static struct ccu_mux osc32k_fanout_clk = {
> },
> };
>
> +static SUNXI_CCU_GATE_FW(hosc_serdes1_clk, "hosc-serdes1", "hosc",
> + DCXO_GATING_REG, BIT(5), 0);
> +static SUNXI_CCU_GATE_FW(hosc_serdes0_clk, "hosc-serdes0", "hosc",
> + DCXO_GATING_REG, BIT(4), 0);
> +static SUNXI_CCU_GATE_FW(hosc_hdmi_clk, "hosc-hdmi", "hosc",
> + DCXO_GATING_REG, BIT(1), 0);
> +static SUNXI_CCU_GATE_FW(hosc_ufs_clk, "hosc-ufs", "hosc",
> + DCXO_GATING_REG, BIT(0), 0);
> +
> static struct ccu_common *sun6i_rtc_ccu_clks[] = {
> &iosc_clk,
> &iosc_32k_clk,
> @@ -275,6 +313,11 @@ static struct ccu_common *sun6i_rtc_ccu_clks[] = {
> &osc24M_32k_clk.common,
> &rtc_32k_clk.common,
> &osc32k_fanout_clk.common,
> + &osc24M_32k_div_a733_clk.common,
> + &hosc_serdes1_clk.common,
> + &hosc_serdes0_clk.common,
> + &hosc_hdmi_clk.common,
> + &hosc_ufs_clk.common,
> };
>
> static struct clk_hw_onecell_data sun6i_rtc_ccu_hw_clks = {
> @@ -288,6 +331,10 @@ static struct clk_hw_onecell_data sun6i_rtc_ccu_hw_clks = {
> [CLK_OSC24M_32K] = &osc24M_32k_clk.common.hw,
> [CLK_RTC_32K] = &rtc_32k_clk.common.hw,
> [CLK_OSC24M_32K_DIV] = &osc24M_32k_div_clk.hw,
> + [CLK_HOSC_UFS] = &hosc_ufs_clk.common.hw,
> + [CLK_HOSC_HDMI] = &hosc_hdmi_clk.common.hw,
> + [CLK_HOSC_SERDES0] = &hosc_serdes0_clk.common.hw,
> + [CLK_HOSC_SERDES1] = &hosc_serdes1_clk.common.hw,
> },
> };
>
> @@ -330,6 +377,15 @@ static const struct sun6i_rtc_match_data sun55i_a523_rtc_ccu_data = {
> .osc32k_fanout_nparents = ARRAY_SIZE(sun50i_r329_osc32k_fanout_parents),
> };
>
> +static const struct sun6i_rtc_match_data sun60i_a733_rtc_ccu_data = {
> + .have_ext_osc32k = true,
> + .have_iosc_calibration = true,
> + .have_dcxo_status = true,
> + .have_phy_ref_gates = true,
> + .osc32k_fanout_parents = sun50i_r329_osc32k_fanout_parents,
> + .osc32k_fanout_nparents = ARRAY_SIZE(sun50i_r329_osc32k_fanout_parents),
> +};
> +
> static const struct of_device_id sun6i_rtc_ccu_match[] = {
> {
> .compatible = "allwinner,sun50i-h616-rtc",
> @@ -343,6 +399,10 @@ static const struct of_device_id sun6i_rtc_ccu_match[] = {
> .compatible = "allwinner,sun55i-a523-rtc",
> .data = &sun55i_a523_rtc_ccu_data,
> },
> + {
> + .compatible = "allwinner,sun60i-a733-rtc",
> + .data = &sun60i_a733_rtc_ccu_data,
> + },
> {},
> };
> MODULE_DEVICE_TABLE(of, sun6i_rtc_ccu_match);
> @@ -375,6 +435,13 @@ int sun6i_rtc_ccu_probe(struct device *dev, void __iomem *reg)
> osc32k_fanout_init_data.parent_data = data->osc32k_fanout_parents;
> osc32k_fanout_init_data.num_parents = data->osc32k_fanout_nparents;
>
> + if (data->have_dcxo_status)
> + sun6i_rtc_ccu_hw_clks.hws[CLK_OSC24M_32K_DIV] =
> + &osc24M_32k_div_a733_clk.common.hw;
> +
> + if (!data->have_phy_ref_gates)
> + sun6i_rtc_ccu_hw_clks.num = CLK_OSC24M_32K_DIV + 1;
Maybe keep the old CLK_NUMBER macro and call the new one CLK_NUMBER_A733?
The point is to not directly use a random macro + 1 here.
Otherwise,
Reviewed-by: Chen-Yu Tsai <wens@kernel.org>
> +
> return devm_sunxi_ccu_probe(dev, reg, &sun6i_rtc_ccu_desc);
> }
>
> diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h
> index ab7b92b47f59..4f4f4cb00f1d 100644
> --- a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h
> +++ b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h
> @@ -11,6 +11,6 @@
> #define CLK_RTC_32K 6
> #define CLK_OSC24M_32K_DIV 7
>
> -#define CLK_NUMBER (CLK_OSC24M_32K_DIV + 1)
> +#define CLK_NUMBER (CLK_HOSC_SERDES1 + 1)
>
> #endif /* _CCU_SUN6I_RTC_H */
>
> --
> 2.47.3
>
On mar. 07 juil. 2026 at 00:47, Chen-Yu Tsai <wens@kernel.org> wrote:
>> +
>> +static struct ccu_div osc24M_32k_div_a733_clk = {
>> + .enable = BIT(1),
>> + .div = _SUNXI_CCU_DIV_TABLE(14, 2, osc24M_32k_div_a733_table),
>> + .common = {
>> + .reg = DCXO_CTRL_REG,
>> + .hw.init = CLK_HW_INIT_PARENTS_DATA("osc24M-32k-div",
>> + osc24M,
>> + &ccu_rodiv_ops,
>> + 0),
>> + },
>> +};
>> +
>> +static SUNXI_CCU_GATE(osc24M_32k_clk, "osc24M-32k", "osc24M-32k-div",
>
> I'm not a big fan of using global clock parent names, especially when we
> can have proper struct clk_hw pointer references. However in this case
> it seems unavoidable without making a huge mess.
>
Indeed there is no way around it to support different SoC path with
static data.
>> + LOSC_OUT_GATING_REG, BIT(16), 0);
>>
>> static const struct clk_hw *rtc_32k_parents[] = {
>> &osc32k_clk.common.hw,
>> @@ -267,6 +296,15 @@ static struct ccu_mux osc32k_fanout_clk = {
>> },
>> };
[...]
>> };
>> MODULE_DEVICE_TABLE(of, sun6i_rtc_ccu_match);
>> @@ -375,6 +435,13 @@ int sun6i_rtc_ccu_probe(struct device *dev, void __iomem *reg)
>> osc32k_fanout_init_data.parent_data = data->osc32k_fanout_parents;
>> osc32k_fanout_init_data.num_parents = data->osc32k_fanout_nparents;
>>
>> + if (data->have_dcxo_status)
>> + sun6i_rtc_ccu_hw_clks.hws[CLK_OSC24M_32K_DIV] =
>> + &osc24M_32k_div_a733_clk.common.hw;
>> +
>> + if (!data->have_phy_ref_gates)
>> + sun6i_rtc_ccu_hw_clks.num = CLK_OSC24M_32K_DIV + 1;
>
> Maybe keep the old CLK_NUMBER macro and call the new one CLK_NUMBER_A733?
> The point is to not directly use a random macro + 1 here.
Are you sure you about this ? You are going to end up with this
CLK_NUMBER_A733 in the table which is going to be odd (unless I put an
explanation next it) then this CLK_NUMBER without any suffix put next to
clock gate things
The choice I made initially was meant to keep thing as clear as possible
* CLK_NUMBER remains the number of clock in the table
* CLK_OSC24M_32K_DIV + 1 (while not very nice) clearly show which is the
last clock in that case. It is not random IMO.
If you still prefer the suggestion above, I'll submit v5 with it but it
look odd to me.
>
> Otherwise,
>
> Reviewed-by: Chen-Yu Tsai <wens@kernel.org>
>
>> +
>> return devm_sunxi_ccu_probe(dev, reg, &sun6i_rtc_ccu_desc);
>> }
>>
>> diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h
>> index ab7b92b47f59..4f4f4cb00f1d 100644
>> --- a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h
>> +++ b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h
>> @@ -11,6 +11,6 @@
>> #define CLK_RTC_32K 6
>> #define CLK_OSC24M_32K_DIV 7
>>
>> -#define CLK_NUMBER (CLK_OSC24M_32K_DIV + 1)
>> +#define CLK_NUMBER (CLK_HOSC_SERDES1 + 1)
>>
>> #endif /* _CCU_SUN6I_RTC_H */
>>
>> --
>> 2.47.3
>>
@@ -44,9 +44,13 @@
#define DCXO_CTRL_REG 0x160
#define DCXO_CTRL_CLK16M_RC_EN BIT(0)
+#define DCXO_GATING_REG 0x16c
+
struct sun6i_rtc_match_data {
bool have_ext_osc32k : 1;
bool have_iosc_calibration : 1;
+ bool have_dcxo_status : 1;
+ bool have_phy_ref_gates : 1;
bool rtc_32k_single_parent : 1;
const struct clk_parent_data *osc32k_fanout_parents;
u8 osc32k_fanout_nparents;
@@ -213,7 +217,12 @@ static struct ccu_mux osc32k_clk = {
},
};
-/* This falls back to the global name for fwnodes without a named reference. */
+/*
+ * This falls back to the global name for fwnodes without a named reference.
+ * NOTE: osc24M name might be misleading the oscillator could also be a 26MHz
+ * or a 19.2MHz one starting with the a733. The original name is kept anyway
+ * in case anything is relying on it.
+ */
static const struct clk_parent_data osc24M[] = {
{ .fw_name = "hosc", .name = "osc24M" }
};
@@ -227,8 +236,28 @@ static struct clk_fixed_factor osc24M_32k_div_clk = {
0),
};
-static SUNXI_CCU_GATE_HW(osc24M_32k_clk, "osc24M-32k", &osc24M_32k_div_clk.hw,
- LOSC_OUT_GATING_REG, BIT(16), 0);
+static struct clk_div_table osc24M_32k_div_a733_table[] = {
+ { .val = 0, .div = 732 },
+ { .val = 1, .div = 586 },
+ { .val = 2, .div = 793 },
+ { .val = 3, .div = 732 },
+ { /* Sentinel */ },
+};
+
+static struct ccu_div osc24M_32k_div_a733_clk = {
+ .enable = BIT(1),
+ .div = _SUNXI_CCU_DIV_TABLE(14, 2, osc24M_32k_div_a733_table),
+ .common = {
+ .reg = DCXO_CTRL_REG,
+ .hw.init = CLK_HW_INIT_PARENTS_DATA("osc24M-32k-div",
+ osc24M,
+ &ccu_rodiv_ops,
+ 0),
+ },
+};
+
+static SUNXI_CCU_GATE(osc24M_32k_clk, "osc24M-32k", "osc24M-32k-div",
+ LOSC_OUT_GATING_REG, BIT(16), 0);
static const struct clk_hw *rtc_32k_parents[] = {
&osc32k_clk.common.hw,
@@ -267,6 +296,15 @@ static struct ccu_mux osc32k_fanout_clk = {
},
};
+static SUNXI_CCU_GATE_FW(hosc_serdes1_clk, "hosc-serdes1", "hosc",
+ DCXO_GATING_REG, BIT(5), 0);
+static SUNXI_CCU_GATE_FW(hosc_serdes0_clk, "hosc-serdes0", "hosc",
+ DCXO_GATING_REG, BIT(4), 0);
+static SUNXI_CCU_GATE_FW(hosc_hdmi_clk, "hosc-hdmi", "hosc",
+ DCXO_GATING_REG, BIT(1), 0);
+static SUNXI_CCU_GATE_FW(hosc_ufs_clk, "hosc-ufs", "hosc",
+ DCXO_GATING_REG, BIT(0), 0);
+
static struct ccu_common *sun6i_rtc_ccu_clks[] = {
&iosc_clk,
&iosc_32k_clk,
@@ -275,6 +313,11 @@ static struct ccu_common *sun6i_rtc_ccu_clks[] = {
&osc24M_32k_clk.common,
&rtc_32k_clk.common,
&osc32k_fanout_clk.common,
+ &osc24M_32k_div_a733_clk.common,
+ &hosc_serdes1_clk.common,
+ &hosc_serdes0_clk.common,
+ &hosc_hdmi_clk.common,
+ &hosc_ufs_clk.common,
};
static struct clk_hw_onecell_data sun6i_rtc_ccu_hw_clks = {
@@ -288,6 +331,10 @@ static struct clk_hw_onecell_data sun6i_rtc_ccu_hw_clks = {
[CLK_OSC24M_32K] = &osc24M_32k_clk.common.hw,
[CLK_RTC_32K] = &rtc_32k_clk.common.hw,
[CLK_OSC24M_32K_DIV] = &osc24M_32k_div_clk.hw,
+ [CLK_HOSC_UFS] = &hosc_ufs_clk.common.hw,
+ [CLK_HOSC_HDMI] = &hosc_hdmi_clk.common.hw,
+ [CLK_HOSC_SERDES0] = &hosc_serdes0_clk.common.hw,
+ [CLK_HOSC_SERDES1] = &hosc_serdes1_clk.common.hw,
},
};
@@ -330,6 +377,15 @@ static const struct sun6i_rtc_match_data sun55i_a523_rtc_ccu_data = {
.osc32k_fanout_nparents = ARRAY_SIZE(sun50i_r329_osc32k_fanout_parents),
};
+static const struct sun6i_rtc_match_data sun60i_a733_rtc_ccu_data = {
+ .have_ext_osc32k = true,
+ .have_iosc_calibration = true,
+ .have_dcxo_status = true,
+ .have_phy_ref_gates = true,
+ .osc32k_fanout_parents = sun50i_r329_osc32k_fanout_parents,
+ .osc32k_fanout_nparents = ARRAY_SIZE(sun50i_r329_osc32k_fanout_parents),
+};
+
static const struct of_device_id sun6i_rtc_ccu_match[] = {
{
.compatible = "allwinner,sun50i-h616-rtc",
@@ -343,6 +399,10 @@ static const struct of_device_id sun6i_rtc_ccu_match[] = {
.compatible = "allwinner,sun55i-a523-rtc",
.data = &sun55i_a523_rtc_ccu_data,
},
+ {
+ .compatible = "allwinner,sun60i-a733-rtc",
+ .data = &sun60i_a733_rtc_ccu_data,
+ },
{},
};
MODULE_DEVICE_TABLE(of, sun6i_rtc_ccu_match);
@@ -375,6 +435,13 @@ int sun6i_rtc_ccu_probe(struct device *dev, void __iomem *reg)
osc32k_fanout_init_data.parent_data = data->osc32k_fanout_parents;
osc32k_fanout_init_data.num_parents = data->osc32k_fanout_nparents;
+ if (data->have_dcxo_status)
+ sun6i_rtc_ccu_hw_clks.hws[CLK_OSC24M_32K_DIV] =
+ &osc24M_32k_div_a733_clk.common.hw;
+
+ if (!data->have_phy_ref_gates)
+ sun6i_rtc_ccu_hw_clks.num = CLK_OSC24M_32K_DIV + 1;
+
return devm_sunxi_ccu_probe(dev, reg, &sun6i_rtc_ccu_desc);
}
@@ -11,6 +11,6 @@
#define CLK_RTC_32K 6
#define CLK_OSC24M_32K_DIV 7
-#define CLK_NUMBER (CLK_OSC24M_32K_DIV + 1)
+#define CLK_NUMBER (CLK_HOSC_SERDES1 + 1)
#endif /* _CCU_SUN6I_RTC_H */