In order to implement clock switching for the CLKID_CPU_CLK and
CLKID_CPUB_CLK, notifiers are added on specific points of the
clock tree :

cpu_clk / cpub_clk
|   \- cpu_clk_dyn
|      |  \- cpu_clk_premux0
|      |        |- cpu_clk_postmux0
|      |        |    |- cpu_clk_dyn0_div
|      |        |    \- xtal/fclk_div2/fclk_div3
|      |        \- xtal/fclk_div2/fclk_div3
|      \- cpu_clk_premux1
|            |- cpu_clk_postmux1
|            |    |- cpu_clk_dyn1_div
|            |    \- xtal/fclk_div2/fclk_div3
|            \- xtal/fclk_div2/fclk_div3
\ sys_pll / sys1_pll

This for each cluster, a single one for G12A, two for G12B.

Each cpu_clk_premux1 tree is marked as read-only and CLK_SET_RATE_NO_REPARENT,
to be used as "parking" clock in a safe clock frequency.

A notifier is added on each cpu_clk_premux0 to detech when CCF want to
change the frequency of the cpu_clk_dyn tree.
In this notifier, the cpu_clk_premux1 tree is configured to use the xtal
clock and then the cpu_clk_dyn is switch to cpu_clk_premux1 while CCF
updates the cpu_clk_premux0 tree.

A notifier is added on each sys_pll/sys1_pll to detect when CCF wants to
change the PLL clock source of the cpu_clk.
In this notifier, the cpu_clk is switched to cpu_clk_dyn while CCF
updates the sys_pll/sys1_pll frequency.

A third small notifier is added on each cpu_clk / cpub_clk and cpu_clk_dyn,
add a small delay at PRE_RATE_CHANGE/POST_RATE_CHANGE to let the other
notofiers change propagate before changing the cpu_clk_premux0 and sys_pll
clock trees.

This notifier set permits switching the cpu_clk / cpub_clk without any
glitches and using a safe parking clock while switching between sub-GHz
clocks using the cpu_clk_dyn tree.

This setup has been tested and validated on the Amlogic G12A and G12B
SoCs running the arm64 cpuburn at [1] and cycling between all the possible
cpufreq translations of each cluster and checking the final frequency using
the clock-measurer, script at [2].

[1] https://github.com/ssvb/cpuburn-arm/blob/master/cpuburn-a53.S
[2] https://gist.github.com/superna9999/d4de964dbc0f84b7d527e1df2ddea25f

Signed-off-by: Neil Armstrong <narmstr...@baylibre.com>
---
 drivers/clk/meson/g12a.c | 535 +++++++++++++++++++++++++++++++++++----
 1 file changed, 481 insertions(+), 54 deletions(-)

diff --git a/drivers/clk/meson/g12a.c b/drivers/clk/meson/g12a.c
index e4957fd9f91f..e6011d18a719 100644
--- a/drivers/clk/meson/g12a.c
+++ b/drivers/clk/meson/g12a.c
@@ -14,10 +14,12 @@
 #include <linux/init.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
+#include <linux/clk.h>
 
 #include "clk-mpll.h"
 #include "clk-pll.h"
 #include "clk-regmap.h"
+#include "clk-cpu-dyndiv.h"
 #include "vid-pll-div.h"
 #include "meson-eeclk.h"
 #include "g12a.h"
@@ -88,16 +90,9 @@ static struct clk_regmap g12a_fixed_pll = {
        },
 };
 
-/*
- * Internal sys pll emulation configuration parameters
- */
-static const struct reg_sequence g12a_sys_init_regs[] = {
-       { .reg = HHI_SYS_PLL_CNTL1,     .def = 0x00000000 },
-       { .reg = HHI_SYS_PLL_CNTL2,     .def = 0x00000000 },
-       { .reg = HHI_SYS_PLL_CNTL3,     .def = 0x48681c00 },
-       { .reg = HHI_SYS_PLL_CNTL4,     .def = 0x88770290 },
-       { .reg = HHI_SYS_PLL_CNTL5,     .def = 0x39272000 },
-       { .reg = HHI_SYS_PLL_CNTL6,     .def = 0x56540000 },
+static const struct pll_mult_range g12a_sys_pll_mult_range = {
+       .min = 128,
+       .max = 250,
 };
 
 static struct clk_regmap g12a_sys_pll_dco = {
@@ -127,16 +122,17 @@ static struct clk_regmap g12a_sys_pll_dco = {
                        .shift   = 29,
                        .width   = 1,
                },
-               .init_regs = g12a_sys_init_regs,
-               .init_count = ARRAY_SIZE(g12a_sys_init_regs),
+               .range = &g12a_sys_pll_mult_range,
        },
        .hw.init = &(struct clk_init_data){
                .name = "sys_pll_dco",
-               .ops = &meson_clk_pll_ro_ops,
+               .ops = &meson_clk_pll_ops,
                .parent_data = &(const struct clk_parent_data) {
                        .fw_name = "xtal",
                },
                .num_parents = 1,
+               /* This clock feeds the CPU, avoid disabling it */
+               .flags = CLK_IS_CRITICAL,
        },
 };
 
@@ -149,11 +145,12 @@ static struct clk_regmap g12a_sys_pll = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "sys_pll",
-               .ops = &clk_regmap_divider_ro_ops,
+               .ops = &clk_regmap_divider_ops,
                .parent_hws = (const struct clk_hw *[]) {
                        &g12a_sys_pll_dco.hw
                },
                .num_parents = 1,
+               .flags = CLK_SET_RATE_PARENT,
        },
 };
 
@@ -184,14 +181,17 @@ static struct clk_regmap g12b_sys1_pll_dco = {
                        .shift   = 29,
                        .width   = 1,
                },
+               .range = &g12a_sys_pll_mult_range,
        },
        .hw.init = &(struct clk_init_data){
                .name = "sys1_pll_dco",
-               .ops = &meson_clk_pll_ro_ops,
+               .ops = &meson_clk_pll_ops,
                .parent_data = &(const struct clk_parent_data) {
                        .fw_name = "xtal",
                },
                .num_parents = 1,
+               /* This clock feeds the CPU, avoid disabling it */
+               .flags = CLK_IS_CRITICAL,
        },
 };
 
@@ -204,11 +204,12 @@ static struct clk_regmap g12b_sys1_pll = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "sys1_pll",
-               .ops = &clk_regmap_divider_ro_ops,
+               .ops = &clk_regmap_divider_ops,
                .parent_hws = (const struct clk_hw *[]) {
                        &g12b_sys1_pll_dco.hw
                },
                .num_parents = 1,
+               .flags = CLK_SET_RATE_PARENT,
        },
 };
 
@@ -345,13 +346,15 @@ static struct clk_regmap g12a_cpu_clk_premux0 = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpu_clk_dyn0_sel",
-               .ops = &clk_regmap_mux_ro_ops,
+               .ops = &clk_regmap_mux_ops,
                .parent_data = (const struct clk_parent_data []) {
                        { .fw_name = "xtal", },
                        { .hw = &g12a_fclk_div2.hw },
                        { .hw = &g12a_fclk_div3.hw },
                },
                .num_parents = 3,
+               /* This sub-tree is used a parking clock */
+               .flags = CLK_SET_RATE_NO_REPARENT,
        },
 };
 
@@ -364,30 +367,40 @@ static struct clk_regmap g12a_cpu_clk_premux1 = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpu_clk_dyn1_sel",
-               .ops = &clk_regmap_mux_ro_ops,
+               .ops = &clk_regmap_mux_ops,
                .parent_data = (const struct clk_parent_data []) {
                        { .fw_name = "xtal", },
                        { .hw = &g12a_fclk_div2.hw },
                        { .hw = &g12a_fclk_div3.hw },
                },
                .num_parents = 3,
+               /* This sub-tree is used a parking clock */
+               .flags = CLK_SET_RATE_NO_REPARENT
        },
 };
 
 /* Datasheet names this field as "mux0_divn_tcnt" */
 static struct clk_regmap g12a_cpu_clk_mux0_div = {
-       .data = &(struct clk_regmap_div_data){
-               .offset = HHI_SYS_CPU_CLK_CNTL0,
-               .shift = 4,
-               .width = 6,
+       .data = &(struct meson_clk_cpu_dyndiv_data){
+               .div = {
+                       .reg_off = HHI_SYS_CPU_CLK_CNTL0,
+                       .shift = 4,
+                       .width = 6,
+               },
+               .dyn = {
+                       .reg_off = HHI_SYS_CPU_CLK_CNTL0,
+                       .shift = 26,
+                       .width = 1,
+               },
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpu_clk_dyn0_div",
-               .ops = &clk_regmap_divider_ro_ops,
+               .ops = &meson_clk_cpu_dyndiv_ops,
                .parent_hws = (const struct clk_hw *[]) {
                        &g12a_cpu_clk_premux0.hw
                },
                .num_parents = 1,
+               .flags = CLK_SET_RATE_PARENT,
        },
 };
 
@@ -400,12 +413,13 @@ static struct clk_regmap g12a_cpu_clk_postmux0 = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpu_clk_dyn0",
-               .ops = &clk_regmap_mux_ro_ops,
+               .ops = &clk_regmap_mux_ops,
                .parent_hws = (const struct clk_hw *[]) {
                        &g12a_cpu_clk_premux0.hw,
                        &g12a_cpu_clk_mux0_div.hw,
                },
                .num_parents = 2,
+               .flags = CLK_SET_RATE_PARENT,
        },
 };
 
@@ -435,12 +449,14 @@ static struct clk_regmap g12a_cpu_clk_postmux1 = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpu_clk_dyn1",
-               .ops = &clk_regmap_mux_ro_ops,
+               .ops = &clk_regmap_mux_ops,
                .parent_hws = (const struct clk_hw *[]) {
                        &g12a_cpu_clk_premux1.hw,
                        &g12a_cpu_clk_mux1_div.hw,
                },
                .num_parents = 2,
+               /* This sub-tree is used a parking clock */
+               .flags = CLK_SET_RATE_NO_REPARENT,
        },
 };
 
@@ -453,12 +469,13 @@ static struct clk_regmap g12a_cpu_clk_dyn = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpu_clk_dyn",
-               .ops = &clk_regmap_mux_ro_ops,
+               .ops = &clk_regmap_mux_ops,
                .parent_hws = (const struct clk_hw *[]) {
                        &g12a_cpu_clk_postmux0.hw,
                        &g12a_cpu_clk_postmux1.hw,
                },
                .num_parents = 2,
+               .flags = CLK_SET_RATE_PARENT,
        },
 };
 
@@ -471,12 +488,13 @@ static struct clk_regmap g12a_cpu_clk = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpu_clk",
-               .ops = &clk_regmap_mux_ro_ops,
+               .ops = &clk_regmap_mux_ops,
                .parent_hws = (const struct clk_hw *[]) {
                        &g12a_cpu_clk_dyn.hw,
                        &g12a_sys_pll.hw,
                },
                .num_parents = 2,
+               .flags = CLK_SET_RATE_PARENT,
        },
 };
 
@@ -489,12 +507,13 @@ static struct clk_regmap g12b_cpu_clk = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpu_clk",
-               .ops = &clk_regmap_mux_ro_ops,
+               .ops = &clk_regmap_mux_ops,
                .parent_hws = (const struct clk_hw *[]) {
                        &g12a_cpu_clk_dyn.hw,
                        &g12b_sys1_pll.hw
                },
                .num_parents = 2,
+               .flags = CLK_SET_RATE_PARENT,
        },
 };
 
@@ -507,7 +526,7 @@ static struct clk_regmap g12b_cpub_clk_premux0 = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpub_clk_dyn0_sel",
-               .ops = &clk_regmap_mux_ro_ops,
+               .ops = &clk_regmap_mux_ops,
                .parent_data = (const struct clk_parent_data []) {
                        { .fw_name = "xtal", },
                        { .hw = &g12a_fclk_div2.hw },
@@ -519,18 +538,26 @@ static struct clk_regmap g12b_cpub_clk_premux0 = {
 
 /* Datasheet names this field as "mux0_divn_tcnt" */
 static struct clk_regmap g12b_cpub_clk_mux0_div = {
-       .data = &(struct clk_regmap_div_data){
-               .offset = HHI_SYS_CPUB_CLK_CNTL,
-               .shift = 4,
-               .width = 6,
+       .data = &(struct meson_clk_cpu_dyndiv_data){
+               .div = {
+                       .reg_off = HHI_SYS_CPUB_CLK_CNTL,
+                       .shift = 4,
+                       .width = 6,
+               },
+               .dyn = {
+                       .reg_off = HHI_SYS_CPUB_CLK_CNTL,
+                       .shift = 26,
+                       .width = 1,
+               },
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpub_clk_dyn0_div",
-               .ops = &clk_regmap_divider_ro_ops,
+               .ops = &meson_clk_cpu_dyndiv_ops,
                .parent_hws = (const struct clk_hw *[]) {
                        &g12b_cpub_clk_premux0.hw
                },
                .num_parents = 1,
+               .flags = CLK_SET_RATE_PARENT,
        },
 };
 
@@ -543,12 +570,13 @@ static struct clk_regmap g12b_cpub_clk_postmux0 = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpub_clk_dyn0",
-               .ops = &clk_regmap_mux_ro_ops,
+               .ops = &clk_regmap_mux_ops,
                .parent_hws = (const struct clk_hw *[]) {
                        &g12b_cpub_clk_premux0.hw,
                        &g12b_cpub_clk_mux0_div.hw
                },
                .num_parents = 2,
+               .flags = CLK_SET_RATE_PARENT,
        },
 };
 
@@ -561,13 +589,15 @@ static struct clk_regmap g12b_cpub_clk_premux1 = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpub_clk_dyn1_sel",
-               .ops = &clk_regmap_mux_ro_ops,
+               .ops = &clk_regmap_mux_ops,
                .parent_data = (const struct clk_parent_data []) {
                        { .fw_name = "xtal", },
                        { .hw = &g12a_fclk_div2.hw },
                        { .hw = &g12a_fclk_div3.hw },
                },
                .num_parents = 3,
+               /* This sub-tree is used a parking clock */
+               .flags = CLK_SET_RATE_NO_REPARENT,
        },
 };
 
@@ -597,12 +627,14 @@ static struct clk_regmap g12b_cpub_clk_postmux1 = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpub_clk_dyn1",
-               .ops = &clk_regmap_mux_ro_ops,
+               .ops = &clk_regmap_mux_ops,
                .parent_hws = (const struct clk_hw *[]) {
                        &g12b_cpub_clk_premux1.hw,
                        &g12b_cpub_clk_mux1_div.hw
                },
                .num_parents = 2,
+               /* This sub-tree is used a parking clock */
+               .flags = CLK_SET_RATE_NO_REPARENT,
        },
 };
 
@@ -615,12 +647,13 @@ static struct clk_regmap g12b_cpub_clk_dyn = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpub_clk_dyn",
-               .ops = &clk_regmap_mux_ro_ops,
+               .ops = &clk_regmap_mux_ops,
                .parent_hws = (const struct clk_hw *[]) {
                        &g12b_cpub_clk_postmux0.hw,
                        &g12b_cpub_clk_postmux1.hw
                },
                .num_parents = 2,
+               .flags = CLK_SET_RATE_PARENT,
        },
 };
 
@@ -633,15 +666,227 @@ static struct clk_regmap g12b_cpub_clk = {
        },
        .hw.init = &(struct clk_init_data){
                .name = "cpub_clk",
-               .ops = &clk_regmap_mux_ro_ops,
+               .ops = &clk_regmap_mux_ops,
                .parent_hws = (const struct clk_hw *[]) {
                        &g12b_cpub_clk_dyn.hw,
                        &g12a_sys_pll.hw
                },
                .num_parents = 2,
+               .flags = CLK_SET_RATE_PARENT,
        },
 };
 
+static int g12a_cpu_clk_mux_notifier_cb(struct notifier_block *nb,
+                                       unsigned long event, void *data)
+{
+       if (event == POST_RATE_CHANGE || event == PRE_RATE_CHANGE) {
+               /* Wait for clock propagation before/after changing the mux */
+               udelay(100);
+               return NOTIFY_OK;
+       }
+
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block g12a_cpu_clk_mux_nb = {
+       .notifier_call = g12a_cpu_clk_mux_notifier_cb,
+};
+
+struct g12a_cpu_clk_postmux_nb_data {
+       struct notifier_block nb;
+       struct clk_hw *xtal;
+       struct clk_hw *cpu_clk_dyn;
+       struct clk_hw *cpu_clk_postmux0;
+       struct clk_hw *cpu_clk_postmux1;
+       struct clk_hw *cpu_clk_premux1;
+};
+
+static int g12a_cpu_clk_postmux_notifier_cb(struct notifier_block *nb,
+                                           unsigned long event, void *data)
+{
+       struct g12a_cpu_clk_postmux_nb_data *nb_data =
+               container_of(nb, struct g12a_cpu_clk_postmux_nb_data, nb);
+
+       switch (event) {
+       case PRE_RATE_CHANGE:
+               /*
+                * This notifier means cpu_clk_postmux0 clock will be changed
+                * to feed cpu_clk, this is the current path :
+                * cpu_clk
+                *    \- cpu_clk_dyn
+                *          \- cpu_clk_postmux0
+                *                \- cpu_clk_muxX_div
+                *                      \- cpu_clk_premux0
+                *                              \- fclk_div3 or fclk_div2
+                *              OR
+                *                \- cpu_clk_premux0
+                *                      \- fclk_div3 or fclk_div2
+                */
+
+               /* Setup cpu_clk_premux1 to xtal */
+               clk_hw_set_parent(nb_data->cpu_clk_premux1,
+                                 nb_data->xtal);
+
+               /* Setup cpu_clk_postmux1 to bypass divider */
+               clk_hw_set_parent(nb_data->cpu_clk_postmux1,
+                                 nb_data->cpu_clk_premux1);
+
+               /* Switch to parking clk on cpu_clk_postmux1 */
+               clk_hw_set_parent(nb_data->cpu_clk_dyn,
+                                 nb_data->cpu_clk_postmux1);
+
+               /*
+                * Now, cpu_clk is 24MHz in the current path :
+                * cpu_clk
+                *    \- cpu_clk_dyn
+                *          \- cpu_clk_postmux1
+                *                \- cpu_clk_premux1
+                *                      \- xtal
+                */
+
+               udelay(100);
+
+               return NOTIFY_OK;
+
+       case POST_RATE_CHANGE:
+               /*
+                * The cpu_clk_postmux0 has ben updated, now switch back
+                * cpu_clk_dyn to cpu_clk_postmux0 and take the changes
+                * in account.
+                */
+
+               /* Configure cpu_clk_dyn back to cpu_clk_postmux0 */
+               clk_hw_set_parent(nb_data->cpu_clk_dyn,
+                                 nb_data->cpu_clk_postmux0);
+
+               /*
+                * new path :
+                * cpu_clk
+                *    \- cpu_clk_dyn
+                *          \- cpu_clk_postmux0
+                *                \- cpu_clk_muxX_div
+                *                      \- cpu_clk_premux0
+                *                              \- fclk_div3 or fclk_div2
+                *              OR
+                *                \- cpu_clk_premux0
+                *                      \- fclk_div3 or fclk_div2
+                */
+
+               udelay(100);
+
+               return NOTIFY_OK;
+
+       default:
+               return NOTIFY_DONE;
+       }
+}
+
+static struct g12a_cpu_clk_postmux_nb_data g12a_cpu_clk_postmux0_nb_data = {
+       .cpu_clk_dyn = &g12a_cpu_clk_dyn.hw,
+       .cpu_clk_postmux0 = &g12a_cpu_clk_postmux0.hw,
+       .cpu_clk_postmux1 = &g12a_cpu_clk_postmux1.hw,
+       .cpu_clk_premux1 = &g12a_cpu_clk_premux1.hw,
+       .nb.notifier_call = g12a_cpu_clk_postmux_notifier_cb,
+};
+
+static struct g12a_cpu_clk_postmux_nb_data g12b_cpub_clk_postmux0_nb_data = {
+       .cpu_clk_dyn = &g12b_cpub_clk_dyn.hw,
+       .cpu_clk_postmux0 = &g12b_cpub_clk_postmux0.hw,
+       .cpu_clk_postmux1 = &g12b_cpub_clk_postmux1.hw,
+       .cpu_clk_premux1 = &g12b_cpub_clk_premux1.hw,
+       .nb.notifier_call = g12a_cpu_clk_postmux_notifier_cb,
+};
+
+struct g12a_sys_pll_nb_data {
+       struct notifier_block nb;
+       struct clk_hw *sys_pll;
+       struct clk_hw *cpu_clk;
+       struct clk_hw *cpu_clk_dyn;
+};
+
+static int g12a_sys_pll_notifier_cb(struct notifier_block *nb,
+                                   unsigned long event, void *data)
+{
+       struct g12a_sys_pll_nb_data *nb_data =
+               container_of(nb, struct g12a_sys_pll_nb_data, nb);
+
+       switch (event) {
+       case PRE_RATE_CHANGE:
+               /*
+                * This notifier means sys_pll clock will be changed
+                * to feed cpu_clk, this the current path :
+                * cpu_clk
+                *    \- sys_pll
+                *          \- sys_pll_dco
+                */
+
+               /* Configure cpu_clk to use cpu_clk_dyn */
+               clk_hw_set_parent(nb_data->cpu_clk,
+                                 nb_data->cpu_clk_dyn);
+
+               /*
+                * Now, cpu_clk uses the dyn path
+                * cpu_clk
+                *    \- cpu_clk_dyn
+                *          \- cpu_clk_dynX
+                *                \- cpu_clk_dynX_sel
+                *                   \- cpu_clk_dynX_div
+                *                      \- xtal/fclk_div2/fclk_div3
+                *                   \- xtal/fclk_div2/fclk_div3
+                */
+
+               udelay(100);
+
+               return NOTIFY_OK;
+
+       case POST_RATE_CHANGE:
+               /*
+                * The sys_pll has ben updated, now switch back cpu_clk to
+                * sys_pll
+                */
+
+               /* Configure cpu_clk to use sys_pll */
+               clk_hw_set_parent(nb_data->cpu_clk,
+                                 nb_data->sys_pll);
+
+               udelay(100);
+
+               /* new path :
+                * cpu_clk
+                *    \- sys_pll
+                *          \- sys_pll_dco
+                */
+
+               return NOTIFY_OK;
+
+       default:
+               return NOTIFY_DONE;
+       }
+}
+
+static struct g12a_sys_pll_nb_data g12a_sys_pll_nb_data = {
+       .sys_pll = &g12a_sys_pll.hw,
+       .cpu_clk = &g12a_cpu_clk.hw,
+       .cpu_clk_dyn = &g12a_cpu_clk_dyn.hw,
+       .nb.notifier_call = g12a_sys_pll_notifier_cb,
+};
+
+/* G12B first CPU cluster uses sys1_pll */
+static struct g12a_sys_pll_nb_data g12b_cpu_clk_sys1_pll_nb_data = {
+       .sys_pll = &g12b_sys1_pll.hw,
+       .cpu_clk = &g12b_cpu_clk.hw,
+       .cpu_clk_dyn = &g12a_cpu_clk_dyn.hw,
+       .nb.notifier_call = g12a_sys_pll_notifier_cb,
+};
+
+/* G12B second CPU cluster uses sys_pll */
+static struct g12a_sys_pll_nb_data g12b_cpub_clk_sys_pll_nb_data = {
+       .sys_pll = &g12a_sys_pll.hw,
+       .cpu_clk = &g12b_cpub_clk.hw,
+       .cpu_clk_dyn = &g12b_cpub_clk_dyn.hw,
+       .nb.notifier_call = g12a_sys_pll_notifier_cb,
+};
+
 static struct clk_regmap g12a_cpu_clk_div16_en = {
        .data = &(struct clk_regmap_gate_data){
                .offset = HHI_SYS_CPU_CLK_CNTL1,
@@ -4097,28 +4342,210 @@ static const struct reg_sequence g12a_init_regs[] = {
        { .reg = HHI_MPLL_CNTL0,        .def = 0x00000543 },
 };
 
-static const struct meson_eeclkc_data g12a_clkc_data = {
-       .regmap_clks = g12a_clk_regmaps,
-       .regmap_clk_num = ARRAY_SIZE(g12a_clk_regmaps),
-       .hw_onecell_data = &g12a_hw_onecell_data,
-       .init_regs = g12a_init_regs,
-       .init_count = ARRAY_SIZE(g12a_init_regs),
-};
-
-static const struct meson_eeclkc_data g12b_clkc_data = {
-       .regmap_clks = g12a_clk_regmaps,
-       .regmap_clk_num = ARRAY_SIZE(g12a_clk_regmaps),
-       .hw_onecell_data = &g12b_hw_onecell_data
+static int meson_g12a_dvfs_setup_common(struct platform_device *pdev,
+                                       struct clk_hw **hws)
+{
+       const char *notifier_clk_name;
+       struct clk *notifier_clk;
+       struct clk_hw *xtal;
+       int ret;
+
+       xtal = clk_hw_get_parent_by_index(hws[CLKID_CPU_CLK_DYN1_SEL], 0);
+
+       /* Setup clock notifier for cpu_clk_postmux0 */
+       g12a_cpu_clk_postmux0_nb_data.xtal = xtal;
+       notifier_clk_name = clk_hw_get_name(&g12a_cpu_clk_postmux0.hw);
+       notifier_clk = __clk_lookup(notifier_clk_name);
+       ret = clk_notifier_register(notifier_clk,
+                                   &g12a_cpu_clk_postmux0_nb_data.nb);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to register the cpu_clk_postmux0 
notifier\n");
+               return ret;
+       }
+
+       /* Setup clock notifier for cpu_clk_dyn mux */
+       notifier_clk_name = clk_hw_get_name(&g12a_cpu_clk_dyn.hw);
+       notifier_clk = __clk_lookup(notifier_clk_name);
+       ret = clk_notifier_register(notifier_clk, &g12a_cpu_clk_mux_nb);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to register the cpu_clk_dyn 
notifier\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static int meson_g12b_dvfs_setup(struct platform_device *pdev)
+{
+       struct clk_hw **hws = g12b_hw_onecell_data.hws;
+       const char *notifier_clk_name;
+       struct clk *notifier_clk;
+       struct clk_hw *xtal;
+       int ret;
+
+       ret = meson_g12a_dvfs_setup_common(pdev, hws);
+       if (ret)
+               return ret;
+
+       xtal = clk_hw_get_parent_by_index(hws[CLKID_CPU_CLK_DYN1_SEL], 0);
+
+       /* Setup clock notifier for cpu_clk mux */
+       notifier_clk_name = clk_hw_get_name(&g12b_cpu_clk.hw);
+       notifier_clk = __clk_lookup(notifier_clk_name);
+       ret = clk_notifier_register(notifier_clk, &g12a_cpu_clk_mux_nb);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to register the cpu_clk 
notifier\n");
+               return ret;
+       }
+
+       /* Setup clock notifier for sys1_pll */
+       notifier_clk_name = clk_hw_get_name(&g12b_sys1_pll.hw);
+       notifier_clk = __clk_lookup(notifier_clk_name);
+       ret = clk_notifier_register(notifier_clk,
+                                   &g12b_cpu_clk_sys1_pll_nb_data.nb);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to register the sys1_pll 
notifier\n");
+               return ret;
+       }
+
+       /* Add notifiers for the second CPU cluster */
+
+       /* Setup clock notifier for cpub_clk_postmux0 */
+       g12b_cpub_clk_postmux0_nb_data.xtal = xtal;
+       notifier_clk_name = clk_hw_get_name(&g12b_cpub_clk_postmux0.hw);
+       notifier_clk = __clk_lookup(notifier_clk_name);
+       ret = clk_notifier_register(notifier_clk,
+                                   &g12b_cpub_clk_postmux0_nb_data.nb);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to register the cpub_clk_postmux0 
notifier\n");
+               return ret;
+       }
+
+       /* Setup clock notifier for cpub_clk_dyn mux */
+       notifier_clk_name = clk_hw_get_name(&g12b_cpub_clk_dyn.hw);
+       notifier_clk = __clk_lookup(notifier_clk_name);
+       ret = clk_notifier_register(notifier_clk, &g12a_cpu_clk_mux_nb);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to register the cpub_clk_dyn 
notifier\n");
+               return ret;
+       }
+
+       /* Setup clock notifier for cpub_clk mux */
+       notifier_clk_name = clk_hw_get_name(&g12b_cpub_clk.hw);
+       notifier_clk = __clk_lookup(notifier_clk_name);
+       ret = clk_notifier_register(notifier_clk, &g12a_cpu_clk_mux_nb);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to register the cpub_clk 
notifier\n");
+               return ret;
+       }
+
+       /* Setup clock notifier for sys_pll */
+       notifier_clk_name = clk_hw_get_name(&g12a_sys_pll.hw);
+       notifier_clk = __clk_lookup(notifier_clk_name);
+       ret = clk_notifier_register(notifier_clk,
+                                   &g12b_cpub_clk_sys_pll_nb_data.nb);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to register the sys_pll 
notifier\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static int meson_g12a_dvfs_setup(struct platform_device *pdev)
+{
+       struct clk_hw **hws = g12a_hw_onecell_data.hws;
+       const char *notifier_clk_name;
+       struct clk *notifier_clk;
+       int ret;
+
+       ret = meson_g12a_dvfs_setup_common(pdev, hws);
+       if (ret)
+               return ret;
+
+       /* Setup clock notifier for cpu_clk mux */
+       notifier_clk_name = clk_hw_get_name(&g12a_cpu_clk.hw);
+       notifier_clk = __clk_lookup(notifier_clk_name);
+       ret = clk_notifier_register(notifier_clk, &g12a_cpu_clk_mux_nb);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to register the cpu_clk 
notifier\n");
+               return ret;
+       }
+
+       /* Setup clock notifier for sys_pll */
+       notifier_clk_name = clk_hw_get_name(&g12a_sys_pll.hw);
+       notifier_clk = __clk_lookup(notifier_clk_name);
+       ret = clk_notifier_register(notifier_clk, &g12a_sys_pll_nb_data.nb);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to register the sys_pll 
notifier\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+struct meson_g12a_data {
+       const struct meson_eeclkc_data eeclkc_data;
+       int (*dvfs_setup)(struct platform_device *pdev);
+};
+
+static int meson_g12a_probe(struct platform_device *pdev)
+{
+       const struct meson_eeclkc_data *eeclkc_data;
+       const struct meson_g12a_data *g12a_data;
+       int ret;
+
+       eeclkc_data = of_device_get_match_data(&pdev->dev);
+       if (!eeclkc_data)
+               return -EINVAL;
+
+       ret = meson_eeclkc_probe(pdev);
+       if (ret)
+               return ret;
+
+       g12a_data = container_of(eeclkc_data, struct meson_g12a_data,
+                                eeclkc_data);
+
+       if (g12a_data->dvfs_setup)
+               return g12a_data->dvfs_setup(pdev);
+
+       return 0;
+}
+
+static const struct meson_g12a_data g12a_clkc_data = {
+       .eeclkc_data = {
+               .regmap_clks = g12a_clk_regmaps,
+               .regmap_clk_num = ARRAY_SIZE(g12a_clk_regmaps),
+               .hw_onecell_data = &g12a_hw_onecell_data,
+               .init_regs = g12a_init_regs,
+               .init_count = ARRAY_SIZE(g12a_init_regs),
+       },
+       .dvfs_setup = meson_g12a_dvfs_setup,
+};
+
+static const struct meson_g12a_data g12b_clkc_data = {
+       .eeclkc_data = {
+               .regmap_clks = g12a_clk_regmaps,
+               .regmap_clk_num = ARRAY_SIZE(g12a_clk_regmaps),
+               .hw_onecell_data = &g12b_hw_onecell_data,
+       },
+       .dvfs_setup = meson_g12b_dvfs_setup,
 };
 
 static const struct of_device_id clkc_match_table[] = {
-       { .compatible = "amlogic,g12a-clkc", .data = &g12a_clkc_data },
-       { .compatible = "amlogic,g12b-clkc", .data = &g12b_clkc_data },
+       {
+               .compatible = "amlogic,g12a-clkc",
+               .data = &g12a_clkc_data.eeclkc_data
+       },
+       {
+               .compatible = "amlogic,g12b-clkc",
+               .data = &g12b_clkc_data.eeclkc_data
+       },
        {}
 };
 
 static struct platform_driver g12a_driver = {
-       .probe          = meson_eeclkc_probe,
+       .probe          = meson_g12a_probe,
        .driver         = {
                .name   = "g12a-clkc",
                .of_match_table = clkc_match_table,
-- 
2.22.0

Reply via email to