Commit | Line | Data |
---|---|---|
a52074ee TS |
1 | /* |
2 | * Samsung HDMI interface 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 | #ifdef CONFIG_VIDEO_SAMSUNG_S5P_HDMI_DEBUG | |
15 | #define DEBUG | |
16 | #endif | |
17 | ||
18 | #include <linux/kernel.h> | |
19 | #include <linux/slab.h> | |
20 | #include <linux/io.h> | |
21 | #include <linux/i2c.h> | |
22 | #include <linux/platform_device.h> | |
23 | #include <media/v4l2-subdev.h> | |
24 | #include <linux/module.h> | |
25 | #include <linux/interrupt.h> | |
26 | #include <linux/irq.h> | |
27 | #include <linux/delay.h> | |
28 | #include <linux/bug.h> | |
29 | #include <linux/pm_runtime.h> | |
30 | #include <linux/clk.h> | |
31 | #include <linux/regulator/consumer.h> | |
32 | ||
350f2f4d | 33 | #include <media/s5p_hdmi.h> |
a52074ee TS |
34 | #include <media/v4l2-common.h> |
35 | #include <media/v4l2-dev.h> | |
36 | #include <media/v4l2-device.h> | |
37 | ||
38 | #include "regs-hdmi.h" | |
39 | ||
40 | MODULE_AUTHOR("Tomasz Stanislawski, <t.stanislaws@samsung.com>"); | |
41 | MODULE_DESCRIPTION("Samsung HDMI"); | |
42 | MODULE_LICENSE("GPL"); | |
43 | ||
44 | /* default preset configured on probe */ | |
3f468acc TS |
45 | #define HDMI_DEFAULT_PRESET V4L2_DV_480P59_94 |
46 | ||
47 | struct hdmi_pulse { | |
48 | u32 beg; | |
49 | u32 end; | |
50 | }; | |
51 | ||
52 | struct hdmi_timings { | |
53 | struct hdmi_pulse hact; | |
54 | u32 hsyn_pol; /* 0 - high, 1 - low */ | |
55 | struct hdmi_pulse hsyn; | |
56 | u32 interlaced; | |
57 | struct hdmi_pulse vact[2]; | |
58 | u32 vsyn_pol; /* 0 - high, 1 - low */ | |
59 | u32 vsyn_off; | |
60 | struct hdmi_pulse vsyn[2]; | |
61 | }; | |
a52074ee TS |
62 | |
63 | struct hdmi_resources { | |
64 | struct clk *hdmi; | |
65 | struct clk *sclk_hdmi; | |
66 | struct clk *sclk_pixel; | |
67 | struct clk *sclk_hdmiphy; | |
68 | struct clk *hdmiphy; | |
69 | struct regulator_bulk_data *regul_bulk; | |
70 | int regul_count; | |
71 | }; | |
72 | ||
73 | struct hdmi_device { | |
74 | /** base address of HDMI registers */ | |
75 | void __iomem *regs; | |
76 | /** HDMI interrupt */ | |
77 | unsigned int irq; | |
78 | /** pointer to device parent */ | |
79 | struct device *dev; | |
80 | /** subdev generated by HDMI device */ | |
81 | struct v4l2_subdev sd; | |
82 | /** V4L2 device structure */ | |
83 | struct v4l2_device v4l2_dev; | |
84 | /** subdev of HDMIPHY interface */ | |
85 | struct v4l2_subdev *phy_sd; | |
5145aa76 TS |
86 | /** subdev of MHL interface */ |
87 | struct v4l2_subdev *mhl_sd; | |
a52074ee | 88 | /** configuration of current graphic mode */ |
3f468acc | 89 | const struct hdmi_timings *cur_conf; |
a52074ee TS |
90 | /** current preset */ |
91 | u32 cur_preset; | |
92 | /** other resources */ | |
93 | struct hdmi_resources res; | |
94 | }; | |
95 | ||
a52074ee TS |
96 | static struct platform_device_id hdmi_driver_types[] = { |
97 | { | |
98 | .name = "s5pv210-hdmi", | |
a52074ee TS |
99 | }, { |
100 | .name = "exynos4-hdmi", | |
a52074ee TS |
101 | }, { |
102 | /* end node */ | |
103 | } | |
104 | }; | |
105 | ||
106 | static const struct v4l2_subdev_ops hdmi_sd_ops; | |
107 | ||
108 | static struct hdmi_device *sd_to_hdmi_dev(struct v4l2_subdev *sd) | |
109 | { | |
110 | return container_of(sd, struct hdmi_device, sd); | |
111 | } | |
112 | ||
113 | static inline | |
114 | void hdmi_write(struct hdmi_device *hdev, u32 reg_id, u32 value) | |
115 | { | |
116 | writel(value, hdev->regs + reg_id); | |
117 | } | |
118 | ||
119 | static inline | |
120 | void hdmi_write_mask(struct hdmi_device *hdev, u32 reg_id, u32 value, u32 mask) | |
121 | { | |
122 | u32 old = readl(hdev->regs + reg_id); | |
123 | value = (value & mask) | (old & ~mask); | |
124 | writel(value, hdev->regs + reg_id); | |
125 | } | |
126 | ||
127 | static inline | |
128 | void hdmi_writeb(struct hdmi_device *hdev, u32 reg_id, u8 value) | |
129 | { | |
130 | writeb(value, hdev->regs + reg_id); | |
131 | } | |
132 | ||
3f468acc TS |
133 | static inline |
134 | void hdmi_writebn(struct hdmi_device *hdev, u32 reg_id, int n, u32 value) | |
135 | { | |
136 | switch (n) { | |
137 | default: | |
138 | writeb(value >> 24, hdev->regs + reg_id + 12); | |
139 | case 3: | |
140 | writeb(value >> 16, hdev->regs + reg_id + 8); | |
141 | case 2: | |
142 | writeb(value >> 8, hdev->regs + reg_id + 4); | |
143 | case 1: | |
144 | writeb(value >> 0, hdev->regs + reg_id + 0); | |
145 | } | |
146 | } | |
147 | ||
a52074ee TS |
148 | static inline u32 hdmi_read(struct hdmi_device *hdev, u32 reg_id) |
149 | { | |
150 | return readl(hdev->regs + reg_id); | |
151 | } | |
152 | ||
153 | static irqreturn_t hdmi_irq_handler(int irq, void *dev_data) | |
154 | { | |
155 | struct hdmi_device *hdev = dev_data; | |
156 | u32 intc_flag; | |
157 | ||
158 | (void)irq; | |
159 | intc_flag = hdmi_read(hdev, HDMI_INTC_FLAG); | |
160 | /* clearing flags for HPD plug/unplug */ | |
161 | if (intc_flag & HDMI_INTC_FLAG_HPD_UNPLUG) { | |
162 | printk(KERN_INFO "unplugged\n"); | |
163 | hdmi_write_mask(hdev, HDMI_INTC_FLAG, ~0, | |
164 | HDMI_INTC_FLAG_HPD_UNPLUG); | |
165 | } | |
166 | if (intc_flag & HDMI_INTC_FLAG_HPD_PLUG) { | |
167 | printk(KERN_INFO "plugged\n"); | |
168 | hdmi_write_mask(hdev, HDMI_INTC_FLAG, ~0, | |
169 | HDMI_INTC_FLAG_HPD_PLUG); | |
170 | } | |
171 | ||
172 | return IRQ_HANDLED; | |
173 | } | |
174 | ||
175 | static void hdmi_reg_init(struct hdmi_device *hdev) | |
176 | { | |
177 | /* enable HPD interrupts */ | |
178 | hdmi_write_mask(hdev, HDMI_INTC_CON, ~0, HDMI_INTC_EN_GLOBAL | | |
179 | HDMI_INTC_EN_HPD_PLUG | HDMI_INTC_EN_HPD_UNPLUG); | |
17b27478 | 180 | /* choose DVI mode */ |
a52074ee | 181 | hdmi_write_mask(hdev, HDMI_MODE_SEL, |
17b27478 TS |
182 | HDMI_MODE_DVI_EN, HDMI_MODE_MASK); |
183 | hdmi_write_mask(hdev, HDMI_CON_2, ~0, | |
184 | HDMI_DVI_PERAMBLE_EN | HDMI_DVI_BAND_EN); | |
a52074ee TS |
185 | /* disable bluescreen */ |
186 | hdmi_write_mask(hdev, HDMI_CON_0, 0, HDMI_BLUE_SCR_EN); | |
187 | /* choose bluescreen (fecal) color */ | |
188 | hdmi_writeb(hdev, HDMI_BLUE_SCREEN_0, 0x12); | |
189 | hdmi_writeb(hdev, HDMI_BLUE_SCREEN_1, 0x34); | |
190 | hdmi_writeb(hdev, HDMI_BLUE_SCREEN_2, 0x56); | |
a52074ee TS |
191 | } |
192 | ||
193 | static void hdmi_timing_apply(struct hdmi_device *hdev, | |
3f468acc | 194 | const struct hdmi_timings *t) |
a52074ee | 195 | { |
a52074ee | 196 | /* setting core registers */ |
3f468acc TS |
197 | hdmi_writebn(hdev, HDMI_H_BLANK_0, 2, t->hact.beg); |
198 | hdmi_writebn(hdev, HDMI_H_SYNC_GEN_0, 3, | |
199 | (t->hsyn_pol << 20) | (t->hsyn.end << 10) | t->hsyn.beg); | |
200 | hdmi_writeb(hdev, HDMI_VSYNC_POL, t->vsyn_pol); | |
201 | hdmi_writebn(hdev, HDMI_V_BLANK_0, 3, | |
202 | (t->vact[0].beg << 11) | t->vact[0].end); | |
203 | hdmi_writebn(hdev, HDMI_V_SYNC_GEN_1_0, 3, | |
204 | (t->vsyn[0].beg << 12) | t->vsyn[0].end); | |
205 | if (t->interlaced) { | |
206 | u32 vsyn_trans = t->hsyn.beg + t->vsyn_off; | |
207 | ||
208 | hdmi_writeb(hdev, HDMI_INT_PRO_MODE, 1); | |
209 | hdmi_writebn(hdev, HDMI_H_V_LINE_0, 3, | |
210 | (t->hact.end << 12) | t->vact[1].end); | |
211 | hdmi_writebn(hdev, HDMI_V_BLANK_F_0, 3, | |
212 | (t->vact[1].end << 11) | t->vact[1].beg); | |
213 | hdmi_writebn(hdev, HDMI_V_SYNC_GEN_2_0, 3, | |
214 | (t->vsyn[1].beg << 12) | t->vsyn[1].end); | |
215 | hdmi_writebn(hdev, HDMI_V_SYNC_GEN_3_0, 3, | |
216 | (vsyn_trans << 12) | vsyn_trans); | |
217 | } else { | |
218 | hdmi_writeb(hdev, HDMI_INT_PRO_MODE, 0); | |
219 | hdmi_writebn(hdev, HDMI_H_V_LINE_0, 3, | |
220 | (t->hact.end << 12) | t->vact[0].end); | |
221 | } | |
222 | ||
a52074ee | 223 | /* Timing generator registers */ |
3f468acc TS |
224 | hdmi_writebn(hdev, HDMI_TG_H_FSZ_L, 2, t->hact.end); |
225 | hdmi_writebn(hdev, HDMI_TG_HACT_ST_L, 2, t->hact.beg); | |
226 | hdmi_writebn(hdev, HDMI_TG_HACT_SZ_L, 2, t->hact.end - t->hact.beg); | |
227 | hdmi_writebn(hdev, HDMI_TG_VSYNC_L, 2, t->vsyn[0].beg); | |
228 | hdmi_writebn(hdev, HDMI_TG_VACT_ST_L, 2, t->vact[0].beg); | |
229 | hdmi_writebn(hdev, HDMI_TG_VACT_SZ_L, 2, | |
230 | t->vact[0].end - t->vact[0].beg); | |
231 | hdmi_writebn(hdev, HDMI_TG_VSYNC_TOP_HDMI_L, 2, t->vsyn[0].beg); | |
232 | hdmi_writebn(hdev, HDMI_TG_FIELD_TOP_HDMI_L, 2, t->vsyn[0].beg); | |
233 | if (t->interlaced) { | |
234 | hdmi_write_mask(hdev, HDMI_TG_CMD, ~0, HDMI_TG_FIELD_EN); | |
235 | hdmi_writebn(hdev, HDMI_TG_V_FSZ_L, 2, t->vact[1].end); | |
236 | hdmi_writebn(hdev, HDMI_TG_VSYNC2_L, 2, t->vsyn[1].beg); | |
237 | hdmi_writebn(hdev, HDMI_TG_FIELD_CHG_L, 2, t->vact[0].end); | |
238 | hdmi_writebn(hdev, HDMI_TG_VACT_ST2_L, 2, t->vact[1].beg); | |
239 | hdmi_writebn(hdev, HDMI_TG_VSYNC_BOT_HDMI_L, 2, t->vsyn[1].beg); | |
240 | hdmi_writebn(hdev, HDMI_TG_FIELD_BOT_HDMI_L, 2, t->vsyn[1].beg); | |
241 | } else { | |
242 | hdmi_write_mask(hdev, HDMI_TG_CMD, 0, HDMI_TG_FIELD_EN); | |
243 | hdmi_writebn(hdev, HDMI_TG_V_FSZ_L, 2, t->vact[0].end); | |
244 | } | |
a52074ee TS |
245 | } |
246 | ||
247 | static int hdmi_conf_apply(struct hdmi_device *hdmi_dev) | |
248 | { | |
249 | struct device *dev = hdmi_dev->dev; | |
3f468acc | 250 | const struct hdmi_timings *conf = hdmi_dev->cur_conf; |
a52074ee TS |
251 | struct v4l2_dv_preset preset; |
252 | int ret; | |
253 | ||
254 | dev_dbg(dev, "%s\n", __func__); | |
255 | ||
256 | /* reset hdmiphy */ | |
257 | hdmi_write_mask(hdmi_dev, HDMI_PHY_RSTOUT, ~0, HDMI_PHY_SW_RSTOUT); | |
258 | mdelay(10); | |
259 | hdmi_write_mask(hdmi_dev, HDMI_PHY_RSTOUT, 0, HDMI_PHY_SW_RSTOUT); | |
260 | mdelay(10); | |
261 | ||
262 | /* configure presets */ | |
263 | preset.preset = hdmi_dev->cur_preset; | |
264 | ret = v4l2_subdev_call(hdmi_dev->phy_sd, video, s_dv_preset, &preset); | |
265 | if (ret) { | |
266 | dev_err(dev, "failed to set preset (%u)\n", preset.preset); | |
267 | return ret; | |
268 | } | |
269 | ||
270 | /* resetting HDMI core */ | |
271 | hdmi_write_mask(hdmi_dev, HDMI_CORE_RSTOUT, 0, HDMI_CORE_SW_RSTOUT); | |
272 | mdelay(10); | |
273 | hdmi_write_mask(hdmi_dev, HDMI_CORE_RSTOUT, ~0, HDMI_CORE_SW_RSTOUT); | |
274 | mdelay(10); | |
275 | ||
276 | hdmi_reg_init(hdmi_dev); | |
277 | ||
278 | /* setting core registers */ | |
279 | hdmi_timing_apply(hdmi_dev, conf); | |
280 | ||
281 | return 0; | |
282 | } | |
283 | ||
284 | static void hdmi_dumpregs(struct hdmi_device *hdev, char *prefix) | |
285 | { | |
286 | #define DUMPREG(reg_id) \ | |
287 | dev_dbg(hdev->dev, "%s:" #reg_id " = %08x\n", prefix, \ | |
288 | readl(hdev->regs + reg_id)) | |
289 | ||
290 | dev_dbg(hdev->dev, "%s: ---- CONTROL REGISTERS ----\n", prefix); | |
291 | DUMPREG(HDMI_INTC_FLAG); | |
292 | DUMPREG(HDMI_INTC_CON); | |
293 | DUMPREG(HDMI_HPD_STATUS); | |
294 | DUMPREG(HDMI_PHY_RSTOUT); | |
295 | DUMPREG(HDMI_PHY_VPLL); | |
296 | DUMPREG(HDMI_PHY_CMU); | |
297 | DUMPREG(HDMI_CORE_RSTOUT); | |
298 | ||
299 | dev_dbg(hdev->dev, "%s: ---- CORE REGISTERS ----\n", prefix); | |
300 | DUMPREG(HDMI_CON_0); | |
301 | DUMPREG(HDMI_CON_1); | |
302 | DUMPREG(HDMI_CON_2); | |
303 | DUMPREG(HDMI_SYS_STATUS); | |
304 | DUMPREG(HDMI_PHY_STATUS); | |
305 | DUMPREG(HDMI_STATUS_EN); | |
306 | DUMPREG(HDMI_HPD); | |
307 | DUMPREG(HDMI_MODE_SEL); | |
308 | DUMPREG(HDMI_HPD_GEN); | |
309 | DUMPREG(HDMI_DC_CONTROL); | |
310 | DUMPREG(HDMI_VIDEO_PATTERN_GEN); | |
311 | ||
312 | dev_dbg(hdev->dev, "%s: ---- CORE SYNC REGISTERS ----\n", prefix); | |
313 | DUMPREG(HDMI_H_BLANK_0); | |
314 | DUMPREG(HDMI_H_BLANK_1); | |
315 | DUMPREG(HDMI_V_BLANK_0); | |
316 | DUMPREG(HDMI_V_BLANK_1); | |
317 | DUMPREG(HDMI_V_BLANK_2); | |
318 | DUMPREG(HDMI_H_V_LINE_0); | |
319 | DUMPREG(HDMI_H_V_LINE_1); | |
320 | DUMPREG(HDMI_H_V_LINE_2); | |
321 | DUMPREG(HDMI_VSYNC_POL); | |
322 | DUMPREG(HDMI_INT_PRO_MODE); | |
323 | DUMPREG(HDMI_V_BLANK_F_0); | |
324 | DUMPREG(HDMI_V_BLANK_F_1); | |
325 | DUMPREG(HDMI_V_BLANK_F_2); | |
326 | DUMPREG(HDMI_H_SYNC_GEN_0); | |
327 | DUMPREG(HDMI_H_SYNC_GEN_1); | |
328 | DUMPREG(HDMI_H_SYNC_GEN_2); | |
329 | DUMPREG(HDMI_V_SYNC_GEN_1_0); | |
330 | DUMPREG(HDMI_V_SYNC_GEN_1_1); | |
331 | DUMPREG(HDMI_V_SYNC_GEN_1_2); | |
332 | DUMPREG(HDMI_V_SYNC_GEN_2_0); | |
333 | DUMPREG(HDMI_V_SYNC_GEN_2_1); | |
334 | DUMPREG(HDMI_V_SYNC_GEN_2_2); | |
335 | DUMPREG(HDMI_V_SYNC_GEN_3_0); | |
336 | DUMPREG(HDMI_V_SYNC_GEN_3_1); | |
337 | DUMPREG(HDMI_V_SYNC_GEN_3_2); | |
338 | ||
339 | dev_dbg(hdev->dev, "%s: ---- TG REGISTERS ----\n", prefix); | |
340 | DUMPREG(HDMI_TG_CMD); | |
341 | DUMPREG(HDMI_TG_H_FSZ_L); | |
342 | DUMPREG(HDMI_TG_H_FSZ_H); | |
343 | DUMPREG(HDMI_TG_HACT_ST_L); | |
344 | DUMPREG(HDMI_TG_HACT_ST_H); | |
345 | DUMPREG(HDMI_TG_HACT_SZ_L); | |
346 | DUMPREG(HDMI_TG_HACT_SZ_H); | |
347 | DUMPREG(HDMI_TG_V_FSZ_L); | |
348 | DUMPREG(HDMI_TG_V_FSZ_H); | |
349 | DUMPREG(HDMI_TG_VSYNC_L); | |
350 | DUMPREG(HDMI_TG_VSYNC_H); | |
351 | DUMPREG(HDMI_TG_VSYNC2_L); | |
352 | DUMPREG(HDMI_TG_VSYNC2_H); | |
353 | DUMPREG(HDMI_TG_VACT_ST_L); | |
354 | DUMPREG(HDMI_TG_VACT_ST_H); | |
355 | DUMPREG(HDMI_TG_VACT_SZ_L); | |
356 | DUMPREG(HDMI_TG_VACT_SZ_H); | |
357 | DUMPREG(HDMI_TG_FIELD_CHG_L); | |
358 | DUMPREG(HDMI_TG_FIELD_CHG_H); | |
359 | DUMPREG(HDMI_TG_VACT_ST2_L); | |
360 | DUMPREG(HDMI_TG_VACT_ST2_H); | |
361 | DUMPREG(HDMI_TG_VSYNC_TOP_HDMI_L); | |
362 | DUMPREG(HDMI_TG_VSYNC_TOP_HDMI_H); | |
363 | DUMPREG(HDMI_TG_VSYNC_BOT_HDMI_L); | |
364 | DUMPREG(HDMI_TG_VSYNC_BOT_HDMI_H); | |
365 | DUMPREG(HDMI_TG_FIELD_TOP_HDMI_L); | |
366 | DUMPREG(HDMI_TG_FIELD_TOP_HDMI_H); | |
367 | DUMPREG(HDMI_TG_FIELD_BOT_HDMI_L); | |
368 | DUMPREG(HDMI_TG_FIELD_BOT_HDMI_H); | |
369 | #undef DUMPREG | |
370 | } | |
371 | ||
3f468acc TS |
372 | static const struct hdmi_timings hdmi_timings_480p = { |
373 | .hact = { .beg = 138, .end = 858 }, | |
374 | .hsyn_pol = 1, | |
375 | .hsyn = { .beg = 16, .end = 16 + 62 }, | |
376 | .interlaced = 0, | |
377 | .vact[0] = { .beg = 42 + 3, .end = 522 + 3 }, | |
378 | .vsyn_pol = 1, | |
379 | .vsyn[0] = { .beg = 6 + 3, .end = 12 + 3}, | |
380 | }; | |
381 | ||
382 | static const struct hdmi_timings hdmi_timings_576p50 = { | |
383 | .hact = { .beg = 144, .end = 864 }, | |
384 | .hsyn_pol = 1, | |
385 | .hsyn = { .beg = 12, .end = 12 + 64 }, | |
386 | .interlaced = 0, | |
387 | .vact[0] = { .beg = 44 + 5, .end = 620 + 5 }, | |
388 | .vsyn_pol = 1, | |
389 | .vsyn[0] = { .beg = 0 + 5, .end = 5 + 5}, | |
390 | }; | |
391 | ||
392 | static const struct hdmi_timings hdmi_timings_720p60 = { | |
393 | .hact = { .beg = 370, .end = 1650 }, | |
394 | .hsyn_pol = 0, | |
395 | .hsyn = { .beg = 110, .end = 110 + 40 }, | |
396 | .interlaced = 0, | |
397 | .vact[0] = { .beg = 25 + 5, .end = 745 + 5 }, | |
398 | .vsyn_pol = 0, | |
399 | .vsyn[0] = { .beg = 0 + 5, .end = 5 + 5}, | |
400 | }; | |
401 | ||
402 | static const struct hdmi_timings hdmi_timings_720p50 = { | |
403 | .hact = { .beg = 700, .end = 1980 }, | |
404 | .hsyn_pol = 0, | |
405 | .hsyn = { .beg = 440, .end = 440 + 40 }, | |
406 | .interlaced = 0, | |
407 | .vact[0] = { .beg = 25 + 5, .end = 745 + 5 }, | |
408 | .vsyn_pol = 0, | |
409 | .vsyn[0] = { .beg = 0 + 5, .end = 5 + 5}, | |
410 | }; | |
411 | ||
412 | static const struct hdmi_timings hdmi_timings_1080p24 = { | |
413 | .hact = { .beg = 830, .end = 2750 }, | |
414 | .hsyn_pol = 0, | |
415 | .hsyn = { .beg = 638, .end = 638 + 44 }, | |
416 | .interlaced = 0, | |
417 | .vact[0] = { .beg = 41 + 4, .end = 1121 + 4 }, | |
418 | .vsyn_pol = 0, | |
419 | .vsyn[0] = { .beg = 0 + 4, .end = 5 + 4}, | |
420 | }; | |
421 | ||
422 | static const struct hdmi_timings hdmi_timings_1080p60 = { | |
423 | .hact = { .beg = 280, .end = 2200 }, | |
424 | .hsyn_pol = 0, | |
425 | .hsyn = { .beg = 88, .end = 88 + 44 }, | |
426 | .interlaced = 0, | |
427 | .vact[0] = { .beg = 41 + 4, .end = 1121 + 4 }, | |
428 | .vsyn_pol = 0, | |
429 | .vsyn[0] = { .beg = 0 + 4, .end = 5 + 4}, | |
a52074ee TS |
430 | }; |
431 | ||
3f468acc TS |
432 | static const struct hdmi_timings hdmi_timings_1080i60 = { |
433 | .hact = { .beg = 280, .end = 2200 }, | |
434 | .hsyn_pol = 0, | |
435 | .hsyn = { .beg = 88, .end = 88 + 44 }, | |
436 | .interlaced = 1, | |
437 | .vact[0] = { .beg = 20 + 2, .end = 560 + 2 }, | |
438 | .vact[1] = { .beg = 583 + 2, .end = 1123 + 2 }, | |
439 | .vsyn_pol = 0, | |
440 | .vsyn_off = 1100, | |
441 | .vsyn[0] = { .beg = 0 + 2, .end = 5 + 2}, | |
442 | .vsyn[1] = { .beg = 562 + 2, .end = 567 + 2}, | |
a52074ee TS |
443 | }; |
444 | ||
3f468acc TS |
445 | static const struct hdmi_timings hdmi_timings_1080i50 = { |
446 | .hact = { .beg = 720, .end = 2640 }, | |
447 | .hsyn_pol = 0, | |
448 | .hsyn = { .beg = 528, .end = 528 + 44 }, | |
449 | .interlaced = 1, | |
450 | .vact[0] = { .beg = 20 + 2, .end = 560 + 2 }, | |
451 | .vact[1] = { .beg = 583 + 2, .end = 1123 + 2 }, | |
452 | .vsyn_pol = 0, | |
453 | .vsyn_off = 1320, | |
454 | .vsyn[0] = { .beg = 0 + 2, .end = 5 + 2}, | |
455 | .vsyn[1] = { .beg = 562 + 2, .end = 567 + 2}, | |
a52074ee TS |
456 | }; |
457 | ||
3f468acc TS |
458 | static const struct hdmi_timings hdmi_timings_1080p50 = { |
459 | .hact = { .beg = 720, .end = 2640 }, | |
460 | .hsyn_pol = 0, | |
461 | .hsyn = { .beg = 528, .end = 528 + 44 }, | |
462 | .interlaced = 0, | |
463 | .vact[0] = { .beg = 41 + 4, .end = 1121 + 4 }, | |
464 | .vsyn_pol = 0, | |
465 | .vsyn[0] = { .beg = 0 + 4, .end = 5 + 4}, | |
a52074ee TS |
466 | }; |
467 | ||
468 | static const struct { | |
469 | u32 preset; | |
3f468acc TS |
470 | const struct hdmi_timings *timings; |
471 | } hdmi_timings[] = { | |
472 | { V4L2_DV_480P59_94, &hdmi_timings_480p }, | |
473 | { V4L2_DV_576P50, &hdmi_timings_576p50 }, | |
474 | { V4L2_DV_720P50, &hdmi_timings_720p50 }, | |
475 | { V4L2_DV_720P59_94, &hdmi_timings_720p60 }, | |
476 | { V4L2_DV_720P60, &hdmi_timings_720p60 }, | |
477 | { V4L2_DV_1080P24, &hdmi_timings_1080p24 }, | |
478 | { V4L2_DV_1080P30, &hdmi_timings_1080p60 }, | |
479 | { V4L2_DV_1080P50, &hdmi_timings_1080p50 }, | |
480 | { V4L2_DV_1080I50, &hdmi_timings_1080i50 }, | |
481 | { V4L2_DV_1080I60, &hdmi_timings_1080i60 }, | |
482 | { V4L2_DV_1080P60, &hdmi_timings_1080p60 }, | |
a52074ee TS |
483 | }; |
484 | ||
3f468acc | 485 | static const struct hdmi_timings *hdmi_preset2timings(u32 preset) |
a52074ee TS |
486 | { |
487 | int i; | |
488 | ||
3f468acc TS |
489 | for (i = 0; i < ARRAY_SIZE(hdmi_timings); ++i) |
490 | if (hdmi_timings[i].preset == preset) | |
491 | return hdmi_timings[i].timings; | |
a52074ee TS |
492 | return NULL; |
493 | } | |
494 | ||
495 | static int hdmi_streamon(struct hdmi_device *hdev) | |
496 | { | |
497 | struct device *dev = hdev->dev; | |
498 | struct hdmi_resources *res = &hdev->res; | |
499 | int ret, tries; | |
500 | ||
501 | dev_dbg(dev, "%s\n", __func__); | |
502 | ||
503 | ret = v4l2_subdev_call(hdev->phy_sd, video, s_stream, 1); | |
504 | if (ret) | |
505 | return ret; | |
506 | ||
507 | /* waiting for HDMIPHY's PLL to get to steady state */ | |
508 | for (tries = 100; tries; --tries) { | |
509 | u32 val = hdmi_read(hdev, HDMI_PHY_STATUS); | |
510 | if (val & HDMI_PHY_STATUS_READY) | |
511 | break; | |
512 | mdelay(1); | |
513 | } | |
514 | /* steady state not achieved */ | |
515 | if (tries == 0) { | |
516 | dev_err(dev, "hdmiphy's pll could not reach steady state.\n"); | |
517 | v4l2_subdev_call(hdev->phy_sd, video, s_stream, 0); | |
5145aa76 TS |
518 | hdmi_dumpregs(hdev, "hdmiphy - s_stream"); |
519 | return -EIO; | |
520 | } | |
521 | ||
522 | /* starting MHL */ | |
523 | ret = v4l2_subdev_call(hdev->mhl_sd, video, s_stream, 1); | |
524 | if (hdev->mhl_sd && ret) { | |
525 | v4l2_subdev_call(hdev->phy_sd, video, s_stream, 0); | |
526 | hdmi_dumpregs(hdev, "mhl - s_stream"); | |
a52074ee TS |
527 | return -EIO; |
528 | } | |
529 | ||
530 | /* hdmiphy clock is used for HDMI in streaming mode */ | |
531 | clk_disable(res->sclk_hdmi); | |
532 | clk_set_parent(res->sclk_hdmi, res->sclk_hdmiphy); | |
533 | clk_enable(res->sclk_hdmi); | |
534 | ||
535 | /* enable HDMI and timing generator */ | |
536 | hdmi_write_mask(hdev, HDMI_CON_0, ~0, HDMI_EN); | |
537 | hdmi_write_mask(hdev, HDMI_TG_CMD, ~0, HDMI_TG_EN); | |
538 | hdmi_dumpregs(hdev, "streamon"); | |
539 | return 0; | |
540 | } | |
541 | ||
542 | static int hdmi_streamoff(struct hdmi_device *hdev) | |
543 | { | |
544 | struct device *dev = hdev->dev; | |
545 | struct hdmi_resources *res = &hdev->res; | |
546 | ||
547 | dev_dbg(dev, "%s\n", __func__); | |
548 | ||
549 | hdmi_write_mask(hdev, HDMI_CON_0, 0, HDMI_EN); | |
550 | hdmi_write_mask(hdev, HDMI_TG_CMD, 0, HDMI_TG_EN); | |
551 | ||
552 | /* pixel(vpll) clock is used for HDMI in config mode */ | |
553 | clk_disable(res->sclk_hdmi); | |
554 | clk_set_parent(res->sclk_hdmi, res->sclk_pixel); | |
555 | clk_enable(res->sclk_hdmi); | |
556 | ||
5145aa76 | 557 | v4l2_subdev_call(hdev->mhl_sd, video, s_stream, 0); |
a52074ee TS |
558 | v4l2_subdev_call(hdev->phy_sd, video, s_stream, 0); |
559 | ||
560 | hdmi_dumpregs(hdev, "streamoff"); | |
561 | return 0; | |
562 | } | |
563 | ||
564 | static int hdmi_s_stream(struct v4l2_subdev *sd, int enable) | |
565 | { | |
566 | struct hdmi_device *hdev = sd_to_hdmi_dev(sd); | |
567 | struct device *dev = hdev->dev; | |
568 | ||
569 | dev_dbg(dev, "%s(%d)\n", __func__, enable); | |
570 | if (enable) | |
571 | return hdmi_streamon(hdev); | |
572 | return hdmi_streamoff(hdev); | |
573 | } | |
574 | ||
575 | static void hdmi_resource_poweron(struct hdmi_resources *res) | |
576 | { | |
577 | /* turn HDMI power on */ | |
578 | regulator_bulk_enable(res->regul_count, res->regul_bulk); | |
579 | /* power-on hdmi physical interface */ | |
580 | clk_enable(res->hdmiphy); | |
581 | /* use VPP as parent clock; HDMIPHY is not working yet */ | |
582 | clk_set_parent(res->sclk_hdmi, res->sclk_pixel); | |
583 | /* turn clocks on */ | |
584 | clk_enable(res->sclk_hdmi); | |
585 | } | |
586 | ||
587 | static void hdmi_resource_poweroff(struct hdmi_resources *res) | |
588 | { | |
589 | /* turn clocks off */ | |
590 | clk_disable(res->sclk_hdmi); | |
591 | /* power-off hdmiphy */ | |
592 | clk_disable(res->hdmiphy); | |
593 | /* turn HDMI power off */ | |
594 | regulator_bulk_disable(res->regul_count, res->regul_bulk); | |
595 | } | |
596 | ||
597 | static int hdmi_s_power(struct v4l2_subdev *sd, int on) | |
598 | { | |
599 | struct hdmi_device *hdev = sd_to_hdmi_dev(sd); | |
600 | int ret; | |
601 | ||
602 | if (on) | |
603 | ret = pm_runtime_get_sync(hdev->dev); | |
604 | else | |
605 | ret = pm_runtime_put_sync(hdev->dev); | |
606 | /* only values < 0 indicate errors */ | |
607 | return IS_ERR_VALUE(ret) ? ret : 0; | |
608 | } | |
609 | ||
610 | static int hdmi_s_dv_preset(struct v4l2_subdev *sd, | |
611 | struct v4l2_dv_preset *preset) | |
612 | { | |
613 | struct hdmi_device *hdev = sd_to_hdmi_dev(sd); | |
614 | struct device *dev = hdev->dev; | |
3f468acc | 615 | const struct hdmi_timings *conf; |
a52074ee | 616 | |
3f468acc | 617 | conf = hdmi_preset2timings(preset->preset); |
a52074ee TS |
618 | if (conf == NULL) { |
619 | dev_err(dev, "preset (%u) not supported\n", preset->preset); | |
620 | return -EINVAL; | |
621 | } | |
622 | hdev->cur_conf = conf; | |
623 | hdev->cur_preset = preset->preset; | |
624 | return 0; | |
625 | } | |
626 | ||
627 | static int hdmi_g_dv_preset(struct v4l2_subdev *sd, | |
628 | struct v4l2_dv_preset *preset) | |
629 | { | |
630 | memset(preset, 0, sizeof(*preset)); | |
631 | preset->preset = sd_to_hdmi_dev(sd)->cur_preset; | |
632 | return 0; | |
633 | } | |
634 | ||
635 | static int hdmi_g_mbus_fmt(struct v4l2_subdev *sd, | |
636 | struct v4l2_mbus_framefmt *fmt) | |
637 | { | |
638 | struct hdmi_device *hdev = sd_to_hdmi_dev(sd); | |
3f468acc | 639 | const struct hdmi_timings *t = hdev->cur_conf; |
a52074ee | 640 | |
3f468acc | 641 | dev_dbg(hdev->dev, "%s\n", __func__); |
a52074ee TS |
642 | if (!hdev->cur_conf) |
643 | return -EINVAL; | |
3f468acc TS |
644 | memset(fmt, 0, sizeof *fmt); |
645 | fmt->width = t->hact.end - t->hact.beg; | |
646 | fmt->height = t->vact[0].end - t->vact[0].beg; | |
647 | fmt->code = V4L2_MBUS_FMT_FIXED; /* means RGB888 */ | |
648 | fmt->colorspace = V4L2_COLORSPACE_SRGB; | |
649 | if (t->interlaced) { | |
650 | fmt->field = V4L2_FIELD_INTERLACED; | |
651 | fmt->height *= 2; | |
652 | } else { | |
653 | fmt->field = V4L2_FIELD_NONE; | |
654 | } | |
a52074ee TS |
655 | return 0; |
656 | } | |
657 | ||
658 | static int hdmi_enum_dv_presets(struct v4l2_subdev *sd, | |
659 | struct v4l2_dv_enum_preset *preset) | |
660 | { | |
3f468acc | 661 | if (preset->index >= ARRAY_SIZE(hdmi_timings)) |
a52074ee | 662 | return -EINVAL; |
3f468acc TS |
663 | return v4l_fill_dv_preset_info(hdmi_timings[preset->index].preset, |
664 | preset); | |
a52074ee TS |
665 | } |
666 | ||
667 | static const struct v4l2_subdev_core_ops hdmi_sd_core_ops = { | |
668 | .s_power = hdmi_s_power, | |
669 | }; | |
670 | ||
671 | static const struct v4l2_subdev_video_ops hdmi_sd_video_ops = { | |
672 | .s_dv_preset = hdmi_s_dv_preset, | |
673 | .g_dv_preset = hdmi_g_dv_preset, | |
674 | .enum_dv_presets = hdmi_enum_dv_presets, | |
675 | .g_mbus_fmt = hdmi_g_mbus_fmt, | |
676 | .s_stream = hdmi_s_stream, | |
677 | }; | |
678 | ||
679 | static const struct v4l2_subdev_ops hdmi_sd_ops = { | |
680 | .core = &hdmi_sd_core_ops, | |
681 | .video = &hdmi_sd_video_ops, | |
682 | }; | |
683 | ||
684 | static int hdmi_runtime_suspend(struct device *dev) | |
685 | { | |
686 | struct v4l2_subdev *sd = dev_get_drvdata(dev); | |
687 | struct hdmi_device *hdev = sd_to_hdmi_dev(sd); | |
688 | ||
689 | dev_dbg(dev, "%s\n", __func__); | |
5145aa76 | 690 | v4l2_subdev_call(hdev->mhl_sd, core, s_power, 0); |
a52074ee TS |
691 | hdmi_resource_poweroff(&hdev->res); |
692 | return 0; | |
693 | } | |
694 | ||
695 | static int hdmi_runtime_resume(struct device *dev) | |
696 | { | |
697 | struct v4l2_subdev *sd = dev_get_drvdata(dev); | |
698 | struct hdmi_device *hdev = sd_to_hdmi_dev(sd); | |
699 | int ret = 0; | |
700 | ||
701 | dev_dbg(dev, "%s\n", __func__); | |
702 | ||
703 | hdmi_resource_poweron(&hdev->res); | |
704 | ||
705 | ret = hdmi_conf_apply(hdev); | |
706 | if (ret) | |
707 | goto fail; | |
708 | ||
5145aa76 TS |
709 | /* starting MHL */ |
710 | ret = v4l2_subdev_call(hdev->mhl_sd, core, s_power, 1); | |
711 | if (hdev->mhl_sd && ret) | |
712 | goto fail; | |
713 | ||
a52074ee TS |
714 | dev_dbg(dev, "poweron succeed\n"); |
715 | ||
716 | return 0; | |
717 | ||
718 | fail: | |
719 | hdmi_resource_poweroff(&hdev->res); | |
720 | dev_err(dev, "poweron failed\n"); | |
721 | ||
722 | return ret; | |
723 | } | |
724 | ||
725 | static const struct dev_pm_ops hdmi_pm_ops = { | |
726 | .runtime_suspend = hdmi_runtime_suspend, | |
727 | .runtime_resume = hdmi_runtime_resume, | |
728 | }; | |
729 | ||
730 | static void hdmi_resources_cleanup(struct hdmi_device *hdev) | |
731 | { | |
732 | struct hdmi_resources *res = &hdev->res; | |
733 | ||
734 | dev_dbg(hdev->dev, "HDMI resource cleanup\n"); | |
735 | /* put clocks, power */ | |
736 | if (res->regul_count) | |
737 | regulator_bulk_free(res->regul_count, res->regul_bulk); | |
738 | /* kfree is NULL-safe */ | |
739 | kfree(res->regul_bulk); | |
740 | if (!IS_ERR_OR_NULL(res->hdmiphy)) | |
741 | clk_put(res->hdmiphy); | |
742 | if (!IS_ERR_OR_NULL(res->sclk_hdmiphy)) | |
743 | clk_put(res->sclk_hdmiphy); | |
744 | if (!IS_ERR_OR_NULL(res->sclk_pixel)) | |
745 | clk_put(res->sclk_pixel); | |
746 | if (!IS_ERR_OR_NULL(res->sclk_hdmi)) | |
747 | clk_put(res->sclk_hdmi); | |
748 | if (!IS_ERR_OR_NULL(res->hdmi)) | |
749 | clk_put(res->hdmi); | |
750 | memset(res, 0, sizeof *res); | |
751 | } | |
752 | ||
753 | static int hdmi_resources_init(struct hdmi_device *hdev) | |
754 | { | |
755 | struct device *dev = hdev->dev; | |
756 | struct hdmi_resources *res = &hdev->res; | |
757 | static char *supply[] = { | |
758 | "hdmi-en", | |
759 | "vdd", | |
760 | "vdd_osc", | |
761 | "vdd_pll", | |
762 | }; | |
763 | int i, ret; | |
764 | ||
765 | dev_dbg(dev, "HDMI resource init\n"); | |
766 | ||
767 | memset(res, 0, sizeof *res); | |
768 | /* get clocks, power */ | |
769 | ||
770 | res->hdmi = clk_get(dev, "hdmi"); | |
771 | if (IS_ERR_OR_NULL(res->hdmi)) { | |
772 | dev_err(dev, "failed to get clock 'hdmi'\n"); | |
773 | goto fail; | |
774 | } | |
775 | res->sclk_hdmi = clk_get(dev, "sclk_hdmi"); | |
776 | if (IS_ERR_OR_NULL(res->sclk_hdmi)) { | |
777 | dev_err(dev, "failed to get clock 'sclk_hdmi'\n"); | |
778 | goto fail; | |
779 | } | |
780 | res->sclk_pixel = clk_get(dev, "sclk_pixel"); | |
781 | if (IS_ERR_OR_NULL(res->sclk_pixel)) { | |
782 | dev_err(dev, "failed to get clock 'sclk_pixel'\n"); | |
783 | goto fail; | |
784 | } | |
785 | res->sclk_hdmiphy = clk_get(dev, "sclk_hdmiphy"); | |
786 | if (IS_ERR_OR_NULL(res->sclk_hdmiphy)) { | |
787 | dev_err(dev, "failed to get clock 'sclk_hdmiphy'\n"); | |
788 | goto fail; | |
789 | } | |
790 | res->hdmiphy = clk_get(dev, "hdmiphy"); | |
791 | if (IS_ERR_OR_NULL(res->hdmiphy)) { | |
792 | dev_err(dev, "failed to get clock 'hdmiphy'\n"); | |
793 | goto fail; | |
794 | } | |
505b534d TM |
795 | res->regul_bulk = kcalloc(ARRAY_SIZE(supply), |
796 | sizeof(res->regul_bulk[0]), GFP_KERNEL); | |
a52074ee TS |
797 | if (!res->regul_bulk) { |
798 | dev_err(dev, "failed to get memory for regulators\n"); | |
799 | goto fail; | |
800 | } | |
801 | for (i = 0; i < ARRAY_SIZE(supply); ++i) { | |
802 | res->regul_bulk[i].supply = supply[i]; | |
803 | res->regul_bulk[i].consumer = NULL; | |
804 | } | |
805 | ||
806 | ret = regulator_bulk_get(dev, ARRAY_SIZE(supply), res->regul_bulk); | |
807 | if (ret) { | |
808 | dev_err(dev, "failed to get regulators\n"); | |
809 | goto fail; | |
810 | } | |
811 | res->regul_count = ARRAY_SIZE(supply); | |
812 | ||
813 | return 0; | |
814 | fail: | |
815 | dev_err(dev, "HDMI resource init - failed\n"); | |
816 | hdmi_resources_cleanup(hdev); | |
817 | return -ENODEV; | |
818 | } | |
819 | ||
820 | static int __devinit hdmi_probe(struct platform_device *pdev) | |
821 | { | |
822 | struct device *dev = &pdev->dev; | |
823 | struct resource *res; | |
5145aa76 | 824 | struct i2c_adapter *adapter; |
a52074ee TS |
825 | struct v4l2_subdev *sd; |
826 | struct hdmi_device *hdmi_dev = NULL; | |
350f2f4d | 827 | struct s5p_hdmi_platform_data *pdata = dev->platform_data; |
a52074ee TS |
828 | int ret; |
829 | ||
830 | dev_dbg(dev, "probe start\n"); | |
831 | ||
350f2f4d TS |
832 | if (!pdata) { |
833 | dev_err(dev, "platform data is missing\n"); | |
834 | ret = -ENODEV; | |
835 | goto fail; | |
836 | } | |
837 | ||
e861dccc | 838 | hdmi_dev = devm_kzalloc(&pdev->dev, sizeof(*hdmi_dev), GFP_KERNEL); |
a52074ee TS |
839 | if (!hdmi_dev) { |
840 | dev_err(dev, "out of memory\n"); | |
841 | ret = -ENOMEM; | |
842 | goto fail; | |
843 | } | |
844 | ||
845 | hdmi_dev->dev = dev; | |
846 | ||
847 | ret = hdmi_resources_init(hdmi_dev); | |
848 | if (ret) | |
e861dccc | 849 | goto fail; |
a52074ee TS |
850 | |
851 | /* mapping HDMI registers */ | |
852 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
853 | if (res == NULL) { | |
854 | dev_err(dev, "get memory resource failed.\n"); | |
855 | ret = -ENXIO; | |
856 | goto fail_init; | |
857 | } | |
858 | ||
e861dccc JL |
859 | hdmi_dev->regs = devm_ioremap(&pdev->dev, res->start, |
860 | resource_size(res)); | |
a52074ee TS |
861 | if (hdmi_dev->regs == NULL) { |
862 | dev_err(dev, "register mapping failed.\n"); | |
863 | ret = -ENXIO; | |
e861dccc | 864 | goto fail_init; |
a52074ee TS |
865 | } |
866 | ||
867 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
868 | if (res == NULL) { | |
869 | dev_err(dev, "get interrupt resource failed.\n"); | |
870 | ret = -ENXIO; | |
e861dccc | 871 | goto fail_init; |
a52074ee TS |
872 | } |
873 | ||
e861dccc JL |
874 | ret = devm_request_irq(&pdev->dev, res->start, hdmi_irq_handler, 0, |
875 | "hdmi", hdmi_dev); | |
a52074ee TS |
876 | if (ret) { |
877 | dev_err(dev, "request interrupt failed.\n"); | |
e861dccc | 878 | goto fail_init; |
a52074ee TS |
879 | } |
880 | hdmi_dev->irq = res->start; | |
881 | ||
882 | /* setting v4l2 name to prevent WARN_ON in v4l2_device_register */ | |
883 | strlcpy(hdmi_dev->v4l2_dev.name, dev_name(dev), | |
884 | sizeof(hdmi_dev->v4l2_dev.name)); | |
885 | /* passing NULL owner prevents driver from erasing drvdata */ | |
886 | ret = v4l2_device_register(NULL, &hdmi_dev->v4l2_dev); | |
887 | if (ret) { | |
888 | dev_err(dev, "could not register v4l2 device.\n"); | |
e861dccc | 889 | goto fail_init; |
a52074ee TS |
890 | } |
891 | ||
350f2f4d TS |
892 | /* testing if hdmiphy info is present */ |
893 | if (!pdata->hdmiphy_info) { | |
894 | dev_err(dev, "hdmiphy info is missing in platform data\n"); | |
895 | ret = -ENXIO; | |
896 | goto fail_vdev; | |
897 | } | |
898 | ||
5145aa76 TS |
899 | adapter = i2c_get_adapter(pdata->hdmiphy_bus); |
900 | if (adapter == NULL) { | |
901 | dev_err(dev, "hdmiphy adapter request failed\n"); | |
a52074ee TS |
902 | ret = -ENXIO; |
903 | goto fail_vdev; | |
904 | } | |
905 | ||
906 | hdmi_dev->phy_sd = v4l2_i2c_new_subdev_board(&hdmi_dev->v4l2_dev, | |
5145aa76 | 907 | adapter, pdata->hdmiphy_info, NULL); |
a52074ee | 908 | /* on failure or not adapter is no longer useful */ |
5145aa76 | 909 | i2c_put_adapter(adapter); |
a52074ee TS |
910 | if (hdmi_dev->phy_sd == NULL) { |
911 | dev_err(dev, "missing subdev for hdmiphy\n"); | |
912 | ret = -ENODEV; | |
913 | goto fail_vdev; | |
914 | } | |
915 | ||
5145aa76 TS |
916 | /* initialization of MHL interface if present */ |
917 | if (pdata->mhl_info) { | |
918 | adapter = i2c_get_adapter(pdata->mhl_bus); | |
919 | if (adapter == NULL) { | |
920 | dev_err(dev, "MHL adapter request failed\n"); | |
921 | ret = -ENXIO; | |
922 | goto fail_vdev; | |
923 | } | |
924 | ||
925 | hdmi_dev->mhl_sd = v4l2_i2c_new_subdev_board( | |
926 | &hdmi_dev->v4l2_dev, adapter, | |
927 | pdata->mhl_info, NULL); | |
928 | /* on failure or not adapter is no longer useful */ | |
929 | i2c_put_adapter(adapter); | |
930 | if (hdmi_dev->mhl_sd == NULL) { | |
931 | dev_err(dev, "missing subdev for MHL\n"); | |
932 | ret = -ENODEV; | |
933 | goto fail_vdev; | |
934 | } | |
935 | } | |
936 | ||
a52074ee TS |
937 | clk_enable(hdmi_dev->res.hdmi); |
938 | ||
939 | pm_runtime_enable(dev); | |
940 | ||
941 | sd = &hdmi_dev->sd; | |
942 | v4l2_subdev_init(sd, &hdmi_sd_ops); | |
943 | sd->owner = THIS_MODULE; | |
944 | ||
945 | strlcpy(sd->name, "s5p-hdmi", sizeof sd->name); | |
946 | hdmi_dev->cur_preset = HDMI_DEFAULT_PRESET; | |
947 | /* FIXME: missing fail preset is not supported */ | |
3f468acc | 948 | hdmi_dev->cur_conf = hdmi_preset2timings(hdmi_dev->cur_preset); |
a52074ee TS |
949 | |
950 | /* storing subdev for call that have only access to struct device */ | |
951 | dev_set_drvdata(dev, sd); | |
952 | ||
41292b16 | 953 | dev_info(dev, "probe successful\n"); |
a52074ee TS |
954 | |
955 | return 0; | |
956 | ||
957 | fail_vdev: | |
958 | v4l2_device_unregister(&hdmi_dev->v4l2_dev); | |
959 | ||
a52074ee TS |
960 | fail_init: |
961 | hdmi_resources_cleanup(hdmi_dev); | |
962 | ||
a52074ee TS |
963 | fail: |
964 | dev_err(dev, "probe failed\n"); | |
965 | return ret; | |
966 | } | |
967 | ||
968 | static int __devexit hdmi_remove(struct platform_device *pdev) | |
969 | { | |
970 | struct device *dev = &pdev->dev; | |
971 | struct v4l2_subdev *sd = dev_get_drvdata(dev); | |
972 | struct hdmi_device *hdmi_dev = sd_to_hdmi_dev(sd); | |
973 | ||
974 | pm_runtime_disable(dev); | |
975 | clk_disable(hdmi_dev->res.hdmi); | |
976 | v4l2_device_unregister(&hdmi_dev->v4l2_dev); | |
977 | disable_irq(hdmi_dev->irq); | |
a52074ee | 978 | hdmi_resources_cleanup(hdmi_dev); |
41292b16 | 979 | dev_info(dev, "remove successful\n"); |
a52074ee TS |
980 | |
981 | return 0; | |
982 | } | |
983 | ||
984 | static struct platform_driver hdmi_driver __refdata = { | |
985 | .probe = hdmi_probe, | |
986 | .remove = __devexit_p(hdmi_remove), | |
987 | .id_table = hdmi_driver_types, | |
988 | .driver = { | |
989 | .name = "s5p-hdmi", | |
990 | .owner = THIS_MODULE, | |
991 | .pm = &hdmi_pm_ops, | |
992 | } | |
993 | }; | |
994 | ||
1d6629b1 | 995 | module_platform_driver(hdmi_driver); |