diff options
author | Laurent Pinchart <laurent.pinchart@ideasonboard.com> | 2012-10-22 09:43:00 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2013-04-14 19:21:12 -0400 |
commit | 9b28ee3c9122cea62f2db02f5bb1e1606bb343a6 (patch) | |
tree | 7092c63fa14f7e5162400ee5a571f92532bcab5c /drivers/media | |
parent | 4290fd1a5688f3510caa0625c62d73de568ed2c2 (diff) |
[media] omap3isp: Use the common clock framework
Expose the two ISP external clocks XCLKA and XCLKB as common clocks for
subdev drivers.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Acked-by: Mike Turquette <mturquette@linaro.org>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media')
-rw-r--r-- | drivers/media/platform/omap3isp/isp.c | 277 | ||||
-rw-r--r-- | drivers/media/platform/omap3isp/isp.h | 22 |
2 files changed, 219 insertions, 80 deletions
diff --git a/drivers/media/platform/omap3isp/isp.c b/drivers/media/platform/omap3isp/isp.c index 6e5ad8ec0a22..1d7dbd5c0fba 100644 --- a/drivers/media/platform/omap3isp/isp.c +++ b/drivers/media/platform/omap3isp/isp.c | |||
@@ -55,6 +55,7 @@ | |||
55 | #include <asm/cacheflush.h> | 55 | #include <asm/cacheflush.h> |
56 | 56 | ||
57 | #include <linux/clk.h> | 57 | #include <linux/clk.h> |
58 | #include <linux/clkdev.h> | ||
58 | #include <linux/delay.h> | 59 | #include <linux/delay.h> |
59 | #include <linux/device.h> | 60 | #include <linux/device.h> |
60 | #include <linux/dma-mapping.h> | 61 | #include <linux/dma-mapping.h> |
@@ -148,6 +149,201 @@ void omap3isp_flush(struct isp_device *isp) | |||
148 | isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_REVISION); | 149 | isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_REVISION); |
149 | } | 150 | } |
150 | 151 | ||
152 | /* ----------------------------------------------------------------------------- | ||
153 | * XCLK | ||
154 | */ | ||
155 | |||
156 | #define to_isp_xclk(_hw) container_of(_hw, struct isp_xclk, hw) | ||
157 | |||
158 | static void isp_xclk_update(struct isp_xclk *xclk, u32 divider) | ||
159 | { | ||
160 | switch (xclk->id) { | ||
161 | case ISP_XCLK_A: | ||
162 | isp_reg_clr_set(xclk->isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL, | ||
163 | ISPTCTRL_CTRL_DIVA_MASK, | ||
164 | divider << ISPTCTRL_CTRL_DIVA_SHIFT); | ||
165 | break; | ||
166 | case ISP_XCLK_B: | ||
167 | isp_reg_clr_set(xclk->isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL, | ||
168 | ISPTCTRL_CTRL_DIVB_MASK, | ||
169 | divider << ISPTCTRL_CTRL_DIVB_SHIFT); | ||
170 | break; | ||
171 | } | ||
172 | } | ||
173 | |||
174 | static int isp_xclk_prepare(struct clk_hw *hw) | ||
175 | { | ||
176 | struct isp_xclk *xclk = to_isp_xclk(hw); | ||
177 | |||
178 | omap3isp_get(xclk->isp); | ||
179 | |||
180 | return 0; | ||
181 | } | ||
182 | |||
183 | static void isp_xclk_unprepare(struct clk_hw *hw) | ||
184 | { | ||
185 | struct isp_xclk *xclk = to_isp_xclk(hw); | ||
186 | |||
187 | omap3isp_put(xclk->isp); | ||
188 | } | ||
189 | |||
190 | static int isp_xclk_enable(struct clk_hw *hw) | ||
191 | { | ||
192 | struct isp_xclk *xclk = to_isp_xclk(hw); | ||
193 | unsigned long flags; | ||
194 | |||
195 | spin_lock_irqsave(&xclk->lock, flags); | ||
196 | isp_xclk_update(xclk, xclk->divider); | ||
197 | xclk->enabled = true; | ||
198 | spin_unlock_irqrestore(&xclk->lock, flags); | ||
199 | |||
200 | return 0; | ||
201 | } | ||
202 | |||
203 | static void isp_xclk_disable(struct clk_hw *hw) | ||
204 | { | ||
205 | struct isp_xclk *xclk = to_isp_xclk(hw); | ||
206 | unsigned long flags; | ||
207 | |||
208 | spin_lock_irqsave(&xclk->lock, flags); | ||
209 | isp_xclk_update(xclk, 0); | ||
210 | xclk->enabled = false; | ||
211 | spin_unlock_irqrestore(&xclk->lock, flags); | ||
212 | } | ||
213 | |||
214 | static unsigned long isp_xclk_recalc_rate(struct clk_hw *hw, | ||
215 | unsigned long parent_rate) | ||
216 | { | ||
217 | struct isp_xclk *xclk = to_isp_xclk(hw); | ||
218 | |||
219 | return parent_rate / xclk->divider; | ||
220 | } | ||
221 | |||
222 | static u32 isp_xclk_calc_divider(unsigned long *rate, unsigned long parent_rate) | ||
223 | { | ||
224 | u32 divider; | ||
225 | |||
226 | if (*rate >= parent_rate) { | ||
227 | *rate = parent_rate; | ||
228 | return ISPTCTRL_CTRL_DIV_BYPASS; | ||
229 | } | ||
230 | |||
231 | divider = DIV_ROUND_CLOSEST(parent_rate, *rate); | ||
232 | if (divider >= ISPTCTRL_CTRL_DIV_BYPASS) | ||
233 | divider = ISPTCTRL_CTRL_DIV_BYPASS - 1; | ||
234 | |||
235 | *rate = parent_rate / divider; | ||
236 | return divider; | ||
237 | } | ||
238 | |||
239 | static long isp_xclk_round_rate(struct clk_hw *hw, unsigned long rate, | ||
240 | unsigned long *parent_rate) | ||
241 | { | ||
242 | isp_xclk_calc_divider(&rate, *parent_rate); | ||
243 | return rate; | ||
244 | } | ||
245 | |||
246 | static int isp_xclk_set_rate(struct clk_hw *hw, unsigned long rate, | ||
247 | unsigned long parent_rate) | ||
248 | { | ||
249 | struct isp_xclk *xclk = to_isp_xclk(hw); | ||
250 | unsigned long flags; | ||
251 | u32 divider; | ||
252 | |||
253 | divider = isp_xclk_calc_divider(&rate, parent_rate); | ||
254 | |||
255 | spin_lock_irqsave(&xclk->lock, flags); | ||
256 | |||
257 | xclk->divider = divider; | ||
258 | if (xclk->enabled) | ||
259 | isp_xclk_update(xclk, divider); | ||
260 | |||
261 | spin_unlock_irqrestore(&xclk->lock, flags); | ||
262 | |||
263 | dev_dbg(xclk->isp->dev, "%s: cam_xclk%c set to %lu Hz (div %u)\n", | ||
264 | __func__, xclk->id == ISP_XCLK_A ? 'a' : 'b', rate, divider); | ||
265 | return 0; | ||
266 | } | ||
267 | |||
268 | static const struct clk_ops isp_xclk_ops = { | ||
269 | .prepare = isp_xclk_prepare, | ||
270 | .unprepare = isp_xclk_unprepare, | ||
271 | .enable = isp_xclk_enable, | ||
272 | .disable = isp_xclk_disable, | ||
273 | .recalc_rate = isp_xclk_recalc_rate, | ||
274 | .round_rate = isp_xclk_round_rate, | ||
275 | .set_rate = isp_xclk_set_rate, | ||
276 | }; | ||
277 | |||
278 | static const char *isp_xclk_parent_name = "cam_mclk"; | ||
279 | |||
280 | static const struct clk_init_data isp_xclk_init_data = { | ||
281 | .name = "cam_xclk", | ||
282 | .ops = &isp_xclk_ops, | ||
283 | .parent_names = &isp_xclk_parent_name, | ||
284 | .num_parents = 1, | ||
285 | }; | ||
286 | |||
287 | static int isp_xclk_init(struct isp_device *isp) | ||
288 | { | ||
289 | struct isp_platform_data *pdata = isp->pdata; | ||
290 | struct clk_init_data init; | ||
291 | unsigned int i; | ||
292 | |||
293 | for (i = 0; i < ARRAY_SIZE(isp->xclks); ++i) { | ||
294 | struct isp_xclk *xclk = &isp->xclks[i]; | ||
295 | struct clk *clk; | ||
296 | |||
297 | xclk->isp = isp; | ||
298 | xclk->id = i == 0 ? ISP_XCLK_A : ISP_XCLK_B; | ||
299 | xclk->divider = 1; | ||
300 | spin_lock_init(&xclk->lock); | ||
301 | |||
302 | init.name = i == 0 ? "cam_xclka" : "cam_xclkb"; | ||
303 | init.ops = &isp_xclk_ops; | ||
304 | init.parent_names = &isp_xclk_parent_name; | ||
305 | init.num_parents = 1; | ||
306 | |||
307 | xclk->hw.init = &init; | ||
308 | |||
309 | clk = devm_clk_register(isp->dev, &xclk->hw); | ||
310 | if (IS_ERR(clk)) | ||
311 | return PTR_ERR(clk); | ||
312 | |||
313 | if (pdata->xclks[i].con_id == NULL && | ||
314 | pdata->xclks[i].dev_id == NULL) | ||
315 | continue; | ||
316 | |||
317 | xclk->lookup = kzalloc(sizeof(*xclk->lookup), GFP_KERNEL); | ||
318 | if (xclk->lookup == NULL) | ||
319 | return -ENOMEM; | ||
320 | |||
321 | xclk->lookup->con_id = pdata->xclks[i].con_id; | ||
322 | xclk->lookup->dev_id = pdata->xclks[i].dev_id; | ||
323 | xclk->lookup->clk = clk; | ||
324 | |||
325 | clkdev_add(xclk->lookup); | ||
326 | } | ||
327 | |||
328 | return 0; | ||
329 | } | ||
330 | |||
331 | static void isp_xclk_cleanup(struct isp_device *isp) | ||
332 | { | ||
333 | unsigned int i; | ||
334 | |||
335 | for (i = 0; i < ARRAY_SIZE(isp->xclks); ++i) { | ||
336 | struct isp_xclk *xclk = &isp->xclks[i]; | ||
337 | |||
338 | if (xclk->lookup) | ||
339 | clkdev_drop(xclk->lookup); | ||
340 | } | ||
341 | } | ||
342 | |||
343 | /* ----------------------------------------------------------------------------- | ||
344 | * Interrupts | ||
345 | */ | ||
346 | |||
151 | /* | 347 | /* |
152 | * isp_enable_interrupts - Enable ISP interrupts. | 348 | * isp_enable_interrupts - Enable ISP interrupts. |
153 | * @isp: OMAP3 ISP device | 349 | * @isp: OMAP3 ISP device |
@@ -180,80 +376,6 @@ static void isp_disable_interrupts(struct isp_device *isp) | |||
180 | isp_reg_writel(isp, 0, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0ENABLE); | 376 | isp_reg_writel(isp, 0, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0ENABLE); |
181 | } | 377 | } |
182 | 378 | ||
183 | /** | ||
184 | * isp_set_xclk - Configures the specified cam_xclk to the desired frequency. | ||
185 | * @isp: OMAP3 ISP device | ||
186 | * @xclk: Desired frequency of the clock in Hz. 0 = stable low, 1 is stable high | ||
187 | * @xclksel: XCLK to configure (0 = A, 1 = B). | ||
188 | * | ||
189 | * Configures the specified MCLK divisor in the ISP timing control register | ||
190 | * (TCTRL_CTRL) to generate the desired xclk clock value. | ||
191 | * | ||
192 | * Divisor = cam_mclk_hz / xclk | ||
193 | * | ||
194 | * Returns the final frequency that is actually being generated | ||
195 | **/ | ||
196 | static u32 isp_set_xclk(struct isp_device *isp, u32 xclk, u8 xclksel) | ||
197 | { | ||
198 | u32 divisor; | ||
199 | u32 currentxclk; | ||
200 | unsigned long mclk_hz; | ||
201 | |||
202 | if (!omap3isp_get(isp)) | ||
203 | return 0; | ||
204 | |||
205 | mclk_hz = clk_get_rate(isp->clock[ISP_CLK_CAM_MCLK]); | ||
206 | |||
207 | if (xclk >= mclk_hz) { | ||
208 | divisor = ISPTCTRL_CTRL_DIV_BYPASS; | ||
209 | currentxclk = mclk_hz; | ||
210 | } else if (xclk >= 2) { | ||
211 | divisor = mclk_hz / xclk; | ||
212 | if (divisor >= ISPTCTRL_CTRL_DIV_BYPASS) | ||
213 | divisor = ISPTCTRL_CTRL_DIV_BYPASS - 1; | ||
214 | currentxclk = mclk_hz / divisor; | ||
215 | } else { | ||
216 | divisor = xclk; | ||
217 | currentxclk = 0; | ||
218 | } | ||
219 | |||
220 | switch (xclksel) { | ||
221 | case ISP_XCLK_A: | ||
222 | isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL, | ||
223 | ISPTCTRL_CTRL_DIVA_MASK, | ||
224 | divisor << ISPTCTRL_CTRL_DIVA_SHIFT); | ||
225 | dev_dbg(isp->dev, "isp_set_xclk(): cam_xclka set to %d Hz\n", | ||
226 | currentxclk); | ||
227 | break; | ||
228 | case ISP_XCLK_B: | ||
229 | isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL, | ||
230 | ISPTCTRL_CTRL_DIVB_MASK, | ||
231 | divisor << ISPTCTRL_CTRL_DIVB_SHIFT); | ||
232 | dev_dbg(isp->dev, "isp_set_xclk(): cam_xclkb set to %d Hz\n", | ||
233 | currentxclk); | ||
234 | break; | ||
235 | case ISP_XCLK_NONE: | ||
236 | default: | ||
237 | omap3isp_put(isp); | ||
238 | dev_dbg(isp->dev, "ISP_ERR: isp_set_xclk(): Invalid requested " | ||
239 | "xclk. Must be 0 (A) or 1 (B).\n"); | ||
240 | return -EINVAL; | ||
241 | } | ||
242 | |||
243 | /* Do we go from stable whatever to clock? */ | ||
244 | if (divisor >= 2 && isp->xclk_divisor[xclksel - 1] < 2) | ||
245 | omap3isp_get(isp); | ||
246 | /* Stopping the clock. */ | ||
247 | else if (divisor < 2 && isp->xclk_divisor[xclksel - 1] >= 2) | ||
248 | omap3isp_put(isp); | ||
249 | |||
250 | isp->xclk_divisor[xclksel - 1] = divisor; | ||
251 | |||
252 | omap3isp_put(isp); | ||
253 | |||
254 | return currentxclk; | ||
255 | } | ||
256 | |||
257 | /* | 379 | /* |
258 | * isp_core_init - ISP core settings | 380 | * isp_core_init - ISP core settings |
259 | * @isp: OMAP3 ISP device | 381 | * @isp: OMAP3 ISP device |
@@ -1969,6 +2091,7 @@ static int isp_remove(struct platform_device *pdev) | |||
1969 | 2091 | ||
1970 | isp_unregister_entities(isp); | 2092 | isp_unregister_entities(isp); |
1971 | isp_cleanup_modules(isp); | 2093 | isp_cleanup_modules(isp); |
2094 | isp_xclk_cleanup(isp); | ||
1972 | 2095 | ||
1973 | __omap3isp_get(isp, false); | 2096 | __omap3isp_get(isp, false); |
1974 | iommu_detach_device(isp->domain, &pdev->dev); | 2097 | iommu_detach_device(isp->domain, &pdev->dev); |
@@ -2042,7 +2165,6 @@ static int isp_probe(struct platform_device *pdev) | |||
2042 | } | 2165 | } |
2043 | 2166 | ||
2044 | isp->autoidle = autoidle; | 2167 | isp->autoidle = autoidle; |
2045 | isp->platform_cb.set_xclk = isp_set_xclk; | ||
2046 | 2168 | ||
2047 | mutex_init(&isp->isp_mutex); | 2169 | mutex_init(&isp->isp_mutex); |
2048 | spin_lock_init(&isp->stat_lock); | 2170 | spin_lock_init(&isp->stat_lock); |
@@ -2093,6 +2215,10 @@ static int isp_probe(struct platform_device *pdev) | |||
2093 | if (ret < 0) | 2215 | if (ret < 0) |
2094 | goto error_isp; | 2216 | goto error_isp; |
2095 | 2217 | ||
2218 | ret = isp_xclk_init(isp); | ||
2219 | if (ret < 0) | ||
2220 | goto error_isp; | ||
2221 | |||
2096 | /* Memory resources */ | 2222 | /* Memory resources */ |
2097 | for (m = 0; m < ARRAY_SIZE(isp_res_maps); m++) | 2223 | for (m = 0; m < ARRAY_SIZE(isp_res_maps); m++) |
2098 | if (isp->revision == isp_res_maps[m].isp_rev) | 2224 | if (isp->revision == isp_res_maps[m].isp_rev) |
@@ -2162,6 +2288,7 @@ detach_dev: | |||
2162 | free_domain: | 2288 | free_domain: |
2163 | iommu_domain_free(isp->domain); | 2289 | iommu_domain_free(isp->domain); |
2164 | error_isp: | 2290 | error_isp: |
2291 | isp_xclk_cleanup(isp); | ||
2165 | omap3isp_put(isp); | 2292 | omap3isp_put(isp); |
2166 | error: | 2293 | error: |
2167 | platform_set_drvdata(pdev, NULL); | 2294 | platform_set_drvdata(pdev, NULL); |
diff --git a/drivers/media/platform/omap3isp/isp.h b/drivers/media/platform/omap3isp/isp.h index c77e1f2ae5ca..cd3eff45ae7d 100644 --- a/drivers/media/platform/omap3isp/isp.h +++ b/drivers/media/platform/omap3isp/isp.h | |||
@@ -29,6 +29,7 @@ | |||
29 | 29 | ||
30 | #include <media/omap3isp.h> | 30 | #include <media/omap3isp.h> |
31 | #include <media/v4l2-device.h> | 31 | #include <media/v4l2-device.h> |
32 | #include <linux/clk-provider.h> | ||
32 | #include <linux/device.h> | 33 | #include <linux/device.h> |
33 | #include <linux/io.h> | 34 | #include <linux/io.h> |
34 | #include <linux/iommu.h> | 35 | #include <linux/iommu.h> |
@@ -125,8 +126,20 @@ struct isp_reg { | |||
125 | u32 val; | 126 | u32 val; |
126 | }; | 127 | }; |
127 | 128 | ||
128 | struct isp_platform_callback { | 129 | enum isp_xclk_id { |
129 | u32 (*set_xclk)(struct isp_device *isp, u32 xclk, u8 xclksel); | 130 | ISP_XCLK_A, |
131 | ISP_XCLK_B, | ||
132 | }; | ||
133 | |||
134 | struct isp_xclk { | ||
135 | struct isp_device *isp; | ||
136 | struct clk_hw hw; | ||
137 | struct clk_lookup *lookup; | ||
138 | enum isp_xclk_id id; | ||
139 | |||
140 | spinlock_t lock; /* Protects enabled and divider */ | ||
141 | bool enabled; | ||
142 | unsigned int divider; | ||
130 | }; | 143 | }; |
131 | 144 | ||
132 | /* | 145 | /* |
@@ -149,6 +162,7 @@ struct isp_platform_callback { | |||
149 | * @cam_mclk: Pointer to camera functional clock structure. | 162 | * @cam_mclk: Pointer to camera functional clock structure. |
150 | * @csi2_fck: Pointer to camera CSI2 complexIO clock structure. | 163 | * @csi2_fck: Pointer to camera CSI2 complexIO clock structure. |
151 | * @l3_ick: Pointer to OMAP3 L3 bus interface clock. | 164 | * @l3_ick: Pointer to OMAP3 L3 bus interface clock. |
165 | * @xclks: External clocks provided by the ISP | ||
152 | * @irq: Currently attached ISP ISR callbacks information structure. | 166 | * @irq: Currently attached ISP ISR callbacks information structure. |
153 | * @isp_af: Pointer to current settings for ISP AutoFocus SCM. | 167 | * @isp_af: Pointer to current settings for ISP AutoFocus SCM. |
154 | * @isp_hist: Pointer to current settings for ISP Histogram SCM. | 168 | * @isp_hist: Pointer to current settings for ISP Histogram SCM. |
@@ -185,12 +199,12 @@ struct isp_device { | |||
185 | int has_context; | 199 | int has_context; |
186 | int ref_count; | 200 | int ref_count; |
187 | unsigned int autoidle; | 201 | unsigned int autoidle; |
188 | u32 xclk_divisor[2]; /* Two clocks, a and b. */ | ||
189 | #define ISP_CLK_CAM_ICK 0 | 202 | #define ISP_CLK_CAM_ICK 0 |
190 | #define ISP_CLK_CAM_MCLK 1 | 203 | #define ISP_CLK_CAM_MCLK 1 |
191 | #define ISP_CLK_CSI2_FCK 2 | 204 | #define ISP_CLK_CSI2_FCK 2 |
192 | #define ISP_CLK_L3_ICK 3 | 205 | #define ISP_CLK_L3_ICK 3 |
193 | struct clk *clock[4]; | 206 | struct clk *clock[4]; |
207 | struct isp_xclk xclks[2]; | ||
194 | 208 | ||
195 | /* ISP modules */ | 209 | /* ISP modules */ |
196 | struct ispstat isp_af; | 210 | struct ispstat isp_af; |
@@ -209,8 +223,6 @@ struct isp_device { | |||
209 | unsigned int subclk_resources; | 223 | unsigned int subclk_resources; |
210 | 224 | ||
211 | struct iommu_domain *domain; | 225 | struct iommu_domain *domain; |
212 | |||
213 | struct isp_platform_callback platform_cb; | ||
214 | }; | 226 | }; |
215 | 227 | ||
216 | #define v4l2_dev_to_isp_device(dev) \ | 228 | #define v4l2_dev_to_isp_device(dev) \ |