Commit | Line | Data |
---|---|---|
70be8323 | 1 | /* |
d875f992 | 2 | * hdmi_panel.c |
70be8323 M |
3 | * |
4 | * HDMI library support functions for TI OMAP4 processors. | |
5 | * | |
6 | * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ | |
7 | * Authors: Mythri P k <mythripk@ti.com> | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify it | |
10 | * under the terms of the GNU General Public License version 2 as published by | |
11 | * the Free Software Foundation. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
16 | * more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License along with | |
19 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
20 | */ | |
21 | ||
22 | #include <linux/kernel.h> | |
23 | #include <linux/err.h> | |
24 | #include <linux/io.h> | |
25 | #include <linux/mutex.h> | |
26 | #include <linux/module.h> | |
a0b38cc4 | 27 | #include <video/omapdss.h> |
759593ff | 28 | #include <linux/slab.h> |
70be8323 M |
29 | |
30 | #include "dss.h" | |
31 | ||
32 | static struct { | |
b7dea05a RN |
33 | /* This protects the panel ops, mainly when accessing the HDMI IP. */ |
34 | struct mutex lock; | |
f3a97491 RN |
35 | #if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) |
36 | /* This protects the audio ops, specifically. */ | |
37 | spinlock_t audio_lock; | |
38 | #endif | |
70be8323 M |
39 | } hdmi; |
40 | ||
41 | ||
42 | static int hdmi_panel_probe(struct omap_dss_device *dssdev) | |
43 | { | |
44 | DSSDBG("ENTER hdmi_panel_probe\n"); | |
45 | ||
46 | dssdev->panel.config = OMAP_DSS_LCD_TFT | | |
47 | OMAP_DSS_LCD_IVS | OMAP_DSS_LCD_IHS; | |
48 | ||
2ea51fef | 49 | dssdev->panel.timings = (struct omap_video_timings){640, 480, 25175, 96, 16, 48, 2 , 11, 31}; |
70be8323 M |
50 | |
51 | DSSDBG("hdmi_panel_probe x_res= %d y_res = %d\n", | |
52 | dssdev->panel.timings.x_res, | |
53 | dssdev->panel.timings.y_res); | |
54 | return 0; | |
55 | } | |
56 | ||
57 | static void hdmi_panel_remove(struct omap_dss_device *dssdev) | |
58 | { | |
59 | ||
60 | } | |
61 | ||
f3a97491 RN |
62 | #if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) |
63 | static int hdmi_panel_audio_enable(struct omap_dss_device *dssdev) | |
64 | { | |
65 | unsigned long flags; | |
66 | int r; | |
67 | ||
68 | mutex_lock(&hdmi.lock); | |
69 | spin_lock_irqsave(&hdmi.audio_lock, flags); | |
70 | ||
71 | /* enable audio only if the display is active and supports audio */ | |
72 | if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE || | |
73 | !hdmi_mode_has_audio()) { | |
74 | DSSERR("audio not supported or display is off\n"); | |
75 | r = -EPERM; | |
76 | goto err; | |
77 | } | |
78 | ||
79 | r = hdmi_audio_enable(); | |
80 | ||
81 | if (!r) | |
82 | dssdev->audio_state = OMAP_DSS_AUDIO_ENABLED; | |
83 | ||
84 | err: | |
85 | spin_unlock_irqrestore(&hdmi.audio_lock, flags); | |
86 | mutex_unlock(&hdmi.lock); | |
87 | return r; | |
88 | } | |
89 | ||
90 | static void hdmi_panel_audio_disable(struct omap_dss_device *dssdev) | |
91 | { | |
92 | unsigned long flags; | |
93 | ||
94 | spin_lock_irqsave(&hdmi.audio_lock, flags); | |
95 | ||
96 | hdmi_audio_disable(); | |
97 | ||
98 | dssdev->audio_state = OMAP_DSS_AUDIO_DISABLED; | |
99 | ||
100 | spin_unlock_irqrestore(&hdmi.audio_lock, flags); | |
101 | } | |
102 | ||
103 | static int hdmi_panel_audio_start(struct omap_dss_device *dssdev) | |
104 | { | |
105 | unsigned long flags; | |
106 | int r; | |
107 | ||
108 | spin_lock_irqsave(&hdmi.audio_lock, flags); | |
109 | /* | |
110 | * No need to check the panel state. It was checked when trasitioning | |
111 | * to AUDIO_ENABLED. | |
112 | */ | |
113 | if (dssdev->audio_state != OMAP_DSS_AUDIO_ENABLED) { | |
114 | DSSERR("audio start from invalid state\n"); | |
115 | r = -EPERM; | |
116 | goto err; | |
117 | } | |
118 | ||
119 | r = hdmi_audio_start(); | |
120 | ||
121 | if (!r) | |
122 | dssdev->audio_state = OMAP_DSS_AUDIO_PLAYING; | |
123 | ||
124 | err: | |
125 | spin_unlock_irqrestore(&hdmi.audio_lock, flags); | |
126 | return r; | |
127 | } | |
128 | ||
129 | static void hdmi_panel_audio_stop(struct omap_dss_device *dssdev) | |
130 | { | |
131 | unsigned long flags; | |
132 | ||
133 | spin_lock_irqsave(&hdmi.audio_lock, flags); | |
134 | ||
135 | hdmi_audio_stop(); | |
136 | dssdev->audio_state = OMAP_DSS_AUDIO_ENABLED; | |
137 | ||
138 | spin_unlock_irqrestore(&hdmi.audio_lock, flags); | |
139 | } | |
140 | ||
141 | static bool hdmi_panel_audio_supported(struct omap_dss_device *dssdev) | |
142 | { | |
143 | bool r = false; | |
144 | ||
145 | mutex_lock(&hdmi.lock); | |
146 | ||
147 | if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) | |
148 | goto err; | |
149 | ||
150 | if (!hdmi_mode_has_audio()) | |
151 | goto err; | |
152 | ||
153 | r = true; | |
154 | err: | |
155 | mutex_unlock(&hdmi.lock); | |
156 | return r; | |
157 | } | |
158 | ||
159 | static int hdmi_panel_audio_config(struct omap_dss_device *dssdev, | |
160 | struct omap_dss_audio *audio) | |
161 | { | |
162 | unsigned long flags; | |
163 | int r; | |
164 | ||
165 | mutex_lock(&hdmi.lock); | |
166 | spin_lock_irqsave(&hdmi.audio_lock, flags); | |
167 | ||
168 | /* config audio only if the display is active and supports audio */ | |
169 | if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE || | |
170 | !hdmi_mode_has_audio()) { | |
171 | DSSERR("audio not supported or display is off\n"); | |
172 | r = -EPERM; | |
173 | goto err; | |
174 | } | |
175 | ||
176 | r = hdmi_audio_config(audio); | |
177 | ||
178 | if (!r) | |
179 | dssdev->audio_state = OMAP_DSS_AUDIO_CONFIGURED; | |
180 | ||
181 | err: | |
182 | spin_unlock_irqrestore(&hdmi.audio_lock, flags); | |
183 | mutex_unlock(&hdmi.lock); | |
184 | return r; | |
185 | } | |
186 | ||
187 | #else | |
188 | static int hdmi_panel_audio_enable(struct omap_dss_device *dssdev) | |
189 | { | |
190 | return -EPERM; | |
191 | } | |
192 | ||
193 | static void hdmi_panel_audio_disable(struct omap_dss_device *dssdev) | |
194 | { | |
195 | } | |
196 | ||
197 | static int hdmi_panel_audio_start(struct omap_dss_device *dssdev) | |
198 | { | |
199 | return -EPERM; | |
200 | } | |
201 | ||
202 | static void hdmi_panel_audio_stop(struct omap_dss_device *dssdev) | |
203 | { | |
204 | } | |
205 | ||
206 | static bool hdmi_panel_audio_supported(struct omap_dss_device *dssdev) | |
207 | { | |
208 | return false; | |
209 | } | |
210 | ||
211 | static int hdmi_panel_audio_config(struct omap_dss_device *dssdev, | |
212 | struct omap_dss_audio *audio) | |
213 | { | |
214 | return -EPERM; | |
215 | } | |
216 | #endif | |
217 | ||
70be8323 M |
218 | static int hdmi_panel_enable(struct omap_dss_device *dssdev) |
219 | { | |
220 | int r = 0; | |
221 | DSSDBG("ENTER hdmi_panel_enable\n"); | |
222 | ||
b7dea05a | 223 | mutex_lock(&hdmi.lock); |
70be8323 M |
224 | |
225 | if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) { | |
226 | r = -EINVAL; | |
227 | goto err; | |
228 | } | |
229 | ||
230 | r = omapdss_hdmi_display_enable(dssdev); | |
231 | if (r) { | |
232 | DSSERR("failed to power on\n"); | |
233 | goto err; | |
234 | } | |
235 | ||
236 | dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; | |
237 | ||
238 | err: | |
b7dea05a | 239 | mutex_unlock(&hdmi.lock); |
70be8323 M |
240 | |
241 | return r; | |
242 | } | |
243 | ||
244 | static void hdmi_panel_disable(struct omap_dss_device *dssdev) | |
245 | { | |
b7dea05a | 246 | mutex_lock(&hdmi.lock); |
70be8323 | 247 | |
f3a97491 RN |
248 | if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) { |
249 | /* | |
250 | * TODO: notify audio users that the display was disabled. For | |
251 | * now, disable audio locally to not break our audio state | |
252 | * machine. | |
253 | */ | |
254 | hdmi_panel_audio_disable(dssdev); | |
70be8323 | 255 | omapdss_hdmi_display_disable(dssdev); |
f3a97491 | 256 | } |
70be8323 M |
257 | |
258 | dssdev->state = OMAP_DSS_DISPLAY_DISABLED; | |
259 | ||
b7dea05a | 260 | mutex_unlock(&hdmi.lock); |
70be8323 M |
261 | } |
262 | ||
263 | static int hdmi_panel_suspend(struct omap_dss_device *dssdev) | |
264 | { | |
265 | int r = 0; | |
266 | ||
b7dea05a | 267 | mutex_lock(&hdmi.lock); |
70be8323 M |
268 | |
269 | if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) { | |
270 | r = -EINVAL; | |
271 | goto err; | |
272 | } | |
273 | ||
f3a97491 RN |
274 | /* |
275 | * TODO: notify audio users that the display was suspended. For now, | |
276 | * disable audio locally to not break our audio state machine. | |
277 | */ | |
278 | hdmi_panel_audio_disable(dssdev); | |
70be8323 | 279 | |
f3a97491 | 280 | dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED; |
70be8323 M |
281 | omapdss_hdmi_display_disable(dssdev); |
282 | ||
283 | err: | |
b7dea05a | 284 | mutex_unlock(&hdmi.lock); |
70be8323 M |
285 | |
286 | return r; | |
287 | } | |
288 | ||
289 | static int hdmi_panel_resume(struct omap_dss_device *dssdev) | |
290 | { | |
291 | int r = 0; | |
292 | ||
b7dea05a | 293 | mutex_lock(&hdmi.lock); |
70be8323 M |
294 | |
295 | if (dssdev->state != OMAP_DSS_DISPLAY_SUSPENDED) { | |
296 | r = -EINVAL; | |
297 | goto err; | |
298 | } | |
299 | ||
300 | r = omapdss_hdmi_display_enable(dssdev); | |
301 | if (r) { | |
302 | DSSERR("failed to power on\n"); | |
303 | goto err; | |
304 | } | |
f3a97491 | 305 | /* TODO: notify audio users that the panel resumed. */ |
70be8323 M |
306 | |
307 | dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; | |
308 | ||
309 | err: | |
b7dea05a | 310 | mutex_unlock(&hdmi.lock); |
70be8323 M |
311 | |
312 | return r; | |
313 | } | |
314 | ||
315 | static void hdmi_get_timings(struct omap_dss_device *dssdev, | |
316 | struct omap_video_timings *timings) | |
317 | { | |
b7dea05a | 318 | mutex_lock(&hdmi.lock); |
70be8323 M |
319 | |
320 | *timings = dssdev->panel.timings; | |
321 | ||
b7dea05a | 322 | mutex_unlock(&hdmi.lock); |
70be8323 M |
323 | } |
324 | ||
325 | static void hdmi_set_timings(struct omap_dss_device *dssdev, | |
326 | struct omap_video_timings *timings) | |
327 | { | |
328 | DSSDBG("hdmi_set_timings\n"); | |
329 | ||
b7dea05a | 330 | mutex_lock(&hdmi.lock); |
70be8323 | 331 | |
f3a97491 RN |
332 | /* |
333 | * TODO: notify audio users that there was a timings change. For | |
334 | * now, disable audio locally to not break our audio state machine. | |
335 | */ | |
336 | hdmi_panel_audio_disable(dssdev); | |
337 | ||
70be8323 | 338 | dssdev->panel.timings = *timings; |
fa70dc5f | 339 | omapdss_hdmi_display_set_timing(dssdev); |
70be8323 | 340 | |
b7dea05a | 341 | mutex_unlock(&hdmi.lock); |
70be8323 M |
342 | } |
343 | ||
344 | static int hdmi_check_timings(struct omap_dss_device *dssdev, | |
345 | struct omap_video_timings *timings) | |
346 | { | |
347 | int r = 0; | |
348 | ||
349 | DSSDBG("hdmi_check_timings\n"); | |
350 | ||
b7dea05a | 351 | mutex_lock(&hdmi.lock); |
70be8323 M |
352 | |
353 | r = omapdss_hdmi_display_check_timing(dssdev, timings); | |
468c1b93 | 354 | |
b7dea05a | 355 | mutex_unlock(&hdmi.lock); |
70be8323 M |
356 | return r; |
357 | } | |
358 | ||
47024565 TV |
359 | static int hdmi_read_edid(struct omap_dss_device *dssdev, u8 *buf, int len) |
360 | { | |
361 | int r; | |
362 | ||
b7dea05a | 363 | mutex_lock(&hdmi.lock); |
47024565 TV |
364 | |
365 | if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) { | |
366 | r = omapdss_hdmi_display_enable(dssdev); | |
367 | if (r) | |
368 | goto err; | |
369 | } | |
370 | ||
371 | r = omapdss_hdmi_read_edid(buf, len); | |
372 | ||
373 | if (dssdev->state == OMAP_DSS_DISPLAY_DISABLED || | |
374 | dssdev->state == OMAP_DSS_DISPLAY_SUSPENDED) | |
375 | omapdss_hdmi_display_disable(dssdev); | |
376 | err: | |
b7dea05a | 377 | mutex_unlock(&hdmi.lock); |
47024565 TV |
378 | |
379 | return r; | |
380 | } | |
381 | ||
759593ff TV |
382 | static bool hdmi_detect(struct omap_dss_device *dssdev) |
383 | { | |
384 | int r; | |
385 | ||
b7dea05a | 386 | mutex_lock(&hdmi.lock); |
759593ff TV |
387 | |
388 | if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) { | |
389 | r = omapdss_hdmi_display_enable(dssdev); | |
390 | if (r) | |
391 | goto err; | |
392 | } | |
393 | ||
394 | r = omapdss_hdmi_detect(); | |
395 | ||
396 | if (dssdev->state == OMAP_DSS_DISPLAY_DISABLED || | |
397 | dssdev->state == OMAP_DSS_DISPLAY_SUSPENDED) | |
398 | omapdss_hdmi_display_disable(dssdev); | |
399 | err: | |
b7dea05a | 400 | mutex_unlock(&hdmi.lock); |
759593ff TV |
401 | |
402 | return r; | |
403 | } | |
404 | ||
70be8323 M |
405 | static struct omap_dss_driver hdmi_driver = { |
406 | .probe = hdmi_panel_probe, | |
407 | .remove = hdmi_panel_remove, | |
408 | .enable = hdmi_panel_enable, | |
409 | .disable = hdmi_panel_disable, | |
410 | .suspend = hdmi_panel_suspend, | |
411 | .resume = hdmi_panel_resume, | |
412 | .get_timings = hdmi_get_timings, | |
413 | .set_timings = hdmi_set_timings, | |
414 | .check_timings = hdmi_check_timings, | |
47024565 | 415 | .read_edid = hdmi_read_edid, |
759593ff | 416 | .detect = hdmi_detect, |
f3a97491 RN |
417 | .audio_enable = hdmi_panel_audio_enable, |
418 | .audio_disable = hdmi_panel_audio_disable, | |
419 | .audio_start = hdmi_panel_audio_start, | |
420 | .audio_stop = hdmi_panel_audio_stop, | |
421 | .audio_supported = hdmi_panel_audio_supported, | |
422 | .audio_config = hdmi_panel_audio_config, | |
70be8323 M |
423 | .driver = { |
424 | .name = "hdmi_panel", | |
425 | .owner = THIS_MODULE, | |
426 | }, | |
427 | }; | |
428 | ||
429 | int hdmi_panel_init(void) | |
430 | { | |
b7dea05a | 431 | mutex_init(&hdmi.lock); |
70be8323 | 432 | |
f3a97491 RN |
433 | #if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) |
434 | spin_lock_init(&hdmi.audio_lock); | |
435 | #endif | |
436 | ||
70be8323 M |
437 | omap_dss_register_driver(&hdmi_driver); |
438 | ||
439 | return 0; | |
440 | } | |
441 | ||
442 | void hdmi_panel_exit(void) | |
443 | { | |
444 | omap_dss_unregister_driver(&hdmi_driver); | |
445 | ||
446 | } |