Commit | Line | Data |
---|---|---|
9a498400 TS |
1 | /* |
2 | * Samsung Standard Definition Output (SDO) 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 <linux/clk.h> | |
15 | #include <linux/delay.h> | |
16 | #include <linux/kernel.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/interrupt.h> | |
19 | #include <linux/io.h> | |
20 | #include <linux/irq.h> | |
21 | #include <linux/platform_device.h> | |
22 | #include <linux/pm_runtime.h> | |
23 | #include <linux/regulator/consumer.h> | |
24 | #include <linux/slab.h> | |
25 | ||
26 | #include <media/v4l2-subdev.h> | |
27 | ||
28 | #include "regs-sdo.h" | |
29 | ||
30 | MODULE_AUTHOR("Tomasz Stanislawski, <t.stanislaws@samsung.com>"); | |
31 | MODULE_DESCRIPTION("Samsung Standard Definition Output (SDO)"); | |
32 | MODULE_LICENSE("GPL"); | |
33 | ||
34 | #define SDO_DEFAULT_STD V4L2_STD_PAL | |
35 | ||
36 | struct sdo_format { | |
37 | v4l2_std_id id; | |
38 | /* all modes are 720 pixels wide */ | |
39 | unsigned int height; | |
40 | unsigned int cookie; | |
41 | }; | |
42 | ||
43 | struct sdo_device { | |
44 | /** pointer to device parent */ | |
45 | struct device *dev; | |
46 | /** base address of SDO registers */ | |
47 | void __iomem *regs; | |
48 | /** SDO interrupt */ | |
49 | unsigned int irq; | |
50 | /** DAC source clock */ | |
51 | struct clk *sclk_dac; | |
52 | /** DAC clock */ | |
53 | struct clk *dac; | |
54 | /** DAC physical interface */ | |
55 | struct clk *dacphy; | |
56 | /** clock for control of VPLL */ | |
57 | struct clk *fout_vpll; | |
58 | /** regulator for SDO IP power */ | |
59 | struct regulator *vdac; | |
60 | /** regulator for SDO plug detection */ | |
61 | struct regulator *vdet; | |
62 | /** subdev used as device interface */ | |
63 | struct v4l2_subdev sd; | |
64 | /** current format */ | |
65 | const struct sdo_format *fmt; | |
66 | }; | |
67 | ||
68 | static inline struct sdo_device *sd_to_sdev(struct v4l2_subdev *sd) | |
69 | { | |
70 | return container_of(sd, struct sdo_device, sd); | |
71 | } | |
72 | ||
73 | static inline | |
74 | void sdo_write_mask(struct sdo_device *sdev, u32 reg_id, u32 value, u32 mask) | |
75 | { | |
76 | u32 old = readl(sdev->regs + reg_id); | |
77 | value = (value & mask) | (old & ~mask); | |
78 | writel(value, sdev->regs + reg_id); | |
79 | } | |
80 | ||
81 | static inline | |
82 | void sdo_write(struct sdo_device *sdev, u32 reg_id, u32 value) | |
83 | { | |
84 | writel(value, sdev->regs + reg_id); | |
85 | } | |
86 | ||
87 | static inline | |
88 | u32 sdo_read(struct sdo_device *sdev, u32 reg_id) | |
89 | { | |
90 | return readl(sdev->regs + reg_id); | |
91 | } | |
92 | ||
93 | static irqreturn_t sdo_irq_handler(int irq, void *dev_data) | |
94 | { | |
95 | struct sdo_device *sdev = dev_data; | |
96 | ||
97 | /* clear interrupt */ | |
98 | sdo_write_mask(sdev, SDO_IRQ, ~0, SDO_VSYNC_IRQ_PEND); | |
99 | return IRQ_HANDLED; | |
100 | } | |
101 | ||
102 | static void sdo_reg_debug(struct sdo_device *sdev) | |
103 | { | |
104 | #define DBGREG(reg_id) \ | |
105 | dev_info(sdev->dev, #reg_id " = %08x\n", \ | |
106 | sdo_read(sdev, reg_id)) | |
107 | ||
108 | DBGREG(SDO_CLKCON); | |
109 | DBGREG(SDO_CONFIG); | |
110 | DBGREG(SDO_VBI); | |
111 | DBGREG(SDO_DAC); | |
112 | DBGREG(SDO_IRQ); | |
113 | DBGREG(SDO_IRQMASK); | |
114 | DBGREG(SDO_VERSION); | |
115 | } | |
116 | ||
117 | static const struct sdo_format sdo_format[] = { | |
118 | { V4L2_STD_PAL_N, .height = 576, .cookie = SDO_PAL_N }, | |
119 | { V4L2_STD_PAL_Nc, .height = 576, .cookie = SDO_PAL_NC }, | |
120 | { V4L2_STD_PAL_M, .height = 480, .cookie = SDO_PAL_M }, | |
121 | { V4L2_STD_PAL_60, .height = 480, .cookie = SDO_PAL_60 }, | |
122 | { V4L2_STD_NTSC_443, .height = 480, .cookie = SDO_NTSC_443 }, | |
123 | { V4L2_STD_PAL, .height = 576, .cookie = SDO_PAL_BGHID }, | |
124 | { V4L2_STD_NTSC_M, .height = 480, .cookie = SDO_NTSC_M }, | |
125 | }; | |
126 | ||
127 | static const struct sdo_format *sdo_find_format(v4l2_std_id id) | |
128 | { | |
129 | int i; | |
130 | for (i = 0; i < ARRAY_SIZE(sdo_format); ++i) | |
131 | if (sdo_format[i].id & id) | |
132 | return &sdo_format[i]; | |
133 | return NULL; | |
134 | } | |
135 | ||
136 | static int sdo_g_tvnorms_output(struct v4l2_subdev *sd, v4l2_std_id *std) | |
137 | { | |
138 | *std = V4L2_STD_NTSC_M | V4L2_STD_PAL_M | V4L2_STD_PAL | | |
139 | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | | |
140 | V4L2_STD_NTSC_443 | V4L2_STD_PAL_60; | |
141 | return 0; | |
142 | } | |
143 | ||
144 | static int sdo_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) | |
145 | { | |
146 | struct sdo_device *sdev = sd_to_sdev(sd); | |
147 | const struct sdo_format *fmt; | |
148 | fmt = sdo_find_format(std); | |
149 | if (fmt == NULL) | |
150 | return -EINVAL; | |
151 | sdev->fmt = fmt; | |
152 | return 0; | |
153 | } | |
154 | ||
155 | static int sdo_g_std_output(struct v4l2_subdev *sd, v4l2_std_id *std) | |
156 | { | |
157 | *std = sd_to_sdev(sd)->fmt->id; | |
158 | return 0; | |
159 | } | |
160 | ||
161 | static int sdo_g_mbus_fmt(struct v4l2_subdev *sd, | |
162 | struct v4l2_mbus_framefmt *fmt) | |
163 | { | |
164 | struct sdo_device *sdev = sd_to_sdev(sd); | |
165 | ||
166 | if (!sdev->fmt) | |
167 | return -ENXIO; | |
168 | /* all modes are 720 pixels wide */ | |
169 | fmt->width = 720; | |
170 | fmt->height = sdev->fmt->height; | |
171 | fmt->code = V4L2_MBUS_FMT_FIXED; | |
172 | fmt->field = V4L2_FIELD_INTERLACED; | |
0689133b | 173 | fmt->colorspace = V4L2_COLORSPACE_JPEG; |
9a498400 TS |
174 | return 0; |
175 | } | |
176 | ||
177 | static int sdo_s_power(struct v4l2_subdev *sd, int on) | |
178 | { | |
179 | struct sdo_device *sdev = sd_to_sdev(sd); | |
180 | struct device *dev = sdev->dev; | |
181 | int ret; | |
182 | ||
183 | dev_info(dev, "sdo_s_power(%d)\n", on); | |
184 | ||
185 | if (on) | |
186 | ret = pm_runtime_get_sync(dev); | |
187 | else | |
188 | ret = pm_runtime_put_sync(dev); | |
189 | ||
190 | /* only values < 0 indicate errors */ | |
191 | return IS_ERR_VALUE(ret) ? ret : 0; | |
192 | } | |
193 | ||
194 | static int sdo_streamon(struct sdo_device *sdev) | |
195 | { | |
196 | /* set proper clock for Timing Generator */ | |
197 | clk_set_rate(sdev->fout_vpll, 54000000); | |
198 | dev_info(sdev->dev, "fout_vpll.rate = %lu\n", | |
199 | clk_get_rate(sdev->fout_vpll)); | |
200 | /* enable clock in SDO */ | |
201 | sdo_write_mask(sdev, SDO_CLKCON, ~0, SDO_TVOUT_CLOCK_ON); | |
202 | clk_enable(sdev->dacphy); | |
203 | /* enable DAC */ | |
204 | sdo_write_mask(sdev, SDO_DAC, ~0, SDO_POWER_ON_DAC); | |
205 | sdo_reg_debug(sdev); | |
206 | return 0; | |
207 | } | |
208 | ||
209 | static int sdo_streamoff(struct sdo_device *sdev) | |
210 | { | |
211 | int tries; | |
212 | ||
213 | sdo_write_mask(sdev, SDO_DAC, 0, SDO_POWER_ON_DAC); | |
214 | clk_disable(sdev->dacphy); | |
215 | sdo_write_mask(sdev, SDO_CLKCON, 0, SDO_TVOUT_CLOCK_ON); | |
216 | for (tries = 100; tries; --tries) { | |
217 | if (sdo_read(sdev, SDO_CLKCON) & SDO_TVOUT_CLOCK_READY) | |
218 | break; | |
219 | mdelay(1); | |
220 | } | |
221 | if (tries == 0) | |
222 | dev_err(sdev->dev, "failed to stop streaming\n"); | |
223 | return tries ? 0 : -EIO; | |
224 | } | |
225 | ||
226 | static int sdo_s_stream(struct v4l2_subdev *sd, int on) | |
227 | { | |
228 | struct sdo_device *sdev = sd_to_sdev(sd); | |
229 | return on ? sdo_streamon(sdev) : sdo_streamoff(sdev); | |
230 | } | |
231 | ||
232 | static const struct v4l2_subdev_core_ops sdo_sd_core_ops = { | |
233 | .s_power = sdo_s_power, | |
234 | }; | |
235 | ||
236 | static const struct v4l2_subdev_video_ops sdo_sd_video_ops = { | |
237 | .s_std_output = sdo_s_std_output, | |
238 | .g_std_output = sdo_g_std_output, | |
239 | .g_tvnorms_output = sdo_g_tvnorms_output, | |
240 | .g_mbus_fmt = sdo_g_mbus_fmt, | |
241 | .s_stream = sdo_s_stream, | |
242 | }; | |
243 | ||
244 | static const struct v4l2_subdev_ops sdo_sd_ops = { | |
245 | .core = &sdo_sd_core_ops, | |
246 | .video = &sdo_sd_video_ops, | |
247 | }; | |
248 | ||
249 | static int sdo_runtime_suspend(struct device *dev) | |
250 | { | |
251 | struct v4l2_subdev *sd = dev_get_drvdata(dev); | |
252 | struct sdo_device *sdev = sd_to_sdev(sd); | |
253 | ||
254 | dev_info(dev, "suspend\n"); | |
255 | regulator_disable(sdev->vdet); | |
256 | regulator_disable(sdev->vdac); | |
257 | clk_disable(sdev->sclk_dac); | |
258 | return 0; | |
259 | } | |
260 | ||
261 | static int sdo_runtime_resume(struct device *dev) | |
262 | { | |
263 | struct v4l2_subdev *sd = dev_get_drvdata(dev); | |
264 | struct sdo_device *sdev = sd_to_sdev(sd); | |
265 | ||
266 | dev_info(dev, "resume\n"); | |
267 | clk_enable(sdev->sclk_dac); | |
268 | regulator_enable(sdev->vdac); | |
269 | regulator_enable(sdev->vdet); | |
270 | ||
271 | /* software reset */ | |
272 | sdo_write_mask(sdev, SDO_CLKCON, ~0, SDO_TVOUT_SW_RESET); | |
273 | mdelay(10); | |
274 | sdo_write_mask(sdev, SDO_CLKCON, 0, SDO_TVOUT_SW_RESET); | |
275 | ||
276 | /* setting TV mode */ | |
277 | sdo_write_mask(sdev, SDO_CONFIG, sdev->fmt->cookie, SDO_STANDARD_MASK); | |
278 | /* XXX: forcing interlaced mode using undocumented bit */ | |
279 | sdo_write_mask(sdev, SDO_CONFIG, 0, SDO_PROGRESSIVE); | |
280 | /* turn all VBI off */ | |
281 | sdo_write_mask(sdev, SDO_VBI, 0, SDO_CVBS_WSS_INS | | |
282 | SDO_CVBS_CLOSED_CAPTION_MASK); | |
283 | /* turn all post processing off */ | |
284 | sdo_write_mask(sdev, SDO_CCCON, ~0, SDO_COMPENSATION_BHS_ADJ_OFF | | |
285 | SDO_COMPENSATION_CVBS_COMP_OFF); | |
286 | sdo_reg_debug(sdev); | |
287 | return 0; | |
288 | } | |
289 | ||
290 | static const struct dev_pm_ops sdo_pm_ops = { | |
291 | .runtime_suspend = sdo_runtime_suspend, | |
292 | .runtime_resume = sdo_runtime_resume, | |
293 | }; | |
294 | ||
4c62e976 | 295 | static int sdo_probe(struct platform_device *pdev) |
9a498400 TS |
296 | { |
297 | struct device *dev = &pdev->dev; | |
298 | struct sdo_device *sdev; | |
299 | struct resource *res; | |
300 | int ret = 0; | |
301 | struct clk *sclk_vpll; | |
302 | ||
303 | dev_info(dev, "probe start\n"); | |
80f0dee2 | 304 | sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL); |
9a498400 TS |
305 | if (!sdev) { |
306 | dev_err(dev, "not enough memory.\n"); | |
307 | ret = -ENOMEM; | |
308 | goto fail; | |
309 | } | |
310 | sdev->dev = dev; | |
311 | ||
312 | /* mapping registers */ | |
313 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
314 | if (res == NULL) { | |
315 | dev_err(dev, "get memory resource failed.\n"); | |
316 | ret = -ENXIO; | |
e861dccc | 317 | goto fail; |
9a498400 TS |
318 | } |
319 | ||
e861dccc | 320 | sdev->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); |
9a498400 TS |
321 | if (sdev->regs == NULL) { |
322 | dev_err(dev, "register mapping failed.\n"); | |
323 | ret = -ENXIO; | |
e861dccc | 324 | goto fail; |
9a498400 TS |
325 | } |
326 | ||
327 | /* acquiring interrupt */ | |
328 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
329 | if (res == NULL) { | |
330 | dev_err(dev, "get interrupt resource failed.\n"); | |
331 | ret = -ENXIO; | |
e861dccc | 332 | goto fail; |
9a498400 | 333 | } |
e861dccc JL |
334 | ret = devm_request_irq(&pdev->dev, res->start, sdo_irq_handler, 0, |
335 | "s5p-sdo", sdev); | |
9a498400 TS |
336 | if (ret) { |
337 | dev_err(dev, "request interrupt failed.\n"); | |
e861dccc | 338 | goto fail; |
9a498400 TS |
339 | } |
340 | sdev->irq = res->start; | |
341 | ||
342 | /* acquire clocks */ | |
343 | sdev->sclk_dac = clk_get(dev, "sclk_dac"); | |
cf48f56c | 344 | if (IS_ERR(sdev->sclk_dac)) { |
9a498400 | 345 | dev_err(dev, "failed to get clock 'sclk_dac'\n"); |
cf48f56c | 346 | ret = PTR_ERR(sdev->sclk_dac); |
e861dccc | 347 | goto fail; |
9a498400 TS |
348 | } |
349 | sdev->dac = clk_get(dev, "dac"); | |
cf48f56c | 350 | if (IS_ERR(sdev->dac)) { |
9a498400 | 351 | dev_err(dev, "failed to get clock 'dac'\n"); |
cf48f56c | 352 | ret = PTR_ERR(sdev->dac); |
9a498400 TS |
353 | goto fail_sclk_dac; |
354 | } | |
355 | sdev->dacphy = clk_get(dev, "dacphy"); | |
cf48f56c | 356 | if (IS_ERR(sdev->dacphy)) { |
9a498400 | 357 | dev_err(dev, "failed to get clock 'dacphy'\n"); |
cf48f56c | 358 | ret = PTR_ERR(sdev->dacphy); |
9a498400 TS |
359 | goto fail_dac; |
360 | } | |
361 | sclk_vpll = clk_get(dev, "sclk_vpll"); | |
cf48f56c | 362 | if (IS_ERR(sclk_vpll)) { |
9a498400 | 363 | dev_err(dev, "failed to get clock 'sclk_vpll'\n"); |
cf48f56c | 364 | ret = PTR_ERR(sclk_vpll); |
9a498400 TS |
365 | goto fail_dacphy; |
366 | } | |
367 | clk_set_parent(sdev->sclk_dac, sclk_vpll); | |
368 | clk_put(sclk_vpll); | |
369 | sdev->fout_vpll = clk_get(dev, "fout_vpll"); | |
cf48f56c | 370 | if (IS_ERR(sdev->fout_vpll)) { |
9a498400 | 371 | dev_err(dev, "failed to get clock 'fout_vpll'\n"); |
cf48f56c | 372 | ret = PTR_ERR(sdev->fout_vpll); |
9a498400 TS |
373 | goto fail_dacphy; |
374 | } | |
375 | dev_info(dev, "fout_vpll.rate = %lu\n", clk_get_rate(sclk_vpll)); | |
376 | ||
377 | /* acquire regulator */ | |
00d98c1b | 378 | sdev->vdac = devm_regulator_get(dev, "vdd33a_dac"); |
cf48f56c | 379 | if (IS_ERR(sdev->vdac)) { |
9a498400 | 380 | dev_err(dev, "failed to get regulator 'vdac'\n"); |
cf48f56c | 381 | ret = PTR_ERR(sdev->vdac); |
9a498400 TS |
382 | goto fail_fout_vpll; |
383 | } | |
00d98c1b | 384 | sdev->vdet = devm_regulator_get(dev, "vdet"); |
cf48f56c | 385 | if (IS_ERR(sdev->vdet)) { |
9a498400 | 386 | dev_err(dev, "failed to get regulator 'vdet'\n"); |
cf48f56c | 387 | ret = PTR_ERR(sdev->vdet); |
00d98c1b | 388 | goto fail_fout_vpll; |
9a498400 TS |
389 | } |
390 | ||
391 | /* enable gate for dac clock, because mixer uses it */ | |
392 | clk_enable(sdev->dac); | |
393 | ||
394 | /* configure power management */ | |
395 | pm_runtime_enable(dev); | |
396 | ||
397 | /* configuration of interface subdevice */ | |
398 | v4l2_subdev_init(&sdev->sd, &sdo_sd_ops); | |
399 | sdev->sd.owner = THIS_MODULE; | |
80f0dee2 | 400 | strlcpy(sdev->sd.name, "s5p-sdo", sizeof(sdev->sd.name)); |
9a498400 TS |
401 | |
402 | /* set default format */ | |
403 | sdev->fmt = sdo_find_format(SDO_DEFAULT_STD); | |
404 | BUG_ON(sdev->fmt == NULL); | |
405 | ||
406 | /* keeping subdev in device's private for use by other drivers */ | |
407 | dev_set_drvdata(dev, &sdev->sd); | |
408 | ||
409 | dev_info(dev, "probe succeeded\n"); | |
410 | return 0; | |
411 | ||
9a498400 TS |
412 | fail_fout_vpll: |
413 | clk_put(sdev->fout_vpll); | |
414 | fail_dacphy: | |
415 | clk_put(sdev->dacphy); | |
416 | fail_dac: | |
417 | clk_put(sdev->dac); | |
418 | fail_sclk_dac: | |
419 | clk_put(sdev->sclk_dac); | |
9a498400 TS |
420 | fail: |
421 | dev_info(dev, "probe failed\n"); | |
422 | return ret; | |
423 | } | |
424 | ||
4c62e976 | 425 | static int sdo_remove(struct platform_device *pdev) |
9a498400 TS |
426 | { |
427 | struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev); | |
428 | struct sdo_device *sdev = sd_to_sdev(sd); | |
429 | ||
430 | pm_runtime_disable(&pdev->dev); | |
431 | clk_disable(sdev->dac); | |
9a498400 TS |
432 | clk_put(sdev->fout_vpll); |
433 | clk_put(sdev->dacphy); | |
434 | clk_put(sdev->dac); | |
435 | clk_put(sdev->sclk_dac); | |
9a498400 TS |
436 | |
437 | dev_info(&pdev->dev, "remove successful\n"); | |
438 | return 0; | |
439 | } | |
440 | ||
441 | static struct platform_driver sdo_driver __refdata = { | |
442 | .probe = sdo_probe, | |
4c62e976 | 443 | .remove = sdo_remove, |
9a498400 TS |
444 | .driver = { |
445 | .name = "s5p-sdo", | |
446 | .owner = THIS_MODULE, | |
447 | .pm = &sdo_pm_ops, | |
448 | } | |
449 | }; | |
450 | ||
1d6629b1 | 451 | module_platform_driver(sdo_driver); |