#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"
        },
 };
 
-/*
- * 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 = {
                        .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,
        },
 };
 
        },
        .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,
        },
 };
 
                        .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,
        },
 };
 
        },
        .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,
        },
 };
 
        },
        .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,
        },
 };
 
        },
        .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,
        },
 };
 
        },
        .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,
        },
 };
 
        },
        .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,
        },
 };
 
        },
        .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,
        },
 };
 
        },
        .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,
        },
 };
 
        },
        .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,
        },
 };
 
        },
        .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 },
 
 /* 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,
        },
 };
 
        },
        .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,
        },
 };
 
        },
        .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,
        },
 };
 
        },
        .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,
        },
 };
 
        },
        .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,
        },
 };
 
        },
        .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,
        { .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,