aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Boyd <sboyd@codeaurora.org>2015-11-30 20:31:39 -0500
committerStephen Boyd <sboyd@codeaurora.org>2015-11-30 21:24:25 -0500
commit8ff1f4c4c47676dfccd56b55104a15dcd4650a8f (patch)
treeefaca2715c4e62b8aa869de0bce9efd0f9cc639e
parentfab88ca788dcacf2fbb006d5663456cbd390ee18 (diff)
clk: qcom: Add Alpha PLL support
Add support for configuring rates of, enabling, and disabling Alpha PLLs. This is sufficient for the types of PLLs found in the global and multimedia clock controllers. Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
-rw-r--r--drivers/clk/qcom/Makefile1
-rw-r--r--drivers/clk/qcom/clk-alpha-pll.c355
-rw-r--r--drivers/clk/qcom/clk-alpha-pll.h57
3 files changed, 413 insertions, 0 deletions
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index fe6252349e55..472200040788 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -2,6 +2,7 @@ obj-$(CONFIG_COMMON_CLK_QCOM) += clk-qcom.o
2 2
3clk-qcom-y += common.o 3clk-qcom-y += common.o
4clk-qcom-y += clk-regmap.o 4clk-qcom-y += clk-regmap.o
5clk-qcom-y += clk-alpha-pll.o
5clk-qcom-y += clk-pll.o 6clk-qcom-y += clk-pll.o
6clk-qcom-y += clk-rcg.o 7clk-qcom-y += clk-rcg.o
7clk-qcom-y += clk-rcg2.o 8clk-qcom-y += clk-rcg2.o
diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c
new file mode 100644
index 000000000000..e6a03eaf7a93
--- /dev/null
+++ b/drivers/clk/qcom/clk-alpha-pll.c
@@ -0,0 +1,355 @@
1/*
2 * Copyright (c) 2015, The Linux Foundation. All rights reserved.
3 *
4 * This software is licensed under the terms of the GNU General Public
5 * License version 2, as published by the Free Software Foundation, and
6 * may be copied, distributed, and modified under those terms.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13
14#include <linux/kernel.h>
15#include <linux/export.h>
16#include <linux/clk-provider.h>
17#include <linux/regmap.h>
18#include <linux/delay.h>
19
20#include "clk-alpha-pll.h"
21
22#define PLL_MODE 0x00
23# define PLL_OUTCTRL BIT(0)
24# define PLL_BYPASSNL BIT(1)
25# define PLL_RESET_N BIT(2)
26# define PLL_LOCK_COUNT_SHIFT 8
27# define PLL_LOCK_COUNT_MASK 0x3f
28# define PLL_BIAS_COUNT_SHIFT 14
29# define PLL_BIAS_COUNT_MASK 0x3f
30# define PLL_VOTE_FSM_ENA BIT(20)
31# define PLL_VOTE_FSM_RESET BIT(21)
32# define PLL_ACTIVE_FLAG BIT(30)
33# define PLL_LOCK_DET BIT(31)
34
35#define PLL_L_VAL 0x04
36#define PLL_ALPHA_VAL 0x08
37#define PLL_ALPHA_VAL_U 0x0c
38
39#define PLL_USER_CTL 0x10
40# define PLL_POST_DIV_SHIFT 8
41# define PLL_POST_DIV_MASK 0xf
42# define PLL_ALPHA_EN BIT(24)
43# define PLL_VCO_SHIFT 20
44# define PLL_VCO_MASK 0x3
45
46#define PLL_USER_CTL_U 0x14
47
48#define PLL_CONFIG_CTL 0x18
49#define PLL_TEST_CTL 0x1c
50#define PLL_TEST_CTL_U 0x20
51#define PLL_STATUS 0x24
52
53/*
54 * Even though 40 bits are present, use only 32 for ease of calculation.
55 */
56#define ALPHA_REG_BITWIDTH 40
57#define ALPHA_BITWIDTH 32
58
59#define to_clk_alpha_pll(_hw) container_of(to_clk_regmap(_hw), \
60 struct clk_alpha_pll, clkr)
61
62#define to_clk_alpha_pll_postdiv(_hw) container_of(to_clk_regmap(_hw), \
63 struct clk_alpha_pll_postdiv, clkr)
64
65static int wait_for_pll(struct clk_alpha_pll *pll)
66{
67 u32 val, mask, off;
68 int count;
69 int ret;
70 const char *name = clk_hw_get_name(&pll->clkr.hw);
71
72 off = pll->offset;
73 ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val);
74 if (ret)
75 return ret;
76
77 if (val & PLL_VOTE_FSM_ENA)
78 mask = PLL_ACTIVE_FLAG;
79 else
80 mask = PLL_LOCK_DET;
81
82 /* Wait for pll to enable. */
83 for (count = 100; count > 0; count--) {
84 ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val);
85 if (ret)
86 return ret;
87 if ((val & mask) == mask)
88 return 0;
89
90 udelay(1);
91 }
92
93 WARN(1, "%s didn't enable after voting for it!\n", name);
94 return -ETIMEDOUT;
95}
96
97static int clk_alpha_pll_enable(struct clk_hw *hw)
98{
99 int ret;
100 struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
101 u32 val, mask, off;
102
103 off = pll->offset;
104
105 mask = PLL_OUTCTRL | PLL_RESET_N | PLL_BYPASSNL;
106 ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val);
107 if (ret)
108 return ret;
109
110 /* If in FSM mode, just vote for it */
111 if (val & PLL_VOTE_FSM_ENA) {
112 ret = clk_enable_regmap(hw);
113 if (ret)
114 return ret;
115 return wait_for_pll(pll);
116 }
117
118 /* Skip if already enabled */
119 if ((val & mask) == mask)
120 return 0;
121
122 ret = regmap_update_bits(pll->clkr.regmap, off + PLL_MODE,
123 PLL_BYPASSNL, PLL_BYPASSNL);
124 if (ret)
125 return ret;
126
127 /*
128 * H/W requires a 5us delay between disabling the bypass and
129 * de-asserting the reset.
130 */
131 mb();
132 udelay(5);
133
134 ret = regmap_update_bits(pll->clkr.regmap, off + PLL_MODE,
135 PLL_RESET_N, PLL_RESET_N);
136 if (ret)
137 return ret;
138
139 ret = wait_for_pll(pll);
140 if (ret)
141 return ret;
142
143 ret = regmap_update_bits(pll->clkr.regmap, off + PLL_MODE,
144 PLL_OUTCTRL, PLL_OUTCTRL);
145
146 /* Ensure that the write above goes through before returning. */
147 mb();
148 return ret;
149}
150
151static void clk_alpha_pll_disable(struct clk_hw *hw)
152{
153 int ret;
154 struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
155 u32 val, mask, off;
156
157 off = pll->offset;
158
159 ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val);
160 if (ret)
161 return;
162
163 /* If in FSM mode, just unvote it */
164 if (val & PLL_VOTE_FSM_ENA) {
165 clk_disable_regmap(hw);
166 return;
167 }
168
169 mask = PLL_OUTCTRL;
170 regmap_update_bits(pll->clkr.regmap, off + PLL_MODE, mask, 0);
171
172 /* Delay of 2 output clock ticks required until output is disabled */
173 mb();
174 udelay(1);
175
176 mask = PLL_RESET_N | PLL_BYPASSNL;
177 regmap_update_bits(pll->clkr.regmap, off + PLL_MODE, mask, 0);
178}
179
180static unsigned long alpha_pll_calc_rate(u64 prate, u32 l, u32 a)
181{
182 return (prate * l) + ((prate * a) >> ALPHA_BITWIDTH);
183}
184
185static unsigned long
186alpha_pll_round_rate(unsigned long rate, unsigned long prate, u32 *l, u64 *a)
187{
188 u64 remainder;
189 u64 quotient;
190
191 quotient = rate;
192 remainder = do_div(quotient, prate);
193 *l = quotient;
194
195 if (!remainder) {
196 *a = 0;
197 return rate;
198 }
199
200 /* Upper ALPHA_BITWIDTH bits of Alpha */
201 quotient = remainder << ALPHA_BITWIDTH;
202 remainder = do_div(quotient, prate);
203
204 if (remainder)
205 quotient++;
206
207 *a = quotient;
208 return alpha_pll_calc_rate(prate, *l, *a);
209}
210
211static const struct pll_vco *
212alpha_pll_find_vco(const struct clk_alpha_pll *pll, unsigned long rate)
213{
214 const struct pll_vco *v = pll->vco_table;
215 const struct pll_vco *end = v + pll->num_vco;
216
217 for (; v < end; v++)
218 if (rate >= v->min_freq && rate <= v->max_freq)
219 return v;
220
221 return NULL;
222}
223
224static unsigned long
225clk_alpha_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
226{
227 u32 l, low, high, ctl;
228 u64 a = 0, prate = parent_rate;
229 struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
230 u32 off = pll->offset;
231
232 regmap_read(pll->clkr.regmap, off + PLL_L_VAL, &l);
233
234 regmap_read(pll->clkr.regmap, off + PLL_USER_CTL, &ctl);
235 if (ctl & PLL_ALPHA_EN) {
236 regmap_read(pll->clkr.regmap, off + PLL_ALPHA_VAL, &low);
237 regmap_read(pll->clkr.regmap, off + PLL_ALPHA_VAL_U, &high);
238 a = (u64)high << 32 | low;
239 a >>= ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH;
240 }
241
242 return alpha_pll_calc_rate(prate, l, a);
243}
244
245static int clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate,
246 unsigned long prate)
247{
248 struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
249 const struct pll_vco *vco;
250 u32 l, off = pll->offset;
251 u64 a;
252
253 rate = alpha_pll_round_rate(rate, prate, &l, &a);
254 vco = alpha_pll_find_vco(pll, rate);
255 if (!vco) {
256 pr_err("alpha pll not in a valid vco range\n");
257 return -EINVAL;
258 }
259
260 a <<= (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH);
261
262 regmap_write(pll->clkr.regmap, off + PLL_L_VAL, l);
263 regmap_write(pll->clkr.regmap, off + PLL_ALPHA_VAL, a);
264 regmap_write(pll->clkr.regmap, off + PLL_ALPHA_VAL_U, a >> 32);
265
266 regmap_update_bits(pll->clkr.regmap, off + PLL_USER_CTL,
267 PLL_VCO_MASK << PLL_VCO_SHIFT,
268 vco->val << PLL_VCO_SHIFT);
269
270 regmap_update_bits(pll->clkr.regmap, off + PLL_USER_CTL, PLL_ALPHA_EN,
271 PLL_ALPHA_EN);
272
273 return 0;
274}
275
276static long clk_alpha_pll_round_rate(struct clk_hw *hw, unsigned long rate,
277 unsigned long *prate)
278{
279 struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
280 u32 l;
281 u64 a;
282 unsigned long min_freq, max_freq;
283
284 rate = alpha_pll_round_rate(rate, *prate, &l, &a);
285 if (alpha_pll_find_vco(pll, rate))
286 return rate;
287
288 min_freq = pll->vco_table[0].min_freq;
289 max_freq = pll->vco_table[pll->num_vco - 1].max_freq;
290
291 return clamp(rate, min_freq, max_freq);
292}
293
294const struct clk_ops clk_alpha_pll_ops = {
295 .enable = clk_alpha_pll_enable,
296 .disable = clk_alpha_pll_disable,
297 .recalc_rate = clk_alpha_pll_recalc_rate,
298 .round_rate = clk_alpha_pll_round_rate,
299 .set_rate = clk_alpha_pll_set_rate,
300};
301EXPORT_SYMBOL_GPL(clk_alpha_pll_ops);
302
303static unsigned long
304clk_alpha_pll_postdiv_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
305{
306 struct clk_alpha_pll_postdiv *pll = to_clk_alpha_pll_postdiv(hw);
307 u32 ctl;
308
309 regmap_read(pll->clkr.regmap, pll->offset + PLL_USER_CTL, &ctl);
310
311 ctl >>= PLL_POST_DIV_SHIFT;
312 ctl &= PLL_POST_DIV_MASK;
313
314 return parent_rate >> fls(ctl);
315}
316
317static const struct clk_div_table clk_alpha_div_table[] = {
318 { 0x0, 1 },
319 { 0x1, 2 },
320 { 0x3, 4 },
321 { 0x7, 8 },
322 { 0xf, 16 },
323 { }
324};
325
326static long
327clk_alpha_pll_postdiv_round_rate(struct clk_hw *hw, unsigned long rate,
328 unsigned long *prate)
329{
330 struct clk_alpha_pll_postdiv *pll = to_clk_alpha_pll_postdiv(hw);
331
332 return divider_round_rate(hw, rate, prate, clk_alpha_div_table,
333 pll->width, CLK_DIVIDER_POWER_OF_TWO);
334}
335
336static int clk_alpha_pll_postdiv_set_rate(struct clk_hw *hw, unsigned long rate,
337 unsigned long parent_rate)
338{
339 struct clk_alpha_pll_postdiv *pll = to_clk_alpha_pll_postdiv(hw);
340 int div;
341
342 /* 16 -> 0xf, 8 -> 0x7, 4 -> 0x3, 2 -> 0x1, 1 -> 0x0 */
343 div = DIV_ROUND_UP_ULL((u64)parent_rate, rate) - 1;
344
345 return regmap_update_bits(pll->clkr.regmap, pll->offset + PLL_USER_CTL,
346 PLL_POST_DIV_MASK << PLL_POST_DIV_SHIFT,
347 div << PLL_POST_DIV_SHIFT);
348}
349
350const struct clk_ops clk_alpha_pll_postdiv_ops = {
351 .recalc_rate = clk_alpha_pll_postdiv_recalc_rate,
352 .round_rate = clk_alpha_pll_postdiv_round_rate,
353 .set_rate = clk_alpha_pll_postdiv_set_rate,
354};
355EXPORT_SYMBOL_GPL(clk_alpha_pll_postdiv_ops);
diff --git a/drivers/clk/qcom/clk-alpha-pll.h b/drivers/clk/qcom/clk-alpha-pll.h
new file mode 100644
index 000000000000..90ce2016e1a0
--- /dev/null
+++ b/drivers/clk/qcom/clk-alpha-pll.h
@@ -0,0 +1,57 @@
1/*
2 * Copyright (c) 2015, The Linux Foundation. All rights reserved.
3 *
4 * This software is licensed under the terms of the GNU General Public
5 * License version 2, as published by the Free Software Foundation, and
6 * may be copied, distributed, and modified under those terms.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13
14#ifndef __QCOM_CLK_ALPHA_PLL_H__
15#define __QCOM_CLK_ALPHA_PLL_H__
16
17#include <linux/clk-provider.h>
18#include "clk-regmap.h"
19
20struct pll_vco {
21 unsigned long min_freq;
22 unsigned long max_freq;
23 u32 val;
24};
25
26/**
27 * struct clk_alpha_pll - phase locked loop (PLL)
28 * @offset: base address of registers
29 * @vco_table: array of VCO settings
30 * @clkr: regmap clock handle
31 */
32struct clk_alpha_pll {
33 u32 offset;
34
35 const struct pll_vco *vco_table;
36 size_t num_vco;
37
38 struct clk_regmap clkr;
39};
40
41/**
42 * struct clk_alpha_pll_postdiv - phase locked loop (PLL) post-divider
43 * @offset: base address of registers
44 * @width: width of post-divider
45 * @clkr: regmap clock handle
46 */
47struct clk_alpha_pll_postdiv {
48 u32 offset;
49 u8 width;
50
51 struct clk_regmap clkr;
52};
53
54extern const struct clk_ops clk_alpha_pll_ops;
55extern const struct clk_ops clk_alpha_pll_postdiv_ops;
56
57#endif