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 struct meson_clk_cpu
{
55 struct notifier_block clk_nb
;
56 const struct clk_div_table
*div_table
;
61 #define to_meson_clk_cpu_hw(_hw) container_of(_hw, struct meson_clk_cpu, hw)
62 #define to_meson_clk_cpu_nb(_nb) container_of(_nb, struct meson_clk_cpu, clk_nb)
64 static long meson_clk_cpu_round_rate(struct clk_hw
*hw
, unsigned long rate
,
67 struct meson_clk_cpu
*clk_cpu
= to_meson_clk_cpu_hw(hw
);
69 return divider_round_rate(hw
, rate
, prate
, clk_cpu
->div_table
,
70 MESON_N_WIDTH
, CLK_DIVIDER_ROUND_CLOSEST
);
73 static int meson_clk_cpu_set_rate(struct clk_hw
*hw
, unsigned long rate
,
74 unsigned long parent_rate
)
76 struct meson_clk_cpu
*clk_cpu
= to_meson_clk_cpu_hw(hw
);
77 unsigned int div
, sel
, N
= 0;
80 div
= DIV_ROUND_UP(parent_rate
, rate
);
89 reg
= readl(clk_cpu
->base
+ clk_cpu
->reg_off
+ MESON_CPU_CLK_CNTL1
);
90 reg
= PARM_SET(MESON_N_WIDTH
, MESON_N_SHIFT
, reg
, N
);
91 writel(reg
, clk_cpu
->base
+ clk_cpu
->reg_off
+ MESON_CPU_CLK_CNTL1
);
93 reg
= readl(clk_cpu
->base
+ clk_cpu
->reg_off
+ MESON_CPU_CLK_CNTL
);
94 reg
= PARM_SET(MESON_SEL_WIDTH
, MESON_SEL_SHIFT
, reg
, sel
);
95 writel(reg
, clk_cpu
->base
+ clk_cpu
->reg_off
+ MESON_CPU_CLK_CNTL
);
100 static unsigned long meson_clk_cpu_recalc_rate(struct clk_hw
*hw
,
101 unsigned long parent_rate
)
103 struct meson_clk_cpu
*clk_cpu
= to_meson_clk_cpu_hw(hw
);
105 unsigned int div
= 1;
108 reg
= readl(clk_cpu
->base
+ clk_cpu
->reg_off
+ MESON_CPU_CLK_CNTL1
);
109 N
= PARM_GET(MESON_N_WIDTH
, MESON_N_SHIFT
, reg
);
111 reg
= readl(clk_cpu
->base
+ clk_cpu
->reg_off
+ MESON_CPU_CLK_CNTL
);
112 sel
= PARM_GET(MESON_SEL_WIDTH
, MESON_SEL_SHIFT
, reg
);
119 return parent_rate
/ div
;
122 static int meson_clk_cpu_pre_rate_change(struct meson_clk_cpu
*clk_cpu
,
123 struct clk_notifier_data
*ndata
)
127 /* switch MUX1 to xtal */
128 cpu_clk_cntl
= readl(clk_cpu
->base
+ clk_cpu
->reg_off
129 + MESON_CPU_CLK_CNTL
);
130 cpu_clk_cntl
&= ~MESON_CPU_CLK_MUX1
;
131 writel(cpu_clk_cntl
, clk_cpu
->base
+ clk_cpu
->reg_off
132 + MESON_CPU_CLK_CNTL
);
135 /* switch MUX2 to sys-pll */
136 cpu_clk_cntl
|= MESON_CPU_CLK_MUX2
;
137 writel(cpu_clk_cntl
, clk_cpu
->base
+ clk_cpu
->reg_off
138 + MESON_CPU_CLK_CNTL
);
143 static int meson_clk_cpu_post_rate_change(struct meson_clk_cpu
*clk_cpu
,
144 struct clk_notifier_data
*ndata
)
148 /* switch MUX1 to divisors' output */
149 cpu_clk_cntl
= readl(clk_cpu
->base
+ clk_cpu
->reg_off
150 + MESON_CPU_CLK_CNTL
);
151 cpu_clk_cntl
|= MESON_CPU_CLK_MUX1
;
152 writel(cpu_clk_cntl
, clk_cpu
->base
+ clk_cpu
->reg_off
153 + MESON_CPU_CLK_CNTL
);
160 * This clock notifier is called when the frequency of the of the parent
161 * PLL clock is to be changed. We use the xtal input as temporary parent
162 * while the PLL frequency is stabilized.
164 static int meson_clk_cpu_notifier_cb(struct notifier_block
*nb
,
165 unsigned long event
, void *data
)
167 struct clk_notifier_data
*ndata
= data
;
168 struct meson_clk_cpu
*clk_cpu
= to_meson_clk_cpu_nb(nb
);
171 if (event
== PRE_RATE_CHANGE
)
172 ret
= meson_clk_cpu_pre_rate_change(clk_cpu
, ndata
);
173 else if (event
== POST_RATE_CHANGE
)
174 ret
= meson_clk_cpu_post_rate_change(clk_cpu
, ndata
);
176 return notifier_from_errno(ret
);
179 static const struct clk_ops meson_clk_cpu_ops
= {
180 .recalc_rate
= meson_clk_cpu_recalc_rate
,
181 .round_rate
= meson_clk_cpu_round_rate
,
182 .set_rate
= meson_clk_cpu_set_rate
,
185 struct clk
*meson_clk_register_cpu(const struct clk_conf
*clk_conf
,
186 void __iomem
*reg_base
,
191 struct meson_clk_cpu
*clk_cpu
;
192 struct clk_init_data init
;
195 clk_cpu
= kzalloc(sizeof(*clk_cpu
), GFP_KERNEL
);
197 return ERR_PTR(-ENOMEM
);
199 clk_cpu
->base
= reg_base
;
200 clk_cpu
->reg_off
= clk_conf
->reg_off
;
201 clk_cpu
->div_table
= clk_conf
->conf
.div_table
;
202 clk_cpu
->clk_nb
.notifier_call
= meson_clk_cpu_notifier_cb
;
204 init
.name
= clk_conf
->clk_name
;
205 init
.ops
= &meson_clk_cpu_ops
;
206 init
.flags
= clk_conf
->flags
| CLK_GET_RATE_NOCACHE
;
207 init
.flags
|= CLK_SET_RATE_PARENT
;
208 init
.parent_names
= clk_conf
->clks_parent
;
209 init
.num_parents
= 1;
211 clk_cpu
->hw
.init
= &init
;
213 pclk
= __clk_lookup(clk_conf
->clks_parent
[0]);
215 pr_err("%s: could not lookup parent clock %s\n",
216 __func__
, clk_conf
->clks_parent
[0]);
221 ret
= clk_notifier_register(pclk
, &clk_cpu
->clk_nb
);
223 pr_err("%s: failed to register clock notifier for %s\n",
224 __func__
, clk_conf
->clk_name
);
228 clk
= clk_register(NULL
, &clk_cpu
->hw
);
231 goto unregister_clk_nb
;
237 clk_notifier_unregister(pclk
, &clk_cpu
->clk_nb
);