diff options
Diffstat (limited to 'drivers/clk')
-rw-r--r-- | drivers/clk/Makefile | 3 | ||||
-rw-r--r-- | drivers/clk/spear/Makefile | 5 | ||||
-rw-r--r-- | drivers/clk/spear/clk-vco-pll.c | 363 | ||||
-rw-r--r-- | drivers/clk/spear/clk.c | 36 | ||||
-rw-r--r-- | drivers/clk/spear/clk.h | 58 |
5 files changed, 465 insertions, 0 deletions
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 24aa7144811b..0f5e03d1ef5c 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile | |||
@@ -2,3 +2,6 @@ | |||
2 | obj-$(CONFIG_CLKDEV_LOOKUP) += clkdev.o | 2 | obj-$(CONFIG_CLKDEV_LOOKUP) += clkdev.o |
3 | obj-$(CONFIG_COMMON_CLK) += clk.o clk-fixed-rate.o clk-gate.o \ | 3 | obj-$(CONFIG_COMMON_CLK) += clk.o clk-fixed-rate.o clk-gate.o \ |
4 | clk-mux.o clk-divider.o clk-fixed-factor.o | 4 | clk-mux.o clk-divider.o clk-fixed-factor.o |
5 | |||
6 | # SoCs specific | ||
7 | obj-$(CONFIG_PLAT_SPEAR) += spear/ | ||
diff --git a/drivers/clk/spear/Makefile b/drivers/clk/spear/Makefile new file mode 100644 index 000000000000..3fc2a3026cc1 --- /dev/null +++ b/drivers/clk/spear/Makefile | |||
@@ -0,0 +1,5 @@ | |||
1 | # | ||
2 | # SPEAr Clock specific Makefile | ||
3 | # | ||
4 | |||
5 | obj-y += clk.o clk-vco-pll.o | ||
diff --git a/drivers/clk/spear/clk-vco-pll.c b/drivers/clk/spear/clk-vco-pll.c new file mode 100644 index 000000000000..dcd4bdf4b0d9 --- /dev/null +++ b/drivers/clk/spear/clk-vco-pll.c | |||
@@ -0,0 +1,363 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2012 ST Microelectronics | ||
3 | * Viresh Kumar <viresh.kumar@st.com> | ||
4 | * | ||
5 | * This file is licensed under the terms of the GNU General Public | ||
6 | * License version 2. This program is licensed "as is" without any | ||
7 | * warranty of any kind, whether express or implied. | ||
8 | * | ||
9 | * VCO-PLL clock implementation | ||
10 | */ | ||
11 | |||
12 | #define pr_fmt(fmt) "clk-vco-pll: " fmt | ||
13 | |||
14 | #include <linux/clk-provider.h> | ||
15 | #include <linux/slab.h> | ||
16 | #include <linux/io.h> | ||
17 | #include <linux/err.h> | ||
18 | #include "clk.h" | ||
19 | |||
20 | /* | ||
21 | * DOC: VCO-PLL clock | ||
22 | * | ||
23 | * VCO and PLL rate are derived from following equations: | ||
24 | * | ||
25 | * In normal mode | ||
26 | * vco = (2 * M[15:8] * Fin)/N | ||
27 | * | ||
28 | * In Dithered mode | ||
29 | * vco = (2 * M[15:0] * Fin)/(256 * N) | ||
30 | * | ||
31 | * pll_rate = pll/2^p | ||
32 | * | ||
33 | * vco and pll are very closely bound to each other, "vco needs to program: | ||
34 | * mode, m & n" and "pll needs to program p", both share common enable/disable | ||
35 | * logic. | ||
36 | * | ||
37 | * clk_register_vco_pll() registers instances of both vco & pll. | ||
38 | * CLK_SET_RATE_PARENT flag is forced for pll, as it will always pass its | ||
39 | * set_rate to vco. A single rate table exists for both the clocks, which | ||
40 | * configures m, n and p. | ||
41 | */ | ||
42 | |||
43 | /* PLL_CTR register masks */ | ||
44 | #define PLL_MODE_NORMAL 0 | ||
45 | #define PLL_MODE_FRACTION 1 | ||
46 | #define PLL_MODE_DITH_DSM 2 | ||
47 | #define PLL_MODE_DITH_SSM 3 | ||
48 | #define PLL_MODE_MASK 3 | ||
49 | #define PLL_MODE_SHIFT 3 | ||
50 | #define PLL_ENABLE 2 | ||
51 | |||
52 | #define PLL_LOCK_SHIFT 0 | ||
53 | #define PLL_LOCK_MASK 1 | ||
54 | |||
55 | /* PLL FRQ register masks */ | ||
56 | #define PLL_NORM_FDBK_M_MASK 0xFF | ||
57 | #define PLL_NORM_FDBK_M_SHIFT 24 | ||
58 | #define PLL_DITH_FDBK_M_MASK 0xFFFF | ||
59 | #define PLL_DITH_FDBK_M_SHIFT 16 | ||
60 | #define PLL_DIV_P_MASK 0x7 | ||
61 | #define PLL_DIV_P_SHIFT 8 | ||
62 | #define PLL_DIV_N_MASK 0xFF | ||
63 | #define PLL_DIV_N_SHIFT 0 | ||
64 | |||
65 | #define to_clk_vco(_hw) container_of(_hw, struct clk_vco, hw) | ||
66 | #define to_clk_pll(_hw) container_of(_hw, struct clk_pll, hw) | ||
67 | |||
68 | /* Calculates pll clk rate for specific value of mode, m, n and p */ | ||
69 | static unsigned long pll_calc_rate(struct pll_rate_tbl *rtbl, | ||
70 | unsigned long prate, int index, unsigned long *pll_rate) | ||
71 | { | ||
72 | unsigned long rate = prate; | ||
73 | unsigned int mode; | ||
74 | |||
75 | mode = rtbl[index].mode ? 256 : 1; | ||
76 | rate = (((2 * rate / 10000) * rtbl[index].m) / (mode * rtbl[index].n)); | ||
77 | |||
78 | if (pll_rate) | ||
79 | *pll_rate = (rate / (1 << rtbl[index].p)) * 10000; | ||
80 | |||
81 | return rate * 10000; | ||
82 | } | ||
83 | |||
84 | static long clk_pll_round_rate_index(struct clk_hw *hw, unsigned long drate, | ||
85 | unsigned long *prate, int *index) | ||
86 | { | ||
87 | struct clk_pll *pll = to_clk_pll(hw); | ||
88 | unsigned long prev_rate, vco_prev_rate, rate = 0; | ||
89 | unsigned long vco_parent_rate = | ||
90 | __clk_get_rate(__clk_get_parent(__clk_get_parent(hw->clk))); | ||
91 | |||
92 | if (!prate) { | ||
93 | pr_err("%s: prate is must for pll clk\n", __func__); | ||
94 | return -EINVAL; | ||
95 | } | ||
96 | |||
97 | for (*index = 0; *index < pll->vco->rtbl_cnt; (*index)++) { | ||
98 | prev_rate = rate; | ||
99 | vco_prev_rate = *prate; | ||
100 | *prate = pll_calc_rate(pll->vco->rtbl, vco_parent_rate, *index, | ||
101 | &rate); | ||
102 | if (drate < rate) { | ||
103 | /* previous clock was best */ | ||
104 | if (*index) { | ||
105 | rate = prev_rate; | ||
106 | *prate = vco_prev_rate; | ||
107 | (*index)--; | ||
108 | } | ||
109 | break; | ||
110 | } | ||
111 | } | ||
112 | |||
113 | return rate; | ||
114 | } | ||
115 | |||
116 | static long clk_pll_round_rate(struct clk_hw *hw, unsigned long drate, | ||
117 | unsigned long *prate) | ||
118 | { | ||
119 | int unused; | ||
120 | |||
121 | return clk_pll_round_rate_index(hw, drate, prate, &unused); | ||
122 | } | ||
123 | |||
124 | static unsigned long clk_pll_recalc_rate(struct clk_hw *hw, unsigned long | ||
125 | parent_rate) | ||
126 | { | ||
127 | struct clk_pll *pll = to_clk_pll(hw); | ||
128 | unsigned long flags = 0; | ||
129 | unsigned int p; | ||
130 | |||
131 | if (pll->vco->lock) | ||
132 | spin_lock_irqsave(pll->vco->lock, flags); | ||
133 | |||
134 | p = readl_relaxed(pll->vco->cfg_reg); | ||
135 | |||
136 | if (pll->vco->lock) | ||
137 | spin_unlock_irqrestore(pll->vco->lock, flags); | ||
138 | |||
139 | p = (p >> PLL_DIV_P_SHIFT) & PLL_DIV_P_MASK; | ||
140 | |||
141 | return parent_rate / (1 << p); | ||
142 | } | ||
143 | |||
144 | static int clk_pll_set_rate(struct clk_hw *hw, unsigned long drate, | ||
145 | unsigned long prate) | ||
146 | { | ||
147 | struct clk_pll *pll = to_clk_pll(hw); | ||
148 | struct pll_rate_tbl *rtbl = pll->vco->rtbl; | ||
149 | unsigned long flags = 0, val; | ||
150 | int i; | ||
151 | |||
152 | clk_pll_round_rate_index(hw, drate, NULL, &i); | ||
153 | |||
154 | if (pll->vco->lock) | ||
155 | spin_lock_irqsave(pll->vco->lock, flags); | ||
156 | |||
157 | val = readl_relaxed(pll->vco->cfg_reg); | ||
158 | val &= ~(PLL_DIV_P_MASK << PLL_DIV_P_SHIFT); | ||
159 | val |= (rtbl[i].p & PLL_DIV_P_MASK) << PLL_DIV_P_SHIFT; | ||
160 | writel_relaxed(val, pll->vco->cfg_reg); | ||
161 | |||
162 | if (pll->vco->lock) | ||
163 | spin_unlock_irqrestore(pll->vco->lock, flags); | ||
164 | |||
165 | return 0; | ||
166 | } | ||
167 | |||
168 | static struct clk_ops clk_pll_ops = { | ||
169 | .recalc_rate = clk_pll_recalc_rate, | ||
170 | .round_rate = clk_pll_round_rate, | ||
171 | .set_rate = clk_pll_set_rate, | ||
172 | }; | ||
173 | |||
174 | static inline unsigned long vco_calc_rate(struct clk_hw *hw, | ||
175 | unsigned long prate, int index) | ||
176 | { | ||
177 | struct clk_vco *vco = to_clk_vco(hw); | ||
178 | |||
179 | return pll_calc_rate(vco->rtbl, prate, index, NULL); | ||
180 | } | ||
181 | |||
182 | static long clk_vco_round_rate(struct clk_hw *hw, unsigned long drate, | ||
183 | unsigned long *prate) | ||
184 | { | ||
185 | struct clk_vco *vco = to_clk_vco(hw); | ||
186 | int unused; | ||
187 | |||
188 | return clk_round_rate_index(hw, drate, *prate, vco_calc_rate, | ||
189 | vco->rtbl_cnt, &unused); | ||
190 | } | ||
191 | |||
192 | static unsigned long clk_vco_recalc_rate(struct clk_hw *hw, | ||
193 | unsigned long parent_rate) | ||
194 | { | ||
195 | struct clk_vco *vco = to_clk_vco(hw); | ||
196 | unsigned long flags = 0; | ||
197 | unsigned int num = 2, den = 0, val, mode = 0; | ||
198 | |||
199 | if (vco->lock) | ||
200 | spin_lock_irqsave(vco->lock, flags); | ||
201 | |||
202 | mode = (readl_relaxed(vco->mode_reg) >> PLL_MODE_SHIFT) & PLL_MODE_MASK; | ||
203 | |||
204 | val = readl_relaxed(vco->cfg_reg); | ||
205 | |||
206 | if (vco->lock) | ||
207 | spin_unlock_irqrestore(vco->lock, flags); | ||
208 | |||
209 | den = (val >> PLL_DIV_N_SHIFT) & PLL_DIV_N_MASK; | ||
210 | |||
211 | /* calculate numerator & denominator */ | ||
212 | if (!mode) { | ||
213 | /* Normal mode */ | ||
214 | num *= (val >> PLL_NORM_FDBK_M_SHIFT) & PLL_NORM_FDBK_M_MASK; | ||
215 | } else { | ||
216 | /* Dithered mode */ | ||
217 | num *= (val >> PLL_DITH_FDBK_M_SHIFT) & PLL_DITH_FDBK_M_MASK; | ||
218 | den *= 256; | ||
219 | } | ||
220 | |||
221 | if (!den) { | ||
222 | WARN(1, "%s: denominator can't be zero\n", __func__); | ||
223 | return 0; | ||
224 | } | ||
225 | |||
226 | return (((parent_rate / 10000) * num) / den) * 10000; | ||
227 | } | ||
228 | |||
229 | /* Configures new clock rate of vco */ | ||
230 | static int clk_vco_set_rate(struct clk_hw *hw, unsigned long drate, | ||
231 | unsigned long prate) | ||
232 | { | ||
233 | struct clk_vco *vco = to_clk_vco(hw); | ||
234 | struct pll_rate_tbl *rtbl = vco->rtbl; | ||
235 | unsigned long flags = 0, val; | ||
236 | int i; | ||
237 | |||
238 | clk_round_rate_index(hw, drate, prate, vco_calc_rate, vco->rtbl_cnt, | ||
239 | &i); | ||
240 | |||
241 | if (vco->lock) | ||
242 | spin_lock_irqsave(vco->lock, flags); | ||
243 | |||
244 | val = readl_relaxed(vco->mode_reg); | ||
245 | val &= ~(PLL_MODE_MASK << PLL_MODE_SHIFT); | ||
246 | val |= (rtbl[i].mode & PLL_MODE_MASK) << PLL_MODE_SHIFT; | ||
247 | writel_relaxed(val, vco->mode_reg); | ||
248 | |||
249 | val = readl_relaxed(vco->cfg_reg); | ||
250 | val &= ~(PLL_DIV_N_MASK << PLL_DIV_N_SHIFT); | ||
251 | val |= (rtbl[i].n & PLL_DIV_N_MASK) << PLL_DIV_N_SHIFT; | ||
252 | |||
253 | val &= ~(PLL_DITH_FDBK_M_MASK << PLL_DITH_FDBK_M_SHIFT); | ||
254 | if (rtbl[i].mode) | ||
255 | val |= (rtbl[i].m & PLL_DITH_FDBK_M_MASK) << | ||
256 | PLL_DITH_FDBK_M_SHIFT; | ||
257 | else | ||
258 | val |= (rtbl[i].m & PLL_NORM_FDBK_M_MASK) << | ||
259 | PLL_NORM_FDBK_M_SHIFT; | ||
260 | |||
261 | writel_relaxed(val, vco->cfg_reg); | ||
262 | |||
263 | if (vco->lock) | ||
264 | spin_unlock_irqrestore(vco->lock, flags); | ||
265 | |||
266 | return 0; | ||
267 | } | ||
268 | |||
269 | static struct clk_ops clk_vco_ops = { | ||
270 | .recalc_rate = clk_vco_recalc_rate, | ||
271 | .round_rate = clk_vco_round_rate, | ||
272 | .set_rate = clk_vco_set_rate, | ||
273 | }; | ||
274 | |||
275 | struct clk *clk_register_vco_pll(const char *vco_name, const char *pll_name, | ||
276 | const char *vco_gate_name, const char *parent_name, | ||
277 | unsigned long flags, void __iomem *mode_reg, void __iomem | ||
278 | *cfg_reg, struct pll_rate_tbl *rtbl, u8 rtbl_cnt, | ||
279 | spinlock_t *lock, struct clk **pll_clk, | ||
280 | struct clk **vco_gate_clk) | ||
281 | { | ||
282 | struct clk_vco *vco; | ||
283 | struct clk_pll *pll; | ||
284 | struct clk *vco_clk, *tpll_clk, *tvco_gate_clk; | ||
285 | struct clk_init_data vco_init, pll_init; | ||
286 | const char **vco_parent_name; | ||
287 | |||
288 | if (!vco_name || !pll_name || !parent_name || !mode_reg || !cfg_reg || | ||
289 | !rtbl || !rtbl_cnt) { | ||
290 | pr_err("Invalid arguments passed"); | ||
291 | return ERR_PTR(-EINVAL); | ||
292 | } | ||
293 | |||
294 | vco = kzalloc(sizeof(*vco), GFP_KERNEL); | ||
295 | if (!vco) { | ||
296 | pr_err("could not allocate vco clk\n"); | ||
297 | return ERR_PTR(-ENOMEM); | ||
298 | } | ||
299 | |||
300 | pll = kzalloc(sizeof(*pll), GFP_KERNEL); | ||
301 | if (!pll) { | ||
302 | pr_err("could not allocate pll clk\n"); | ||
303 | goto free_vco; | ||
304 | } | ||
305 | |||
306 | /* struct clk_vco assignments */ | ||
307 | vco->mode_reg = mode_reg; | ||
308 | vco->cfg_reg = cfg_reg; | ||
309 | vco->rtbl = rtbl; | ||
310 | vco->rtbl_cnt = rtbl_cnt; | ||
311 | vco->lock = lock; | ||
312 | vco->hw.init = &vco_init; | ||
313 | |||
314 | pll->vco = vco; | ||
315 | pll->hw.init = &pll_init; | ||
316 | |||
317 | if (vco_gate_name) { | ||
318 | tvco_gate_clk = clk_register_gate(NULL, vco_gate_name, | ||
319 | parent_name, 0, mode_reg, PLL_ENABLE, 0, lock); | ||
320 | if (IS_ERR_OR_NULL(tvco_gate_clk)) | ||
321 | goto free_pll; | ||
322 | |||
323 | if (vco_gate_clk) | ||
324 | *vco_gate_clk = tvco_gate_clk; | ||
325 | vco_parent_name = &vco_gate_name; | ||
326 | } else { | ||
327 | vco_parent_name = &parent_name; | ||
328 | } | ||
329 | |||
330 | vco_init.name = vco_name; | ||
331 | vco_init.ops = &clk_vco_ops; | ||
332 | vco_init.flags = flags; | ||
333 | vco_init.parent_names = vco_parent_name; | ||
334 | vco_init.num_parents = 1; | ||
335 | |||
336 | pll_init.name = pll_name; | ||
337 | pll_init.ops = &clk_pll_ops; | ||
338 | pll_init.flags = CLK_SET_RATE_PARENT; | ||
339 | pll_init.parent_names = &vco_name; | ||
340 | pll_init.num_parents = 1; | ||
341 | |||
342 | vco_clk = clk_register(NULL, &vco->hw); | ||
343 | if (IS_ERR_OR_NULL(vco_clk)) | ||
344 | goto free_pll; | ||
345 | |||
346 | tpll_clk = clk_register(NULL, &pll->hw); | ||
347 | if (IS_ERR_OR_NULL(tpll_clk)) | ||
348 | goto free_pll; | ||
349 | |||
350 | if (pll_clk) | ||
351 | *pll_clk = tpll_clk; | ||
352 | |||
353 | return vco_clk; | ||
354 | |||
355 | free_pll: | ||
356 | kfree(pll); | ||
357 | free_vco: | ||
358 | kfree(vco); | ||
359 | |||
360 | pr_err("Failed to register vco pll clock\n"); | ||
361 | |||
362 | return ERR_PTR(-ENOMEM); | ||
363 | } | ||
diff --git a/drivers/clk/spear/clk.c b/drivers/clk/spear/clk.c new file mode 100644 index 000000000000..376d4e5ff326 --- /dev/null +++ b/drivers/clk/spear/clk.c | |||
@@ -0,0 +1,36 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2012 ST Microelectronics | ||
3 | * Viresh Kumar <viresh.kumar@st.com> | ||
4 | * | ||
5 | * This file is licensed under the terms of the GNU General Public | ||
6 | * License version 2. This program is licensed "as is" without any | ||
7 | * warranty of any kind, whether express or implied. | ||
8 | * | ||
9 | * SPEAr clk - Common routines | ||
10 | */ | ||
11 | |||
12 | #include <linux/clk-provider.h> | ||
13 | #include <linux/types.h> | ||
14 | #include "clk.h" | ||
15 | |||
16 | long clk_round_rate_index(struct clk_hw *hw, unsigned long drate, | ||
17 | unsigned long parent_rate, clk_calc_rate calc_rate, u8 rtbl_cnt, | ||
18 | int *index) | ||
19 | { | ||
20 | unsigned long prev_rate, rate = 0; | ||
21 | |||
22 | for (*index = 0; *index < rtbl_cnt; (*index)++) { | ||
23 | prev_rate = rate; | ||
24 | rate = calc_rate(hw, parent_rate, *index); | ||
25 | if (drate < rate) { | ||
26 | /* previous clock was best */ | ||
27 | if (*index) { | ||
28 | rate = prev_rate; | ||
29 | (*index)--; | ||
30 | } | ||
31 | break; | ||
32 | } | ||
33 | } | ||
34 | |||
35 | return rate; | ||
36 | } | ||
diff --git a/drivers/clk/spear/clk.h b/drivers/clk/spear/clk.h new file mode 100644 index 000000000000..9979b7f7e767 --- /dev/null +++ b/drivers/clk/spear/clk.h | |||
@@ -0,0 +1,58 @@ | |||
1 | /* | ||
2 | * Clock framework definitions for SPEAr platform | ||
3 | * | ||
4 | * Copyright (C) 2012 ST Microelectronics | ||
5 | * Viresh Kumar <viresh.kumar@st.com> | ||
6 | * | ||
7 | * This file is licensed under the terms of the GNU General Public | ||
8 | * License version 2. This program is licensed "as is" without any | ||
9 | * warranty of any kind, whether express or implied. | ||
10 | */ | ||
11 | |||
12 | #ifndef __SPEAR_CLK_H | ||
13 | #define __SPEAR_CLK_H | ||
14 | |||
15 | #include <linux/clk-provider.h> | ||
16 | #include <linux/spinlock_types.h> | ||
17 | #include <linux/types.h> | ||
18 | |||
19 | /* VCO-PLL clk */ | ||
20 | struct pll_rate_tbl { | ||
21 | u8 mode; | ||
22 | u16 m; | ||
23 | u8 n; | ||
24 | u8 p; | ||
25 | }; | ||
26 | |||
27 | struct clk_vco { | ||
28 | struct clk_hw hw; | ||
29 | void __iomem *mode_reg; | ||
30 | void __iomem *cfg_reg; | ||
31 | struct pll_rate_tbl *rtbl; | ||
32 | u8 rtbl_cnt; | ||
33 | spinlock_t *lock; | ||
34 | }; | ||
35 | |||
36 | struct clk_pll { | ||
37 | struct clk_hw hw; | ||
38 | struct clk_vco *vco; | ||
39 | const char *parent[1]; | ||
40 | spinlock_t *lock; | ||
41 | }; | ||
42 | |||
43 | typedef unsigned long (*clk_calc_rate)(struct clk_hw *hw, unsigned long prate, | ||
44 | int index); | ||
45 | |||
46 | /* clk register routines */ | ||
47 | struct clk *clk_register_vco_pll(const char *vco_name, const char *pll_name, | ||
48 | const char *vco_gate_name, const char *parent_name, | ||
49 | unsigned long flags, void __iomem *mode_reg, void __iomem | ||
50 | *cfg_reg, struct pll_rate_tbl *rtbl, u8 rtbl_cnt, | ||
51 | spinlock_t *lock, struct clk **pll_clk, | ||
52 | struct clk **vco_gate_clk); | ||
53 | |||
54 | long clk_round_rate_index(struct clk_hw *hw, unsigned long drate, | ||
55 | unsigned long parent_rate, clk_calc_rate calc_rate, u8 rtbl_cnt, | ||
56 | int *index); | ||
57 | |||
58 | #endif /* __SPEAR_CLK_H */ | ||