2 * Copyright (c) 2015 Endless Mobile, Inc.
3 * Author: Carlo Caione <carlo@endlessm.com>
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms and conditions of the GNU General Public License,
7 * version 2, as published by the Free Software Foundation.
9 * This program is distributed in the hope it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * You should have received a copy of the GNU General Public License along with
15 * this program. If not, see <http://www.gnu.org/licenses/>.
22 * MUX2 +--[/3]-+----------|2| MUX1
23 * [sys_pll]---|1| |--[/2]------------|1|-|1|
24 * | |---+------------------|0| | |----- [a5_clk]
26 * [xtal]---+-------------------------------|0|
32 #include <linux/delay.h>
33 #include <linux/err.h>
35 #include <linux/module.h>
36 #include <linux/of_address.h>
37 #include <linux/slab.h>
38 #include <linux/clk.h>
39 #include <linux/clk-provider.h>
41 #define MESON_CPU_CLK_CNTL1 0x00
42 #define MESON_CPU_CLK_CNTL 0x40
44 #define MESON_CPU_CLK_MUX1 BIT(7)
45 #define MESON_CPU_CLK_MUX2 BIT(0)
47 #define MESON_N_WIDTH 9
48 #define MESON_N_SHIFT 20
49 #define MESON_SEL_WIDTH 2
50 #define MESON_SEL_SHIFT 2
54 #define to_meson_clk_cpu_hw(_hw) container_of(_hw, struct meson_clk_cpu, hw)
55 #define to_meson_clk_cpu_nb(_nb) container_of(_nb, struct meson_clk_cpu, clk_nb)
57 static long meson_clk_cpu_round_rate(struct clk_hw
*hw
, unsigned long rate
,
60 struct meson_clk_cpu
*clk_cpu
= to_meson_clk_cpu_hw(hw
);
62 return divider_round_rate(hw
, rate
, prate
, clk_cpu
->div_table
,
63 MESON_N_WIDTH
, CLK_DIVIDER_ROUND_CLOSEST
);
66 static int meson_clk_cpu_set_rate(struct clk_hw
*hw
, unsigned long rate
,
67 unsigned long parent_rate
)
69 struct meson_clk_cpu
*clk_cpu
= to_meson_clk_cpu_hw(hw
);
70 unsigned int div
, sel
, N
= 0;
73 div
= DIV_ROUND_UP(parent_rate
, rate
);
82 reg
= readl(clk_cpu
->base
+ clk_cpu
->reg_off
+ MESON_CPU_CLK_CNTL1
);
83 reg
= PARM_SET(MESON_N_WIDTH
, MESON_N_SHIFT
, reg
, N
);
84 writel(reg
, clk_cpu
->base
+ clk_cpu
->reg_off
+ MESON_CPU_CLK_CNTL1
);
86 reg
= readl(clk_cpu
->base
+ clk_cpu
->reg_off
+ MESON_CPU_CLK_CNTL
);
87 reg
= PARM_SET(MESON_SEL_WIDTH
, MESON_SEL_SHIFT
, reg
, sel
);
88 writel(reg
, clk_cpu
->base
+ clk_cpu
->reg_off
+ MESON_CPU_CLK_CNTL
);
93 static unsigned long meson_clk_cpu_recalc_rate(struct clk_hw
*hw
,
94 unsigned long parent_rate
)
96 struct meson_clk_cpu
*clk_cpu
= to_meson_clk_cpu_hw(hw
);
101 reg
= readl(clk_cpu
->base
+ clk_cpu
->reg_off
+ MESON_CPU_CLK_CNTL1
);
102 N
= PARM_GET(MESON_N_WIDTH
, MESON_N_SHIFT
, reg
);
104 reg
= readl(clk_cpu
->base
+ clk_cpu
->reg_off
+ MESON_CPU_CLK_CNTL
);
105 sel
= PARM_GET(MESON_SEL_WIDTH
, MESON_SEL_SHIFT
, reg
);
112 return parent_rate
/ div
;
115 /* FIXME MUX1 & MUX2 should be struct clk_hw objects */
116 static int meson_clk_cpu_pre_rate_change(struct meson_clk_cpu
*clk_cpu
,
117 struct clk_notifier_data
*ndata
)
121 /* switch MUX1 to xtal */
122 cpu_clk_cntl
= readl(clk_cpu
->base
+ clk_cpu
->reg_off
123 + MESON_CPU_CLK_CNTL
);
124 cpu_clk_cntl
&= ~MESON_CPU_CLK_MUX1
;
125 writel(cpu_clk_cntl
, clk_cpu
->base
+ clk_cpu
->reg_off
126 + MESON_CPU_CLK_CNTL
);
129 /* switch MUX2 to sys-pll */
130 cpu_clk_cntl
|= MESON_CPU_CLK_MUX2
;
131 writel(cpu_clk_cntl
, clk_cpu
->base
+ clk_cpu
->reg_off
132 + MESON_CPU_CLK_CNTL
);
137 /* FIXME MUX1 & MUX2 should be struct clk_hw objects */
138 static int meson_clk_cpu_post_rate_change(struct meson_clk_cpu
*clk_cpu
,
139 struct clk_notifier_data
*ndata
)
143 /* switch MUX1 to divisors' output */
144 cpu_clk_cntl
= readl(clk_cpu
->base
+ clk_cpu
->reg_off
145 + MESON_CPU_CLK_CNTL
);
146 cpu_clk_cntl
|= MESON_CPU_CLK_MUX1
;
147 writel(cpu_clk_cntl
, clk_cpu
->base
+ clk_cpu
->reg_off
148 + MESON_CPU_CLK_CNTL
);
155 * This clock notifier is called when the frequency of the of the parent
156 * PLL clock is to be changed. We use the xtal input as temporary parent
157 * while the PLL frequency is stabilized.
159 int meson_clk_cpu_notifier_cb(struct notifier_block
*nb
,
160 unsigned long event
, void *data
)
162 struct clk_notifier_data
*ndata
= data
;
163 struct meson_clk_cpu
*clk_cpu
= to_meson_clk_cpu_nb(nb
);
166 if (event
== PRE_RATE_CHANGE
)
167 ret
= meson_clk_cpu_pre_rate_change(clk_cpu
, ndata
);
168 else if (event
== POST_RATE_CHANGE
)
169 ret
= meson_clk_cpu_post_rate_change(clk_cpu
, ndata
);
171 return notifier_from_errno(ret
);
174 const struct clk_ops meson_clk_cpu_ops
= {
175 .recalc_rate
= meson_clk_cpu_recalc_rate
,
176 .round_rate
= meson_clk_cpu_round_rate
,
177 .set_rate
= meson_clk_cpu_set_rate
,