1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright (C) 2016 Freescale Semiconductor, Inc. |
4 | * Copyright 2017~2018 NXP |
5 | * |
6 | * Author: Dong Aisheng <aisheng.dong@nxp.com> |
7 | * |
8 | */ |
9 | |
10 | #include <linux/bits.h> |
11 | #include <linux/clk-provider.h> |
12 | #include <linux/err.h> |
13 | #include <linux/io.h> |
14 | #include <linux/iopoll.h> |
15 | #include <linux/slab.h> |
16 | |
17 | #include "clk.h" |
18 | |
19 | /* PLL Control Status Register (xPLLCSR) */ |
20 | #define PLL_CSR_OFFSET 0x0 |
21 | #define PLL_VLD BIT(24) |
22 | #define PLL_EN BIT(0) |
23 | |
24 | /* PLL Configuration Register (xPLLCFG) */ |
25 | #define PLL_CFG_OFFSET 0x08 |
26 | #define IMX8ULP_PLL_CFG_OFFSET 0x10 |
27 | #define BP_PLL_MULT 16 |
28 | #define BM_PLL_MULT (0x7f << 16) |
29 | |
30 | /* PLL Numerator Register (xPLLNUM) */ |
31 | #define PLL_NUM_OFFSET 0x10 |
32 | #define IMX8ULP_PLL_NUM_OFFSET 0x1c |
33 | |
34 | /* PLL Denominator Register (xPLLDENOM) */ |
35 | #define PLL_DENOM_OFFSET 0x14 |
36 | #define IMX8ULP_PLL_DENOM_OFFSET 0x18 |
37 | |
38 | #define MAX_MFD 0x3fffffff |
39 | #define DEFAULT_MFD 1000000 |
40 | |
41 | struct clk_pllv4 { |
42 | struct clk_hw hw; |
43 | void __iomem *base; |
44 | u32 cfg_offset; |
45 | u32 num_offset; |
46 | u32 denom_offset; |
47 | bool use_mult_range; |
48 | }; |
49 | |
50 | /* Valid PLL MULT Table */ |
51 | static const int pllv4_mult_table[] = {33, 27, 22, 20, 17, 16}; |
52 | |
53 | /* Valid PLL MULT range, (max, min) */ |
54 | static const int pllv4_mult_range[] = {54, 27}; |
55 | |
56 | #define to_clk_pllv4(__hw) container_of(__hw, struct clk_pllv4, hw) |
57 | |
58 | #define LOCK_TIMEOUT_US USEC_PER_MSEC |
59 | |
60 | static inline int clk_pllv4_wait_lock(struct clk_pllv4 *pll) |
61 | { |
62 | u32 csr; |
63 | |
64 | return readl_poll_timeout(pll->base + PLL_CSR_OFFSET, |
65 | csr, csr & PLL_VLD, 0, LOCK_TIMEOUT_US); |
66 | } |
67 | |
68 | static int clk_pllv4_is_prepared(struct clk_hw *hw) |
69 | { |
70 | struct clk_pllv4 *pll = to_clk_pllv4(hw); |
71 | |
72 | if (readl_relaxed(pll->base) & PLL_EN) |
73 | return 1; |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | static unsigned long clk_pllv4_recalc_rate(struct clk_hw *hw, |
79 | unsigned long parent_rate) |
80 | { |
81 | struct clk_pllv4 *pll = to_clk_pllv4(hw); |
82 | u32 mult, mfn, mfd; |
83 | u64 temp64; |
84 | |
85 | mult = readl_relaxed(pll->base + pll->cfg_offset); |
86 | mult &= BM_PLL_MULT; |
87 | mult >>= BP_PLL_MULT; |
88 | |
89 | mfn = readl_relaxed(pll->base + pll->num_offset); |
90 | mfd = readl_relaxed(pll->base + pll->denom_offset); |
91 | temp64 = parent_rate; |
92 | temp64 *= mfn; |
93 | do_div(temp64, mfd); |
94 | |
95 | return (parent_rate * mult) + (u32)temp64; |
96 | } |
97 | |
98 | static long clk_pllv4_round_rate(struct clk_hw *hw, unsigned long rate, |
99 | unsigned long *prate) |
100 | { |
101 | struct clk_pllv4 *pll = to_clk_pllv4(hw); |
102 | unsigned long parent_rate = *prate; |
103 | unsigned long round_rate, i; |
104 | u32 mfn, mfd = DEFAULT_MFD; |
105 | bool found = false; |
106 | u64 temp64; |
107 | u32 mult; |
108 | |
109 | if (pll->use_mult_range) { |
110 | temp64 = (u64)rate; |
111 | do_div(temp64, parent_rate); |
112 | mult = temp64; |
113 | if (mult >= pllv4_mult_range[1] && |
114 | mult <= pllv4_mult_range[0]) { |
115 | round_rate = parent_rate * mult; |
116 | found = true; |
117 | } |
118 | } else { |
119 | for (i = 0; i < ARRAY_SIZE(pllv4_mult_table); i++) { |
120 | round_rate = parent_rate * pllv4_mult_table[i]; |
121 | if (rate >= round_rate) { |
122 | found = true; |
123 | break; |
124 | } |
125 | } |
126 | } |
127 | |
128 | if (!found) { |
129 | pr_warn("%s: unable to round rate %lu, parent rate %lu\n" , |
130 | clk_hw_get_name(hw), rate, parent_rate); |
131 | return 0; |
132 | } |
133 | |
134 | if (parent_rate <= MAX_MFD) |
135 | mfd = parent_rate; |
136 | |
137 | temp64 = (u64)(rate - round_rate); |
138 | temp64 *= mfd; |
139 | do_div(temp64, parent_rate); |
140 | mfn = temp64; |
141 | |
142 | /* |
143 | * NOTE: The value of numerator must always be configured to be |
144 | * less than the value of the denominator. If we can't get a proper |
145 | * pair of mfn/mfd, we simply return the round_rate without using |
146 | * the frac part. |
147 | */ |
148 | if (mfn >= mfd) |
149 | return round_rate; |
150 | |
151 | temp64 = (u64)parent_rate; |
152 | temp64 *= mfn; |
153 | do_div(temp64, mfd); |
154 | |
155 | return round_rate + (u32)temp64; |
156 | } |
157 | |
158 | static bool clk_pllv4_is_valid_mult(struct clk_pllv4 *pll, unsigned int mult) |
159 | { |
160 | int i; |
161 | |
162 | /* check if mult is in valid MULT table */ |
163 | if (pll->use_mult_range) { |
164 | if (mult >= pllv4_mult_range[1] && |
165 | mult <= pllv4_mult_range[0]) |
166 | return true; |
167 | } else { |
168 | for (i = 0; i < ARRAY_SIZE(pllv4_mult_table); i++) { |
169 | if (pllv4_mult_table[i] == mult) |
170 | return true; |
171 | } |
172 | } |
173 | |
174 | return false; |
175 | } |
176 | |
177 | static int clk_pllv4_set_rate(struct clk_hw *hw, unsigned long rate, |
178 | unsigned long parent_rate) |
179 | { |
180 | struct clk_pllv4 *pll = to_clk_pllv4(hw); |
181 | u32 val, mult, mfn, mfd = DEFAULT_MFD; |
182 | u64 temp64; |
183 | |
184 | mult = rate / parent_rate; |
185 | |
186 | if (!clk_pllv4_is_valid_mult(pll, mult)) |
187 | return -EINVAL; |
188 | |
189 | if (parent_rate <= MAX_MFD) |
190 | mfd = parent_rate; |
191 | |
192 | temp64 = (u64)(rate - mult * parent_rate); |
193 | temp64 *= mfd; |
194 | do_div(temp64, parent_rate); |
195 | mfn = temp64; |
196 | |
197 | val = readl_relaxed(pll->base + pll->cfg_offset); |
198 | val &= ~BM_PLL_MULT; |
199 | val |= mult << BP_PLL_MULT; |
200 | writel_relaxed(val, pll->base + pll->cfg_offset); |
201 | |
202 | writel_relaxed(mfn, pll->base + pll->num_offset); |
203 | writel_relaxed(mfd, pll->base + pll->denom_offset); |
204 | |
205 | return 0; |
206 | } |
207 | |
208 | static int clk_pllv4_prepare(struct clk_hw *hw) |
209 | { |
210 | u32 val; |
211 | struct clk_pllv4 *pll = to_clk_pllv4(hw); |
212 | |
213 | val = readl_relaxed(pll->base); |
214 | val |= PLL_EN; |
215 | writel_relaxed(val, pll->base); |
216 | |
217 | return clk_pllv4_wait_lock(pll); |
218 | } |
219 | |
220 | static void clk_pllv4_unprepare(struct clk_hw *hw) |
221 | { |
222 | u32 val; |
223 | struct clk_pllv4 *pll = to_clk_pllv4(hw); |
224 | |
225 | val = readl_relaxed(pll->base); |
226 | val &= ~PLL_EN; |
227 | writel_relaxed(val, pll->base); |
228 | } |
229 | |
230 | static const struct clk_ops clk_pllv4_ops = { |
231 | .recalc_rate = clk_pllv4_recalc_rate, |
232 | .round_rate = clk_pllv4_round_rate, |
233 | .set_rate = clk_pllv4_set_rate, |
234 | .prepare = clk_pllv4_prepare, |
235 | .unprepare = clk_pllv4_unprepare, |
236 | .is_prepared = clk_pllv4_is_prepared, |
237 | }; |
238 | |
239 | struct clk_hw *imx_clk_hw_pllv4(enum imx_pllv4_type type, const char *name, |
240 | const char *parent_name, void __iomem *base) |
241 | { |
242 | struct clk_pllv4 *pll; |
243 | struct clk_hw *hw; |
244 | struct clk_init_data init; |
245 | int ret; |
246 | |
247 | pll = kzalloc(size: sizeof(*pll), GFP_KERNEL); |
248 | if (!pll) |
249 | return ERR_PTR(error: -ENOMEM); |
250 | |
251 | pll->base = base; |
252 | |
253 | if (type == IMX_PLLV4_IMX8ULP || |
254 | type == IMX_PLLV4_IMX8ULP_1GHZ) { |
255 | pll->cfg_offset = IMX8ULP_PLL_CFG_OFFSET; |
256 | pll->num_offset = IMX8ULP_PLL_NUM_OFFSET; |
257 | pll->denom_offset = IMX8ULP_PLL_DENOM_OFFSET; |
258 | if (type == IMX_PLLV4_IMX8ULP_1GHZ) |
259 | pll->use_mult_range = true; |
260 | } else { |
261 | pll->cfg_offset = PLL_CFG_OFFSET; |
262 | pll->num_offset = PLL_NUM_OFFSET; |
263 | pll->denom_offset = PLL_DENOM_OFFSET; |
264 | } |
265 | |
266 | init.name = name; |
267 | init.ops = &clk_pllv4_ops; |
268 | init.parent_names = &parent_name; |
269 | init.num_parents = 1; |
270 | init.flags = CLK_SET_RATE_GATE; |
271 | |
272 | pll->hw.init = &init; |
273 | |
274 | hw = &pll->hw; |
275 | ret = clk_hw_register(NULL, hw); |
276 | if (ret) { |
277 | kfree(objp: pll); |
278 | hw = ERR_PTR(error: ret); |
279 | } |
280 | |
281 | return hw; |
282 | } |
283 | EXPORT_SYMBOL_GPL(imx_clk_hw_pllv4); |
284 | |