diff options
Diffstat (limited to 'drivers/media/video/s5p-tv/mixer_drv.c')
-rw-r--r-- | drivers/media/video/s5p-tv/mixer_drv.c | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/drivers/media/video/s5p-tv/mixer_drv.c b/drivers/media/video/s5p-tv/mixer_drv.c new file mode 100644 index 00000000000..00643094b22 --- /dev/null +++ b/drivers/media/video/s5p-tv/mixer_drv.c | |||
@@ -0,0 +1,487 @@ | |||
1 | /* | ||
2 | * Samsung TV Mixer driver | ||
3 | * | ||
4 | * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. | ||
5 | * | ||
6 | * Tomasz Stanislawski, <t.stanislaws@samsung.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published | ||
10 | * by the Free Software Foundiation. either version 2 of the License, | ||
11 | * or (at your option) any later version | ||
12 | */ | ||
13 | |||
14 | #include "mixer.h" | ||
15 | |||
16 | #include <linux/module.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/io.h> | ||
19 | #include <linux/interrupt.h> | ||
20 | #include <linux/irq.h> | ||
21 | #include <linux/fb.h> | ||
22 | #include <linux/delay.h> | ||
23 | #include <linux/pm_runtime.h> | ||
24 | #include <linux/clk.h> | ||
25 | |||
26 | MODULE_AUTHOR("Tomasz Stanislawski, <t.stanislaws@samsung.com>"); | ||
27 | MODULE_DESCRIPTION("Samsung MIXER"); | ||
28 | MODULE_LICENSE("GPL"); | ||
29 | |||
30 | /* --------- DRIVER PARAMETERS ---------- */ | ||
31 | |||
32 | static struct mxr_output_conf mxr_output_conf[] = { | ||
33 | { | ||
34 | .output_name = "S5P HDMI connector", | ||
35 | .module_name = "s5p-hdmi", | ||
36 | .cookie = 1, | ||
37 | }, | ||
38 | { | ||
39 | .output_name = "S5P SDO connector", | ||
40 | .module_name = "s5p-sdo", | ||
41 | .cookie = 0, | ||
42 | }, | ||
43 | }; | ||
44 | |||
45 | void mxr_get_mbus_fmt(struct mxr_device *mdev, | ||
46 | struct v4l2_mbus_framefmt *mbus_fmt) | ||
47 | { | ||
48 | struct v4l2_subdev *sd; | ||
49 | int ret; | ||
50 | |||
51 | mutex_lock(&mdev->mutex); | ||
52 | sd = to_outsd(mdev); | ||
53 | ret = v4l2_subdev_call(sd, video, g_mbus_fmt, mbus_fmt); | ||
54 | WARN(ret, "failed to get mbus_fmt for output %s\n", sd->name); | ||
55 | mutex_unlock(&mdev->mutex); | ||
56 | } | ||
57 | |||
58 | void mxr_streamer_get(struct mxr_device *mdev) | ||
59 | { | ||
60 | mutex_lock(&mdev->mutex); | ||
61 | ++mdev->n_streamer; | ||
62 | mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_streamer); | ||
63 | if (mdev->n_streamer == 1) { | ||
64 | struct v4l2_subdev *sd = to_outsd(mdev); | ||
65 | struct v4l2_mbus_framefmt mbus_fmt; | ||
66 | struct mxr_resources *res = &mdev->res; | ||
67 | int ret; | ||
68 | |||
69 | if (to_output(mdev)->cookie == 0) | ||
70 | clk_set_parent(res->sclk_mixer, res->sclk_dac); | ||
71 | else | ||
72 | clk_set_parent(res->sclk_mixer, res->sclk_hdmi); | ||
73 | mxr_reg_s_output(mdev, to_output(mdev)->cookie); | ||
74 | |||
75 | ret = v4l2_subdev_call(sd, video, g_mbus_fmt, &mbus_fmt); | ||
76 | WARN(ret, "failed to get mbus_fmt for output %s\n", sd->name); | ||
77 | ret = v4l2_subdev_call(sd, video, s_stream, 1); | ||
78 | WARN(ret, "starting stream failed for output %s\n", sd->name); | ||
79 | |||
80 | mxr_reg_set_mbus_fmt(mdev, &mbus_fmt); | ||
81 | mxr_reg_streamon(mdev); | ||
82 | ret = mxr_reg_wait4vsync(mdev); | ||
83 | WARN(ret, "failed to get vsync (%d) from output\n", ret); | ||
84 | } | ||
85 | mutex_unlock(&mdev->mutex); | ||
86 | mxr_reg_dump(mdev); | ||
87 | /* FIXME: what to do when streaming fails? */ | ||
88 | } | ||
89 | |||
90 | void mxr_streamer_put(struct mxr_device *mdev) | ||
91 | { | ||
92 | mutex_lock(&mdev->mutex); | ||
93 | --mdev->n_streamer; | ||
94 | mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_streamer); | ||
95 | if (mdev->n_streamer == 0) { | ||
96 | int ret; | ||
97 | struct v4l2_subdev *sd = to_outsd(mdev); | ||
98 | |||
99 | mxr_reg_streamoff(mdev); | ||
100 | /* vsync applies Mixer setup */ | ||
101 | ret = mxr_reg_wait4vsync(mdev); | ||
102 | WARN(ret, "failed to get vsync (%d) from output\n", ret); | ||
103 | ret = v4l2_subdev_call(sd, video, s_stream, 0); | ||
104 | WARN(ret, "stopping stream failed for output %s\n", sd->name); | ||
105 | } | ||
106 | WARN(mdev->n_streamer < 0, "negative number of streamers (%d)\n", | ||
107 | mdev->n_streamer); | ||
108 | mutex_unlock(&mdev->mutex); | ||
109 | mxr_reg_dump(mdev); | ||
110 | } | ||
111 | |||
112 | void mxr_output_get(struct mxr_device *mdev) | ||
113 | { | ||
114 | mutex_lock(&mdev->mutex); | ||
115 | ++mdev->n_output; | ||
116 | mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_output); | ||
117 | /* turn on auxiliary driver */ | ||
118 | if (mdev->n_output == 1) | ||
119 | v4l2_subdev_call(to_outsd(mdev), core, s_power, 1); | ||
120 | mutex_unlock(&mdev->mutex); | ||
121 | } | ||
122 | |||
123 | void mxr_output_put(struct mxr_device *mdev) | ||
124 | { | ||
125 | mutex_lock(&mdev->mutex); | ||
126 | --mdev->n_output; | ||
127 | mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_output); | ||
128 | /* turn on auxiliary driver */ | ||
129 | if (mdev->n_output == 0) | ||
130 | v4l2_subdev_call(to_outsd(mdev), core, s_power, 0); | ||
131 | WARN(mdev->n_output < 0, "negative number of output users (%d)\n", | ||
132 | mdev->n_output); | ||
133 | mutex_unlock(&mdev->mutex); | ||
134 | } | ||
135 | |||
136 | int mxr_power_get(struct mxr_device *mdev) | ||
137 | { | ||
138 | int ret = pm_runtime_get_sync(mdev->dev); | ||
139 | |||
140 | /* returning 1 means that power is already enabled, | ||
141 | * so zero success be returned */ | ||
142 | if (IS_ERR_VALUE(ret)) | ||
143 | return ret; | ||
144 | return 0; | ||
145 | } | ||
146 | |||
147 | void mxr_power_put(struct mxr_device *mdev) | ||
148 | { | ||
149 | pm_runtime_put_sync(mdev->dev); | ||
150 | } | ||
151 | |||
152 | /* --------- RESOURCE MANAGEMENT -------------*/ | ||
153 | |||
154 | static int __devinit mxr_acquire_plat_resources(struct mxr_device *mdev, | ||
155 | struct platform_device *pdev) | ||
156 | { | ||
157 | struct resource *res; | ||
158 | int ret; | ||
159 | |||
160 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mxr"); | ||
161 | if (res == NULL) { | ||
162 | mxr_err(mdev, "get memory resource failed.\n"); | ||
163 | ret = -ENXIO; | ||
164 | goto fail; | ||
165 | } | ||
166 | |||
167 | mdev->res.mxr_regs = ioremap(res->start, resource_size(res)); | ||
168 | if (mdev->res.mxr_regs == NULL) { | ||
169 | mxr_err(mdev, "register mapping failed.\n"); | ||
170 | ret = -ENXIO; | ||
171 | goto fail; | ||
172 | } | ||
173 | |||
174 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vp"); | ||
175 | if (res == NULL) { | ||
176 | mxr_err(mdev, "get memory resource failed.\n"); | ||
177 | ret = -ENXIO; | ||
178 | goto fail_mxr_regs; | ||
179 | } | ||
180 | |||
181 | mdev->res.vp_regs = ioremap(res->start, resource_size(res)); | ||
182 | if (mdev->res.vp_regs == NULL) { | ||
183 | mxr_err(mdev, "register mapping failed.\n"); | ||
184 | ret = -ENXIO; | ||
185 | goto fail_mxr_regs; | ||
186 | } | ||
187 | |||
188 | res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "irq"); | ||
189 | if (res == NULL) { | ||
190 | mxr_err(mdev, "get interrupt resource failed.\n"); | ||
191 | ret = -ENXIO; | ||
192 | goto fail_vp_regs; | ||
193 | } | ||
194 | |||
195 | ret = request_irq(res->start, mxr_irq_handler, 0, "s5p-mixer", mdev); | ||
196 | if (ret) { | ||
197 | mxr_err(mdev, "request interrupt failed.\n"); | ||
198 | goto fail_vp_regs; | ||
199 | } | ||
200 | mdev->res.irq = res->start; | ||
201 | |||
202 | return 0; | ||
203 | |||
204 | fail_vp_regs: | ||
205 | iounmap(mdev->res.vp_regs); | ||
206 | |||
207 | fail_mxr_regs: | ||
208 | iounmap(mdev->res.mxr_regs); | ||
209 | |||
210 | fail: | ||
211 | return ret; | ||
212 | } | ||
213 | |||
214 | static void mxr_release_plat_resources(struct mxr_device *mdev) | ||
215 | { | ||
216 | free_irq(mdev->res.irq, mdev); | ||
217 | iounmap(mdev->res.vp_regs); | ||
218 | iounmap(mdev->res.mxr_regs); | ||
219 | } | ||
220 | |||
221 | static void mxr_release_clocks(struct mxr_device *mdev) | ||
222 | { | ||
223 | struct mxr_resources *res = &mdev->res; | ||
224 | |||
225 | if (!IS_ERR_OR_NULL(res->sclk_dac)) | ||
226 | clk_put(res->sclk_dac); | ||
227 | if (!IS_ERR_OR_NULL(res->sclk_hdmi)) | ||
228 | clk_put(res->sclk_hdmi); | ||
229 | if (!IS_ERR_OR_NULL(res->sclk_mixer)) | ||
230 | clk_put(res->sclk_mixer); | ||
231 | if (!IS_ERR_OR_NULL(res->vp)) | ||
232 | clk_put(res->vp); | ||
233 | if (!IS_ERR_OR_NULL(res->mixer)) | ||
234 | clk_put(res->mixer); | ||
235 | } | ||
236 | |||
237 | static int mxr_acquire_clocks(struct mxr_device *mdev) | ||
238 | { | ||
239 | struct mxr_resources *res = &mdev->res; | ||
240 | struct device *dev = mdev->dev; | ||
241 | |||
242 | res->mixer = clk_get(dev, "mixer"); | ||
243 | if (IS_ERR_OR_NULL(res->mixer)) { | ||
244 | mxr_err(mdev, "failed to get clock 'mixer'\n"); | ||
245 | goto fail; | ||
246 | } | ||
247 | res->vp = clk_get(dev, "vp"); | ||
248 | if (IS_ERR_OR_NULL(res->vp)) { | ||
249 | mxr_err(mdev, "failed to get clock 'vp'\n"); | ||
250 | goto fail; | ||
251 | } | ||
252 | res->sclk_mixer = clk_get(dev, "sclk_mixer"); | ||
253 | if (IS_ERR_OR_NULL(res->sclk_mixer)) { | ||
254 | mxr_err(mdev, "failed to get clock 'sclk_mixer'\n"); | ||
255 | goto fail; | ||
256 | } | ||
257 | res->sclk_hdmi = clk_get(dev, "sclk_hdmi"); | ||
258 | if (IS_ERR_OR_NULL(res->sclk_hdmi)) { | ||
259 | mxr_err(mdev, "failed to get clock 'sclk_hdmi'\n"); | ||
260 | goto fail; | ||
261 | } | ||
262 | res->sclk_dac = clk_get(dev, "sclk_dac"); | ||
263 | if (IS_ERR_OR_NULL(res->sclk_dac)) { | ||
264 | mxr_err(mdev, "failed to get clock 'sclk_dac'\n"); | ||
265 | goto fail; | ||
266 | } | ||
267 | |||
268 | return 0; | ||
269 | fail: | ||
270 | mxr_release_clocks(mdev); | ||
271 | return -ENODEV; | ||
272 | } | ||
273 | |||
274 | static int __devinit mxr_acquire_resources(struct mxr_device *mdev, | ||
275 | struct platform_device *pdev) | ||
276 | { | ||
277 | int ret; | ||
278 | ret = mxr_acquire_plat_resources(mdev, pdev); | ||
279 | |||
280 | if (ret) | ||
281 | goto fail; | ||
282 | |||
283 | ret = mxr_acquire_clocks(mdev); | ||
284 | if (ret) | ||
285 | goto fail_plat; | ||
286 | |||
287 | mxr_info(mdev, "resources acquired\n"); | ||
288 | return 0; | ||
289 | |||
290 | fail_plat: | ||
291 | mxr_release_plat_resources(mdev); | ||
292 | fail: | ||
293 | mxr_err(mdev, "resources acquire failed\n"); | ||
294 | return ret; | ||
295 | } | ||
296 | |||
297 | static void mxr_release_resources(struct mxr_device *mdev) | ||
298 | { | ||
299 | mxr_release_clocks(mdev); | ||
300 | mxr_release_plat_resources(mdev); | ||
301 | memset(&mdev->res, 0, sizeof mdev->res); | ||
302 | } | ||
303 | |||
304 | static void mxr_release_layers(struct mxr_device *mdev) | ||
305 | { | ||
306 | int i; | ||
307 | |||
308 | for (i = 0; i < ARRAY_SIZE(mdev->layer); ++i) | ||
309 | if (mdev->layer[i]) | ||
310 | mxr_layer_release(mdev->layer[i]); | ||
311 | } | ||
312 | |||
313 | static int __devinit mxr_acquire_layers(struct mxr_device *mdev, | ||
314 | struct mxr_platform_data *pdata) | ||
315 | { | ||
316 | mdev->layer[0] = mxr_graph_layer_create(mdev, 0); | ||
317 | mdev->layer[1] = mxr_graph_layer_create(mdev, 1); | ||
318 | mdev->layer[2] = mxr_vp_layer_create(mdev, 0); | ||
319 | |||
320 | if (!mdev->layer[0] || !mdev->layer[1] || !mdev->layer[2]) { | ||
321 | mxr_err(mdev, "failed to acquire layers\n"); | ||
322 | goto fail; | ||
323 | } | ||
324 | |||
325 | return 0; | ||
326 | |||
327 | fail: | ||
328 | mxr_release_layers(mdev); | ||
329 | return -ENODEV; | ||
330 | } | ||
331 | |||
332 | /* ---------- POWER MANAGEMENT ----------- */ | ||
333 | |||
334 | static int mxr_runtime_resume(struct device *dev) | ||
335 | { | ||
336 | struct mxr_device *mdev = to_mdev(dev); | ||
337 | struct mxr_resources *res = &mdev->res; | ||
338 | |||
339 | mxr_dbg(mdev, "resume - start\n"); | ||
340 | mutex_lock(&mdev->mutex); | ||
341 | /* turn clocks on */ | ||
342 | clk_enable(res->mixer); | ||
343 | clk_enable(res->vp); | ||
344 | clk_enable(res->sclk_mixer); | ||
345 | /* apply default configuration */ | ||
346 | mxr_reg_reset(mdev); | ||
347 | mxr_dbg(mdev, "resume - finished\n"); | ||
348 | |||
349 | mutex_unlock(&mdev->mutex); | ||
350 | return 0; | ||
351 | } | ||
352 | |||
353 | static int mxr_runtime_suspend(struct device *dev) | ||
354 | { | ||
355 | struct mxr_device *mdev = to_mdev(dev); | ||
356 | struct mxr_resources *res = &mdev->res; | ||
357 | mxr_dbg(mdev, "suspend - start\n"); | ||
358 | mutex_lock(&mdev->mutex); | ||
359 | /* turn clocks off */ | ||
360 | clk_disable(res->sclk_mixer); | ||
361 | clk_disable(res->vp); | ||
362 | clk_disable(res->mixer); | ||
363 | mutex_unlock(&mdev->mutex); | ||
364 | mxr_dbg(mdev, "suspend - finished\n"); | ||
365 | return 0; | ||
366 | } | ||
367 | |||
368 | static const struct dev_pm_ops mxr_pm_ops = { | ||
369 | .runtime_suspend = mxr_runtime_suspend, | ||
370 | .runtime_resume = mxr_runtime_resume, | ||
371 | }; | ||
372 | |||
373 | /* --------- DRIVER INITIALIZATION ---------- */ | ||
374 | |||
375 | static int __devinit mxr_probe(struct platform_device *pdev) | ||
376 | { | ||
377 | struct device *dev = &pdev->dev; | ||
378 | struct mxr_platform_data *pdata = dev->platform_data; | ||
379 | struct mxr_device *mdev; | ||
380 | int ret; | ||
381 | |||
382 | /* mdev does not exist yet so no mxr_dbg is used */ | ||
383 | dev_info(dev, "probe start\n"); | ||
384 | |||
385 | mdev = kzalloc(sizeof *mdev, GFP_KERNEL); | ||
386 | if (!mdev) { | ||
387 | mxr_err(mdev, "not enough memory.\n"); | ||
388 | ret = -ENOMEM; | ||
389 | goto fail; | ||
390 | } | ||
391 | |||
392 | /* setup pointer to master device */ | ||
393 | mdev->dev = dev; | ||
394 | |||
395 | mutex_init(&mdev->mutex); | ||
396 | spin_lock_init(&mdev->reg_slock); | ||
397 | init_waitqueue_head(&mdev->event_queue); | ||
398 | |||
399 | /* acquire resources: regs, irqs, clocks, regulators */ | ||
400 | ret = mxr_acquire_resources(mdev, pdev); | ||
401 | if (ret) | ||
402 | goto fail_mem; | ||
403 | |||
404 | /* configure resources for video output */ | ||
405 | ret = mxr_acquire_video(mdev, mxr_output_conf, | ||
406 | ARRAY_SIZE(mxr_output_conf)); | ||
407 | if (ret) | ||
408 | goto fail_resources; | ||
409 | |||
410 | /* configure layers */ | ||
411 | ret = mxr_acquire_layers(mdev, pdata); | ||
412 | if (ret) | ||
413 | goto fail_video; | ||
414 | |||
415 | pm_runtime_enable(dev); | ||
416 | |||
417 | mxr_info(mdev, "probe successful\n"); | ||
418 | return 0; | ||
419 | |||
420 | fail_video: | ||
421 | mxr_release_video(mdev); | ||
422 | |||
423 | fail_resources: | ||
424 | mxr_release_resources(mdev); | ||
425 | |||
426 | fail_mem: | ||
427 | kfree(mdev); | ||
428 | |||
429 | fail: | ||
430 | dev_info(dev, "probe failed\n"); | ||
431 | return ret; | ||
432 | } | ||
433 | |||
434 | static int __devexit mxr_remove(struct platform_device *pdev) | ||
435 | { | ||
436 | struct device *dev = &pdev->dev; | ||
437 | struct mxr_device *mdev = to_mdev(dev); | ||
438 | |||
439 | pm_runtime_disable(dev); | ||
440 | |||
441 | mxr_release_layers(mdev); | ||
442 | mxr_release_video(mdev); | ||
443 | mxr_release_resources(mdev); | ||
444 | |||
445 | kfree(mdev); | ||
446 | |||
447 | dev_info(dev, "remove sucessful\n"); | ||
448 | return 0; | ||
449 | } | ||
450 | |||
451 | static struct platform_driver mxr_driver __refdata = { | ||
452 | .probe = mxr_probe, | ||
453 | .remove = __devexit_p(mxr_remove), | ||
454 | .driver = { | ||
455 | .name = MXR_DRIVER_NAME, | ||
456 | .owner = THIS_MODULE, | ||
457 | .pm = &mxr_pm_ops, | ||
458 | } | ||
459 | }; | ||
460 | |||
461 | static int __init mxr_init(void) | ||
462 | { | ||
463 | int i, ret; | ||
464 | static const char banner[] __initdata = KERN_INFO | ||
465 | "Samsung TV Mixer driver, " | ||
466 | "(c) 2010-2011 Samsung Electronics Co., Ltd.\n"; | ||
467 | printk(banner); | ||
468 | |||
469 | /* Loading auxiliary modules */ | ||
470 | for (i = 0; i < ARRAY_SIZE(mxr_output_conf); ++i) | ||
471 | request_module(mxr_output_conf[i].module_name); | ||
472 | |||
473 | ret = platform_driver_register(&mxr_driver); | ||
474 | if (ret != 0) { | ||
475 | printk(KERN_ERR "registration of MIXER driver failed\n"); | ||
476 | return -ENXIO; | ||
477 | } | ||
478 | |||
479 | return 0; | ||
480 | } | ||
481 | module_init(mxr_init); | ||
482 | |||
483 | static void __exit mxr_exit(void) | ||
484 | { | ||
485 | platform_driver_unregister(&mxr_driver); | ||
486 | } | ||
487 | module_exit(mxr_exit); | ||