From 7b9f8fe9c9e0e028cdc99343f4ff75bcfb9e57aa Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 6 Jul 2026 11:32:09 +0200 Subject: [PATCH 01/10] dt-bindings: rtc: sun6i: no clock-output-names on h616/r329 On h616 and r329 chips, clock output names are never defined through DT and are not meant to be. Just disallow the property for those chips. Reported-by: Sashiko Closes: http://lore.kernel.org/r/20260629125305.0DF981F000E9@smtp.kernel.org Acked-by: Alexandre Belloni Acked-by: Conor Dooley Reviewed-by: Chen-Yu Tsai Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260706-a733-rtc-v4-1-f330728db3d3@baylibre.com Signed-off-by: Chen-Yu Tsai --- .../bindings/rtc/allwinner,sun6i-a31-rtc.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml b/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml index 9df5cdb6f63f..959a012c626f 100644 --- a/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml +++ b/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml @@ -175,6 +175,18 @@ allOf: interrupts: minItems: 2 + - if: + properties: + compatible: + contains: + enum: + - allwinner,sun50i-h616-rtc + - allwinner,sun50i-r329-rtc + + then: + properties: + clock-output-names: false + required: - "#clock-cells" - compatible From 38d6b194a21c8626647b0a773bb8db5f2b84b0ad Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 6 Jul 2026 11:32:10 +0200 Subject: [PATCH 02/10] dt-bindings: rtc: sun6i: add sun60i-a733 support Add a new rtc compatible for the sun60i-a733 SoC and new IDs for the peripheral oscillator clock gates of this SoC. Acked-by: Alexandre Belloni Acked-by: Conor Dooley Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260706-a733-rtc-v4-2-f330728db3d3@baylibre.com Signed-off-by: Chen-Yu Tsai --- .../devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml | 1 + include/dt-bindings/clock/sun6i-rtc.h | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml b/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml index 959a012c626f..f2b91186ed37 100644 --- a/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml +++ b/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml @@ -33,6 +33,7 @@ properties: - enum: - allwinner,sun20i-d1-rtc - allwinner,sun55i-a523-rtc + - allwinner,sun60i-a733-rtc - const: allwinner,sun50i-r329-rtc reg: diff --git a/include/dt-bindings/clock/sun6i-rtc.h b/include/dt-bindings/clock/sun6i-rtc.h index 3bd3aa3d57ce..5132a393ca4b 100644 --- a/include/dt-bindings/clock/sun6i-rtc.h +++ b/include/dt-bindings/clock/sun6i-rtc.h @@ -6,5 +6,9 @@ #define CLK_OSC32K 0 #define CLK_OSC32K_FANOUT 1 #define CLK_IOSC 2 +#define CLK_HOSC_UFS 8 +#define CLK_HOSC_HDMI 9 +#define CLK_HOSC_SERDES0 10 +#define CLK_HOSC_SERDES1 11 #endif /* _DT_BINDINGS_CLK_SUN6I_RTC_H_ */ From 81f4ddc9f7e0c11d3e9d24d0f30023845a02f7ee Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 6 Jul 2026 11:32:11 +0200 Subject: [PATCH 03/10] clk: sunxi-ng: fix ccu probe clock unregister on error When registering clocks with sunxi_ccu_probe(), the number of ccu_clocks and the number of hw clocks might be different, eventhough they usually are the same. If they are different, it could lead to out-of-bound access or registered clock left behind on error. Use a different variable when iterating on hw clocks so every registered clock, and only those, gets unregistered on error. Reported-by: Sashiko Closes: https://lore.kernel.org/r/20260629131254.7E34C1F00A3A@smtp.kernel.org Reviewed-by: Chen-Yu Tsai Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260706-a733-rtc-v4-3-f330728db3d3@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu_common.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/clk/sunxi-ng/ccu_common.c b/drivers/clk/sunxi-ng/ccu_common.c index 1c083b4d0b7e..43d8eca6abee 100644 --- a/drivers/clk/sunxi-ng/ccu_common.c +++ b/drivers/clk/sunxi-ng/ccu_common.c @@ -114,7 +114,7 @@ static int sunxi_ccu_probe(struct sunxi_ccu *ccu, struct device *dev, const struct sunxi_ccu_desc *desc) { struct ccu_reset *reset; - int i, ret; + int i, j, ret; ccu->desc = desc; @@ -130,8 +130,8 @@ static int sunxi_ccu_probe(struct sunxi_ccu *ccu, struct device *dev, cclk->lock = &ccu->lock; } - for (i = 0; i < desc->hw_clks->num ; i++) { - struct clk_hw *hw = desc->hw_clks->hws[i]; + for (j = 0; j < desc->hw_clks->num ; j++) { + struct clk_hw *hw = desc->hw_clks->hws[j]; const char *name; if (!hw) @@ -143,7 +143,7 @@ static int sunxi_ccu_probe(struct sunxi_ccu *ccu, struct device *dev, else ret = of_clk_hw_register(node, hw); if (ret) { - pr_err("Couldn't register clock %d - %s\n", i, name); + pr_err("Couldn't register clock %d - %s\n", j, name); goto err_clk_unreg; } } @@ -186,8 +186,8 @@ static int sunxi_ccu_probe(struct sunxi_ccu *ccu, struct device *dev, err_del_provider: of_clk_del_provider(node); err_clk_unreg: - while (--i >= 0) { - struct clk_hw *hw = desc->hw_clks->hws[i]; + while (--j >= 0) { + struct clk_hw *hw = desc->hw_clks->hws[j]; if (!hw) continue; From 192c6220e14d5b7924f116680bdf073179cde025 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 6 Jul 2026 11:32:12 +0200 Subject: [PATCH 04/10] clk: sunxi-ng: sun6i-rtc: clean up DT usage With sun6i-rtc compatible devices, the "ext-osc32k" clock input is optional for the devices that support this input (r329 and onward). Probably preparing for older SoC support, the driver does something funny when parsing DT. It check if "ext-osc32k" is present in the clock-names and if it is not, it uses the first clock as "ext-osc32k". This clock will actually be the rtc bus clock so what the driver does is wrong. At the moment, the driver does not support the older SoCs that would have an external 32k clock provided on index #0 so just remove this quirk. Reviewed-by: Chen-Yu Tsai Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260706-a733-rtc-v4-4-f330728db3d3@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu-sun6i-rtc.c | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c index f6bfeba009e8..0f528bfaed00 100644 --- a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c +++ b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c @@ -191,10 +191,8 @@ static struct ccu_common iosc_32k_clk = { CLK_GET_RATE_NOCACHE), }; -static const struct clk_hw *ext_osc32k[] = { NULL }; /* updated during probe */ - -static SUNXI_CCU_GATE_HWS(ext_osc32k_gate_clk, "ext-osc32k-gate", - ext_osc32k, 0x0, BIT(4), 0); +static SUNXI_CCU_GATE_FW(ext_osc32k_gate_clk, "ext-osc32k-gate", + "ext-osc32k", 0x0, BIT(4), 0); static const struct clk_hw *osc32k_parents[] = { &iosc_32k_clk.hw, @@ -352,7 +350,6 @@ MODULE_DEVICE_TABLE(of, sun6i_rtc_ccu_match); int sun6i_rtc_ccu_probe(struct device *dev, void __iomem *reg) { const struct sun6i_rtc_match_data *data; - struct clk *ext_osc32k_clk = NULL; const struct of_device_id *match; /* This driver is only used for newer variants of the hardware. */ @@ -363,21 +360,7 @@ int sun6i_rtc_ccu_probe(struct device *dev, void __iomem *reg) data = match->data; have_iosc_calibration = data->have_iosc_calibration; - if (data->have_ext_osc32k) { - const char *fw_name; - - /* ext-osc32k was the only input clock in the old binding. */ - fw_name = of_property_present(dev->of_node, "clock-names") - ? "ext-osc32k" : NULL; - ext_osc32k_clk = devm_clk_get_optional(dev, fw_name); - if (IS_ERR(ext_osc32k_clk)) - return PTR_ERR(ext_osc32k_clk); - } - - if (ext_osc32k_clk) { - /* Link ext-osc32k-gate to its parent. */ - *ext_osc32k = __clk_get_hw(ext_osc32k_clk); - } else { + if (!data->have_ext_osc32k) { /* ext-osc32k-gate is an orphan, so do not register it. */ sun6i_rtc_ccu_hw_clks.hws[CLK_EXT_OSC32K_GATE] = NULL; osc32k_init_data.num_parents = 1; From d2fcd82ca07dad71fc6102db7cb7b3225c489774 Mon Sep 17 00:00:00 2001 From: Junhui Liu Date: Mon, 6 Jul 2026 11:32:13 +0200 Subject: [PATCH 05/10] clk: sunxi-ng: sun6i-rtc: Add feature bit for IOSC calibration The sun6i-rtc CCU driver currently uses a global static variable to denote whether calibration is supported, which makes IOSC operations tightly coupled to this file. Convert this into a feature bit to decouple the logic. This allows the IOSC clock code to be moved into a shared module for reuse by other SoCs. Signed-off-by: Junhui Liu Reviewed-by: Chen-Yu Tsai Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260706-a733-rtc-v4-5-f330728db3d3@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu-sun6i-rtc.c | 17 +++++++++-------- drivers/clk/sunxi-ng/ccu_common.h | 1 + 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c index 0f528bfaed00..b24c8b196e66 100644 --- a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c +++ b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c @@ -52,8 +52,6 @@ struct sun6i_rtc_match_data { u8 osc32k_fanout_nparents; }; -static bool have_iosc_calibration; - static int ccu_iosc_enable(struct clk_hw *hw) { struct ccu_common *cm = hw_to_ccu_common(hw); @@ -80,7 +78,7 @@ static unsigned long ccu_iosc_recalc_rate(struct clk_hw *hw, { struct ccu_common *cm = hw_to_ccu_common(hw); - if (have_iosc_calibration) { + if (cm->features & CCU_FEATURE_IOSC_CALIBRATION) { u32 reg = readl(cm->base + IOSC_CLK_CALI_REG); /* @@ -119,7 +117,7 @@ static int ccu_iosc_32k_prepare(struct clk_hw *hw) struct ccu_common *cm = hw_to_ccu_common(hw); u32 val; - if (!have_iosc_calibration) + if (!(cm->features & CCU_FEATURE_IOSC_CALIBRATION)) return 0; val = readl(cm->base + IOSC_CLK_CALI_REG); @@ -134,7 +132,7 @@ static void ccu_iosc_32k_unprepare(struct clk_hw *hw) struct ccu_common *cm = hw_to_ccu_common(hw); u32 val; - if (!have_iosc_calibration) + if (!(cm->features & CCU_FEATURE_IOSC_CALIBRATION)) return; val = readl(cm->base + IOSC_CLK_CALI_REG); @@ -148,7 +146,7 @@ static unsigned long ccu_iosc_32k_recalc_rate(struct clk_hw *hw, struct ccu_common *cm = hw_to_ccu_common(hw); u32 val; - if (have_iosc_calibration) { + if (cm->features & CCU_FEATURE_IOSC_CALIBRATION) { val = readl(cm->base + IOSC_CLK_CALI_REG); /* Assume the calibrated 32k clock is accurate. */ @@ -167,7 +165,7 @@ static unsigned long ccu_iosc_32k_recalc_accuracy(struct clk_hw *hw, struct ccu_common *cm = hw_to_ccu_common(hw); u32 val; - if (have_iosc_calibration) { + if (cm->features & CCU_FEATURE_IOSC_CALIBRATION) { val = readl(cm->base + IOSC_CLK_CALI_REG); /* Assume the calibrated 32k clock is accurate. */ @@ -358,7 +356,10 @@ int sun6i_rtc_ccu_probe(struct device *dev, void __iomem *reg) return 0; data = match->data; - have_iosc_calibration = data->have_iosc_calibration; + if (data->have_iosc_calibration) { + iosc_clk.features |= CCU_FEATURE_IOSC_CALIBRATION; + iosc_32k_clk.features |= CCU_FEATURE_IOSC_CALIBRATION; + } if (!data->have_ext_osc32k) { /* ext-osc32k-gate is an orphan, so do not register it. */ diff --git a/drivers/clk/sunxi-ng/ccu_common.h b/drivers/clk/sunxi-ng/ccu_common.h index bbec283b9d99..d9dc24ad5503 100644 --- a/drivers/clk/sunxi-ng/ccu_common.h +++ b/drivers/clk/sunxi-ng/ccu_common.h @@ -21,6 +21,7 @@ #define CCU_FEATURE_CLOSEST_RATE BIT(9) #define CCU_FEATURE_DUAL_DIV BIT(10) #define CCU_FEATURE_UPDATE_BIT BIT(11) +#define CCU_FEATURE_IOSC_CALIBRATION BIT(12) /* MMC timing mode switch bit */ #define CCU_MMC_NEW_TIMING_MODE BIT(30) From c52b5090bf1e3b88745f8a81f264f34100ecde42 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 6 Jul 2026 11:32:15 +0200 Subject: [PATCH 06/10] clk: sunxi-ng: mux: remove unneeded export The export ccu_mux_helper_apply_prediv() symbol is actually not necessary. No module other than sunxi-ccu would use it at the moment so just remove the export. Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260706-a733-rtc-v4-7-f330728db3d3@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu_mux.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c index 766f27cff748..09230728c400 100644 --- a/drivers/clk/sunxi-ng/ccu_mux.c +++ b/drivers/clk/sunxi-ng/ccu_mux.c @@ -66,7 +66,6 @@ unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common, { return parent_rate / ccu_mux_get_prediv(common, cm, parent_index); } -EXPORT_SYMBOL_NS_GPL(ccu_mux_helper_apply_prediv, "SUNXI_CCU"); static unsigned long ccu_mux_helper_unapply_prediv(struct ccu_common *common, struct ccu_mux_internal *cm, From 4bcba49984ff8c77729f003dd32082b10c02c23b Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Thu, 23 Jul 2026 11:39:51 +0200 Subject: [PATCH 07/10] clk: sunxi-ng: mux: fix determine helper rate propagation Applying the pre divider on the parent rate is wrong because, while handling rate propagation through determine_rate(), the framework will likely round the parent rate again while cycling through the possibilities, throwing away the prediv applied. This means, the parent rate will then be wrong when the prediv is unapplied from a parent rate on which it was never applied to begin with. The right way to do it is to unapply the prediv from the requested rate, which is the wanted rate at the input on the clock element, and pass this to framework to do its thing. Change the determine rate mux helper in this way. Fixes: 1c8d7af61b37 ("clk: sunxi-ng: convert from divider_round_rate_parent() to divider_determine_rate()") Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260723-a733-rtc-v7-1-8fd68aab94ae@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu_mux.c | 57 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c index 09230728c400..4503c9780c39 100644 --- a/drivers/clk/sunxi-ng/ccu_mux.c +++ b/drivers/clk/sunxi-ng/ccu_mux.c @@ -92,66 +92,65 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common, struct clk_rate_request adj_req = *req; best_parent = clk_hw_get_parent(hw); - best_parent_rate = clk_hw_get_rate(best_parent); - + adj_req.best_parent_rate = clk_hw_get_rate(best_parent); adj_req.best_parent_hw = best_parent; - adj_req.best_parent_rate = ccu_mux_helper_apply_prediv(common, cm, -1, - best_parent_rate); + + /* + * This effectively treats the predivider as a postdivider. + * It stays mathematically correct and ensures whatever + * round() will do stays correct while walking the tree. + * It may query the parent rate too while handling rate + * propagation. + */ + adj_req.rate = ccu_mux_helper_unapply_prediv(common, cm, -1, + req->rate); ret = round(cm, &adj_req, data); if (ret) return ret; - best_rate = adj_req.rate; - /* - * best_parent_rate might have been modified by our clock. - * Unapply the pre-divider if there's one, and give - * the actual frequency the parent needs to run at. + * parent_rate might have been modified by our clock as part + * of the rate propagation mechanism. Same goes below. */ - best_parent_rate = ccu_mux_helper_unapply_prediv(common, cm, -1, - adj_req.best_parent_rate); + best_parent_rate = adj_req.best_parent_rate; + best_rate = ccu_mux_helper_apply_prediv(common, cm, -1, + adj_req.rate); goto out; } for (i = 0; i < clk_hw_get_num_parents(hw); i++) { struct clk_rate_request tmp_req = *req; - unsigned long parent_rate; + unsigned long rate; struct clk_hw *parent; parent = clk_hw_get_parent_by_index(hw, i); if (!parent) continue; - parent_rate = ccu_mux_helper_apply_prediv(common, cm, i, - clk_hw_get_rate(parent)); - tmp_req.best_parent_hw = parent; - tmp_req.best_parent_rate = parent_rate; + tmp_req.best_parent_rate = clk_hw_get_rate(parent); + tmp_req.rate = ccu_mux_helper_unapply_prediv(common, cm, i, + req->rate); ret = round(cm, &tmp_req, data); if (ret) continue; - /* - * parent_rate might have been modified by our clock. - * Unapply the pre-divider if there's one, and give - * the actual frequency the parent needs to run at. - */ - parent_rate = ccu_mux_helper_unapply_prediv(common, cm, i, - tmp_req.best_parent_rate); + rate = ccu_mux_helper_apply_prediv(common, cm, i, + tmp_req.rate); - if (tmp_req.rate == req->rate) { + if (rate == req->rate) { best_parent = parent; - best_parent_rate = parent_rate; - best_rate = tmp_req.rate; + best_parent_rate = tmp_req.best_parent_rate; + best_rate = rate; goto out; } - if (ccu_is_better_rate(common, req->rate, tmp_req.rate, best_rate)) { - best_rate = tmp_req.rate; - best_parent_rate = parent_rate; + if (ccu_is_better_rate(common, req->rate, rate, best_rate)) { + best_rate = rate; + best_parent_rate = tmp_req.best_parent_rate; best_parent = parent; } } From 7bbbfcd4e4956a126b9599bd8aa316cf0c63c8ed Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Thu, 23 Jul 2026 11:39:52 +0200 Subject: [PATCH 08/10] clk: sunxi-ng: div: add read-only operation support Add support for sunxi-ng read-only dividers. This will be useful to the a733 oscillator detection logic. Reviewed-by: Chen-Yu Tsai Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260723-a733-rtc-v7-2-8fd68aab94ae@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu_div.c | 31 +++++++++++++++++++++++++++++-- drivers/clk/sunxi-ng/ccu_div.h | 1 + 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/drivers/clk/sunxi-ng/ccu_div.c b/drivers/clk/sunxi-ng/ccu_div.c index 62d680ccb524..c385e0160f13 100644 --- a/drivers/clk/sunxi-ng/ccu_div.c +++ b/drivers/clk/sunxi-ng/ccu_div.c @@ -20,8 +20,22 @@ static int ccu_div_determine_rate_helper(struct ccu_mux_internal *mux, if (cd->common.features & CCU_FEATURE_FIXED_POSTDIV) req->rate *= cd->fixed_post_div; - ret = divider_determine_rate(&cd->common.hw, req, cd->div.table, - cd->div.width, cd->div.flags); + if (cd->div.flags & CLK_DIVIDER_READ_ONLY) { + unsigned long val; + u32 reg; + + reg = readl(cd->common.base + cd->common.reg); + val = reg >> cd->div.shift; + val &= (1 << cd->div.width) - 1; + + ret = divider_ro_determine_rate(&cd->common.hw, req, cd->div.table, + cd->div.width, cd->div.flags, val); + + } else { + ret = divider_determine_rate(&cd->common.hw, req, cd->div.table, + cd->div.width, cd->div.flags); + } + if (ret) return ret; @@ -143,3 +157,16 @@ const struct clk_ops ccu_div_ops = { .set_rate = ccu_div_set_rate, }; EXPORT_SYMBOL_NS_GPL(ccu_div_ops, "SUNXI_CCU"); + +const struct clk_ops ccu_rodiv_ops = { + .disable = ccu_div_disable, + .enable = ccu_div_enable, + .is_enabled = ccu_div_is_enabled, + + .get_parent = ccu_div_get_parent, + .set_parent = ccu_div_set_parent, + + .determine_rate = ccu_div_determine_rate, + .recalc_rate = ccu_div_recalc_rate, +}; +EXPORT_SYMBOL_NS_GPL(ccu_rodiv_ops, "SUNXI_CCU"); diff --git a/drivers/clk/sunxi-ng/ccu_div.h b/drivers/clk/sunxi-ng/ccu_div.h index be00b3277e97..a30a92780a05 100644 --- a/drivers/clk/sunxi-ng/ccu_div.h +++ b/drivers/clk/sunxi-ng/ccu_div.h @@ -300,5 +300,6 @@ static inline struct ccu_div *hw_to_ccu_div(struct clk_hw *hw) } extern const struct clk_ops ccu_div_ops; +extern const struct clk_ops ccu_rodiv_ops; #endif /* _CCU_DIV_H_ */ From 176184e7f799fc18fe9cae63e21d7b16b41a0d59 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Thu, 23 Jul 2026 11:39:53 +0200 Subject: [PATCH 09/10] clk: sunxi-ng: sun6i-rtc: split main oscillator div and gate On the a733 the "osc24M-32k" clock has the same gate bits as the previously supported SoC but a different divider implementation. Instead of a fixed 750 divider, the divider is selected based on the rate of the oscillator. It can be seen as a simple read-only divider. On the A733 the gate and the divider are on different register which is not supported by sunxi combined clocks. These element will be split on the A733. Split them for the older SoC as well to keep on sharing the gate definition. Reviewed-by: Chen-Yu Tsai Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260723-a733-rtc-v7-3-8fd68aab94ae@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu-sun6i-rtc.c | 20 +++++++++++--------- drivers/clk/sunxi-ng/ccu-sun6i-rtc.h | 3 ++- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c index b24c8b196e66..25dd87e78eb7 100644 --- a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c +++ b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c @@ -218,17 +218,18 @@ static const struct clk_parent_data osc24M[] = { { .fw_name = "hosc", .name = "osc24M" } }; -static struct ccu_gate osc24M_32k_clk = { - .enable = BIT(16), - .common = { - .reg = LOSC_OUT_GATING_REG, - .prediv = 750, - .features = CCU_FEATURE_ALL_PREDIV, - .hw.init = CLK_HW_INIT_PARENTS_DATA("osc24M-32k", osc24M, - &ccu_gate_ops, 0), - }, +static struct clk_fixed_factor osc24M_32k_div_clk = { + .mult = 1, + .div = 750, + .hw.init = CLK_HW_INIT_PARENTS_DATA("osc24M-32k-div", + osc24M, + &clk_fixed_factor_ops, + 0), }; +static SUNXI_CCU_GATE_HW(osc24M_32k_clk, "osc24M-32k", &osc24M_32k_div_clk.hw, + LOSC_OUT_GATING_REG, BIT(16), 0); + static const struct clk_hw *rtc_32k_parents[] = { &osc32k_clk.common.hw, &osc24M_32k_clk.common.hw @@ -286,6 +287,7 @@ static struct clk_hw_onecell_data sun6i_rtc_ccu_hw_clks = { [CLK_EXT_OSC32K_GATE] = &ext_osc32k_gate_clk.common.hw, [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, }, }; diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h index 9ae821fc2599..ab7b92b47f59 100644 --- a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h +++ b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h @@ -9,7 +9,8 @@ #define CLK_EXT_OSC32K_GATE 4 #define CLK_OSC24M_32K 5 #define CLK_RTC_32K 6 +#define CLK_OSC24M_32K_DIV 7 -#define CLK_NUMBER (CLK_RTC_32K + 1) +#define CLK_NUMBER (CLK_OSC24M_32K_DIV + 1) #endif /* _CCU_SUN6I_RTC_H */ From 0a136efc0fc2d53b1b1389b3c3fb0f76edca38bf Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Thu, 23 Jul 2026 11:39:54 +0200 Subject: [PATCH 10/10] clk: sunxi-ng: sun6i-rtc: add a733 support 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. Reviewed-by: Chen-Yu Tsai Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260723-a733-rtc-v7-4-8fd68aab94ae@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu-sun6i-rtc.c | 77 ++++++++++++++++++++++++++-- drivers/clk/sunxi-ng/ccu-sun6i-rtc.h | 2 +- 2 files changed, 75 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..98ae10307f43 100644 --- a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c +++ b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c @@ -44,9 +44,15 @@ #define DCXO_CTRL_REG 0x160 #define DCXO_CTRL_CLK16M_RC_EN BIT(0) +#define DCXO_GATING_REG 0x16c + +#define CLK_NUMBER_NO_GATES (CLK_OSC24M_32K_DIV + 1) + 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 +219,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 +238,30 @@ 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_FLAGS(14, 2, + osc24M_32k_div_a733_table, + CLK_DIVIDER_READ_ONLY), + .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 +300,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 +317,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 +335,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 +381,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 +403,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 +439,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_NUMBER_NO_GATES; + 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 */