]>
Commit | Line | Data |
---|---|---|
d006af40 AF |
1 | From 369b60c157f67a71a6f302ab9843ae2de1805a2a Mon Sep 17 00:00:00 2001 |
2 | From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> | |
3 | Date: Mon, 22 Oct 2012 15:43:00 +0200 | |
4 | Subject: [PATCH 1/6] omap3isp: Use the common clock framework | |
5 | ||
6 | Expose the two ISP external clocks XCLKA and XCLKB as common clocks for | |
7 | subdev drivers. | |
8 | ||
9 | Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> | |
10 | Acked-by: Mike Turquette <mturquette@linaro.org> | |
11 | --- | |
12 | drivers/media/platform/omap3isp/isp.c | 277 ++++++++++++++++++++++++--------- | |
13 | drivers/media/platform/omap3isp/isp.h | 22 ++- | |
14 | include/media/omap3isp.h | 10 +- | |
15 | 3 files changed, 225 insertions(+), 84 deletions(-) | |
16 | ||
17 | diff --git a/drivers/media/platform/omap3isp/isp.c b/drivers/media/platform/omap3isp/isp.c | |
18 | index 6e5ad8e..1d7dbd5 100644 | |
19 | --- a/drivers/media/platform/omap3isp/isp.c | |
20 | +++ b/drivers/media/platform/omap3isp/isp.c | |
21 | @@ -55,6 +55,7 @@ | |
22 | #include <asm/cacheflush.h> | |
23 | ||
24 | #include <linux/clk.h> | |
25 | +#include <linux/clkdev.h> | |
26 | #include <linux/delay.h> | |
27 | #include <linux/device.h> | |
28 | #include <linux/dma-mapping.h> | |
29 | @@ -148,6 +149,201 @@ void omap3isp_flush(struct isp_device *isp) | |
30 | isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_REVISION); | |
31 | } | |
32 | ||
33 | +/* ----------------------------------------------------------------------------- | |
34 | + * XCLK | |
35 | + */ | |
36 | + | |
37 | +#define to_isp_xclk(_hw) container_of(_hw, struct isp_xclk, hw) | |
38 | + | |
39 | +static void isp_xclk_update(struct isp_xclk *xclk, u32 divider) | |
40 | +{ | |
41 | + switch (xclk->id) { | |
42 | + case ISP_XCLK_A: | |
43 | + isp_reg_clr_set(xclk->isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL, | |
44 | + ISPTCTRL_CTRL_DIVA_MASK, | |
45 | + divider << ISPTCTRL_CTRL_DIVA_SHIFT); | |
46 | + break; | |
47 | + case ISP_XCLK_B: | |
48 | + isp_reg_clr_set(xclk->isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL, | |
49 | + ISPTCTRL_CTRL_DIVB_MASK, | |
50 | + divider << ISPTCTRL_CTRL_DIVB_SHIFT); | |
51 | + break; | |
52 | + } | |
53 | +} | |
54 | + | |
55 | +static int isp_xclk_prepare(struct clk_hw *hw) | |
56 | +{ | |
57 | + struct isp_xclk *xclk = to_isp_xclk(hw); | |
58 | + | |
59 | + omap3isp_get(xclk->isp); | |
60 | + | |
61 | + return 0; | |
62 | +} | |
63 | + | |
64 | +static void isp_xclk_unprepare(struct clk_hw *hw) | |
65 | +{ | |
66 | + struct isp_xclk *xclk = to_isp_xclk(hw); | |
67 | + | |
68 | + omap3isp_put(xclk->isp); | |
69 | +} | |
70 | + | |
71 | +static int isp_xclk_enable(struct clk_hw *hw) | |
72 | +{ | |
73 | + struct isp_xclk *xclk = to_isp_xclk(hw); | |
74 | + unsigned long flags; | |
75 | + | |
76 | + spin_lock_irqsave(&xclk->lock, flags); | |
77 | + isp_xclk_update(xclk, xclk->divider); | |
78 | + xclk->enabled = true; | |
79 | + spin_unlock_irqrestore(&xclk->lock, flags); | |
80 | + | |
81 | + return 0; | |
82 | +} | |
83 | + | |
84 | +static void isp_xclk_disable(struct clk_hw *hw) | |
85 | +{ | |
86 | + struct isp_xclk *xclk = to_isp_xclk(hw); | |
87 | + unsigned long flags; | |
88 | + | |
89 | + spin_lock_irqsave(&xclk->lock, flags); | |
90 | + isp_xclk_update(xclk, 0); | |
91 | + xclk->enabled = false; | |
92 | + spin_unlock_irqrestore(&xclk->lock, flags); | |
93 | +} | |
94 | + | |
95 | +static unsigned long isp_xclk_recalc_rate(struct clk_hw *hw, | |
96 | + unsigned long parent_rate) | |
97 | +{ | |
98 | + struct isp_xclk *xclk = to_isp_xclk(hw); | |
99 | + | |
100 | + return parent_rate / xclk->divider; | |
101 | +} | |
102 | + | |
103 | +static u32 isp_xclk_calc_divider(unsigned long *rate, unsigned long parent_rate) | |
104 | +{ | |
105 | + u32 divider; | |
106 | + | |
107 | + if (*rate >= parent_rate) { | |
108 | + *rate = parent_rate; | |
109 | + return ISPTCTRL_CTRL_DIV_BYPASS; | |
110 | + } | |
111 | + | |
112 | + divider = DIV_ROUND_CLOSEST(parent_rate, *rate); | |
113 | + if (divider >= ISPTCTRL_CTRL_DIV_BYPASS) | |
114 | + divider = ISPTCTRL_CTRL_DIV_BYPASS - 1; | |
115 | + | |
116 | + *rate = parent_rate / divider; | |
117 | + return divider; | |
118 | +} | |
119 | + | |
120 | +static long isp_xclk_round_rate(struct clk_hw *hw, unsigned long rate, | |
121 | + unsigned long *parent_rate) | |
122 | +{ | |
123 | + isp_xclk_calc_divider(&rate, *parent_rate); | |
124 | + return rate; | |
125 | +} | |
126 | + | |
127 | +static int isp_xclk_set_rate(struct clk_hw *hw, unsigned long rate, | |
128 | + unsigned long parent_rate) | |
129 | +{ | |
130 | + struct isp_xclk *xclk = to_isp_xclk(hw); | |
131 | + unsigned long flags; | |
132 | + u32 divider; | |
133 | + | |
134 | + divider = isp_xclk_calc_divider(&rate, parent_rate); | |
135 | + | |
136 | + spin_lock_irqsave(&xclk->lock, flags); | |
137 | + | |
138 | + xclk->divider = divider; | |
139 | + if (xclk->enabled) | |
140 | + isp_xclk_update(xclk, divider); | |
141 | + | |
142 | + spin_unlock_irqrestore(&xclk->lock, flags); | |
143 | + | |
144 | + dev_dbg(xclk->isp->dev, "%s: cam_xclk%c set to %lu Hz (div %u)\n", | |
145 | + __func__, xclk->id == ISP_XCLK_A ? 'a' : 'b', rate, divider); | |
146 | + return 0; | |
147 | +} | |
148 | + | |
149 | +static const struct clk_ops isp_xclk_ops = { | |
150 | + .prepare = isp_xclk_prepare, | |
151 | + .unprepare = isp_xclk_unprepare, | |
152 | + .enable = isp_xclk_enable, | |
153 | + .disable = isp_xclk_disable, | |
154 | + .recalc_rate = isp_xclk_recalc_rate, | |
155 | + .round_rate = isp_xclk_round_rate, | |
156 | + .set_rate = isp_xclk_set_rate, | |
157 | +}; | |
158 | + | |
159 | +static const char *isp_xclk_parent_name = "cam_mclk"; | |
160 | + | |
161 | +static const struct clk_init_data isp_xclk_init_data = { | |
162 | + .name = "cam_xclk", | |
163 | + .ops = &isp_xclk_ops, | |
164 | + .parent_names = &isp_xclk_parent_name, | |
165 | + .num_parents = 1, | |
166 | +}; | |
167 | + | |
168 | +static int isp_xclk_init(struct isp_device *isp) | |
169 | +{ | |
170 | + struct isp_platform_data *pdata = isp->pdata; | |
171 | + struct clk_init_data init; | |
172 | + unsigned int i; | |
173 | + | |
174 | + for (i = 0; i < ARRAY_SIZE(isp->xclks); ++i) { | |
175 | + struct isp_xclk *xclk = &isp->xclks[i]; | |
176 | + struct clk *clk; | |
177 | + | |
178 | + xclk->isp = isp; | |
179 | + xclk->id = i == 0 ? ISP_XCLK_A : ISP_XCLK_B; | |
180 | + xclk->divider = 1; | |
181 | + spin_lock_init(&xclk->lock); | |
182 | + | |
183 | + init.name = i == 0 ? "cam_xclka" : "cam_xclkb"; | |
184 | + init.ops = &isp_xclk_ops; | |
185 | + init.parent_names = &isp_xclk_parent_name; | |
186 | + init.num_parents = 1; | |
187 | + | |
188 | + xclk->hw.init = &init; | |
189 | + | |
190 | + clk = devm_clk_register(isp->dev, &xclk->hw); | |
191 | + if (IS_ERR(clk)) | |
192 | + return PTR_ERR(clk); | |
193 | + | |
194 | + if (pdata->xclks[i].con_id == NULL && | |
195 | + pdata->xclks[i].dev_id == NULL) | |
196 | + continue; | |
197 | + | |
198 | + xclk->lookup = kzalloc(sizeof(*xclk->lookup), GFP_KERNEL); | |
199 | + if (xclk->lookup == NULL) | |
200 | + return -ENOMEM; | |
201 | + | |
202 | + xclk->lookup->con_id = pdata->xclks[i].con_id; | |
203 | + xclk->lookup->dev_id = pdata->xclks[i].dev_id; | |
204 | + xclk->lookup->clk = clk; | |
205 | + | |
206 | + clkdev_add(xclk->lookup); | |
207 | + } | |
208 | + | |
209 | + return 0; | |
210 | +} | |
211 | + | |
212 | +static void isp_xclk_cleanup(struct isp_device *isp) | |
213 | +{ | |
214 | + unsigned int i; | |
215 | + | |
216 | + for (i = 0; i < ARRAY_SIZE(isp->xclks); ++i) { | |
217 | + struct isp_xclk *xclk = &isp->xclks[i]; | |
218 | + | |
219 | + if (xclk->lookup) | |
220 | + clkdev_drop(xclk->lookup); | |
221 | + } | |
222 | +} | |
223 | + | |
224 | +/* ----------------------------------------------------------------------------- | |
225 | + * Interrupts | |
226 | + */ | |
227 | + | |
228 | /* | |
229 | * isp_enable_interrupts - Enable ISP interrupts. | |
230 | * @isp: OMAP3 ISP device | |
231 | @@ -180,80 +376,6 @@ static void isp_disable_interrupts(struct isp_device *isp) | |
232 | isp_reg_writel(isp, 0, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0ENABLE); | |
233 | } | |
234 | ||
235 | -/** | |
236 | - * isp_set_xclk - Configures the specified cam_xclk to the desired frequency. | |
237 | - * @isp: OMAP3 ISP device | |
238 | - * @xclk: Desired frequency of the clock in Hz. 0 = stable low, 1 is stable high | |
239 | - * @xclksel: XCLK to configure (0 = A, 1 = B). | |
240 | - * | |
241 | - * Configures the specified MCLK divisor in the ISP timing control register | |
242 | - * (TCTRL_CTRL) to generate the desired xclk clock value. | |
243 | - * | |
244 | - * Divisor = cam_mclk_hz / xclk | |
245 | - * | |
246 | - * Returns the final frequency that is actually being generated | |
247 | - **/ | |
248 | -static u32 isp_set_xclk(struct isp_device *isp, u32 xclk, u8 xclksel) | |
249 | -{ | |
250 | - u32 divisor; | |
251 | - u32 currentxclk; | |
252 | - unsigned long mclk_hz; | |
253 | - | |
254 | - if (!omap3isp_get(isp)) | |
255 | - return 0; | |
256 | - | |
257 | - mclk_hz = clk_get_rate(isp->clock[ISP_CLK_CAM_MCLK]); | |
258 | - | |
259 | - if (xclk >= mclk_hz) { | |
260 | - divisor = ISPTCTRL_CTRL_DIV_BYPASS; | |
261 | - currentxclk = mclk_hz; | |
262 | - } else if (xclk >= 2) { | |
263 | - divisor = mclk_hz / xclk; | |
264 | - if (divisor >= ISPTCTRL_CTRL_DIV_BYPASS) | |
265 | - divisor = ISPTCTRL_CTRL_DIV_BYPASS - 1; | |
266 | - currentxclk = mclk_hz / divisor; | |
267 | - } else { | |
268 | - divisor = xclk; | |
269 | - currentxclk = 0; | |
270 | - } | |
271 | - | |
272 | - switch (xclksel) { | |
273 | - case ISP_XCLK_A: | |
274 | - isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL, | |
275 | - ISPTCTRL_CTRL_DIVA_MASK, | |
276 | - divisor << ISPTCTRL_CTRL_DIVA_SHIFT); | |
277 | - dev_dbg(isp->dev, "isp_set_xclk(): cam_xclka set to %d Hz\n", | |
278 | - currentxclk); | |
279 | - break; | |
280 | - case ISP_XCLK_B: | |
281 | - isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL, | |
282 | - ISPTCTRL_CTRL_DIVB_MASK, | |
283 | - divisor << ISPTCTRL_CTRL_DIVB_SHIFT); | |
284 | - dev_dbg(isp->dev, "isp_set_xclk(): cam_xclkb set to %d Hz\n", | |
285 | - currentxclk); | |
286 | - break; | |
287 | - case ISP_XCLK_NONE: | |
288 | - default: | |
289 | - omap3isp_put(isp); | |
290 | - dev_dbg(isp->dev, "ISP_ERR: isp_set_xclk(): Invalid requested " | |
291 | - "xclk. Must be 0 (A) or 1 (B).\n"); | |
292 | - return -EINVAL; | |
293 | - } | |
294 | - | |
295 | - /* Do we go from stable whatever to clock? */ | |
296 | - if (divisor >= 2 && isp->xclk_divisor[xclksel - 1] < 2) | |
297 | - omap3isp_get(isp); | |
298 | - /* Stopping the clock. */ | |
299 | - else if (divisor < 2 && isp->xclk_divisor[xclksel - 1] >= 2) | |
300 | - omap3isp_put(isp); | |
301 | - | |
302 | - isp->xclk_divisor[xclksel - 1] = divisor; | |
303 | - | |
304 | - omap3isp_put(isp); | |
305 | - | |
306 | - return currentxclk; | |
307 | -} | |
308 | - | |
309 | /* | |
310 | * isp_core_init - ISP core settings | |
311 | * @isp: OMAP3 ISP device | |
312 | @@ -1969,6 +2091,7 @@ static int isp_remove(struct platform_device *pdev) | |
313 | ||
314 | isp_unregister_entities(isp); | |
315 | isp_cleanup_modules(isp); | |
316 | + isp_xclk_cleanup(isp); | |
317 | ||
318 | __omap3isp_get(isp, false); | |
319 | iommu_detach_device(isp->domain, &pdev->dev); | |
320 | @@ -2042,7 +2165,6 @@ static int isp_probe(struct platform_device *pdev) | |
321 | } | |
322 | ||
323 | isp->autoidle = autoidle; | |
324 | - isp->platform_cb.set_xclk = isp_set_xclk; | |
325 | ||
326 | mutex_init(&isp->isp_mutex); | |
327 | spin_lock_init(&isp->stat_lock); | |
328 | @@ -2093,6 +2215,10 @@ static int isp_probe(struct platform_device *pdev) | |
329 | if (ret < 0) | |
330 | goto error_isp; | |
331 | ||
332 | + ret = isp_xclk_init(isp); | |
333 | + if (ret < 0) | |
334 | + goto error_isp; | |
335 | + | |
336 | /* Memory resources */ | |
337 | for (m = 0; m < ARRAY_SIZE(isp_res_maps); m++) | |
338 | if (isp->revision == isp_res_maps[m].isp_rev) | |
339 | @@ -2162,6 +2288,7 @@ detach_dev: | |
340 | free_domain: | |
341 | iommu_domain_free(isp->domain); | |
342 | error_isp: | |
343 | + isp_xclk_cleanup(isp); | |
344 | omap3isp_put(isp); | |
345 | error: | |
346 | platform_set_drvdata(pdev, NULL); | |
347 | diff --git a/drivers/media/platform/omap3isp/isp.h b/drivers/media/platform/omap3isp/isp.h | |
348 | index c77e1f2..cd3eff4 100644 | |
349 | --- a/drivers/media/platform/omap3isp/isp.h | |
350 | +++ b/drivers/media/platform/omap3isp/isp.h | |
351 | @@ -29,6 +29,7 @@ | |
352 | ||
353 | #include <media/omap3isp.h> | |
354 | #include <media/v4l2-device.h> | |
355 | +#include <linux/clk-provider.h> | |
356 | #include <linux/device.h> | |
357 | #include <linux/io.h> | |
358 | #include <linux/iommu.h> | |
359 | @@ -125,8 +126,20 @@ struct isp_reg { | |
360 | u32 val; | |
361 | }; | |
362 | ||
363 | -struct isp_platform_callback { | |
364 | - u32 (*set_xclk)(struct isp_device *isp, u32 xclk, u8 xclksel); | |
365 | +enum isp_xclk_id { | |
366 | + ISP_XCLK_A, | |
367 | + ISP_XCLK_B, | |
368 | +}; | |
369 | + | |
370 | +struct isp_xclk { | |
371 | + struct isp_device *isp; | |
372 | + struct clk_hw hw; | |
373 | + struct clk_lookup *lookup; | |
374 | + enum isp_xclk_id id; | |
375 | + | |
376 | + spinlock_t lock; /* Protects enabled and divider */ | |
377 | + bool enabled; | |
378 | + unsigned int divider; | |
379 | }; | |
380 | ||
381 | /* | |
382 | @@ -149,6 +162,7 @@ struct isp_platform_callback { | |
383 | * @cam_mclk: Pointer to camera functional clock structure. | |
384 | * @csi2_fck: Pointer to camera CSI2 complexIO clock structure. | |
385 | * @l3_ick: Pointer to OMAP3 L3 bus interface clock. | |
386 | + * @xclks: External clocks provided by the ISP | |
387 | * @irq: Currently attached ISP ISR callbacks information structure. | |
388 | * @isp_af: Pointer to current settings for ISP AutoFocus SCM. | |
389 | * @isp_hist: Pointer to current settings for ISP Histogram SCM. | |
390 | @@ -185,12 +199,12 @@ struct isp_device { | |
391 | int has_context; | |
392 | int ref_count; | |
393 | unsigned int autoidle; | |
394 | - u32 xclk_divisor[2]; /* Two clocks, a and b. */ | |
395 | #define ISP_CLK_CAM_ICK 0 | |
396 | #define ISP_CLK_CAM_MCLK 1 | |
397 | #define ISP_CLK_CSI2_FCK 2 | |
398 | #define ISP_CLK_L3_ICK 3 | |
399 | struct clk *clock[4]; | |
400 | + struct isp_xclk xclks[2]; | |
401 | ||
402 | /* ISP modules */ | |
403 | struct ispstat isp_af; | |
404 | @@ -209,8 +223,6 @@ struct isp_device { | |
405 | unsigned int subclk_resources; | |
406 | ||
407 | struct iommu_domain *domain; | |
408 | - | |
409 | - struct isp_platform_callback platform_cb; | |
410 | }; | |
411 | ||
412 | #define v4l2_dev_to_isp_device(dev) \ | |
413 | diff --git a/include/media/omap3isp.h b/include/media/omap3isp.h | |
414 | index 9584269..c9d06d9 100644 | |
415 | --- a/include/media/omap3isp.h | |
416 | +++ b/include/media/omap3isp.h | |
417 | @@ -29,10 +29,6 @@ | |
418 | struct i2c_board_info; | |
419 | struct isp_device; | |
420 | ||
421 | -#define ISP_XCLK_NONE 0 | |
422 | -#define ISP_XCLK_A 1 | |
423 | -#define ISP_XCLK_B 2 | |
424 | - | |
425 | enum isp_interface_type { | |
426 | ISP_INTERFACE_PARALLEL, | |
427 | ISP_INTERFACE_CSI2A_PHY2, | |
428 | @@ -153,7 +149,13 @@ struct isp_v4l2_subdevs_group { | |
429 | } bus; /* gcc < 4.6.0 chokes on anonymous union initializers */ | |
430 | }; | |
431 | ||
432 | +struct isp_platform_xclk { | |
433 | + const char *dev_id; | |
434 | + const char *con_id; | |
435 | +}; | |
436 | + | |
437 | struct isp_platform_data { | |
438 | + struct isp_platform_xclk xclks[2]; | |
439 | struct isp_v4l2_subdevs_group *subdevs; | |
440 | void (*set_constraints)(struct isp_device *isp, bool enable); | |
441 | }; | |
442 | -- | |
443 | 1.7.10.4 | |
444 |