Commit | Line | Data |
---|---|---|
fef1c8d0 TS |
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; | |
da298c6d HV |
49 | struct v4l2_subdev_format fmt = { |
50 | .which = V4L2_SUBDEV_FORMAT_ACTIVE, | |
51 | }; | |
fef1c8d0 TS |
52 | int ret; |
53 | ||
54 | mutex_lock(&mdev->mutex); | |
55 | sd = to_outsd(mdev); | |
da298c6d HV |
56 | ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); |
57 | *mbus_fmt = fmt.format; | |
fef1c8d0 TS |
58 | WARN(ret, "failed to get mbus_fmt for output %s\n", sd->name); |
59 | mutex_unlock(&mdev->mutex); | |
60 | } | |
61 | ||
62 | void mxr_streamer_get(struct mxr_device *mdev) | |
63 | { | |
64 | mutex_lock(&mdev->mutex); | |
65 | ++mdev->n_streamer; | |
66 | mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_streamer); | |
67 | if (mdev->n_streamer == 1) { | |
68 | struct v4l2_subdev *sd = to_outsd(mdev); | |
da298c6d HV |
69 | struct v4l2_subdev_format fmt = { |
70 | .which = V4L2_SUBDEV_FORMAT_ACTIVE, | |
71 | }; | |
72 | struct v4l2_mbus_framefmt *mbus_fmt = &fmt.format; | |
fef1c8d0 TS |
73 | struct mxr_resources *res = &mdev->res; |
74 | int ret; | |
75 | ||
76 | if (to_output(mdev)->cookie == 0) | |
77 | clk_set_parent(res->sclk_mixer, res->sclk_dac); | |
78 | else | |
79 | clk_set_parent(res->sclk_mixer, res->sclk_hdmi); | |
80 | mxr_reg_s_output(mdev, to_output(mdev)->cookie); | |
81 | ||
da298c6d | 82 | ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); |
fef1c8d0 TS |
83 | WARN(ret, "failed to get mbus_fmt for output %s\n", sd->name); |
84 | ret = v4l2_subdev_call(sd, video, s_stream, 1); | |
85 | WARN(ret, "starting stream failed for output %s\n", sd->name); | |
86 | ||
da298c6d | 87 | mxr_reg_set_mbus_fmt(mdev, mbus_fmt); |
fef1c8d0 TS |
88 | mxr_reg_streamon(mdev); |
89 | ret = mxr_reg_wait4vsync(mdev); | |
90 | WARN(ret, "failed to get vsync (%d) from output\n", ret); | |
91 | } | |
92 | mutex_unlock(&mdev->mutex); | |
93 | mxr_reg_dump(mdev); | |
94 | /* FIXME: what to do when streaming fails? */ | |
95 | } | |
96 | ||
97 | void mxr_streamer_put(struct mxr_device *mdev) | |
98 | { | |
99 | mutex_lock(&mdev->mutex); | |
100 | --mdev->n_streamer; | |
101 | mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_streamer); | |
102 | if (mdev->n_streamer == 0) { | |
103 | int ret; | |
104 | struct v4l2_subdev *sd = to_outsd(mdev); | |
105 | ||
106 | mxr_reg_streamoff(mdev); | |
107 | /* vsync applies Mixer setup */ | |
108 | ret = mxr_reg_wait4vsync(mdev); | |
109 | WARN(ret, "failed to get vsync (%d) from output\n", ret); | |
110 | ret = v4l2_subdev_call(sd, video, s_stream, 0); | |
111 | WARN(ret, "stopping stream failed for output %s\n", sd->name); | |
112 | } | |
113 | WARN(mdev->n_streamer < 0, "negative number of streamers (%d)\n", | |
114 | mdev->n_streamer); | |
115 | mutex_unlock(&mdev->mutex); | |
116 | mxr_reg_dump(mdev); | |
117 | } | |
118 | ||
119 | void mxr_output_get(struct mxr_device *mdev) | |
120 | { | |
121 | mutex_lock(&mdev->mutex); | |
122 | ++mdev->n_output; | |
123 | mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_output); | |
124 | /* turn on auxiliary driver */ | |
125 | if (mdev->n_output == 1) | |
126 | v4l2_subdev_call(to_outsd(mdev), core, s_power, 1); | |
127 | mutex_unlock(&mdev->mutex); | |
128 | } | |
129 | ||
130 | void mxr_output_put(struct mxr_device *mdev) | |
131 | { | |
132 | mutex_lock(&mdev->mutex); | |
133 | --mdev->n_output; | |
134 | mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_output); | |
135 | /* turn on auxiliary driver */ | |
136 | if (mdev->n_output == 0) | |
137 | v4l2_subdev_call(to_outsd(mdev), core, s_power, 0); | |
138 | WARN(mdev->n_output < 0, "negative number of output users (%d)\n", | |
139 | mdev->n_output); | |
140 | mutex_unlock(&mdev->mutex); | |
141 | } | |
142 | ||
143 | int mxr_power_get(struct mxr_device *mdev) | |
144 | { | |
145 | int ret = pm_runtime_get_sync(mdev->dev); | |
146 | ||
147 | /* returning 1 means that power is already enabled, | |
148 | * so zero success be returned */ | |
287980e4 | 149 | if (ret < 0) |
fef1c8d0 TS |
150 | return ret; |
151 | return 0; | |
152 | } | |
153 | ||
154 | void mxr_power_put(struct mxr_device *mdev) | |
155 | { | |
156 | pm_runtime_put_sync(mdev->dev); | |
157 | } | |
158 | ||
159 | /* --------- RESOURCE MANAGEMENT -------------*/ | |
160 | ||
4c62e976 GKH |
161 | static int mxr_acquire_plat_resources(struct mxr_device *mdev, |
162 | struct platform_device *pdev) | |
fef1c8d0 TS |
163 | { |
164 | struct resource *res; | |
165 | int ret; | |
166 | ||
167 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mxr"); | |
168 | if (res == NULL) { | |
169 | mxr_err(mdev, "get memory resource failed.\n"); | |
170 | ret = -ENXIO; | |
171 | goto fail; | |
172 | } | |
173 | ||
174 | mdev->res.mxr_regs = ioremap(res->start, resource_size(res)); | |
175 | if (mdev->res.mxr_regs == NULL) { | |
176 | mxr_err(mdev, "register mapping failed.\n"); | |
177 | ret = -ENXIO; | |
178 | goto fail; | |
179 | } | |
180 | ||
181 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vp"); | |
182 | if (res == NULL) { | |
183 | mxr_err(mdev, "get memory resource failed.\n"); | |
184 | ret = -ENXIO; | |
185 | goto fail_mxr_regs; | |
186 | } | |
187 | ||
188 | mdev->res.vp_regs = ioremap(res->start, resource_size(res)); | |
189 | if (mdev->res.vp_regs == NULL) { | |
190 | mxr_err(mdev, "register mapping failed.\n"); | |
191 | ret = -ENXIO; | |
192 | goto fail_mxr_regs; | |
193 | } | |
194 | ||
195 | res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "irq"); | |
196 | if (res == NULL) { | |
197 | mxr_err(mdev, "get interrupt resource failed.\n"); | |
198 | ret = -ENXIO; | |
199 | goto fail_vp_regs; | |
200 | } | |
201 | ||
202 | ret = request_irq(res->start, mxr_irq_handler, 0, "s5p-mixer", mdev); | |
203 | if (ret) { | |
204 | mxr_err(mdev, "request interrupt failed.\n"); | |
205 | goto fail_vp_regs; | |
206 | } | |
207 | mdev->res.irq = res->start; | |
208 | ||
209 | return 0; | |
210 | ||
211 | fail_vp_regs: | |
212 | iounmap(mdev->res.vp_regs); | |
213 | ||
214 | fail_mxr_regs: | |
215 | iounmap(mdev->res.mxr_regs); | |
216 | ||
217 | fail: | |
218 | return ret; | |
219 | } | |
220 | ||
0781c057 SK |
221 | static void mxr_resource_clear_clocks(struct mxr_resources *res) |
222 | { | |
223 | res->mixer = ERR_PTR(-EINVAL); | |
224 | res->vp = ERR_PTR(-EINVAL); | |
225 | res->sclk_mixer = ERR_PTR(-EINVAL); | |
226 | res->sclk_hdmi = ERR_PTR(-EINVAL); | |
227 | res->sclk_dac = ERR_PTR(-EINVAL); | |
228 | } | |
229 | ||
fef1c8d0 TS |
230 | static void mxr_release_plat_resources(struct mxr_device *mdev) |
231 | { | |
232 | free_irq(mdev->res.irq, mdev); | |
233 | iounmap(mdev->res.vp_regs); | |
234 | iounmap(mdev->res.mxr_regs); | |
235 | } | |
236 | ||
237 | static void mxr_release_clocks(struct mxr_device *mdev) | |
238 | { | |
239 | struct mxr_resources *res = &mdev->res; | |
240 | ||
0781c057 | 241 | if (!IS_ERR(res->sclk_dac)) |
fef1c8d0 | 242 | clk_put(res->sclk_dac); |
0781c057 | 243 | if (!IS_ERR(res->sclk_hdmi)) |
fef1c8d0 | 244 | clk_put(res->sclk_hdmi); |
0781c057 | 245 | if (!IS_ERR(res->sclk_mixer)) |
fef1c8d0 | 246 | clk_put(res->sclk_mixer); |
0781c057 | 247 | if (!IS_ERR(res->vp)) |
fef1c8d0 | 248 | clk_put(res->vp); |
0781c057 | 249 | if (!IS_ERR(res->mixer)) |
fef1c8d0 TS |
250 | clk_put(res->mixer); |
251 | } | |
252 | ||
253 | static int mxr_acquire_clocks(struct mxr_device *mdev) | |
254 | { | |
255 | struct mxr_resources *res = &mdev->res; | |
256 | struct device *dev = mdev->dev; | |
257 | ||
0781c057 SK |
258 | mxr_resource_clear_clocks(res); |
259 | ||
fef1c8d0 | 260 | res->mixer = clk_get(dev, "mixer"); |
aeae3db1 | 261 | if (IS_ERR(res->mixer)) { |
fef1c8d0 TS |
262 | mxr_err(mdev, "failed to get clock 'mixer'\n"); |
263 | goto fail; | |
264 | } | |
265 | res->vp = clk_get(dev, "vp"); | |
aeae3db1 | 266 | if (IS_ERR(res->vp)) { |
fef1c8d0 TS |
267 | mxr_err(mdev, "failed to get clock 'vp'\n"); |
268 | goto fail; | |
269 | } | |
270 | res->sclk_mixer = clk_get(dev, "sclk_mixer"); | |
aeae3db1 | 271 | if (IS_ERR(res->sclk_mixer)) { |
fef1c8d0 TS |
272 | mxr_err(mdev, "failed to get clock 'sclk_mixer'\n"); |
273 | goto fail; | |
274 | } | |
275 | res->sclk_hdmi = clk_get(dev, "sclk_hdmi"); | |
aeae3db1 | 276 | if (IS_ERR(res->sclk_hdmi)) { |
fef1c8d0 TS |
277 | mxr_err(mdev, "failed to get clock 'sclk_hdmi'\n"); |
278 | goto fail; | |
279 | } | |
280 | res->sclk_dac = clk_get(dev, "sclk_dac"); | |
aeae3db1 | 281 | if (IS_ERR(res->sclk_dac)) { |
fef1c8d0 TS |
282 | mxr_err(mdev, "failed to get clock 'sclk_dac'\n"); |
283 | goto fail; | |
284 | } | |
285 | ||
286 | return 0; | |
287 | fail: | |
288 | mxr_release_clocks(mdev); | |
289 | return -ENODEV; | |
290 | } | |
291 | ||
4c62e976 GKH |
292 | static int mxr_acquire_resources(struct mxr_device *mdev, |
293 | struct platform_device *pdev) | |
fef1c8d0 TS |
294 | { |
295 | int ret; | |
296 | ret = mxr_acquire_plat_resources(mdev, pdev); | |
297 | ||
298 | if (ret) | |
299 | goto fail; | |
300 | ||
301 | ret = mxr_acquire_clocks(mdev); | |
302 | if (ret) | |
303 | goto fail_plat; | |
304 | ||
305 | mxr_info(mdev, "resources acquired\n"); | |
306 | return 0; | |
307 | ||
308 | fail_plat: | |
309 | mxr_release_plat_resources(mdev); | |
310 | fail: | |
311 | mxr_err(mdev, "resources acquire failed\n"); | |
312 | return ret; | |
313 | } | |
314 | ||
315 | static void mxr_release_resources(struct mxr_device *mdev) | |
316 | { | |
317 | mxr_release_clocks(mdev); | |
318 | mxr_release_plat_resources(mdev); | |
dc033985 | 319 | memset(&mdev->res, 0, sizeof(mdev->res)); |
0781c057 | 320 | mxr_resource_clear_clocks(&mdev->res); |
fef1c8d0 TS |
321 | } |
322 | ||
323 | static void mxr_release_layers(struct mxr_device *mdev) | |
324 | { | |
325 | int i; | |
326 | ||
327 | for (i = 0; i < ARRAY_SIZE(mdev->layer); ++i) | |
328 | if (mdev->layer[i]) | |
329 | mxr_layer_release(mdev->layer[i]); | |
330 | } | |
331 | ||
4c62e976 GKH |
332 | static int mxr_acquire_layers(struct mxr_device *mdev, |
333 | struct mxr_platform_data *pdata) | |
fef1c8d0 TS |
334 | { |
335 | mdev->layer[0] = mxr_graph_layer_create(mdev, 0); | |
336 | mdev->layer[1] = mxr_graph_layer_create(mdev, 1); | |
337 | mdev->layer[2] = mxr_vp_layer_create(mdev, 0); | |
338 | ||
339 | if (!mdev->layer[0] || !mdev->layer[1] || !mdev->layer[2]) { | |
340 | mxr_err(mdev, "failed to acquire layers\n"); | |
341 | goto fail; | |
342 | } | |
343 | ||
344 | return 0; | |
345 | ||
346 | fail: | |
347 | mxr_release_layers(mdev); | |
348 | return -ENODEV; | |
349 | } | |
350 | ||
351 | /* ---------- POWER MANAGEMENT ----------- */ | |
352 | ||
353 | static int mxr_runtime_resume(struct device *dev) | |
354 | { | |
355 | struct mxr_device *mdev = to_mdev(dev); | |
356 | struct mxr_resources *res = &mdev->res; | |
03ce1a13 | 357 | int ret; |
fef1c8d0 TS |
358 | |
359 | mxr_dbg(mdev, "resume - start\n"); | |
360 | mutex_lock(&mdev->mutex); | |
361 | /* turn clocks on */ | |
03ce1a13 MK |
362 | ret = clk_prepare_enable(res->mixer); |
363 | if (ret < 0) { | |
364 | dev_err(mdev->dev, "clk_prepare_enable(mixer) failed\n"); | |
365 | goto fail; | |
366 | } | |
367 | ret = clk_prepare_enable(res->vp); | |
368 | if (ret < 0) { | |
369 | dev_err(mdev->dev, "clk_prepare_enable(vp) failed\n"); | |
370 | goto fail_mixer; | |
371 | } | |
372 | ret = clk_prepare_enable(res->sclk_mixer); | |
373 | if (ret < 0) { | |
374 | dev_err(mdev->dev, "clk_prepare_enable(sclk_mixer) failed\n"); | |
375 | goto fail_vp; | |
376 | } | |
fef1c8d0 TS |
377 | /* apply default configuration */ |
378 | mxr_reg_reset(mdev); | |
379 | mxr_dbg(mdev, "resume - finished\n"); | |
380 | ||
381 | mutex_unlock(&mdev->mutex); | |
382 | return 0; | |
03ce1a13 MK |
383 | |
384 | fail_vp: | |
385 | clk_disable_unprepare(res->vp); | |
386 | fail_mixer: | |
387 | clk_disable_unprepare(res->mixer); | |
388 | fail: | |
389 | mutex_unlock(&mdev->mutex); | |
390 | dev_err(mdev->dev, "resume failed\n"); | |
391 | return ret; | |
fef1c8d0 TS |
392 | } |
393 | ||
394 | static int mxr_runtime_suspend(struct device *dev) | |
395 | { | |
396 | struct mxr_device *mdev = to_mdev(dev); | |
397 | struct mxr_resources *res = &mdev->res; | |
398 | mxr_dbg(mdev, "suspend - start\n"); | |
399 | mutex_lock(&mdev->mutex); | |
400 | /* turn clocks off */ | |
03ce1a13 MK |
401 | clk_disable_unprepare(res->sclk_mixer); |
402 | clk_disable_unprepare(res->vp); | |
403 | clk_disable_unprepare(res->mixer); | |
fef1c8d0 TS |
404 | mutex_unlock(&mdev->mutex); |
405 | mxr_dbg(mdev, "suspend - finished\n"); | |
406 | return 0; | |
407 | } | |
408 | ||
409 | static const struct dev_pm_ops mxr_pm_ops = { | |
410 | .runtime_suspend = mxr_runtime_suspend, | |
411 | .runtime_resume = mxr_runtime_resume, | |
412 | }; | |
413 | ||
414 | /* --------- DRIVER INITIALIZATION ---------- */ | |
415 | ||
4c62e976 | 416 | static int mxr_probe(struct platform_device *pdev) |
fef1c8d0 TS |
417 | { |
418 | struct device *dev = &pdev->dev; | |
419 | struct mxr_platform_data *pdata = dev->platform_data; | |
420 | struct mxr_device *mdev; | |
421 | int ret; | |
422 | ||
423 | /* mdev does not exist yet so no mxr_dbg is used */ | |
424 | dev_info(dev, "probe start\n"); | |
425 | ||
dc033985 | 426 | mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); |
fef1c8d0 | 427 | if (!mdev) { |
970e85f8 | 428 | dev_err(dev, "not enough memory.\n"); |
fef1c8d0 TS |
429 | ret = -ENOMEM; |
430 | goto fail; | |
431 | } | |
432 | ||
433 | /* setup pointer to master device */ | |
434 | mdev->dev = dev; | |
435 | ||
436 | mutex_init(&mdev->mutex); | |
437 | spin_lock_init(&mdev->reg_slock); | |
438 | init_waitqueue_head(&mdev->event_queue); | |
439 | ||
440 | /* acquire resources: regs, irqs, clocks, regulators */ | |
441 | ret = mxr_acquire_resources(mdev, pdev); | |
442 | if (ret) | |
443 | goto fail_mem; | |
444 | ||
445 | /* configure resources for video output */ | |
446 | ret = mxr_acquire_video(mdev, mxr_output_conf, | |
447 | ARRAY_SIZE(mxr_output_conf)); | |
448 | if (ret) | |
449 | goto fail_resources; | |
450 | ||
451 | /* configure layers */ | |
452 | ret = mxr_acquire_layers(mdev, pdata); | |
453 | if (ret) | |
454 | goto fail_video; | |
455 | ||
456 | pm_runtime_enable(dev); | |
457 | ||
458 | mxr_info(mdev, "probe successful\n"); | |
459 | return 0; | |
460 | ||
461 | fail_video: | |
462 | mxr_release_video(mdev); | |
463 | ||
464 | fail_resources: | |
465 | mxr_release_resources(mdev); | |
466 | ||
467 | fail_mem: | |
468 | kfree(mdev); | |
469 | ||
470 | fail: | |
471 | dev_info(dev, "probe failed\n"); | |
472 | return ret; | |
473 | } | |
474 | ||
4c62e976 | 475 | static int mxr_remove(struct platform_device *pdev) |
fef1c8d0 TS |
476 | { |
477 | struct device *dev = &pdev->dev; | |
478 | struct mxr_device *mdev = to_mdev(dev); | |
479 | ||
480 | pm_runtime_disable(dev); | |
481 | ||
482 | mxr_release_layers(mdev); | |
483 | mxr_release_video(mdev); | |
484 | mxr_release_resources(mdev); | |
485 | ||
486 | kfree(mdev); | |
487 | ||
41292b16 | 488 | dev_info(dev, "remove successful\n"); |
fef1c8d0 TS |
489 | return 0; |
490 | } | |
491 | ||
492 | static struct platform_driver mxr_driver __refdata = { | |
493 | .probe = mxr_probe, | |
4c62e976 | 494 | .remove = mxr_remove, |
fef1c8d0 TS |
495 | .driver = { |
496 | .name = MXR_DRIVER_NAME, | |
fef1c8d0 TS |
497 | .pm = &mxr_pm_ops, |
498 | } | |
499 | }; | |
500 | ||
501 | static int __init mxr_init(void) | |
502 | { | |
503 | int i, ret; | |
f5c99037 | 504 | static const char banner[] __initconst = |
fef1c8d0 TS |
505 | "Samsung TV Mixer driver, " |
506 | "(c) 2010-2011 Samsung Electronics Co., Ltd.\n"; | |
f5c99037 | 507 | pr_info("%s\n", banner); |
fef1c8d0 TS |
508 | |
509 | /* Loading auxiliary modules */ | |
510 | for (i = 0; i < ARRAY_SIZE(mxr_output_conf); ++i) | |
511 | request_module(mxr_output_conf[i].module_name); | |
512 | ||
513 | ret = platform_driver_register(&mxr_driver); | |
514 | if (ret != 0) { | |
f5c99037 | 515 | pr_err("s5p-tv: registration of MIXER driver failed\n"); |
fef1c8d0 TS |
516 | return -ENXIO; |
517 | } | |
518 | ||
519 | return 0; | |
520 | } | |
521 | module_init(mxr_init); | |
522 | ||
523 | static void __exit mxr_exit(void) | |
524 | { | |
525 | platform_driver_unregister(&mxr_driver); | |
526 | } | |
527 | module_exit(mxr_exit); |