Commit | Line | Data |
---|---|---|
caab277b | 1 | // SPDX-License-Identifier: GPL-2.0-only |
f5bab222 TV |
2 | /* |
3 | * HDMI driver for OMAP5 | |
4 | * | |
1b409fda | 5 | * Copyright (C) 2014 Texas Instruments Incorporated - https://www.ti.com/ |
f5bab222 TV |
6 | * |
7 | * Authors: | |
8 | * Yong Zhi | |
9 | * Mythri pk | |
10 | * Archit Taneja <archit@ti.com> | |
11 | * Tomi Valkeinen <tomi.valkeinen@ti.com> | |
f5bab222 TV |
12 | */ |
13 | ||
14 | #define DSS_SUBSYS_NAME "HDMI" | |
15 | ||
16 | #include <linux/kernel.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/err.h> | |
19 | #include <linux/io.h> | |
20 | #include <linux/interrupt.h> | |
21 | #include <linux/mutex.h> | |
22 | #include <linux/delay.h> | |
23 | #include <linux/string.h> | |
24 | #include <linux/platform_device.h> | |
25 | #include <linux/pm_runtime.h> | |
26 | #include <linux/clk.h> | |
f5bab222 | 27 | #include <linux/regulator/consumer.h> |
736e60dd | 28 | #include <linux/component.h> |
d9e32ecd | 29 | #include <linux/of.h> |
09bffa6e | 30 | #include <linux/of_graph.h> |
45302d7e | 31 | #include <sound/omap-hdmi-audio.h> |
f5bab222 | 32 | |
615de6ce LP |
33 | #include <drm/drm_atomic.h> |
34 | #include <drm/drm_atomic_state_helper.h> | |
255490f9 | 35 | #include <drm/drm_edid.h> |
615de6ce | 36 | |
32043da7 | 37 | #include "omapdss.h" |
f5bab222 TV |
38 | #include "hdmi5_core.h" |
39 | #include "dss.h" | |
f5bab222 | 40 | |
c44991ce | 41 | static int hdmi_runtime_get(struct omap_hdmi *hdmi) |
f5bab222 TV |
42 | { |
43 | int r; | |
44 | ||
45 | DSSDBG("hdmi_runtime_get\n"); | |
46 | ||
c44991ce | 47 | r = pm_runtime_get_sync(&hdmi->pdev->dev); |
a5d704d3 DL |
48 | if (WARN_ON(r < 0)) { |
49 | pm_runtime_put_noidle(&hdmi->pdev->dev); | |
f5bab222 | 50 | return r; |
a5d704d3 | 51 | } |
f5bab222 TV |
52 | return 0; |
53 | } | |
54 | ||
c44991ce | 55 | static void hdmi_runtime_put(struct omap_hdmi *hdmi) |
f5bab222 TV |
56 | { |
57 | int r; | |
58 | ||
59 | DSSDBG("hdmi_runtime_put\n"); | |
60 | ||
c44991ce | 61 | r = pm_runtime_put_sync(&hdmi->pdev->dev); |
f5bab222 TV |
62 | WARN_ON(r < 0 && r != -ENOSYS); |
63 | } | |
64 | ||
65 | static irqreturn_t hdmi_irq_handler(int irq, void *data) | |
66 | { | |
c44991ce LP |
67 | struct omap_hdmi *hdmi = data; |
68 | struct hdmi_wp_data *wp = &hdmi->wp; | |
f5bab222 TV |
69 | u32 irqstatus; |
70 | ||
71 | irqstatus = hdmi_wp_get_irqstatus(wp); | |
72 | hdmi_wp_set_irqstatus(wp, irqstatus); | |
73 | ||
74 | if ((irqstatus & HDMI_IRQ_LINK_CONNECT) && | |
75 | irqstatus & HDMI_IRQ_LINK_DISCONNECT) { | |
76 | u32 v; | |
77 | /* | |
78 | * If we get both connect and disconnect interrupts at the same | |
79 | * time, turn off the PHY, clear interrupts, and restart, which | |
80 | * raises connect interrupt if a cable is connected, or nothing | |
81 | * if cable is not connected. | |
82 | */ | |
83 | ||
84 | hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_OFF); | |
85 | ||
86 | /* | |
87 | * We always get bogus CONNECT & DISCONNECT interrupts when | |
88 | * setting the PHY to LDOON. To ignore those, we force the RXDET | |
89 | * line to 0 until the PHY power state has been changed. | |
90 | */ | |
c44991ce | 91 | v = hdmi_read_reg(hdmi->phy.base, HDMI_TXPHY_PAD_CFG_CTRL); |
f5bab222 TV |
92 | v = FLD_MOD(v, 1, 15, 15); /* FORCE_RXDET_HIGH */ |
93 | v = FLD_MOD(v, 0, 14, 7); /* RXDET_LINE */ | |
c44991ce | 94 | hdmi_write_reg(hdmi->phy.base, HDMI_TXPHY_PAD_CFG_CTRL, v); |
f5bab222 TV |
95 | |
96 | hdmi_wp_set_irqstatus(wp, HDMI_IRQ_LINK_CONNECT | | |
97 | HDMI_IRQ_LINK_DISCONNECT); | |
98 | ||
99 | hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON); | |
100 | ||
c44991ce | 101 | REG_FLD_MOD(hdmi->phy.base, HDMI_TXPHY_PAD_CFG_CTRL, 0, 15, 15); |
f5bab222 TV |
102 | |
103 | } else if (irqstatus & HDMI_IRQ_LINK_CONNECT) { | |
104 | hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_TXON); | |
105 | } else if (irqstatus & HDMI_IRQ_LINK_DISCONNECT) { | |
106 | hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON); | |
107 | } | |
108 | ||
109 | return IRQ_HANDLED; | |
110 | } | |
111 | ||
c44991ce | 112 | static int hdmi_power_on_core(struct omap_hdmi *hdmi) |
f5bab222 TV |
113 | { |
114 | int r; | |
115 | ||
c44991ce | 116 | r = regulator_enable(hdmi->vdda_reg); |
f5bab222 TV |
117 | if (r) |
118 | return r; | |
119 | ||
c44991ce | 120 | r = hdmi_runtime_get(hdmi); |
f5bab222 TV |
121 | if (r) |
122 | goto err_runtime_get; | |
123 | ||
124 | /* Make selection of HDMI in DSS */ | |
c44991ce | 125 | dss_select_hdmi_venc_clk_source(hdmi->dss, DSS_HDMI_M_PCLK); |
f5bab222 | 126 | |
c44991ce | 127 | hdmi->core_enabled = true; |
f5bab222 TV |
128 | |
129 | return 0; | |
130 | ||
131 | err_runtime_get: | |
c44991ce | 132 | regulator_disable(hdmi->vdda_reg); |
f5bab222 TV |
133 | |
134 | return r; | |
135 | } | |
136 | ||
c44991ce | 137 | static void hdmi_power_off_core(struct omap_hdmi *hdmi) |
f5bab222 | 138 | { |
c44991ce | 139 | hdmi->core_enabled = false; |
f5bab222 | 140 | |
c44991ce LP |
141 | hdmi_runtime_put(hdmi); |
142 | regulator_disable(hdmi->vdda_reg); | |
f5bab222 TV |
143 | } |
144 | ||
c44991ce | 145 | static int hdmi_power_on_full(struct omap_hdmi *hdmi) |
f5bab222 TV |
146 | { |
147 | int r; | |
95e472da | 148 | const struct videomode *vm; |
c84c3a5b | 149 | struct dss_pll_clock_info hdmi_cinfo = { 0 }; |
d11e5c82 | 150 | unsigned int pc; |
f5bab222 | 151 | |
c44991ce | 152 | r = hdmi_power_on_core(hdmi); |
f5bab222 TV |
153 | if (r) |
154 | return r; | |
155 | ||
c44991ce | 156 | vm = &hdmi->cfg.vm; |
f5bab222 | 157 | |
da11bbbb PU |
158 | DSSDBG("hdmi_power_on hactive= %d vactive = %d\n", vm->hactive, |
159 | vm->vactive); | |
f5bab222 | 160 | |
da11bbbb PU |
161 | pc = vm->pixelclock; |
162 | if (vm->flags & DISPLAY_FLAGS_DOUBLECLK) | |
67d8ffdd TV |
163 | pc *= 2; |
164 | ||
c107751d TV |
165 | /* DSS_HDMI_TCLK is bitclk / 10 */ |
166 | pc *= 10; | |
167 | ||
c44991ce | 168 | dss_pll_calc_b(&hdmi->pll.pll, clk_get_rate(hdmi->pll.pll.clkin), |
c17dc0e3 | 169 | pc, &hdmi_cinfo); |
f5bab222 TV |
170 | |
171 | /* disable and clear irqs */ | |
c44991ce LP |
172 | hdmi_wp_clear_irqenable(&hdmi->wp, 0xffffffff); |
173 | hdmi_wp_set_irqstatus(&hdmi->wp, | |
174 | hdmi_wp_get_irqstatus(&hdmi->wp)); | |
f5bab222 | 175 | |
c44991ce | 176 | r = dss_pll_enable(&hdmi->pll.pll); |
f5bab222 | 177 | if (r) { |
c2fbd061 | 178 | DSSERR("Failed to enable PLL\n"); |
f5bab222 TV |
179 | goto err_pll_enable; |
180 | } | |
181 | ||
c44991ce | 182 | r = dss_pll_set_config(&hdmi->pll.pll, &hdmi_cinfo); |
c2fbd061 TV |
183 | if (r) { |
184 | DSSERR("Failed to configure PLL\n"); | |
185 | goto err_pll_cfg; | |
186 | } | |
187 | ||
c44991ce | 188 | r = hdmi_phy_configure(&hdmi->phy, hdmi_cinfo.clkdco, |
c84c3a5b | 189 | hdmi_cinfo.clkout[0]); |
f5bab222 TV |
190 | if (r) { |
191 | DSSDBG("Failed to start PHY\n"); | |
192 | goto err_phy_cfg; | |
193 | } | |
194 | ||
c44991ce | 195 | r = hdmi_wp_set_phy_pwr(&hdmi->wp, HDMI_PHYPWRCMD_LDOON); |
f5bab222 TV |
196 | if (r) |
197 | goto err_phy_pwr; | |
198 | ||
c44991ce | 199 | hdmi5_configure(&hdmi->core, &hdmi->wp, &hdmi->cfg); |
f5bab222 | 200 | |
c44991ce | 201 | r = dss_mgr_enable(&hdmi->output); |
f5bab222 TV |
202 | if (r) |
203 | goto err_mgr_enable; | |
204 | ||
c44991ce | 205 | r = hdmi_wp_video_start(&hdmi->wp); |
4e4b53ce TV |
206 | if (r) |
207 | goto err_vid_enable; | |
208 | ||
c44991ce | 209 | hdmi_wp_set_irqenable(&hdmi->wp, |
f5bab222 TV |
210 | HDMI_IRQ_LINK_CONNECT | HDMI_IRQ_LINK_DISCONNECT); |
211 | ||
212 | return 0; | |
213 | ||
f5bab222 | 214 | err_vid_enable: |
c44991ce | 215 | dss_mgr_disable(&hdmi->output); |
4e4b53ce | 216 | err_mgr_enable: |
c44991ce | 217 | hdmi_wp_set_phy_pwr(&hdmi->wp, HDMI_PHYPWRCMD_OFF); |
f5bab222 TV |
218 | err_phy_pwr: |
219 | err_phy_cfg: | |
c2fbd061 | 220 | err_pll_cfg: |
c44991ce | 221 | dss_pll_disable(&hdmi->pll.pll); |
f5bab222 | 222 | err_pll_enable: |
c44991ce | 223 | hdmi_power_off_core(hdmi); |
f5bab222 TV |
224 | return -EIO; |
225 | } | |
226 | ||
c44991ce | 227 | static void hdmi_power_off_full(struct omap_hdmi *hdmi) |
f5bab222 | 228 | { |
c44991ce | 229 | hdmi_wp_clear_irqenable(&hdmi->wp, 0xffffffff); |
f5bab222 | 230 | |
c44991ce | 231 | hdmi_wp_video_stop(&hdmi->wp); |
f5bab222 | 232 | |
c44991ce | 233 | dss_mgr_disable(&hdmi->output); |
4e4b53ce | 234 | |
c44991ce | 235 | hdmi_wp_set_phy_pwr(&hdmi->wp, HDMI_PHYPWRCMD_OFF); |
f5bab222 | 236 | |
c44991ce | 237 | dss_pll_disable(&hdmi->pll.pll); |
f5bab222 | 238 | |
c44991ce | 239 | hdmi_power_off_core(hdmi); |
f5bab222 TV |
240 | } |
241 | ||
f33656e1 | 242 | static int hdmi_dump_regs(struct seq_file *s, void *p) |
f5bab222 | 243 | { |
c44991ce LP |
244 | struct omap_hdmi *hdmi = s->private; |
245 | ||
246 | mutex_lock(&hdmi->lock); | |
f5bab222 | 247 | |
c44991ce LP |
248 | if (hdmi_runtime_get(hdmi)) { |
249 | mutex_unlock(&hdmi->lock); | |
f33656e1 | 250 | return 0; |
f5bab222 TV |
251 | } |
252 | ||
c44991ce LP |
253 | hdmi_wp_dump(&hdmi->wp, s); |
254 | hdmi_pll_dump(&hdmi->pll, s); | |
255 | hdmi_phy_dump(&hdmi->phy, s); | |
256 | hdmi5_core_dump(&hdmi->core, s); | |
f5bab222 | 257 | |
c44991ce LP |
258 | hdmi_runtime_put(hdmi); |
259 | mutex_unlock(&hdmi->lock); | |
f33656e1 | 260 | return 0; |
f5bab222 TV |
261 | } |
262 | ||
8a9d4626 JS |
263 | static void hdmi_start_audio_stream(struct omap_hdmi *hd) |
264 | { | |
c44991ce | 265 | REG_FLD_MOD(hd->wp.base, HDMI_WP_SYSCONFIG, 1, 3, 2); |
8a9d4626 JS |
266 | hdmi_wp_audio_enable(&hd->wp, true); |
267 | hdmi_wp_audio_core_req_enable(&hd->wp, true); | |
268 | } | |
269 | ||
270 | static void hdmi_stop_audio_stream(struct omap_hdmi *hd) | |
271 | { | |
272 | hdmi_wp_audio_core_req_enable(&hd->wp, false); | |
273 | hdmi_wp_audio_enable(&hd->wp, false); | |
274 | REG_FLD_MOD(hd->wp.base, HDMI_WP_SYSCONFIG, hd->wp_idlemode, 3, 2); | |
275 | } | |
276 | ||
c44991ce | 277 | static int hdmi_core_enable(struct omap_hdmi *hdmi) |
f5bab222 TV |
278 | { |
279 | int r = 0; | |
280 | ||
281 | DSSDBG("ENTER omapdss_hdmi_core_enable\n"); | |
282 | ||
c44991ce | 283 | mutex_lock(&hdmi->lock); |
f5bab222 | 284 | |
c44991ce | 285 | r = hdmi_power_on_core(hdmi); |
f5bab222 TV |
286 | if (r) { |
287 | DSSERR("failed to power on device\n"); | |
288 | goto err0; | |
289 | } | |
290 | ||
c44991ce | 291 | mutex_unlock(&hdmi->lock); |
f5bab222 TV |
292 | return 0; |
293 | ||
294 | err0: | |
c44991ce | 295 | mutex_unlock(&hdmi->lock); |
f5bab222 TV |
296 | return r; |
297 | } | |
298 | ||
c44991ce | 299 | static void hdmi_core_disable(struct omap_hdmi *hdmi) |
f5bab222 TV |
300 | { |
301 | DSSDBG("Enter omapdss_hdmi_core_disable\n"); | |
302 | ||
c44991ce | 303 | mutex_lock(&hdmi->lock); |
f5bab222 | 304 | |
c44991ce | 305 | hdmi_power_off_core(hdmi); |
f5bab222 | 306 | |
c44991ce | 307 | mutex_unlock(&hdmi->lock); |
f5bab222 TV |
308 | } |
309 | ||
6c623d6b LP |
310 | /* ----------------------------------------------------------------------------- |
311 | * DRM Bridge Operations | |
312 | */ | |
313 | ||
314 | static int hdmi5_bridge_attach(struct drm_bridge *bridge, | |
315 | enum drm_bridge_attach_flags flags) | |
316 | { | |
317 | struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); | |
318 | ||
e7e67d9a LP |
319 | if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) |
320 | return -EINVAL; | |
6c623d6b LP |
321 | |
322 | return drm_bridge_attach(bridge->encoder, hdmi->output.next_bridge, | |
323 | bridge, flags); | |
324 | } | |
325 | ||
615de6ce LP |
326 | static void hdmi5_bridge_mode_set(struct drm_bridge *bridge, |
327 | const struct drm_display_mode *mode, | |
328 | const struct drm_display_mode *adjusted_mode) | |
329 | { | |
330 | struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); | |
331 | ||
332 | mutex_lock(&hdmi->lock); | |
333 | ||
334 | drm_display_mode_to_videomode(adjusted_mode, &hdmi->cfg.vm); | |
335 | ||
336 | dispc_set_tv_pclk(hdmi->dss->dispc, adjusted_mode->clock * 1000); | |
337 | ||
338 | mutex_unlock(&hdmi->lock); | |
339 | } | |
340 | ||
341 | static void hdmi5_bridge_enable(struct drm_bridge *bridge, | |
342 | struct drm_bridge_state *bridge_state) | |
343 | { | |
344 | struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); | |
345 | struct drm_atomic_state *state = bridge_state->base.state; | |
346 | struct drm_connector_state *conn_state; | |
347 | struct drm_connector *connector; | |
348 | struct drm_crtc_state *crtc_state; | |
349 | unsigned long flags; | |
350 | int ret; | |
351 | ||
352 | /* | |
353 | * None of these should fail, as the bridge can't be enabled without a | |
354 | * valid CRTC to connector path with fully populated new states. | |
355 | */ | |
356 | connector = drm_atomic_get_new_connector_for_encoder(state, | |
357 | bridge->encoder); | |
358 | if (WARN_ON(!connector)) | |
359 | return; | |
360 | conn_state = drm_atomic_get_new_connector_state(state, connector); | |
361 | if (WARN_ON(!conn_state)) | |
362 | return; | |
363 | crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); | |
364 | if (WARN_ON(!crtc_state)) | |
365 | return; | |
366 | ||
367 | hdmi->cfg.hdmi_dvi_mode = connector->display_info.is_hdmi | |
368 | ? HDMI_HDMI : HDMI_DVI; | |
369 | ||
370 | if (connector->display_info.is_hdmi) { | |
371 | const struct drm_display_mode *mode; | |
372 | struct hdmi_avi_infoframe avi; | |
373 | ||
374 | mode = &crtc_state->adjusted_mode; | |
375 | ret = drm_hdmi_avi_infoframe_from_display_mode(&avi, connector, | |
376 | mode); | |
377 | if (ret == 0) | |
378 | hdmi->cfg.infoframe = avi; | |
379 | } | |
380 | ||
381 | mutex_lock(&hdmi->lock); | |
382 | ||
383 | ret = hdmi_power_on_full(hdmi); | |
384 | if (ret) { | |
385 | DSSERR("failed to power on device\n"); | |
386 | goto done; | |
387 | } | |
388 | ||
389 | if (hdmi->audio_configured) { | |
390 | ret = hdmi5_audio_config(&hdmi->core, &hdmi->wp, | |
391 | &hdmi->audio_config, | |
392 | hdmi->cfg.vm.pixelclock); | |
393 | if (ret) { | |
394 | DSSERR("Error restoring audio configuration: %d", ret); | |
395 | hdmi->audio_abort_cb(&hdmi->pdev->dev); | |
396 | hdmi->audio_configured = false; | |
397 | } | |
398 | } | |
399 | ||
400 | spin_lock_irqsave(&hdmi->audio_playing_lock, flags); | |
401 | if (hdmi->audio_configured && hdmi->audio_playing) | |
402 | hdmi_start_audio_stream(hdmi); | |
403 | hdmi->display_enabled = true; | |
404 | spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags); | |
405 | ||
406 | done: | |
407 | mutex_unlock(&hdmi->lock); | |
408 | } | |
409 | ||
410 | static void hdmi5_bridge_disable(struct drm_bridge *bridge, | |
411 | struct drm_bridge_state *bridge_state) | |
412 | { | |
413 | struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); | |
414 | unsigned long flags; | |
415 | ||
416 | mutex_lock(&hdmi->lock); | |
417 | ||
418 | spin_lock_irqsave(&hdmi->audio_playing_lock, flags); | |
419 | hdmi_stop_audio_stream(hdmi); | |
420 | hdmi->display_enabled = false; | |
421 | spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags); | |
422 | ||
423 | hdmi_power_off_full(hdmi); | |
424 | ||
425 | mutex_unlock(&hdmi->lock); | |
426 | } | |
427 | ||
46876af3 JN |
428 | static const struct drm_edid *hdmi5_bridge_edid_read(struct drm_bridge *bridge, |
429 | struct drm_connector *connector) | |
6c623d6b LP |
430 | { |
431 | struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); | |
46876af3 | 432 | const struct drm_edid *drm_edid; |
b6067da4 LP |
433 | bool need_enable; |
434 | int idlemode; | |
435 | int r; | |
436 | ||
437 | need_enable = hdmi->core_enabled == false; | |
438 | ||
439 | if (need_enable) { | |
440 | r = hdmi_core_enable(hdmi); | |
441 | if (r) | |
442 | return NULL; | |
443 | } | |
444 | ||
445 | mutex_lock(&hdmi->lock); | |
446 | r = hdmi_runtime_get(hdmi); | |
447 | BUG_ON(r); | |
6c623d6b | 448 | |
b6067da4 LP |
449 | idlemode = REG_GET(hdmi->wp.base, HDMI_WP_SYSCONFIG, 3, 2); |
450 | /* No-idle mode */ | |
451 | REG_FLD_MOD(hdmi->wp.base, HDMI_WP_SYSCONFIG, 1, 3, 2); | |
452 | ||
453 | hdmi5_core_ddc_init(&hdmi->core); | |
454 | ||
46876af3 | 455 | drm_edid = drm_edid_read_custom(connector, hdmi5_core_ddc_read, &hdmi->core); |
b6067da4 LP |
456 | |
457 | hdmi5_core_ddc_uninit(&hdmi->core); | |
458 | ||
459 | REG_FLD_MOD(hdmi->wp.base, HDMI_WP_SYSCONFIG, idlemode, 3, 2); | |
460 | ||
461 | hdmi_runtime_put(hdmi); | |
462 | mutex_unlock(&hdmi->lock); | |
463 | ||
464 | if (need_enable) | |
465 | hdmi_core_disable(hdmi); | |
466 | ||
46876af3 | 467 | return drm_edid; |
6c623d6b LP |
468 | } |
469 | ||
470 | static const struct drm_bridge_funcs hdmi5_bridge_funcs = { | |
471 | .attach = hdmi5_bridge_attach, | |
615de6ce LP |
472 | .mode_set = hdmi5_bridge_mode_set, |
473 | .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, | |
474 | .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, | |
475 | .atomic_reset = drm_atomic_helper_bridge_reset, | |
476 | .atomic_enable = hdmi5_bridge_enable, | |
477 | .atomic_disable = hdmi5_bridge_disable, | |
46876af3 | 478 | .edid_read = hdmi5_bridge_edid_read, |
6c623d6b LP |
479 | }; |
480 | ||
481 | static void hdmi5_bridge_init(struct omap_hdmi *hdmi) | |
482 | { | |
483 | hdmi->bridge.funcs = &hdmi5_bridge_funcs; | |
484 | hdmi->bridge.of_node = hdmi->pdev->dev.of_node; | |
485 | hdmi->bridge.ops = DRM_BRIDGE_OP_EDID; | |
486 | hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA; | |
487 | ||
488 | drm_bridge_add(&hdmi->bridge); | |
489 | } | |
490 | ||
491 | static void hdmi5_bridge_cleanup(struct omap_hdmi *hdmi) | |
492 | { | |
493 | drm_bridge_remove(&hdmi->bridge); | |
494 | } | |
495 | ||
5f031b47 LP |
496 | /* ----------------------------------------------------------------------------- |
497 | * Audio Callbacks | |
498 | */ | |
f5bab222 | 499 | |
45302d7e JS |
500 | static int hdmi_audio_startup(struct device *dev, |
501 | void (*abort_cb)(struct device *dev)) | |
502 | { | |
503 | struct omap_hdmi *hd = dev_get_drvdata(dev); | |
45302d7e JS |
504 | |
505 | mutex_lock(&hd->lock); | |
506 | ||
c1899cb3 | 507 | WARN_ON(hd->audio_abort_cb != NULL); |
45302d7e JS |
508 | |
509 | hd->audio_abort_cb = abort_cb; | |
510 | ||
45302d7e JS |
511 | mutex_unlock(&hd->lock); |
512 | ||
c1899cb3 | 513 | return 0; |
45302d7e JS |
514 | } |
515 | ||
516 | static int hdmi_audio_shutdown(struct device *dev) | |
517 | { | |
518 | struct omap_hdmi *hd = dev_get_drvdata(dev); | |
519 | ||
520 | mutex_lock(&hd->lock); | |
521 | hd->audio_abort_cb = NULL; | |
8a9d4626 JS |
522 | hd->audio_configured = false; |
523 | hd->audio_playing = false; | |
45302d7e JS |
524 | mutex_unlock(&hd->lock); |
525 | ||
526 | return 0; | |
527 | } | |
528 | ||
529 | static int hdmi_audio_start(struct device *dev) | |
530 | { | |
531 | struct omap_hdmi *hd = dev_get_drvdata(dev); | |
8a9d4626 | 532 | unsigned long flags; |
45302d7e | 533 | |
8a9d4626 | 534 | spin_lock_irqsave(&hd->audio_playing_lock, flags); |
2d7639bc | 535 | |
c1899cb3 JS |
536 | if (hd->display_enabled) { |
537 | if (!hdmi_mode_has_audio(&hd->cfg)) | |
538 | DSSERR("%s: Video mode does not support audio\n", | |
539 | __func__); | |
8a9d4626 | 540 | hdmi_start_audio_stream(hd); |
c1899cb3 | 541 | } |
8a9d4626 | 542 | hd->audio_playing = true; |
45302d7e | 543 | |
8a9d4626 | 544 | spin_unlock_irqrestore(&hd->audio_playing_lock, flags); |
45302d7e JS |
545 | return 0; |
546 | } | |
547 | ||
548 | static void hdmi_audio_stop(struct device *dev) | |
549 | { | |
550 | struct omap_hdmi *hd = dev_get_drvdata(dev); | |
8a9d4626 | 551 | unsigned long flags; |
45302d7e | 552 | |
c1899cb3 JS |
553 | if (!hdmi_mode_has_audio(&hd->cfg)) |
554 | DSSERR("%s: Video mode does not support audio\n", __func__); | |
45302d7e | 555 | |
8a9d4626 JS |
556 | spin_lock_irqsave(&hd->audio_playing_lock, flags); |
557 | ||
558 | if (hd->display_enabled) | |
559 | hdmi_stop_audio_stream(hd); | |
560 | hd->audio_playing = false; | |
2d7639bc | 561 | |
8a9d4626 | 562 | spin_unlock_irqrestore(&hd->audio_playing_lock, flags); |
45302d7e JS |
563 | } |
564 | ||
565 | static int hdmi_audio_config(struct device *dev, | |
566 | struct omap_dss_audio *dss_audio) | |
567 | { | |
568 | struct omap_hdmi *hd = dev_get_drvdata(dev); | |
77eeac24 | 569 | int ret = 0; |
45302d7e JS |
570 | |
571 | mutex_lock(&hd->lock); | |
572 | ||
c1899cb3 JS |
573 | if (hd->display_enabled) { |
574 | ret = hdmi5_audio_config(&hd->core, &hd->wp, dss_audio, | |
575 | hd->cfg.vm.pixelclock); | |
576 | if (ret) | |
577 | goto out; | |
45302d7e JS |
578 | } |
579 | ||
c1899cb3 JS |
580 | hd->audio_configured = true; |
581 | hd->audio_config = *dss_audio; | |
45302d7e JS |
582 | out: |
583 | mutex_unlock(&hd->lock); | |
584 | ||
585 | return ret; | |
586 | } | |
587 | ||
588 | static const struct omap_hdmi_audio_ops hdmi_audio_ops = { | |
589 | .audio_startup = hdmi_audio_startup, | |
590 | .audio_shutdown = hdmi_audio_shutdown, | |
591 | .audio_start = hdmi_audio_start, | |
592 | .audio_stop = hdmi_audio_stop, | |
593 | .audio_config = hdmi_audio_config, | |
594 | }; | |
595 | ||
c44991ce | 596 | static int hdmi_audio_register(struct omap_hdmi *hdmi) |
45302d7e JS |
597 | { |
598 | struct omap_hdmi_audio_pdata pdata = { | |
c44991ce | 599 | .dev = &hdmi->pdev->dev, |
d20fa5a0 | 600 | .version = 5, |
c44991ce | 601 | .audio_dma_addr = hdmi_wp_get_audio_dma_addr(&hdmi->wp), |
45302d7e JS |
602 | .ops = &hdmi_audio_ops, |
603 | }; | |
604 | ||
c44991ce LP |
605 | hdmi->audio_pdev = platform_device_register_data( |
606 | &hdmi->pdev->dev, "omap-hdmi-audio", PLATFORM_DEVID_AUTO, | |
45302d7e JS |
607 | &pdata, sizeof(pdata)); |
608 | ||
c44991ce LP |
609 | if (IS_ERR(hdmi->audio_pdev)) |
610 | return PTR_ERR(hdmi->audio_pdev); | |
45302d7e | 611 | |
c44991ce LP |
612 | hdmi_runtime_get(hdmi); |
613 | hdmi->wp_idlemode = | |
614 | REG_GET(hdmi->wp.base, HDMI_WP_SYSCONFIG, 3, 2); | |
615 | hdmi_runtime_put(hdmi); | |
8a9d4626 | 616 | |
45302d7e JS |
617 | return 0; |
618 | } | |
619 | ||
5f031b47 LP |
620 | /* ----------------------------------------------------------------------------- |
621 | * Component Bind & Unbind | |
622 | */ | |
623 | ||
736e60dd | 624 | static int hdmi5_bind(struct device *dev, struct device *master, void *data) |
f5bab222 | 625 | { |
7b295257 | 626 | struct dss_device *dss = dss_get_device(master); |
5f031b47 | 627 | struct omap_hdmi *hdmi = dev_get_drvdata(dev); |
f5bab222 | 628 | int r; |
5f031b47 LP |
629 | |
630 | hdmi->dss = dss; | |
631 | ||
632 | r = hdmi_pll_init(dss, hdmi->pdev, &hdmi->pll, &hdmi->wp); | |
633 | if (r) | |
634 | return r; | |
635 | ||
636 | r = hdmi_audio_register(hdmi); | |
637 | if (r) { | |
638 | DSSERR("Registering HDMI audio failed %d\n", r); | |
639 | goto err_pll_uninit; | |
640 | } | |
641 | ||
642 | hdmi->debugfs = dss_debugfs_create_file(dss, "hdmi", hdmi_dump_regs, | |
643 | hdmi); | |
644 | ||
645 | return 0; | |
646 | ||
647 | err_pll_uninit: | |
648 | hdmi_pll_uninit(&hdmi->pll); | |
649 | return r; | |
650 | } | |
651 | ||
652 | static void hdmi5_unbind(struct device *dev, struct device *master, void *data) | |
653 | { | |
654 | struct omap_hdmi *hdmi = dev_get_drvdata(dev); | |
655 | ||
656 | dss_debugfs_remove_file(hdmi->debugfs); | |
657 | ||
658 | if (hdmi->audio_pdev) | |
659 | platform_device_unregister(hdmi->audio_pdev); | |
660 | ||
661 | hdmi_pll_uninit(&hdmi->pll); | |
662 | } | |
663 | ||
664 | static const struct component_ops hdmi5_component_ops = { | |
665 | .bind = hdmi5_bind, | |
666 | .unbind = hdmi5_unbind, | |
667 | }; | |
668 | ||
669 | /* ----------------------------------------------------------------------------- | |
670 | * Probe & Remove, Suspend & Resume | |
671 | */ | |
672 | ||
27d62452 | 673 | static int hdmi5_init_output(struct omap_hdmi *hdmi) |
5f031b47 LP |
674 | { |
675 | struct omap_dss_device *out = &hdmi->output; | |
71316556 | 676 | int r; |
5f031b47 | 677 | |
6c623d6b LP |
678 | hdmi5_bridge_init(hdmi); |
679 | ||
5f031b47 LP |
680 | out->dev = &hdmi->pdev->dev; |
681 | out->id = OMAP_DSS_OUTPUT_HDMI; | |
0dbfc396 | 682 | out->type = OMAP_DISPLAY_TYPE_HDMI; |
5f031b47 LP |
683 | out->name = "hdmi.0"; |
684 | out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT; | |
c83fefd7 | 685 | out->of_port = 0; |
5f031b47 | 686 | |
6c623d6b LP |
687 | r = omapdss_device_init_output(out, &hdmi->bridge); |
688 | if (r < 0) { | |
689 | hdmi5_bridge_cleanup(hdmi); | |
71316556 | 690 | return r; |
6c623d6b | 691 | } |
71316556 | 692 | |
5f031b47 | 693 | omapdss_device_register(out); |
27d62452 LP |
694 | |
695 | return 0; | |
5f031b47 LP |
696 | } |
697 | ||
698 | static void hdmi5_uninit_output(struct omap_hdmi *hdmi) | |
699 | { | |
700 | struct omap_dss_device *out = &hdmi->output; | |
701 | ||
702 | omapdss_device_unregister(out); | |
d17eb453 | 703 | omapdss_device_cleanup_output(out); |
6c623d6b LP |
704 | |
705 | hdmi5_bridge_cleanup(hdmi); | |
5f031b47 LP |
706 | } |
707 | ||
708 | static int hdmi5_probe_of(struct omap_hdmi *hdmi) | |
709 | { | |
710 | struct platform_device *pdev = hdmi->pdev; | |
711 | struct device_node *node = pdev->dev.of_node; | |
712 | struct device_node *ep; | |
713 | int r; | |
714 | ||
715 | ep = of_graph_get_endpoint_by_regs(node, 0, 0); | |
716 | if (!ep) | |
717 | return 0; | |
718 | ||
719 | r = hdmi_parse_lanes_of(pdev, ep, &hdmi->phy); | |
720 | of_node_put(ep); | |
721 | return r; | |
722 | } | |
723 | ||
724 | static int hdmi5_probe(struct platform_device *pdev) | |
725 | { | |
726 | struct omap_hdmi *hdmi; | |
f5bab222 | 727 | int irq; |
5f031b47 | 728 | int r; |
f5bab222 | 729 | |
c44991ce LP |
730 | hdmi = kzalloc(sizeof(*hdmi), GFP_KERNEL); |
731 | if (!hdmi) | |
732 | return -ENOMEM; | |
f5bab222 | 733 | |
c44991ce | 734 | hdmi->pdev = pdev; |
5f031b47 | 735 | |
c44991ce | 736 | dev_set_drvdata(&pdev->dev, hdmi); |
f5bab222 | 737 | |
c44991ce LP |
738 | mutex_init(&hdmi->lock); |
739 | spin_lock_init(&hdmi->audio_playing_lock); | |
740 | ||
5f031b47 | 741 | r = hdmi5_probe_of(hdmi); |
1dff212c | 742 | if (r) |
c44991ce | 743 | goto err_free; |
f5bab222 | 744 | |
c44991ce | 745 | r = hdmi_wp_init(pdev, &hdmi->wp, 5); |
f5bab222 | 746 | if (r) |
c44991ce | 747 | goto err_free; |
f5bab222 | 748 | |
c44991ce | 749 | r = hdmi_phy_init(pdev, &hdmi->phy, 5); |
f5bab222 | 750 | if (r) |
5f031b47 | 751 | goto err_free; |
f5bab222 | 752 | |
c44991ce | 753 | r = hdmi5_core_init(pdev, &hdmi->core); |
f5bab222 | 754 | if (r) |
5f031b47 | 755 | goto err_free; |
f5bab222 TV |
756 | |
757 | irq = platform_get_irq(pdev, 0); | |
758 | if (irq < 0) { | |
759 | DSSERR("platform_get_irq failed\n"); | |
c84c3a5b | 760 | r = -ENODEV; |
5f031b47 | 761 | goto err_free; |
f5bab222 TV |
762 | } |
763 | ||
764 | r = devm_request_threaded_irq(&pdev->dev, irq, | |
765 | NULL, hdmi_irq_handler, | |
c44991ce | 766 | IRQF_ONESHOT, "OMAP HDMI", hdmi); |
f5bab222 TV |
767 | if (r) { |
768 | DSSERR("HDMI IRQ request failed\n"); | |
5f031b47 | 769 | goto err_free; |
f5bab222 TV |
770 | } |
771 | ||
8a36357a LP |
772 | hdmi->vdda_reg = devm_regulator_get(&pdev->dev, "vdda"); |
773 | if (IS_ERR(hdmi->vdda_reg)) { | |
774 | r = PTR_ERR(hdmi->vdda_reg); | |
775 | if (r != -EPROBE_DEFER) | |
776 | DSSERR("can't get VDDA regulator\n"); | |
777 | goto err_free; | |
778 | } | |
779 | ||
f5bab222 TV |
780 | pm_runtime_enable(&pdev->dev); |
781 | ||
27d62452 LP |
782 | r = hdmi5_init_output(hdmi); |
783 | if (r) | |
784 | goto err_pm_disable; | |
f5bab222 | 785 | |
5f031b47 LP |
786 | r = component_add(&pdev->dev, &hdmi5_component_ops); |
787 | if (r) | |
66aacfe2 | 788 | goto err_uninit_output; |
f5bab222 TV |
789 | |
790 | return 0; | |
c44991ce | 791 | |
66aacfe2 | 792 | err_uninit_output: |
5f031b47 | 793 | hdmi5_uninit_output(hdmi); |
27d62452 | 794 | err_pm_disable: |
66aacfe2 | 795 | pm_runtime_disable(&pdev->dev); |
c44991ce LP |
796 | err_free: |
797 | kfree(hdmi); | |
c84c3a5b | 798 | return r; |
f5bab222 TV |
799 | } |
800 | ||
c2807ecb | 801 | static void hdmi5_remove(struct platform_device *pdev) |
f5bab222 | 802 | { |
5f031b47 | 803 | struct omap_hdmi *hdmi = platform_get_drvdata(pdev); |
736e60dd | 804 | |
5f031b47 | 805 | component_del(&pdev->dev, &hdmi5_component_ops); |
45302d7e | 806 | |
5f031b47 | 807 | hdmi5_uninit_output(hdmi); |
f5bab222 | 808 | |
5f031b47 | 809 | pm_runtime_disable(&pdev->dev); |
c84c3a5b | 810 | |
c44991ce | 811 | kfree(hdmi); |
f5bab222 TV |
812 | } |
813 | ||
f5bab222 TV |
814 | static const struct of_device_id hdmi_of_match[] = { |
815 | { .compatible = "ti,omap5-hdmi", }, | |
adb5ff83 | 816 | { .compatible = "ti,dra7-hdmi", }, |
f5bab222 TV |
817 | {}, |
818 | }; | |
819 | ||
d66c36a3 | 820 | struct platform_driver omapdss_hdmi5hw_driver = { |
736e60dd | 821 | .probe = hdmi5_probe, |
c2807ecb | 822 | .remove_new = hdmi5_remove, |
f5bab222 TV |
823 | .driver = { |
824 | .name = "omapdss_hdmi5", | |
f5bab222 | 825 | .of_match_table = hdmi_of_match, |
422ccbd5 | 826 | .suppress_bind_attrs = true, |
f5bab222 TV |
827 | }, |
828 | }; |