mirror of
https://github.com/torvalds/linux.git
synced 2024-11-15 00:21:59 +00:00
clk: meson: g12a: add notifiers to handle cpu clock change
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 <narmstrong@baylibre.com> Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
This commit is contained in:
parent
26d34431ad
commit
ffae8475b9
@ -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 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 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_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,
|
||||
|
Loading…
Reference in New Issue
Block a user