diff options
author | Chunyan Zhang <chunyan.zhang@spreadtrum.com> | 2017-12-07 07:57:10 -0500 |
---|---|---|
committer | Stephen Boyd <sboyd@codeaurora.org> | 2017-12-21 18:00:53 -0500 |
commit | 3e37b005580b9db89d7f335e121d52d3bd58e234 (patch) | |
tree | 49012bb7f11db65920d47855a3a29cd8fac8ab9a | |
parent | 4fcba55cc621795caee6ba3503dbe70d10e268b2 (diff) |
clk: sprd: add adjustable pll support
Introduced a common adjustable pll clock driver for Spreadtrum SoCs.
Signed-off-by: Chunyan Zhang <chunyan.zhang@spreadtrum.com>
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
-rw-r--r-- | drivers/clk/sprd/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/sprd/pll.c | 266 | ||||
-rw-r--r-- | drivers/clk/sprd/pll.h | 108 |
3 files changed, 375 insertions, 0 deletions
diff --git a/drivers/clk/sprd/Makefile b/drivers/clk/sprd/Makefile index 2262e76750fd..d693969ff056 100644 --- a/drivers/clk/sprd/Makefile +++ b/drivers/clk/sprd/Makefile | |||
@@ -5,3 +5,4 @@ clk-sprd-y += gate.o | |||
5 | clk-sprd-y += mux.o | 5 | clk-sprd-y += mux.o |
6 | clk-sprd-y += div.o | 6 | clk-sprd-y += div.o |
7 | clk-sprd-y += composite.o | 7 | clk-sprd-y += composite.o |
8 | clk-sprd-y += pll.o | ||
diff --git a/drivers/clk/sprd/pll.c b/drivers/clk/sprd/pll.c new file mode 100644 index 000000000000..36b4402bf09e --- /dev/null +++ b/drivers/clk/sprd/pll.c | |||
@@ -0,0 +1,266 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | // | ||
3 | // Spreadtrum pll clock driver | ||
4 | // | ||
5 | // Copyright (C) 2015~2017 Spreadtrum, Inc. | ||
6 | // Author: Chunyan Zhang <chunyan.zhang@spreadtrum.com> | ||
7 | |||
8 | #include <linux/delay.h> | ||
9 | #include <linux/err.h> | ||
10 | #include <linux/regmap.h> | ||
11 | #include <linux/slab.h> | ||
12 | |||
13 | #include "pll.h" | ||
14 | |||
15 | #define CLK_PLL_1M 1000000 | ||
16 | #define CLK_PLL_10M (CLK_PLL_1M * 10) | ||
17 | |||
18 | #define pindex(pll, member) \ | ||
19 | (pll->factors[member].shift / (8 * sizeof(pll->regs_num))) | ||
20 | |||
21 | #define pshift(pll, member) \ | ||
22 | (pll->factors[member].shift % (8 * sizeof(pll->regs_num))) | ||
23 | |||
24 | #define pwidth(pll, member) \ | ||
25 | pll->factors[member].width | ||
26 | |||
27 | #define pmask(pll, member) \ | ||
28 | ((pwidth(pll, member)) ? \ | ||
29 | GENMASK(pwidth(pll, member) + pshift(pll, member) - 1, \ | ||
30 | pshift(pll, member)) : 0) | ||
31 | |||
32 | #define pinternal(pll, cfg, member) \ | ||
33 | (cfg[pindex(pll, member)] & pmask(pll, member)) | ||
34 | |||
35 | #define pinternal_val(pll, cfg, member) \ | ||
36 | (pinternal(pll, cfg, member) >> pshift(pll, member)) | ||
37 | |||
38 | static inline unsigned int | ||
39 | sprd_pll_read(const struct sprd_pll *pll, u8 index) | ||
40 | { | ||
41 | const struct sprd_clk_common *common = &pll->common; | ||
42 | unsigned int val = 0; | ||
43 | |||
44 | if (WARN_ON(index >= pll->regs_num)) | ||
45 | return 0; | ||
46 | |||
47 | regmap_read(common->regmap, common->reg + index * 4, &val); | ||
48 | |||
49 | return val; | ||
50 | } | ||
51 | |||
52 | static inline void | ||
53 | sprd_pll_write(const struct sprd_pll *pll, u8 index, | ||
54 | u32 msk, u32 val) | ||
55 | { | ||
56 | const struct sprd_clk_common *common = &pll->common; | ||
57 | unsigned int offset, reg; | ||
58 | int ret = 0; | ||
59 | |||
60 | if (WARN_ON(index >= pll->regs_num)) | ||
61 | return; | ||
62 | |||
63 | offset = common->reg + index * 4; | ||
64 | ret = regmap_read(common->regmap, offset, ®); | ||
65 | if (!ret) | ||
66 | regmap_write(common->regmap, offset, (reg & ~msk) | val); | ||
67 | } | ||
68 | |||
69 | static unsigned long pll_get_refin(const struct sprd_pll *pll) | ||
70 | { | ||
71 | u32 shift, mask, index, refin_id = 3; | ||
72 | const unsigned long refin[4] = { 2, 4, 13, 26 }; | ||
73 | |||
74 | if (pwidth(pll, PLL_REFIN)) { | ||
75 | index = pindex(pll, PLL_REFIN); | ||
76 | shift = pshift(pll, PLL_REFIN); | ||
77 | mask = pmask(pll, PLL_REFIN); | ||
78 | refin_id = (sprd_pll_read(pll, index) & mask) >> shift; | ||
79 | if (refin_id > 3) | ||
80 | refin_id = 3; | ||
81 | } | ||
82 | |||
83 | return refin[refin_id]; | ||
84 | } | ||
85 | |||
86 | static u32 pll_get_ibias(u64 rate, const u64 *table) | ||
87 | { | ||
88 | u32 i, num = table[0]; | ||
89 | |||
90 | for (i = 1; i < num + 1; i++) | ||
91 | if (rate <= table[i]) | ||
92 | break; | ||
93 | |||
94 | return (i == num + 1) ? num : i; | ||
95 | } | ||
96 | |||
97 | static unsigned long _sprd_pll_recalc_rate(const struct sprd_pll *pll, | ||
98 | unsigned long parent_rate) | ||
99 | { | ||
100 | u32 *cfg; | ||
101 | u32 i, mask, regs_num = pll->regs_num; | ||
102 | unsigned long rate, nint, kint = 0; | ||
103 | u64 refin; | ||
104 | u16 k1, k2; | ||
105 | |||
106 | cfg = kcalloc(regs_num, sizeof(*cfg), GFP_KERNEL); | ||
107 | if (!cfg) | ||
108 | return -ENOMEM; | ||
109 | |||
110 | for (i = 0; i < regs_num; i++) | ||
111 | cfg[i] = sprd_pll_read(pll, i); | ||
112 | |||
113 | refin = pll_get_refin(pll); | ||
114 | |||
115 | if (pinternal(pll, cfg, PLL_PREDIV)) | ||
116 | refin = refin * 2; | ||
117 | |||
118 | if (pwidth(pll, PLL_POSTDIV) && | ||
119 | ((pll->fflag == 1 && pinternal(pll, cfg, PLL_POSTDIV)) || | ||
120 | (!pll->fflag && !pinternal(pll, cfg, PLL_POSTDIV)))) | ||
121 | refin = refin / 2; | ||
122 | |||
123 | if (!pinternal(pll, cfg, PLL_DIV_S)) { | ||
124 | rate = refin * pinternal_val(pll, cfg, PLL_N) * CLK_PLL_10M; | ||
125 | } else { | ||
126 | nint = pinternal_val(pll, cfg, PLL_NINT); | ||
127 | if (pinternal(pll, cfg, PLL_SDM_EN)) | ||
128 | kint = pinternal_val(pll, cfg, PLL_KINT); | ||
129 | |||
130 | mask = pmask(pll, PLL_KINT); | ||
131 | |||
132 | k1 = pll->k1; | ||
133 | k2 = pll->k2; | ||
134 | rate = DIV_ROUND_CLOSEST_ULL(refin * kint * k1, | ||
135 | ((mask >> __ffs(mask)) + 1)) * | ||
136 | k2 + refin * nint * CLK_PLL_1M; | ||
137 | } | ||
138 | |||
139 | return rate; | ||
140 | } | ||
141 | |||
142 | #define SPRD_PLL_WRITE_CHECK(pll, i, mask, val) \ | ||
143 | (((sprd_pll_read(pll, i) & mask) == val) ? 0 : (-EFAULT)) | ||
144 | |||
145 | static int _sprd_pll_set_rate(const struct sprd_pll *pll, | ||
146 | unsigned long rate, | ||
147 | unsigned long parent_rate) | ||
148 | { | ||
149 | struct reg_cfg *cfg; | ||
150 | int ret = 0; | ||
151 | u32 mask, shift, width, ibias_val, index; | ||
152 | u32 regs_num = pll->regs_num, i = 0; | ||
153 | unsigned long kint, nint; | ||
154 | u64 tmp, refin, fvco = rate; | ||
155 | |||
156 | cfg = kcalloc(regs_num, sizeof(*cfg), GFP_KERNEL); | ||
157 | if (!cfg) | ||
158 | return -ENOMEM; | ||
159 | |||
160 | refin = pll_get_refin(pll); | ||
161 | |||
162 | mask = pmask(pll, PLL_PREDIV); | ||
163 | index = pindex(pll, PLL_PREDIV); | ||
164 | width = pwidth(pll, PLL_PREDIV); | ||
165 | if (width && (sprd_pll_read(pll, index) & mask)) | ||
166 | refin = refin * 2; | ||
167 | |||
168 | mask = pmask(pll, PLL_POSTDIV); | ||
169 | index = pindex(pll, PLL_POSTDIV); | ||
170 | width = pwidth(pll, PLL_POSTDIV); | ||
171 | cfg[index].msk = mask; | ||
172 | if (width && ((pll->fflag == 1 && fvco <= pll->fvco) || | ||
173 | (pll->fflag == 0 && fvco > pll->fvco))) | ||
174 | cfg[index].val |= mask; | ||
175 | |||
176 | if (width && fvco <= pll->fvco) | ||
177 | fvco = fvco * 2; | ||
178 | |||
179 | mask = pmask(pll, PLL_DIV_S); | ||
180 | index = pindex(pll, PLL_DIV_S); | ||
181 | cfg[index].val |= mask; | ||
182 | cfg[index].msk |= mask; | ||
183 | |||
184 | mask = pmask(pll, PLL_SDM_EN); | ||
185 | index = pindex(pll, PLL_SDM_EN); | ||
186 | cfg[index].val |= mask; | ||
187 | cfg[index].msk |= mask; | ||
188 | |||
189 | nint = do_div(fvco, refin * CLK_PLL_1M); | ||
190 | mask = pmask(pll, PLL_NINT); | ||
191 | index = pindex(pll, PLL_NINT); | ||
192 | shift = pshift(pll, PLL_NINT); | ||
193 | cfg[index].val |= (nint << shift) & mask; | ||
194 | cfg[index].msk |= mask; | ||
195 | |||
196 | mask = pmask(pll, PLL_KINT); | ||
197 | index = pindex(pll, PLL_KINT); | ||
198 | width = pwidth(pll, PLL_KINT); | ||
199 | shift = pshift(pll, PLL_KINT); | ||
200 | tmp = fvco - refin * nint * CLK_PLL_1M; | ||
201 | tmp = do_div(tmp, 10000) * ((mask >> shift) + 1); | ||
202 | kint = DIV_ROUND_CLOSEST_ULL(tmp, refin * 100); | ||
203 | cfg[index].val |= (kint << shift) & mask; | ||
204 | cfg[index].msk |= mask; | ||
205 | |||
206 | ibias_val = pll_get_ibias(fvco, pll->itable); | ||
207 | |||
208 | mask = pmask(pll, PLL_IBIAS); | ||
209 | index = pindex(pll, PLL_IBIAS); | ||
210 | shift = pshift(pll, PLL_IBIAS); | ||
211 | cfg[index].val |= ibias_val << shift & mask; | ||
212 | cfg[index].msk |= mask; | ||
213 | |||
214 | for (i = 0; i < regs_num; i++) { | ||
215 | if (cfg[i].msk) { | ||
216 | sprd_pll_write(pll, i, cfg[i].msk, cfg[i].val); | ||
217 | ret |= SPRD_PLL_WRITE_CHECK(pll, i, cfg[i].msk, | ||
218 | cfg[i].val); | ||
219 | } | ||
220 | } | ||
221 | |||
222 | if (!ret) | ||
223 | udelay(pll->udelay); | ||
224 | |||
225 | return ret; | ||
226 | } | ||
227 | |||
228 | static unsigned long sprd_pll_recalc_rate(struct clk_hw *hw, | ||
229 | unsigned long parent_rate) | ||
230 | { | ||
231 | struct sprd_pll *pll = hw_to_sprd_pll(hw); | ||
232 | |||
233 | return _sprd_pll_recalc_rate(pll, parent_rate); | ||
234 | } | ||
235 | |||
236 | static int sprd_pll_set_rate(struct clk_hw *hw, | ||
237 | unsigned long rate, | ||
238 | unsigned long parent_rate) | ||
239 | { | ||
240 | struct sprd_pll *pll = hw_to_sprd_pll(hw); | ||
241 | |||
242 | return _sprd_pll_set_rate(pll, rate, parent_rate); | ||
243 | } | ||
244 | |||
245 | static int sprd_pll_clk_prepare(struct clk_hw *hw) | ||
246 | { | ||
247 | struct sprd_pll *pll = hw_to_sprd_pll(hw); | ||
248 | |||
249 | udelay(pll->udelay); | ||
250 | |||
251 | return 0; | ||
252 | } | ||
253 | |||
254 | static long sprd_pll_round_rate(struct clk_hw *hw, unsigned long rate, | ||
255 | unsigned long *prate) | ||
256 | { | ||
257 | return rate; | ||
258 | } | ||
259 | |||
260 | const struct clk_ops sprd_pll_ops = { | ||
261 | .prepare = sprd_pll_clk_prepare, | ||
262 | .recalc_rate = sprd_pll_recalc_rate, | ||
263 | .round_rate = sprd_pll_round_rate, | ||
264 | .set_rate = sprd_pll_set_rate, | ||
265 | }; | ||
266 | EXPORT_SYMBOL_GPL(sprd_pll_ops); | ||
diff --git a/drivers/clk/sprd/pll.h b/drivers/clk/sprd/pll.h new file mode 100644 index 000000000000..514175621099 --- /dev/null +++ b/drivers/clk/sprd/pll.h | |||
@@ -0,0 +1,108 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | // | ||
3 | // Spreadtrum pll clock driver | ||
4 | // | ||
5 | // Copyright (C) 2015~2017 Spreadtrum, Inc. | ||
6 | // Author: Chunyan Zhang <chunyan.zhang@spreadtrum.com> | ||
7 | |||
8 | #ifndef _SPRD_PLL_H_ | ||
9 | #define _SPRD_PLL_H_ | ||
10 | |||
11 | #include "common.h" | ||
12 | |||
13 | struct reg_cfg { | ||
14 | u32 val; | ||
15 | u32 msk; | ||
16 | }; | ||
17 | |||
18 | struct clk_bit_field { | ||
19 | u8 shift; | ||
20 | u8 width; | ||
21 | }; | ||
22 | |||
23 | enum { | ||
24 | PLL_LOCK_DONE, | ||
25 | PLL_DIV_S, | ||
26 | PLL_MOD_EN, | ||
27 | PLL_SDM_EN, | ||
28 | PLL_REFIN, | ||
29 | PLL_IBIAS, | ||
30 | PLL_N, | ||
31 | PLL_NINT, | ||
32 | PLL_KINT, | ||
33 | PLL_PREDIV, | ||
34 | PLL_POSTDIV, | ||
35 | |||
36 | PLL_FACT_MAX | ||
37 | }; | ||
38 | |||
39 | /* | ||
40 | * struct sprd_pll - definition of adjustable pll clock | ||
41 | * | ||
42 | * @reg: registers used to set the configuration of pll clock, | ||
43 | * reg[0] shows how many registers this pll clock uses. | ||
44 | * @itable: pll ibias table, itable[0] means how many items this | ||
45 | * table includes | ||
46 | * @udelay delay time after setting rate | ||
47 | * @factors used to calculate the pll clock rate | ||
48 | * @fvco: fvco threshold rate | ||
49 | * @fflag: fvco flag | ||
50 | */ | ||
51 | struct sprd_pll { | ||
52 | u32 regs_num; | ||
53 | const u64 *itable; | ||
54 | const struct clk_bit_field *factors; | ||
55 | u16 udelay; | ||
56 | u16 k1; | ||
57 | u16 k2; | ||
58 | u16 fflag; | ||
59 | u64 fvco; | ||
60 | |||
61 | struct sprd_clk_common common; | ||
62 | }; | ||
63 | |||
64 | #define SPRD_PLL_WITH_ITABLE_K_FVCO(_struct, _name, _parent, _reg, \ | ||
65 | _regs_num, _itable, _factors, \ | ||
66 | _udelay, _k1, _k2, _fflag, _fvco) \ | ||
67 | struct sprd_pll _struct = { \ | ||
68 | .regs_num = _regs_num, \ | ||
69 | .itable = _itable, \ | ||
70 | .factors = _factors, \ | ||
71 | .udelay = _udelay, \ | ||
72 | .k1 = _k1, \ | ||
73 | .k2 = _k2, \ | ||
74 | .fflag = _fflag, \ | ||
75 | .fvco = _fvco, \ | ||
76 | .common = { \ | ||
77 | .regmap = NULL, \ | ||
78 | .reg = _reg, \ | ||
79 | .hw.init = CLK_HW_INIT(_name, \ | ||
80 | _parent, \ | ||
81 | &sprd_pll_ops, \ | ||
82 | 0), \ | ||
83 | }, \ | ||
84 | } | ||
85 | |||
86 | #define SPRD_PLL_WITH_ITABLE_K(_struct, _name, _parent, _reg, \ | ||
87 | _regs_num, _itable, _factors, \ | ||
88 | _udelay, _k1, _k2) \ | ||
89 | SPRD_PLL_WITH_ITABLE_K_FVCO(_struct, _name, _parent, _reg, \ | ||
90 | _regs_num, _itable, _factors, \ | ||
91 | _udelay, _k1, _k2, 0, 0) | ||
92 | |||
93 | #define SPRD_PLL_WITH_ITABLE_1K(_struct, _name, _parent, _reg, \ | ||
94 | _regs_num, _itable, _factors, _udelay) \ | ||
95 | SPRD_PLL_WITH_ITABLE_K_FVCO(_struct, _name, _parent, _reg, \ | ||
96 | _regs_num, _itable, _factors, \ | ||
97 | _udelay, 1000, 1000, 0, 0) | ||
98 | |||
99 | static inline struct sprd_pll *hw_to_sprd_pll(struct clk_hw *hw) | ||
100 | { | ||
101 | struct sprd_clk_common *common = hw_to_sprd_clk_common(hw); | ||
102 | |||
103 | return container_of(common, struct sprd_pll, common); | ||
104 | } | ||
105 | |||
106 | extern const struct clk_ops sprd_pll_ops; | ||
107 | |||
108 | #endif /* _SPRD_PLL_H_ */ | ||