Commit | Line | Data |
---|---|---|
cf1c5fae SA |
1 | /* |
2 | * drivers/media/video/smiapp-pll.c | |
3 | * | |
4 | * Generic driver for SMIA/SMIA++ compliant camera modules | |
5 | * | |
6 | * Copyright (C) 2011--2012 Nokia Corporation | |
7 | * Contact: Sakari Ailus <sakari.ailus@maxwell.research.nokia.com> | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or | |
10 | * modify it under the terms of the GNU General Public License | |
11 | * version 2 as published by the Free Software Foundation. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, but | |
14 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | * General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | |
21 | * 02110-1301 USA | |
22 | * | |
23 | */ | |
24 | ||
25 | #include <linux/gcd.h> | |
26 | #include <linux/lcm.h> | |
27 | #include <linux/module.h> | |
28 | ||
29 | #include "smiapp-pll.h" | |
30 | ||
31 | /* Return an even number or one. */ | |
32 | static inline uint32_t clk_div_even(uint32_t a) | |
33 | { | |
34 | return max_t(uint32_t, 1, a & ~1); | |
35 | } | |
36 | ||
37 | /* Return an even number or one. */ | |
38 | static inline uint32_t clk_div_even_up(uint32_t a) | |
39 | { | |
40 | if (a == 1) | |
41 | return 1; | |
42 | return (a + 1) & ~1; | |
43 | } | |
44 | ||
45 | static inline uint32_t is_one_or_even(uint32_t a) | |
46 | { | |
47 | if (a == 1) | |
48 | return 1; | |
49 | if (a & 1) | |
50 | return 0; | |
51 | ||
52 | return 1; | |
53 | } | |
54 | ||
55 | static int bounds_check(struct device *dev, uint32_t val, | |
56 | uint32_t min, uint32_t max, char *str) | |
57 | { | |
58 | if (val >= min && val <= max) | |
59 | return 0; | |
60 | ||
61 | dev_warn(dev, "%s out of bounds: %d (%d--%d)\n", str, val, min, max); | |
62 | ||
63 | return -EINVAL; | |
64 | } | |
65 | ||
66 | static void print_pll(struct device *dev, struct smiapp_pll *pll) | |
67 | { | |
68 | dev_dbg(dev, "pre_pll_clk_div\t%d\n", pll->pre_pll_clk_div); | |
69 | dev_dbg(dev, "pll_multiplier \t%d\n", pll->pll_multiplier); | |
70 | if (pll->flags != SMIAPP_PLL_FLAG_NO_OP_CLOCKS) { | |
71 | dev_dbg(dev, "op_sys_clk_div \t%d\n", pll->op_sys_clk_div); | |
72 | dev_dbg(dev, "op_pix_clk_div \t%d\n", pll->op_pix_clk_div); | |
73 | } | |
74 | dev_dbg(dev, "vt_sys_clk_div \t%d\n", pll->vt_sys_clk_div); | |
75 | dev_dbg(dev, "vt_pix_clk_div \t%d\n", pll->vt_pix_clk_div); | |
76 | ||
77 | dev_dbg(dev, "ext_clk_freq_hz \t%d\n", pll->ext_clk_freq_hz); | |
78 | dev_dbg(dev, "pll_ip_clk_freq_hz \t%d\n", pll->pll_ip_clk_freq_hz); | |
79 | dev_dbg(dev, "pll_op_clk_freq_hz \t%d\n", pll->pll_op_clk_freq_hz); | |
80 | if (pll->flags & SMIAPP_PLL_FLAG_NO_OP_CLOCKS) { | |
81 | dev_dbg(dev, "op_sys_clk_freq_hz \t%d\n", | |
82 | pll->op_sys_clk_freq_hz); | |
83 | dev_dbg(dev, "op_pix_clk_freq_hz \t%d\n", | |
84 | pll->op_pix_clk_freq_hz); | |
85 | } | |
86 | dev_dbg(dev, "vt_sys_clk_freq_hz \t%d\n", pll->vt_sys_clk_freq_hz); | |
87 | dev_dbg(dev, "vt_pix_clk_freq_hz \t%d\n", pll->vt_pix_clk_freq_hz); | |
88 | } | |
89 | ||
90 | int smiapp_pll_calculate(struct device *dev, struct smiapp_pll_limits *limits, | |
91 | struct smiapp_pll *pll) | |
92 | { | |
93 | uint32_t sys_div; | |
94 | uint32_t best_pix_div = INT_MAX >> 1; | |
95 | uint32_t vt_op_binning_div; | |
96 | uint32_t lane_op_clock_ratio; | |
97 | uint32_t mul, div; | |
98 | uint32_t more_mul_min, more_mul_max; | |
99 | uint32_t more_mul_factor; | |
100 | uint32_t min_vt_div, max_vt_div, vt_div; | |
101 | uint32_t min_sys_div, max_sys_div; | |
102 | unsigned int i; | |
103 | int rval; | |
104 | ||
105 | if (pll->flags & SMIAPP_PLL_FLAG_OP_PIX_CLOCK_PER_LANE) | |
106 | lane_op_clock_ratio = pll->lanes; | |
107 | else | |
108 | lane_op_clock_ratio = 1; | |
109 | dev_dbg(dev, "lane_op_clock_ratio: %d\n", lane_op_clock_ratio); | |
110 | ||
111 | dev_dbg(dev, "binning: %dx%d\n", pll->binning_horizontal, | |
112 | pll->binning_vertical); | |
113 | ||
114 | /* CSI transfers 2 bits per clock per lane; thus times 2 */ | |
115 | pll->pll_op_clk_freq_hz = pll->link_freq * 2 | |
116 | * (pll->lanes / lane_op_clock_ratio); | |
117 | ||
118 | /* Figure out limits for pre-pll divider based on extclk */ | |
119 | dev_dbg(dev, "min / max pre_pll_clk_div: %d / %d\n", | |
120 | limits->min_pre_pll_clk_div, limits->max_pre_pll_clk_div); | |
121 | limits->max_pre_pll_clk_div = | |
122 | min_t(uint16_t, limits->max_pre_pll_clk_div, | |
123 | clk_div_even(pll->ext_clk_freq_hz / | |
124 | limits->min_pll_ip_freq_hz)); | |
125 | limits->min_pre_pll_clk_div = | |
126 | max_t(uint16_t, limits->min_pre_pll_clk_div, | |
127 | clk_div_even(pll->ext_clk_freq_hz / | |
128 | limits->max_pll_ip_freq_hz)); | |
129 | dev_dbg(dev, "pre-pll check: min / max pre_pll_clk_div: %d / %d\n", | |
130 | limits->min_pre_pll_clk_div, limits->max_pre_pll_clk_div); | |
131 | ||
132 | i = gcd(pll->pll_op_clk_freq_hz, pll->ext_clk_freq_hz); | |
133 | mul = div_u64(pll->pll_op_clk_freq_hz, i); | |
134 | div = pll->ext_clk_freq_hz / i; | |
135 | dev_dbg(dev, "mul %d / div %d\n", mul, div); | |
136 | ||
137 | limits->min_pre_pll_clk_div = | |
138 | max_t(uint16_t, limits->min_pre_pll_clk_div, | |
139 | clk_div_even_up( | |
140 | DIV_ROUND_UP(mul * pll->ext_clk_freq_hz, | |
141 | limits->max_pll_op_freq_hz))); | |
142 | dev_dbg(dev, "pll_op check: min / max pre_pll_clk_div: %d / %d\n", | |
143 | limits->min_pre_pll_clk_div, limits->max_pre_pll_clk_div); | |
144 | ||
145 | if (limits->min_pre_pll_clk_div > limits->max_pre_pll_clk_div) { | |
146 | dev_err(dev, "unable to compute pre_pll divisor\n"); | |
147 | return -EINVAL; | |
148 | } | |
149 | ||
150 | pll->pre_pll_clk_div = limits->min_pre_pll_clk_div; | |
151 | ||
152 | /* | |
153 | * Get pre_pll_clk_div so that our pll_op_clk_freq_hz won't be | |
154 | * too high. | |
155 | */ | |
156 | dev_dbg(dev, "pre_pll_clk_div %d\n", pll->pre_pll_clk_div); | |
157 | ||
158 | /* Don't go above max pll multiplier. */ | |
159 | more_mul_max = limits->max_pll_multiplier / mul; | |
160 | dev_dbg(dev, "more_mul_max: max_pll_multiplier check: %d\n", | |
161 | more_mul_max); | |
162 | /* Don't go above max pll op frequency. */ | |
163 | more_mul_max = | |
164 | min_t(int, | |
165 | more_mul_max, | |
166 | limits->max_pll_op_freq_hz | |
167 | / (pll->ext_clk_freq_hz / pll->pre_pll_clk_div * mul)); | |
168 | dev_dbg(dev, "more_mul_max: max_pll_op_freq_hz check: %d\n", | |
169 | more_mul_max); | |
170 | /* Don't go above the division capability of op sys clock divider. */ | |
171 | more_mul_max = min(more_mul_max, | |
172 | limits->max_op_sys_clk_div * pll->pre_pll_clk_div | |
173 | / div); | |
174 | dev_dbg(dev, "more_mul_max: max_op_sys_clk_div check: %d\n", | |
175 | more_mul_max); | |
176 | /* Ensure we won't go above min_pll_multiplier. */ | |
177 | more_mul_max = min(more_mul_max, | |
178 | DIV_ROUND_UP(limits->max_pll_multiplier, mul)); | |
179 | dev_dbg(dev, "more_mul_max: min_pll_multiplier check: %d\n", | |
180 | more_mul_max); | |
181 | ||
182 | /* Ensure we won't go below min_pll_op_freq_hz. */ | |
183 | more_mul_min = DIV_ROUND_UP(limits->min_pll_op_freq_hz, | |
184 | pll->ext_clk_freq_hz / pll->pre_pll_clk_div | |
185 | * mul); | |
186 | dev_dbg(dev, "more_mul_min: min_pll_op_freq_hz check: %d\n", | |
187 | more_mul_min); | |
188 | /* Ensure we won't go below min_pll_multiplier. */ | |
189 | more_mul_min = max(more_mul_min, | |
190 | DIV_ROUND_UP(limits->min_pll_multiplier, mul)); | |
191 | dev_dbg(dev, "more_mul_min: min_pll_multiplier check: %d\n", | |
192 | more_mul_min); | |
193 | ||
194 | if (more_mul_min > more_mul_max) { | |
195 | dev_warn(dev, | |
196 | "unable to compute more_mul_min and more_mul_max"); | |
197 | return -EINVAL; | |
198 | } | |
199 | ||
200 | more_mul_factor = lcm(div, pll->pre_pll_clk_div) / div; | |
201 | dev_dbg(dev, "more_mul_factor: %d\n", more_mul_factor); | |
202 | more_mul_factor = lcm(more_mul_factor, limits->min_op_sys_clk_div); | |
203 | dev_dbg(dev, "more_mul_factor: min_op_sys_clk_div: %d\n", | |
204 | more_mul_factor); | |
205 | i = roundup(more_mul_min, more_mul_factor); | |
206 | if (!is_one_or_even(i)) | |
207 | i <<= 1; | |
208 | ||
209 | dev_dbg(dev, "final more_mul: %d\n", i); | |
210 | if (i > more_mul_max) { | |
211 | dev_warn(dev, "final more_mul is bad, max %d", more_mul_max); | |
212 | return -EINVAL; | |
213 | } | |
214 | ||
215 | pll->pll_multiplier = mul * i; | |
216 | pll->op_sys_clk_div = div * i / pll->pre_pll_clk_div; | |
217 | dev_dbg(dev, "op_sys_clk_div: %d\n", pll->op_sys_clk_div); | |
218 | ||
219 | pll->pll_ip_clk_freq_hz = pll->ext_clk_freq_hz | |
220 | / pll->pre_pll_clk_div; | |
221 | ||
222 | pll->pll_op_clk_freq_hz = pll->pll_ip_clk_freq_hz | |
223 | * pll->pll_multiplier; | |
224 | ||
225 | /* Derive pll_op_clk_freq_hz. */ | |
226 | pll->op_sys_clk_freq_hz = | |
227 | pll->pll_op_clk_freq_hz / pll->op_sys_clk_div; | |
228 | ||
229 | pll->op_pix_clk_div = pll->bits_per_pixel; | |
230 | dev_dbg(dev, "op_pix_clk_div: %d\n", pll->op_pix_clk_div); | |
231 | ||
232 | pll->op_pix_clk_freq_hz = | |
233 | pll->op_sys_clk_freq_hz / pll->op_pix_clk_div; | |
234 | ||
235 | /* | |
236 | * Some sensors perform analogue binning and some do this | |
237 | * digitally. The ones doing this digitally can be roughly be | |
238 | * found out using this formula. The ones doing this digitally | |
239 | * should run at higher clock rate, so smaller divisor is used | |
240 | * on video timing side. | |
241 | */ | |
242 | if (limits->min_line_length_pck_bin > limits->min_line_length_pck | |
243 | / pll->binning_horizontal) | |
244 | vt_op_binning_div = pll->binning_horizontal; | |
245 | else | |
246 | vt_op_binning_div = 1; | |
247 | dev_dbg(dev, "vt_op_binning_div: %d\n", vt_op_binning_div); | |
248 | ||
249 | /* | |
250 | * Profile 2 supports vt_pix_clk_div E [4, 10] | |
251 | * | |
252 | * Horizontal binning can be used as a base for difference in | |
253 | * divisors. One must make sure that horizontal blanking is | |
254 | * enough to accommodate the CSI-2 sync codes. | |
255 | * | |
256 | * Take scaling factor into account as well. | |
257 | * | |
258 | * Find absolute limits for the factor of vt divider. | |
259 | */ | |
260 | dev_dbg(dev, "scale_m: %d\n", pll->scale_m); | |
261 | min_vt_div = DIV_ROUND_UP(pll->op_pix_clk_div * pll->op_sys_clk_div | |
262 | * pll->scale_n, | |
263 | lane_op_clock_ratio * vt_op_binning_div | |
264 | * pll->scale_m); | |
265 | ||
266 | /* Find smallest and biggest allowed vt divisor. */ | |
267 | dev_dbg(dev, "min_vt_div: %d\n", min_vt_div); | |
268 | min_vt_div = max(min_vt_div, | |
269 | DIV_ROUND_UP(pll->pll_op_clk_freq_hz, | |
270 | limits->max_vt_pix_clk_freq_hz)); | |
271 | dev_dbg(dev, "min_vt_div: max_vt_pix_clk_freq_hz: %d\n", | |
272 | min_vt_div); | |
273 | min_vt_div = max_t(uint32_t, min_vt_div, | |
274 | limits->min_vt_pix_clk_div | |
275 | * limits->min_vt_sys_clk_div); | |
276 | dev_dbg(dev, "min_vt_div: min_vt_clk_div: %d\n", min_vt_div); | |
277 | ||
278 | max_vt_div = limits->max_vt_sys_clk_div * limits->max_vt_pix_clk_div; | |
279 | dev_dbg(dev, "max_vt_div: %d\n", max_vt_div); | |
280 | max_vt_div = min(max_vt_div, | |
281 | DIV_ROUND_UP(pll->pll_op_clk_freq_hz, | |
282 | limits->min_vt_pix_clk_freq_hz)); | |
283 | dev_dbg(dev, "max_vt_div: min_vt_pix_clk_freq_hz: %d\n", | |
284 | max_vt_div); | |
285 | ||
286 | /* | |
287 | * Find limitsits for sys_clk_div. Not all values are possible | |
288 | * with all values of pix_clk_div. | |
289 | */ | |
290 | min_sys_div = limits->min_vt_sys_clk_div; | |
291 | dev_dbg(dev, "min_sys_div: %d\n", min_sys_div); | |
292 | min_sys_div = max(min_sys_div, | |
293 | DIV_ROUND_UP(min_vt_div, | |
294 | limits->max_vt_pix_clk_div)); | |
295 | dev_dbg(dev, "min_sys_div: max_vt_pix_clk_div: %d\n", min_sys_div); | |
296 | min_sys_div = max(min_sys_div, | |
297 | pll->pll_op_clk_freq_hz | |
298 | / limits->max_vt_sys_clk_freq_hz); | |
299 | dev_dbg(dev, "min_sys_div: max_pll_op_clk_freq_hz: %d\n", min_sys_div); | |
300 | min_sys_div = clk_div_even_up(min_sys_div); | |
301 | dev_dbg(dev, "min_sys_div: one or even: %d\n", min_sys_div); | |
302 | ||
303 | max_sys_div = limits->max_vt_sys_clk_div; | |
304 | dev_dbg(dev, "max_sys_div: %d\n", max_sys_div); | |
305 | max_sys_div = min(max_sys_div, | |
306 | DIV_ROUND_UP(max_vt_div, | |
307 | limits->min_vt_pix_clk_div)); | |
308 | dev_dbg(dev, "max_sys_div: min_vt_pix_clk_div: %d\n", max_sys_div); | |
309 | max_sys_div = min(max_sys_div, | |
310 | DIV_ROUND_UP(pll->pll_op_clk_freq_hz, | |
311 | limits->min_vt_pix_clk_freq_hz)); | |
312 | dev_dbg(dev, "max_sys_div: min_vt_pix_clk_freq_hz: %d\n", max_sys_div); | |
313 | ||
314 | /* | |
315 | * Find pix_div such that a legal pix_div * sys_div results | |
316 | * into a value which is not smaller than div, the desired | |
317 | * divisor. | |
318 | */ | |
319 | for (vt_div = min_vt_div; vt_div <= max_vt_div; | |
320 | vt_div += 2 - (vt_div & 1)) { | |
321 | for (sys_div = min_sys_div; | |
322 | sys_div <= max_sys_div; | |
323 | sys_div += 2 - (sys_div & 1)) { | |
324 | int pix_div = DIV_ROUND_UP(vt_div, sys_div); | |
325 | ||
326 | if (pix_div < limits->min_vt_pix_clk_div | |
327 | || pix_div > limits->max_vt_pix_clk_div) { | |
328 | dev_dbg(dev, | |
329 | "pix_div %d too small or too big (%d--%d)\n", | |
330 | pix_div, | |
331 | limits->min_vt_pix_clk_div, | |
332 | limits->max_vt_pix_clk_div); | |
333 | continue; | |
334 | } | |
335 | ||
336 | /* Check if this one is better. */ | |
337 | if (pix_div * sys_div | |
338 | <= roundup(min_vt_div, best_pix_div)) | |
339 | best_pix_div = pix_div; | |
340 | } | |
341 | if (best_pix_div < INT_MAX >> 1) | |
342 | break; | |
343 | } | |
344 | ||
345 | pll->vt_sys_clk_div = DIV_ROUND_UP(min_vt_div, best_pix_div); | |
346 | pll->vt_pix_clk_div = best_pix_div; | |
347 | ||
348 | pll->vt_sys_clk_freq_hz = | |
349 | pll->pll_op_clk_freq_hz / pll->vt_sys_clk_div; | |
350 | pll->vt_pix_clk_freq_hz = | |
351 | pll->vt_sys_clk_freq_hz / pll->vt_pix_clk_div; | |
352 | ||
353 | pll->pixel_rate_csi = | |
354 | pll->op_pix_clk_freq_hz * lane_op_clock_ratio; | |
355 | ||
356 | print_pll(dev, pll); | |
357 | ||
358 | rval = bounds_check(dev, pll->pre_pll_clk_div, | |
359 | limits->min_pre_pll_clk_div, | |
360 | limits->max_pre_pll_clk_div, "pre_pll_clk_div"); | |
361 | if (!rval) | |
362 | rval = bounds_check( | |
363 | dev, pll->pll_ip_clk_freq_hz, | |
364 | limits->min_pll_ip_freq_hz, limits->max_pll_ip_freq_hz, | |
365 | "pll_ip_clk_freq_hz"); | |
366 | if (!rval) | |
367 | rval = bounds_check( | |
368 | dev, pll->pll_multiplier, | |
369 | limits->min_pll_multiplier, limits->max_pll_multiplier, | |
370 | "pll_multiplier"); | |
371 | if (!rval) | |
372 | rval = bounds_check( | |
373 | dev, pll->pll_op_clk_freq_hz, | |
374 | limits->min_pll_op_freq_hz, limits->max_pll_op_freq_hz, | |
375 | "pll_op_clk_freq_hz"); | |
376 | if (!rval) | |
377 | rval = bounds_check( | |
378 | dev, pll->op_sys_clk_div, | |
379 | limits->min_op_sys_clk_div, limits->max_op_sys_clk_div, | |
380 | "op_sys_clk_div"); | |
381 | if (!rval) | |
382 | rval = bounds_check( | |
383 | dev, pll->op_pix_clk_div, | |
384 | limits->min_op_pix_clk_div, limits->max_op_pix_clk_div, | |
385 | "op_pix_clk_div"); | |
386 | if (!rval) | |
387 | rval = bounds_check( | |
388 | dev, pll->op_sys_clk_freq_hz, | |
389 | limits->min_op_sys_clk_freq_hz, | |
390 | limits->max_op_sys_clk_freq_hz, | |
391 | "op_sys_clk_freq_hz"); | |
392 | if (!rval) | |
393 | rval = bounds_check( | |
394 | dev, pll->op_pix_clk_freq_hz, | |
395 | limits->min_op_pix_clk_freq_hz, | |
396 | limits->max_op_pix_clk_freq_hz, | |
397 | "op_pix_clk_freq_hz"); | |
398 | if (!rval) | |
399 | rval = bounds_check( | |
400 | dev, pll->vt_sys_clk_freq_hz, | |
401 | limits->min_vt_sys_clk_freq_hz, | |
402 | limits->max_vt_sys_clk_freq_hz, | |
403 | "vt_sys_clk_freq_hz"); | |
404 | if (!rval) | |
405 | rval = bounds_check( | |
406 | dev, pll->vt_pix_clk_freq_hz, | |
407 | limits->min_vt_pix_clk_freq_hz, | |
408 | limits->max_vt_pix_clk_freq_hz, | |
409 | "vt_pix_clk_freq_hz"); | |
410 | ||
411 | return rval; | |
412 | } | |
413 | EXPORT_SYMBOL_GPL(smiapp_pll_calculate); | |
414 | ||
415 | MODULE_AUTHOR("Sakari Ailus <sakari.ailus@maxwell.research.nokia.com>"); | |
416 | MODULE_DESCRIPTION("Generic SMIA/SMIA++ PLL calculator"); | |
417 | MODULE_LICENSE("GPL"); |