1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * HDMI driver for OMAP5 |
4 | * |
5 | * Copyright (C) 2014 Texas Instruments Incorporated - https://www.ti.com/ |
6 | * |
7 | * Authors: |
8 | * Yong Zhi |
9 | * Mythri pk |
10 | * Archit Taneja <archit@ti.com> |
11 | * Tomi Valkeinen <tomi.valkeinen@ti.com> |
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> |
27 | #include <linux/regulator/consumer.h> |
28 | #include <linux/component.h> |
29 | #include <linux/of.h> |
30 | #include <linux/of_graph.h> |
31 | #include <sound/omap-hdmi-audio.h> |
32 | |
33 | #include <drm/drm_atomic.h> |
34 | #include <drm/drm_atomic_state_helper.h> |
35 | #include <drm/drm_edid.h> |
36 | |
37 | #include "omapdss.h" |
38 | #include "hdmi5_core.h" |
39 | #include "dss.h" |
40 | |
41 | static int hdmi_runtime_get(struct omap_hdmi *hdmi) |
42 | { |
43 | int r; |
44 | |
45 | DSSDBG("hdmi_runtime_get\n" ); |
46 | |
47 | r = pm_runtime_get_sync(dev: &hdmi->pdev->dev); |
48 | if (WARN_ON(r < 0)) { |
49 | pm_runtime_put_noidle(dev: &hdmi->pdev->dev); |
50 | return r; |
51 | } |
52 | return 0; |
53 | } |
54 | |
55 | static void hdmi_runtime_put(struct omap_hdmi *hdmi) |
56 | { |
57 | int r; |
58 | |
59 | DSSDBG("hdmi_runtime_put\n" ); |
60 | |
61 | r = pm_runtime_put_sync(dev: &hdmi->pdev->dev); |
62 | WARN_ON(r < 0 && r != -ENOSYS); |
63 | } |
64 | |
65 | static irqreturn_t hdmi_irq_handler(int irq, void *data) |
66 | { |
67 | struct omap_hdmi *hdmi = data; |
68 | struct hdmi_wp_data *wp = &hdmi->wp; |
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, val: 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 | */ |
91 | v = hdmi_read_reg(base_addr: hdmi->phy.base, HDMI_TXPHY_PAD_CFG_CTRL); |
92 | v = FLD_MOD(v, 1, 15, 15); /* FORCE_RXDET_HIGH */ |
93 | v = FLD_MOD(v, 0, 14, 7); /* RXDET_LINE */ |
94 | hdmi_write_reg(base_addr: hdmi->phy.base, HDMI_TXPHY_PAD_CFG_CTRL, val: v); |
95 | |
96 | hdmi_wp_set_irqstatus(wp, HDMI_IRQ_LINK_CONNECT | |
97 | HDMI_IRQ_LINK_DISCONNECT); |
98 | |
99 | hdmi_wp_set_phy_pwr(wp, val: HDMI_PHYPWRCMD_LDOON); |
100 | |
101 | REG_FLD_MOD(hdmi->phy.base, HDMI_TXPHY_PAD_CFG_CTRL, 0, 15, 15); |
102 | |
103 | } else if (irqstatus & HDMI_IRQ_LINK_CONNECT) { |
104 | hdmi_wp_set_phy_pwr(wp, val: HDMI_PHYPWRCMD_TXON); |
105 | } else if (irqstatus & HDMI_IRQ_LINK_DISCONNECT) { |
106 | hdmi_wp_set_phy_pwr(wp, val: HDMI_PHYPWRCMD_LDOON); |
107 | } |
108 | |
109 | return IRQ_HANDLED; |
110 | } |
111 | |
112 | static int hdmi_power_on_core(struct omap_hdmi *hdmi) |
113 | { |
114 | int r; |
115 | |
116 | r = regulator_enable(regulator: hdmi->vdda_reg); |
117 | if (r) |
118 | return r; |
119 | |
120 | r = hdmi_runtime_get(hdmi); |
121 | if (r) |
122 | goto err_runtime_get; |
123 | |
124 | /* Make selection of HDMI in DSS */ |
125 | dss_select_hdmi_venc_clk_source(dss: hdmi->dss, src: DSS_HDMI_M_PCLK); |
126 | |
127 | hdmi->core_enabled = true; |
128 | |
129 | return 0; |
130 | |
131 | err_runtime_get: |
132 | regulator_disable(regulator: hdmi->vdda_reg); |
133 | |
134 | return r; |
135 | } |
136 | |
137 | static void hdmi_power_off_core(struct omap_hdmi *hdmi) |
138 | { |
139 | hdmi->core_enabled = false; |
140 | |
141 | hdmi_runtime_put(hdmi); |
142 | regulator_disable(regulator: hdmi->vdda_reg); |
143 | } |
144 | |
145 | static int hdmi_power_on_full(struct omap_hdmi *hdmi) |
146 | { |
147 | int r; |
148 | const struct videomode *vm; |
149 | struct dss_pll_clock_info hdmi_cinfo = { 0 }; |
150 | unsigned int pc; |
151 | |
152 | r = hdmi_power_on_core(hdmi); |
153 | if (r) |
154 | return r; |
155 | |
156 | vm = &hdmi->cfg.vm; |
157 | |
158 | DSSDBG("hdmi_power_on hactive= %d vactive = %d\n" , vm->hactive, |
159 | vm->vactive); |
160 | |
161 | pc = vm->pixelclock; |
162 | if (vm->flags & DISPLAY_FLAGS_DOUBLECLK) |
163 | pc *= 2; |
164 | |
165 | /* DSS_HDMI_TCLK is bitclk / 10 */ |
166 | pc *= 10; |
167 | |
168 | dss_pll_calc_b(pll: &hdmi->pll.pll, clkin: clk_get_rate(clk: hdmi->pll.pll.clkin), |
169 | target_clkout: pc, cinfo: &hdmi_cinfo); |
170 | |
171 | /* disable and clear irqs */ |
172 | hdmi_wp_clear_irqenable(wp: &hdmi->wp, mask: 0xffffffff); |
173 | hdmi_wp_set_irqstatus(wp: &hdmi->wp, |
174 | irqstatus: hdmi_wp_get_irqstatus(wp: &hdmi->wp)); |
175 | |
176 | r = dss_pll_enable(pll: &hdmi->pll.pll); |
177 | if (r) { |
178 | DSSERR("Failed to enable PLL\n" ); |
179 | goto err_pll_enable; |
180 | } |
181 | |
182 | r = dss_pll_set_config(pll: &hdmi->pll.pll, cinfo: &hdmi_cinfo); |
183 | if (r) { |
184 | DSSERR("Failed to configure PLL\n" ); |
185 | goto err_pll_cfg; |
186 | } |
187 | |
188 | r = hdmi_phy_configure(phy: &hdmi->phy, hfbitclk: hdmi_cinfo.clkdco, |
189 | lfbitclk: hdmi_cinfo.clkout[0]); |
190 | if (r) { |
191 | DSSDBG("Failed to start PHY\n" ); |
192 | goto err_phy_cfg; |
193 | } |
194 | |
195 | r = hdmi_wp_set_phy_pwr(wp: &hdmi->wp, val: HDMI_PHYPWRCMD_LDOON); |
196 | if (r) |
197 | goto err_phy_pwr; |
198 | |
199 | hdmi5_configure(core: &hdmi->core, wp: &hdmi->wp, cfg: &hdmi->cfg); |
200 | |
201 | r = dss_mgr_enable(dssdev: &hdmi->output); |
202 | if (r) |
203 | goto err_mgr_enable; |
204 | |
205 | r = hdmi_wp_video_start(wp: &hdmi->wp); |
206 | if (r) |
207 | goto err_vid_enable; |
208 | |
209 | hdmi_wp_set_irqenable(wp: &hdmi->wp, |
210 | HDMI_IRQ_LINK_CONNECT | HDMI_IRQ_LINK_DISCONNECT); |
211 | |
212 | return 0; |
213 | |
214 | err_vid_enable: |
215 | dss_mgr_disable(dssdev: &hdmi->output); |
216 | err_mgr_enable: |
217 | hdmi_wp_set_phy_pwr(wp: &hdmi->wp, val: HDMI_PHYPWRCMD_OFF); |
218 | err_phy_pwr: |
219 | err_phy_cfg: |
220 | err_pll_cfg: |
221 | dss_pll_disable(pll: &hdmi->pll.pll); |
222 | err_pll_enable: |
223 | hdmi_power_off_core(hdmi); |
224 | return -EIO; |
225 | } |
226 | |
227 | static void hdmi_power_off_full(struct omap_hdmi *hdmi) |
228 | { |
229 | hdmi_wp_clear_irqenable(wp: &hdmi->wp, mask: 0xffffffff); |
230 | |
231 | hdmi_wp_video_stop(wp: &hdmi->wp); |
232 | |
233 | dss_mgr_disable(dssdev: &hdmi->output); |
234 | |
235 | hdmi_wp_set_phy_pwr(wp: &hdmi->wp, val: HDMI_PHYPWRCMD_OFF); |
236 | |
237 | dss_pll_disable(pll: &hdmi->pll.pll); |
238 | |
239 | hdmi_power_off_core(hdmi); |
240 | } |
241 | |
242 | static int hdmi_dump_regs(struct seq_file *s, void *p) |
243 | { |
244 | struct omap_hdmi *hdmi = s->private; |
245 | |
246 | mutex_lock(&hdmi->lock); |
247 | |
248 | if (hdmi_runtime_get(hdmi)) { |
249 | mutex_unlock(lock: &hdmi->lock); |
250 | return 0; |
251 | } |
252 | |
253 | hdmi_wp_dump(wp: &hdmi->wp, s); |
254 | hdmi_pll_dump(pll: &hdmi->pll, s); |
255 | hdmi_phy_dump(phy: &hdmi->phy, s); |
256 | hdmi5_core_dump(core: &hdmi->core, s); |
257 | |
258 | hdmi_runtime_put(hdmi); |
259 | mutex_unlock(lock: &hdmi->lock); |
260 | return 0; |
261 | } |
262 | |
263 | static void hdmi_start_audio_stream(struct omap_hdmi *hd) |
264 | { |
265 | REG_FLD_MOD(hd->wp.base, HDMI_WP_SYSCONFIG, 1, 3, 2); |
266 | hdmi_wp_audio_enable(wp: &hd->wp, enable: true); |
267 | hdmi_wp_audio_core_req_enable(wp: &hd->wp, enable: true); |
268 | } |
269 | |
270 | static void hdmi_stop_audio_stream(struct omap_hdmi *hd) |
271 | { |
272 | hdmi_wp_audio_core_req_enable(wp: &hd->wp, enable: false); |
273 | hdmi_wp_audio_enable(wp: &hd->wp, enable: false); |
274 | REG_FLD_MOD(hd->wp.base, HDMI_WP_SYSCONFIG, hd->wp_idlemode, 3, 2); |
275 | } |
276 | |
277 | static int hdmi_core_enable(struct omap_hdmi *hdmi) |
278 | { |
279 | int r = 0; |
280 | |
281 | DSSDBG("ENTER omapdss_hdmi_core_enable\n" ); |
282 | |
283 | mutex_lock(&hdmi->lock); |
284 | |
285 | r = hdmi_power_on_core(hdmi); |
286 | if (r) { |
287 | DSSERR("failed to power on device\n" ); |
288 | goto err0; |
289 | } |
290 | |
291 | mutex_unlock(lock: &hdmi->lock); |
292 | return 0; |
293 | |
294 | err0: |
295 | mutex_unlock(lock: &hdmi->lock); |
296 | return r; |
297 | } |
298 | |
299 | static void hdmi_core_disable(struct omap_hdmi *hdmi) |
300 | { |
301 | DSSDBG("Enter omapdss_hdmi_core_disable\n" ); |
302 | |
303 | mutex_lock(&hdmi->lock); |
304 | |
305 | hdmi_power_off_core(hdmi); |
306 | |
307 | mutex_unlock(lock: &hdmi->lock); |
308 | } |
309 | |
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 | |
319 | if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) |
320 | return -EINVAL; |
321 | |
322 | return drm_bridge_attach(encoder: bridge->encoder, bridge: hdmi->output.next_bridge, |
323 | previous: bridge, flags); |
324 | } |
325 | |
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(dmode: adjusted_mode, vm: &hdmi->cfg.vm); |
335 | |
336 | dispc_set_tv_pclk(dispc: hdmi->dss->dispc, pclk: adjusted_mode->clock * 1000); |
337 | |
338 | mutex_unlock(lock: &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 | encoder: 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, crtc: 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(frame: &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(core: &hdmi->core, wp: &hdmi->wp, |
391 | audio: &hdmi->audio_config, |
392 | pclk: 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(hd: hdmi); |
403 | hdmi->display_enabled = true; |
404 | spin_unlock_irqrestore(lock: &hdmi->audio_playing_lock, flags); |
405 | |
406 | done: |
407 | mutex_unlock(lock: &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(hd: hdmi); |
420 | hdmi->display_enabled = false; |
421 | spin_unlock_irqrestore(lock: &hdmi->audio_playing_lock, flags); |
422 | |
423 | hdmi_power_off_full(hdmi); |
424 | |
425 | mutex_unlock(lock: &hdmi->lock); |
426 | } |
427 | |
428 | static const struct drm_edid *hdmi5_bridge_edid_read(struct drm_bridge *bridge, |
429 | struct drm_connector *connector) |
430 | { |
431 | struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); |
432 | const struct drm_edid *drm_edid; |
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); |
448 | |
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(core: &hdmi->core); |
454 | |
455 | drm_edid = drm_edid_read_custom(connector, read_block: hdmi5_core_ddc_read, context: &hdmi->core); |
456 | |
457 | hdmi5_core_ddc_uninit(core: &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(lock: &hdmi->lock); |
463 | |
464 | if (need_enable) |
465 | hdmi_core_disable(hdmi); |
466 | |
467 | return drm_edid; |
468 | } |
469 | |
470 | static const struct drm_bridge_funcs hdmi5_bridge_funcs = { |
471 | .attach = hdmi5_bridge_attach, |
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, |
478 | .edid_read = hdmi5_bridge_edid_read, |
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(bridge: &hdmi->bridge); |
489 | } |
490 | |
491 | static void hdmi5_bridge_cleanup(struct omap_hdmi *hdmi) |
492 | { |
493 | drm_bridge_remove(bridge: &hdmi->bridge); |
494 | } |
495 | |
496 | /* ----------------------------------------------------------------------------- |
497 | * Audio Callbacks |
498 | */ |
499 | |
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); |
504 | |
505 | mutex_lock(&hd->lock); |
506 | |
507 | WARN_ON(hd->audio_abort_cb != NULL); |
508 | |
509 | hd->audio_abort_cb = abort_cb; |
510 | |
511 | mutex_unlock(lock: &hd->lock); |
512 | |
513 | return 0; |
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; |
522 | hd->audio_configured = false; |
523 | hd->audio_playing = false; |
524 | mutex_unlock(lock: &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); |
532 | unsigned long flags; |
533 | |
534 | spin_lock_irqsave(&hd->audio_playing_lock, flags); |
535 | |
536 | if (hd->display_enabled) { |
537 | if (!hdmi_mode_has_audio(cfg: &hd->cfg)) |
538 | DSSERR("%s: Video mode does not support audio\n" , |
539 | __func__); |
540 | hdmi_start_audio_stream(hd); |
541 | } |
542 | hd->audio_playing = true; |
543 | |
544 | spin_unlock_irqrestore(lock: &hd->audio_playing_lock, flags); |
545 | return 0; |
546 | } |
547 | |
548 | static void hdmi_audio_stop(struct device *dev) |
549 | { |
550 | struct omap_hdmi *hd = dev_get_drvdata(dev); |
551 | unsigned long flags; |
552 | |
553 | if (!hdmi_mode_has_audio(cfg: &hd->cfg)) |
554 | DSSERR("%s: Video mode does not support audio\n" , __func__); |
555 | |
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; |
561 | |
562 | spin_unlock_irqrestore(lock: &hd->audio_playing_lock, flags); |
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); |
569 | int ret = 0; |
570 | |
571 | mutex_lock(&hd->lock); |
572 | |
573 | if (hd->display_enabled) { |
574 | ret = hdmi5_audio_config(core: &hd->core, wp: &hd->wp, audio: dss_audio, |
575 | pclk: hd->cfg.vm.pixelclock); |
576 | if (ret) |
577 | goto out; |
578 | } |
579 | |
580 | hd->audio_configured = true; |
581 | hd->audio_config = *dss_audio; |
582 | out: |
583 | mutex_unlock(lock: &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 | |
596 | static int hdmi_audio_register(struct omap_hdmi *hdmi) |
597 | { |
598 | struct omap_hdmi_audio_pdata pdata = { |
599 | .dev = &hdmi->pdev->dev, |
600 | .version = 5, |
601 | .audio_dma_addr = hdmi_wp_get_audio_dma_addr(wp: &hdmi->wp), |
602 | .ops = &hdmi_audio_ops, |
603 | }; |
604 | |
605 | hdmi->audio_pdev = platform_device_register_data( |
606 | parent: &hdmi->pdev->dev, name: "omap-hdmi-audio" , PLATFORM_DEVID_AUTO, |
607 | data: &pdata, size: sizeof(pdata)); |
608 | |
609 | if (IS_ERR(ptr: hdmi->audio_pdev)) |
610 | return PTR_ERR(ptr: hdmi->audio_pdev); |
611 | |
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); |
616 | |
617 | return 0; |
618 | } |
619 | |
620 | /* ----------------------------------------------------------------------------- |
621 | * Component Bind & Unbind |
622 | */ |
623 | |
624 | static int hdmi5_bind(struct device *dev, struct device *master, void *data) |
625 | { |
626 | struct dss_device *dss = dss_get_device(dev: master); |
627 | struct omap_hdmi *hdmi = dev_get_drvdata(dev); |
628 | int r; |
629 | |
630 | hdmi->dss = dss; |
631 | |
632 | r = hdmi_pll_init(dss, pdev: hdmi->pdev, pll: &hdmi->pll, wp: &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, name: "hdmi" , show_fn: hdmi_dump_regs, |
643 | data: hdmi); |
644 | |
645 | return 0; |
646 | |
647 | err_pll_uninit: |
648 | hdmi_pll_uninit(hpll: &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(entry: hdmi->debugfs); |
657 | |
658 | if (hdmi->audio_pdev) |
659 | platform_device_unregister(hdmi->audio_pdev); |
660 | |
661 | hdmi_pll_uninit(hpll: &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 | |
673 | static int hdmi5_init_output(struct omap_hdmi *hdmi) |
674 | { |
675 | struct omap_dss_device *out = &hdmi->output; |
676 | int r; |
677 | |
678 | hdmi5_bridge_init(hdmi); |
679 | |
680 | out->dev = &hdmi->pdev->dev; |
681 | out->id = OMAP_DSS_OUTPUT_HDMI; |
682 | out->type = OMAP_DISPLAY_TYPE_HDMI; |
683 | out->name = "hdmi.0" ; |
684 | out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT; |
685 | out->of_port = 0; |
686 | |
687 | r = omapdss_device_init_output(out, local_bridge: &hdmi->bridge); |
688 | if (r < 0) { |
689 | hdmi5_bridge_cleanup(hdmi); |
690 | return r; |
691 | } |
692 | |
693 | omapdss_device_register(dssdev: out); |
694 | |
695 | return 0; |
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(dssdev: out); |
703 | omapdss_device_cleanup_output(out); |
704 | |
705 | hdmi5_bridge_cleanup(hdmi); |
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(parent: node, port_reg: 0, reg: 0); |
716 | if (!ep) |
717 | return 0; |
718 | |
719 | r = hdmi_parse_lanes_of(pdev, ep, phy: &hdmi->phy); |
720 | of_node_put(node: ep); |
721 | return r; |
722 | } |
723 | |
724 | static int hdmi5_probe(struct platform_device *pdev) |
725 | { |
726 | struct omap_hdmi *hdmi; |
727 | int irq; |
728 | int r; |
729 | |
730 | hdmi = kzalloc(size: sizeof(*hdmi), GFP_KERNEL); |
731 | if (!hdmi) |
732 | return -ENOMEM; |
733 | |
734 | hdmi->pdev = pdev; |
735 | |
736 | dev_set_drvdata(dev: &pdev->dev, data: hdmi); |
737 | |
738 | mutex_init(&hdmi->lock); |
739 | spin_lock_init(&hdmi->audio_playing_lock); |
740 | |
741 | r = hdmi5_probe_of(hdmi); |
742 | if (r) |
743 | goto err_free; |
744 | |
745 | r = hdmi_wp_init(pdev, wp: &hdmi->wp, version: 5); |
746 | if (r) |
747 | goto err_free; |
748 | |
749 | r = hdmi_phy_init(pdev, phy: &hdmi->phy, version: 5); |
750 | if (r) |
751 | goto err_free; |
752 | |
753 | r = hdmi5_core_init(pdev, core: &hdmi->core); |
754 | if (r) |
755 | goto err_free; |
756 | |
757 | irq = platform_get_irq(pdev, 0); |
758 | if (irq < 0) { |
759 | DSSERR("platform_get_irq failed\n" ); |
760 | r = -ENODEV; |
761 | goto err_free; |
762 | } |
763 | |
764 | r = devm_request_threaded_irq(dev: &pdev->dev, irq, |
765 | NULL, thread_fn: hdmi_irq_handler, |
766 | IRQF_ONESHOT, devname: "OMAP HDMI" , dev_id: hdmi); |
767 | if (r) { |
768 | DSSERR("HDMI IRQ request failed\n" ); |
769 | goto err_free; |
770 | } |
771 | |
772 | hdmi->vdda_reg = devm_regulator_get(dev: &pdev->dev, id: "vdda" ); |
773 | if (IS_ERR(ptr: hdmi->vdda_reg)) { |
774 | r = PTR_ERR(ptr: hdmi->vdda_reg); |
775 | if (r != -EPROBE_DEFER) |
776 | DSSERR("can't get VDDA regulator\n" ); |
777 | goto err_free; |
778 | } |
779 | |
780 | pm_runtime_enable(dev: &pdev->dev); |
781 | |
782 | r = hdmi5_init_output(hdmi); |
783 | if (r) |
784 | goto err_pm_disable; |
785 | |
786 | r = component_add(&pdev->dev, &hdmi5_component_ops); |
787 | if (r) |
788 | goto err_uninit_output; |
789 | |
790 | return 0; |
791 | |
792 | err_uninit_output: |
793 | hdmi5_uninit_output(hdmi); |
794 | err_pm_disable: |
795 | pm_runtime_disable(dev: &pdev->dev); |
796 | err_free: |
797 | kfree(objp: hdmi); |
798 | return r; |
799 | } |
800 | |
801 | static void hdmi5_remove(struct platform_device *pdev) |
802 | { |
803 | struct omap_hdmi *hdmi = platform_get_drvdata(pdev); |
804 | |
805 | component_del(&pdev->dev, &hdmi5_component_ops); |
806 | |
807 | hdmi5_uninit_output(hdmi); |
808 | |
809 | pm_runtime_disable(dev: &pdev->dev); |
810 | |
811 | kfree(objp: hdmi); |
812 | } |
813 | |
814 | static const struct of_device_id hdmi_of_match[] = { |
815 | { .compatible = "ti,omap5-hdmi" , }, |
816 | { .compatible = "ti,dra7-hdmi" , }, |
817 | {}, |
818 | }; |
819 | |
820 | struct platform_driver omapdss_hdmi5hw_driver = { |
821 | .probe = hdmi5_probe, |
822 | .remove_new = hdmi5_remove, |
823 | .driver = { |
824 | .name = "omapdss_hdmi5" , |
825 | .of_match_table = hdmi_of_match, |
826 | .suppress_bind_attrs = true, |
827 | }, |
828 | }; |
829 | |