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, | |
6f367993 SA |
127 | clk_div_even_up( |
128 | DIV_ROUND_UP(pll->ext_clk_freq_hz, | |
129 | limits->max_pll_ip_freq_hz))); | |
cf1c5fae SA |
130 | dev_dbg(dev, "pre-pll check: min / max pre_pll_clk_div: %d / %d\n", |
131 | limits->min_pre_pll_clk_div, limits->max_pre_pll_clk_div); | |
132 | ||
133 | i = gcd(pll->pll_op_clk_freq_hz, pll->ext_clk_freq_hz); | |
134 | mul = div_u64(pll->pll_op_clk_freq_hz, i); | |
135 | div = pll->ext_clk_freq_hz / i; | |
136 | dev_dbg(dev, "mul %d / div %d\n", mul, div); | |
137 | ||
138 | limits->min_pre_pll_clk_div = | |
139 | max_t(uint16_t, limits->min_pre_pll_clk_div, | |
140 | clk_div_even_up( | |
141 | DIV_ROUND_UP(mul * pll->ext_clk_freq_hz, | |
142 | limits->max_pll_op_freq_hz))); | |
143 | dev_dbg(dev, "pll_op check: min / max pre_pll_clk_div: %d / %d\n", | |
144 | limits->min_pre_pll_clk_div, limits->max_pre_pll_clk_div); | |
145 | ||
146 | if (limits->min_pre_pll_clk_div > limits->max_pre_pll_clk_div) { | |
147 | dev_err(dev, "unable to compute pre_pll divisor\n"); | |
148 | return -EINVAL; | |
149 | } | |
150 | ||
151 | pll->pre_pll_clk_div = limits->min_pre_pll_clk_div; | |
152 | ||
153 | /* | |
154 | * Get pre_pll_clk_div so that our pll_op_clk_freq_hz won't be | |
155 | * too high. | |
156 | */ | |
157 | dev_dbg(dev, "pre_pll_clk_div %d\n", pll->pre_pll_clk_div); | |
158 | ||
159 | /* Don't go above max pll multiplier. */ | |
160 | more_mul_max = limits->max_pll_multiplier / mul; | |
161 | dev_dbg(dev, "more_mul_max: max_pll_multiplier check: %d\n", | |
162 | more_mul_max); | |
163 | /* Don't go above max pll op frequency. */ | |
164 | more_mul_max = | |
165 | min_t(int, | |
166 | more_mul_max, | |
167 | limits->max_pll_op_freq_hz | |
168 | / (pll->ext_clk_freq_hz / pll->pre_pll_clk_div * mul)); | |
169 | dev_dbg(dev, "more_mul_max: max_pll_op_freq_hz check: %d\n", | |
170 | more_mul_max); | |
171 | /* Don't go above the division capability of op sys clock divider. */ | |
172 | more_mul_max = min(more_mul_max, | |
173 | limits->max_op_sys_clk_div * pll->pre_pll_clk_div | |
174 | / div); | |
175 | dev_dbg(dev, "more_mul_max: max_op_sys_clk_div check: %d\n", | |
176 | more_mul_max); | |
177 | /* Ensure we won't go above min_pll_multiplier. */ | |
178 | more_mul_max = min(more_mul_max, | |
179 | DIV_ROUND_UP(limits->max_pll_multiplier, mul)); | |
180 | dev_dbg(dev, "more_mul_max: min_pll_multiplier check: %d\n", | |
181 | more_mul_max); | |
182 | ||
183 | /* Ensure we won't go below min_pll_op_freq_hz. */ | |
184 | more_mul_min = DIV_ROUND_UP(limits->min_pll_op_freq_hz, | |
185 | pll->ext_clk_freq_hz / pll->pre_pll_clk_div | |
186 | * mul); | |
187 | dev_dbg(dev, "more_mul_min: min_pll_op_freq_hz check: %d\n", | |
188 | more_mul_min); | |
189 | /* Ensure we won't go below min_pll_multiplier. */ | |
190 | more_mul_min = max(more_mul_min, | |
191 | DIV_ROUND_UP(limits->min_pll_multiplier, mul)); | |
192 | dev_dbg(dev, "more_mul_min: min_pll_multiplier check: %d\n", | |
193 | more_mul_min); | |
194 | ||
195 | if (more_mul_min > more_mul_max) { | |
196 | dev_warn(dev, | |
197 | "unable to compute more_mul_min and more_mul_max"); | |
198 | return -EINVAL; | |
199 | } | |
200 | ||
201 | more_mul_factor = lcm(div, pll->pre_pll_clk_div) / div; | |
202 | dev_dbg(dev, "more_mul_factor: %d\n", more_mul_factor); | |
203 | more_mul_factor = lcm(more_mul_factor, limits->min_op_sys_clk_div); | |
204 | dev_dbg(dev, "more_mul_factor: min_op_sys_clk_div: %d\n", | |
205 | more_mul_factor); | |
206 | i = roundup(more_mul_min, more_mul_factor); | |
207 | if (!is_one_or_even(i)) | |
208 | i <<= 1; | |
209 | ||
210 | dev_dbg(dev, "final more_mul: %d\n", i); | |
211 | if (i > more_mul_max) { | |
212 | dev_warn(dev, "final more_mul is bad, max %d", more_mul_max); | |
213 | return -EINVAL; | |
214 | } | |
215 | ||
216 | pll->pll_multiplier = mul * i; | |
217 | pll->op_sys_clk_div = div * i / pll->pre_pll_clk_div; | |
218 | dev_dbg(dev, "op_sys_clk_div: %d\n", pll->op_sys_clk_div); | |
219 | ||
220 | pll->pll_ip_clk_freq_hz = pll->ext_clk_freq_hz | |
221 | / pll->pre_pll_clk_div; | |
222 | ||
223 | pll->pll_op_clk_freq_hz = pll->pll_ip_clk_freq_hz | |
224 | * pll->pll_multiplier; | |
225 | ||
226 | /* Derive pll_op_clk_freq_hz. */ | |
227 | pll->op_sys_clk_freq_hz = | |
228 | pll->pll_op_clk_freq_hz / pll->op_sys_clk_div; | |
229 | ||
230 | pll->op_pix_clk_div = pll->bits_per_pixel; | |
231 | dev_dbg(dev, "op_pix_clk_div: %d\n", pll->op_pix_clk_div); | |
232 | ||
233 | pll->op_pix_clk_freq_hz = | |
234 | pll->op_sys_clk_freq_hz / pll->op_pix_clk_div; | |
235 | ||
236 | /* | |
237 | * Some sensors perform analogue binning and some do this | |
238 | * digitally. The ones doing this digitally can be roughly be | |
239 | * found out using this formula. The ones doing this digitally | |
240 | * should run at higher clock rate, so smaller divisor is used | |
241 | * on video timing side. | |
242 | */ | |
243 | if (limits->min_line_length_pck_bin > limits->min_line_length_pck | |
244 | / pll->binning_horizontal) | |
245 | vt_op_binning_div = pll->binning_horizontal; | |
246 | else | |
247 | vt_op_binning_div = 1; | |
248 | dev_dbg(dev, "vt_op_binning_div: %d\n", vt_op_binning_div); | |
249 | ||
250 | /* | |
251 | * Profile 2 supports vt_pix_clk_div E [4, 10] | |
252 | * | |
253 | * Horizontal binning can be used as a base for difference in | |
254 | * divisors. One must make sure that horizontal blanking is | |
255 | * enough to accommodate the CSI-2 sync codes. | |
256 | * | |
257 | * Take scaling factor into account as well. | |
258 | * | |
259 | * Find absolute limits for the factor of vt divider. | |
260 | */ | |
261 | dev_dbg(dev, "scale_m: %d\n", pll->scale_m); | |
262 | min_vt_div = DIV_ROUND_UP(pll->op_pix_clk_div * pll->op_sys_clk_div | |
263 | * pll->scale_n, | |
264 | lane_op_clock_ratio * vt_op_binning_div | |
265 | * pll->scale_m); | |
266 | ||
267 | /* Find smallest and biggest allowed vt divisor. */ | |
268 | dev_dbg(dev, "min_vt_div: %d\n", min_vt_div); | |
269 | min_vt_div = max(min_vt_div, | |
270 | DIV_ROUND_UP(pll->pll_op_clk_freq_hz, | |
271 | limits->max_vt_pix_clk_freq_hz)); | |
272 | dev_dbg(dev, "min_vt_div: max_vt_pix_clk_freq_hz: %d\n", | |
273 | min_vt_div); | |
274 | min_vt_div = max_t(uint32_t, min_vt_div, | |
275 | limits->min_vt_pix_clk_div | |
276 | * limits->min_vt_sys_clk_div); | |
277 | dev_dbg(dev, "min_vt_div: min_vt_clk_div: %d\n", min_vt_div); | |
278 | ||
279 | max_vt_div = limits->max_vt_sys_clk_div * limits->max_vt_pix_clk_div; | |
280 | dev_dbg(dev, "max_vt_div: %d\n", max_vt_div); | |
281 | max_vt_div = min(max_vt_div, | |
282 | DIV_ROUND_UP(pll->pll_op_clk_freq_hz, | |
283 | limits->min_vt_pix_clk_freq_hz)); | |
284 | dev_dbg(dev, "max_vt_div: min_vt_pix_clk_freq_hz: %d\n", | |
285 | max_vt_div); | |
286 | ||
287 | /* | |
288 | * Find limitsits for sys_clk_div. Not all values are possible | |
289 | * with all values of pix_clk_div. | |
290 | */ | |
291 | min_sys_div = limits->min_vt_sys_clk_div; | |
292 | dev_dbg(dev, "min_sys_div: %d\n", min_sys_div); | |
293 | min_sys_div = max(min_sys_div, | |
294 | DIV_ROUND_UP(min_vt_div, | |
295 | limits->max_vt_pix_clk_div)); | |
296 | dev_dbg(dev, "min_sys_div: max_vt_pix_clk_div: %d\n", min_sys_div); | |
297 | min_sys_div = max(min_sys_div, | |
298 | pll->pll_op_clk_freq_hz | |
299 | / limits->max_vt_sys_clk_freq_hz); | |
300 | dev_dbg(dev, "min_sys_div: max_pll_op_clk_freq_hz: %d\n", min_sys_div); | |
301 | min_sys_div = clk_div_even_up(min_sys_div); | |
302 | dev_dbg(dev, "min_sys_div: one or even: %d\n", min_sys_div); | |
303 | ||
304 | max_sys_div = limits->max_vt_sys_clk_div; | |
305 | dev_dbg(dev, "max_sys_div: %d\n", max_sys_div); | |
306 | max_sys_div = min(max_sys_div, | |
307 | DIV_ROUND_UP(max_vt_div, | |
308 | limits->min_vt_pix_clk_div)); | |
309 | dev_dbg(dev, "max_sys_div: min_vt_pix_clk_div: %d\n", max_sys_div); | |
310 | max_sys_div = min(max_sys_div, | |
311 | DIV_ROUND_UP(pll->pll_op_clk_freq_hz, | |
312 | limits->min_vt_pix_clk_freq_hz)); | |
313 | dev_dbg(dev, "max_sys_div: min_vt_pix_clk_freq_hz: %d\n", max_sys_div); | |
314 | ||
315 | /* | |
316 | * Find pix_div such that a legal pix_div * sys_div results | |
317 | * into a value which is not smaller than div, the desired | |
318 | * divisor. | |
319 | */ | |
320 | for (vt_div = min_vt_div; vt_div <= max_vt_div; | |
321 | vt_div += 2 - (vt_div & 1)) { | |
322 | for (sys_div = min_sys_div; | |
323 | sys_div <= max_sys_div; | |
324 | sys_div += 2 - (sys_div & 1)) { | |
325 | int pix_div = DIV_ROUND_UP(vt_div, sys_div); | |
326 | ||
327 | if (pix_div < limits->min_vt_pix_clk_div | |
328 | || pix_div > limits->max_vt_pix_clk_div) { | |
329 | dev_dbg(dev, | |
330 | "pix_div %d too small or too big (%d--%d)\n", | |
331 | pix_div, | |
332 | limits->min_vt_pix_clk_div, | |
333 | limits->max_vt_pix_clk_div); | |
334 | continue; | |
335 | } | |
336 | ||
337 | /* Check if this one is better. */ | |
338 | if (pix_div * sys_div | |
339 | <= roundup(min_vt_div, best_pix_div)) | |
340 | best_pix_div = pix_div; | |
341 | } | |
342 | if (best_pix_div < INT_MAX >> 1) | |
343 | break; | |
344 | } | |
345 | ||
346 | pll->vt_sys_clk_div = DIV_ROUND_UP(min_vt_div, best_pix_div); | |
347 | pll->vt_pix_clk_div = best_pix_div; | |
348 | ||
349 | pll->vt_sys_clk_freq_hz = | |
350 | pll->pll_op_clk_freq_hz / pll->vt_sys_clk_div; | |
351 | pll->vt_pix_clk_freq_hz = | |
352 | pll->vt_sys_clk_freq_hz / pll->vt_pix_clk_div; | |
353 | ||
354 | pll->pixel_rate_csi = | |
355 | pll->op_pix_clk_freq_hz * lane_op_clock_ratio; | |
356 | ||
357 | print_pll(dev, pll); | |
358 | ||
359 | rval = bounds_check(dev, pll->pre_pll_clk_div, | |
360 | limits->min_pre_pll_clk_div, | |
361 | limits->max_pre_pll_clk_div, "pre_pll_clk_div"); | |
362 | if (!rval) | |
363 | rval = bounds_check( | |
364 | dev, pll->pll_ip_clk_freq_hz, | |
365 | limits->min_pll_ip_freq_hz, limits->max_pll_ip_freq_hz, | |
366 | "pll_ip_clk_freq_hz"); | |
367 | if (!rval) | |
368 | rval = bounds_check( | |
369 | dev, pll->pll_multiplier, | |
370 | limits->min_pll_multiplier, limits->max_pll_multiplier, | |
371 | "pll_multiplier"); | |
372 | if (!rval) | |
373 | rval = bounds_check( | |
374 | dev, pll->pll_op_clk_freq_hz, | |
375 | limits->min_pll_op_freq_hz, limits->max_pll_op_freq_hz, | |
376 | "pll_op_clk_freq_hz"); | |
377 | if (!rval) | |
378 | rval = bounds_check( | |
379 | dev, pll->op_sys_clk_div, | |
380 | limits->min_op_sys_clk_div, limits->max_op_sys_clk_div, | |
381 | "op_sys_clk_div"); | |
382 | if (!rval) | |
383 | rval = bounds_check( | |
384 | dev, pll->op_pix_clk_div, | |
385 | limits->min_op_pix_clk_div, limits->max_op_pix_clk_div, | |
386 | "op_pix_clk_div"); | |
387 | if (!rval) | |
388 | rval = bounds_check( | |
389 | dev, pll->op_sys_clk_freq_hz, | |
390 | limits->min_op_sys_clk_freq_hz, | |
391 | limits->max_op_sys_clk_freq_hz, | |
392 | "op_sys_clk_freq_hz"); | |
393 | if (!rval) | |
394 | rval = bounds_check( | |
395 | dev, pll->op_pix_clk_freq_hz, | |
396 | limits->min_op_pix_clk_freq_hz, | |
397 | limits->max_op_pix_clk_freq_hz, | |
398 | "op_pix_clk_freq_hz"); | |
399 | if (!rval) | |
400 | rval = bounds_check( | |
401 | dev, pll->vt_sys_clk_freq_hz, | |
402 | limits->min_vt_sys_clk_freq_hz, | |
403 | limits->max_vt_sys_clk_freq_hz, | |
404 | "vt_sys_clk_freq_hz"); | |
405 | if (!rval) | |
406 | rval = bounds_check( | |
407 | dev, pll->vt_pix_clk_freq_hz, | |
408 | limits->min_vt_pix_clk_freq_hz, | |
409 | limits->max_vt_pix_clk_freq_hz, | |
410 | "vt_pix_clk_freq_hz"); | |
411 | ||
412 | return rval; | |
413 | } | |
414 | EXPORT_SYMBOL_GPL(smiapp_pll_calculate); | |
415 | ||
416 | MODULE_AUTHOR("Sakari Ailus <sakari.ailus@maxwell.research.nokia.com>"); | |
417 | MODULE_DESCRIPTION("Generic SMIA/SMIA++ PLL calculator"); | |
418 | MODULE_LICENSE("GPL"); |