1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2016 BayLibre, SAS |
4 | * Author: Neil Armstrong <narmstrong@baylibre.com> |
5 | * Copyright (C) 2015 Amlogic, Inc. All rights reserved. |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/component.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | #include <linux/of_graph.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/regulator/consumer.h> |
16 | #include <linux/reset.h> |
17 | |
18 | #include <drm/bridge/dw_hdmi.h> |
19 | #include <drm/drm_atomic_helper.h> |
20 | #include <drm/drm_bridge.h> |
21 | #include <drm/drm_device.h> |
22 | #include <drm/drm_edid.h> |
23 | #include <drm/drm_probe_helper.h> |
24 | #include <drm/drm_print.h> |
25 | |
26 | #include <linux/videodev2.h> |
27 | |
28 | #include "meson_drv.h" |
29 | #include "meson_dw_hdmi.h" |
30 | #include "meson_registers.h" |
31 | |
32 | #define DRIVER_NAME "meson-dw-hdmi" |
33 | #define DRIVER_DESC "Amlogic Meson HDMI-TX DRM driver" |
34 | |
35 | /** |
36 | * DOC: HDMI Output |
37 | * |
38 | * HDMI Output is composed of : |
39 | * |
40 | * - A Synopsys DesignWare HDMI Controller IP |
41 | * - A TOP control block controlling the Clocks and PHY |
42 | * - A custom HDMI PHY in order convert video to TMDS signal |
43 | * |
44 | * .. code:: |
45 | * |
46 | * ___________________________________ |
47 | * | HDMI TOP |<= HPD |
48 | * |___________________________________| |
49 | * | | | |
50 | * | Synopsys HDMI | HDMI PHY |=> TMDS |
51 | * | Controller |________________| |
52 | * |___________________________________|<=> DDC |
53 | * |
54 | * |
55 | * The HDMI TOP block only supports HPD sensing. |
56 | * The Synopsys HDMI Controller interrupt is routed |
57 | * through the TOP Block interrupt. |
58 | * Communication to the TOP Block and the Synopsys |
59 | * HDMI Controller is done a pair of addr+read/write |
60 | * registers. |
61 | * The HDMI PHY is configured by registers in the |
62 | * HHI register block. |
63 | * |
64 | * Pixel data arrives in 4:4:4 format from the VENC |
65 | * block and the VPU HDMI mux selects either the ENCI |
66 | * encoder for the 576i or 480i formats or the ENCP |
67 | * encoder for all the other formats including |
68 | * interlaced HD formats. |
69 | * The VENC uses a DVI encoder on top of the ENCI |
70 | * or ENCP encoders to generate DVI timings for the |
71 | * HDMI controller. |
72 | * |
73 | * GXBB, GXL and GXM embeds the Synopsys DesignWare |
74 | * HDMI TX IP version 2.01a with HDCP and I2C & S/PDIF |
75 | * audio source interfaces. |
76 | * |
77 | * We handle the following features : |
78 | * |
79 | * - HPD Rise & Fall interrupt |
80 | * - HDMI Controller Interrupt |
81 | * - HDMI PHY Init for 480i to 1080p60 |
82 | * - VENC & HDMI Clock setup for 480i to 1080p60 |
83 | * - VENC Mode setup for 480i to 1080p60 |
84 | * |
85 | * What is missing : |
86 | * |
87 | * - PHY, Clock and Mode setup for 2k && 4k modes |
88 | * - SDDC Scrambling mode for HDMI 2.0a |
89 | * - HDCP Setup |
90 | * - CEC Management |
91 | */ |
92 | |
93 | /* TOP Block Communication Channel */ |
94 | #define HDMITX_TOP_ADDR_REG 0x0 |
95 | #define HDMITX_TOP_DATA_REG 0x4 |
96 | #define HDMITX_TOP_CTRL_REG 0x8 |
97 | #define HDMITX_TOP_G12A_OFFSET 0x8000 |
98 | |
99 | /* Controller Communication Channel */ |
100 | #define HDMITX_DWC_ADDR_REG 0x10 |
101 | #define HDMITX_DWC_DATA_REG 0x14 |
102 | #define HDMITX_DWC_CTRL_REG 0x18 |
103 | |
104 | /* HHI Registers */ |
105 | #define HHI_MEM_PD_REG0 0x100 /* 0x40 */ |
106 | #define HHI_HDMI_CLK_CNTL 0x1cc /* 0x73 */ |
107 | #define HHI_HDMI_PHY_CNTL0 0x3a0 /* 0xe8 */ |
108 | #define HHI_HDMI_PHY_CNTL1 0x3a4 /* 0xe9 */ |
109 | #define HHI_HDMI_PHY_CNTL2 0x3a8 /* 0xea */ |
110 | #define HHI_HDMI_PHY_CNTL3 0x3ac /* 0xeb */ |
111 | #define HHI_HDMI_PHY_CNTL4 0x3b0 /* 0xec */ |
112 | #define HHI_HDMI_PHY_CNTL5 0x3b4 /* 0xed */ |
113 | |
114 | static DEFINE_SPINLOCK(reg_lock); |
115 | |
116 | enum meson_venc_source { |
117 | MESON_VENC_SOURCE_NONE = 0, |
118 | MESON_VENC_SOURCE_ENCI = 1, |
119 | MESON_VENC_SOURCE_ENCP = 2, |
120 | }; |
121 | |
122 | struct meson_dw_hdmi; |
123 | |
124 | struct meson_dw_hdmi_data { |
125 | unsigned int (*top_read)(struct meson_dw_hdmi *dw_hdmi, |
126 | unsigned int addr); |
127 | void (*top_write)(struct meson_dw_hdmi *dw_hdmi, |
128 | unsigned int addr, unsigned int data); |
129 | unsigned int (*dwc_read)(struct meson_dw_hdmi *dw_hdmi, |
130 | unsigned int addr); |
131 | void (*dwc_write)(struct meson_dw_hdmi *dw_hdmi, |
132 | unsigned int addr, unsigned int data); |
133 | }; |
134 | |
135 | struct meson_dw_hdmi { |
136 | struct dw_hdmi_plat_data dw_plat_data; |
137 | struct meson_drm *priv; |
138 | struct device *dev; |
139 | void __iomem *hdmitx; |
140 | const struct meson_dw_hdmi_data *data; |
141 | struct reset_control *hdmitx_apb; |
142 | struct reset_control *hdmitx_ctrl; |
143 | struct reset_control *hdmitx_phy; |
144 | u32 irq_stat; |
145 | struct dw_hdmi *hdmi; |
146 | struct drm_bridge *bridge; |
147 | }; |
148 | |
149 | static inline int dw_hdmi_is_compatible(struct meson_dw_hdmi *dw_hdmi, |
150 | const char *compat) |
151 | { |
152 | return of_device_is_compatible(device: dw_hdmi->dev->of_node, compat); |
153 | } |
154 | |
155 | /* PHY (via TOP bridge) and Controller dedicated register interface */ |
156 | |
157 | static unsigned int dw_hdmi_top_read(struct meson_dw_hdmi *dw_hdmi, |
158 | unsigned int addr) |
159 | { |
160 | unsigned long flags; |
161 | unsigned int data; |
162 | |
163 | spin_lock_irqsave(®_lock, flags); |
164 | |
165 | /* ADDR must be written twice */ |
166 | writel(val: addr & 0xffff, addr: dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG); |
167 | writel(val: addr & 0xffff, addr: dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG); |
168 | |
169 | /* Read needs a second DATA read */ |
170 | data = readl(addr: dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG); |
171 | data = readl(addr: dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG); |
172 | |
173 | spin_unlock_irqrestore(lock: ®_lock, flags); |
174 | |
175 | return data; |
176 | } |
177 | |
178 | static unsigned int dw_hdmi_g12a_top_read(struct meson_dw_hdmi *dw_hdmi, |
179 | unsigned int addr) |
180 | { |
181 | return readl(addr: dw_hdmi->hdmitx + HDMITX_TOP_G12A_OFFSET + (addr << 2)); |
182 | } |
183 | |
184 | static inline void dw_hdmi_top_write(struct meson_dw_hdmi *dw_hdmi, |
185 | unsigned int addr, unsigned int data) |
186 | { |
187 | unsigned long flags; |
188 | |
189 | spin_lock_irqsave(®_lock, flags); |
190 | |
191 | /* ADDR must be written twice */ |
192 | writel(val: addr & 0xffff, addr: dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG); |
193 | writel(val: addr & 0xffff, addr: dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG); |
194 | |
195 | /* Write needs single DATA write */ |
196 | writel(val: data, addr: dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG); |
197 | |
198 | spin_unlock_irqrestore(lock: ®_lock, flags); |
199 | } |
200 | |
201 | static inline void dw_hdmi_g12a_top_write(struct meson_dw_hdmi *dw_hdmi, |
202 | unsigned int addr, unsigned int data) |
203 | { |
204 | writel(val: data, addr: dw_hdmi->hdmitx + HDMITX_TOP_G12A_OFFSET + (addr << 2)); |
205 | } |
206 | |
207 | /* Helper to change specific bits in PHY registers */ |
208 | static inline void dw_hdmi_top_write_bits(struct meson_dw_hdmi *dw_hdmi, |
209 | unsigned int addr, |
210 | unsigned int mask, |
211 | unsigned int val) |
212 | { |
213 | unsigned int data = dw_hdmi->data->top_read(dw_hdmi, addr); |
214 | |
215 | data &= ~mask; |
216 | data |= val; |
217 | |
218 | dw_hdmi->data->top_write(dw_hdmi, addr, data); |
219 | } |
220 | |
221 | static unsigned int dw_hdmi_dwc_read(struct meson_dw_hdmi *dw_hdmi, |
222 | unsigned int addr) |
223 | { |
224 | unsigned long flags; |
225 | unsigned int data; |
226 | |
227 | spin_lock_irqsave(®_lock, flags); |
228 | |
229 | /* ADDR must be written twice */ |
230 | writel(val: addr & 0xffff, addr: dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG); |
231 | writel(val: addr & 0xffff, addr: dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG); |
232 | |
233 | /* Read needs a second DATA read */ |
234 | data = readl(addr: dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG); |
235 | data = readl(addr: dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG); |
236 | |
237 | spin_unlock_irqrestore(lock: ®_lock, flags); |
238 | |
239 | return data; |
240 | } |
241 | |
242 | static unsigned int dw_hdmi_g12a_dwc_read(struct meson_dw_hdmi *dw_hdmi, |
243 | unsigned int addr) |
244 | { |
245 | return readb(addr: dw_hdmi->hdmitx + addr); |
246 | } |
247 | |
248 | static inline void dw_hdmi_dwc_write(struct meson_dw_hdmi *dw_hdmi, |
249 | unsigned int addr, unsigned int data) |
250 | { |
251 | unsigned long flags; |
252 | |
253 | spin_lock_irqsave(®_lock, flags); |
254 | |
255 | /* ADDR must be written twice */ |
256 | writel(val: addr & 0xffff, addr: dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG); |
257 | writel(val: addr & 0xffff, addr: dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG); |
258 | |
259 | /* Write needs single DATA write */ |
260 | writel(val: data, addr: dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG); |
261 | |
262 | spin_unlock_irqrestore(lock: ®_lock, flags); |
263 | } |
264 | |
265 | static inline void dw_hdmi_g12a_dwc_write(struct meson_dw_hdmi *dw_hdmi, |
266 | unsigned int addr, unsigned int data) |
267 | { |
268 | writeb(val: data, addr: dw_hdmi->hdmitx + addr); |
269 | } |
270 | |
271 | /* Helper to change specific bits in controller registers */ |
272 | static inline void dw_hdmi_dwc_write_bits(struct meson_dw_hdmi *dw_hdmi, |
273 | unsigned int addr, |
274 | unsigned int mask, |
275 | unsigned int val) |
276 | { |
277 | unsigned int data = dw_hdmi->data->dwc_read(dw_hdmi, addr); |
278 | |
279 | data &= ~mask; |
280 | data |= val; |
281 | |
282 | dw_hdmi->data->dwc_write(dw_hdmi, addr, data); |
283 | } |
284 | |
285 | /* Bridge */ |
286 | |
287 | /* Setup PHY bandwidth modes */ |
288 | static void meson_hdmi_phy_setup_mode(struct meson_dw_hdmi *dw_hdmi, |
289 | const struct drm_display_mode *mode, |
290 | bool mode_is_420) |
291 | { |
292 | struct meson_drm *priv = dw_hdmi->priv; |
293 | unsigned int pixel_clock = mode->clock; |
294 | |
295 | /* For 420, pixel clock is half unlike venc clock */ |
296 | if (mode_is_420) pixel_clock /= 2; |
297 | |
298 | if (dw_hdmi_is_compatible(dw_hdmi, compat: "amlogic,meson-gxl-dw-hdmi" ) || |
299 | dw_hdmi_is_compatible(dw_hdmi, compat: "amlogic,meson-gxm-dw-hdmi" )) { |
300 | if (pixel_clock >= 371250) { |
301 | /* 5.94Gbps, 3.7125Gbps */ |
302 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL0, val: 0x333d3282); |
303 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL3, val: 0x2136315b); |
304 | } else if (pixel_clock >= 297000) { |
305 | /* 2.97Gbps */ |
306 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL0, val: 0x33303382); |
307 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL3, val: 0x2036315b); |
308 | } else if (pixel_clock >= 148500) { |
309 | /* 1.485Gbps */ |
310 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL0, val: 0x33303362); |
311 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL3, val: 0x2016315b); |
312 | } else { |
313 | /* 742.5Mbps, and below */ |
314 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL0, val: 0x33604142); |
315 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL3, val: 0x0016315b); |
316 | } |
317 | } else if (dw_hdmi_is_compatible(dw_hdmi, |
318 | compat: "amlogic,meson-gxbb-dw-hdmi" )) { |
319 | if (pixel_clock >= 371250) { |
320 | /* 5.94Gbps, 3.7125Gbps */ |
321 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL0, val: 0x33353245); |
322 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL3, val: 0x2100115b); |
323 | } else if (pixel_clock >= 297000) { |
324 | /* 2.97Gbps */ |
325 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL0, val: 0x33634283); |
326 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL3, val: 0xb000115b); |
327 | } else { |
328 | /* 1.485Gbps, and below */ |
329 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL0, val: 0x33632122); |
330 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL3, val: 0x2000115b); |
331 | } |
332 | } else if (dw_hdmi_is_compatible(dw_hdmi, |
333 | compat: "amlogic,meson-g12a-dw-hdmi" )) { |
334 | if (pixel_clock >= 371250) { |
335 | /* 5.94Gbps, 3.7125Gbps */ |
336 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL0, val: 0x37eb65c4); |
337 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL3, val: 0x2ab0ff3b); |
338 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL5, val: 0x0000080b); |
339 | } else if (pixel_clock >= 297000) { |
340 | /* 2.97Gbps */ |
341 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL0, val: 0x33eb6262); |
342 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL3, val: 0x2ab0ff3b); |
343 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL5, val: 0x00000003); |
344 | } else { |
345 | /* 1.485Gbps, and below */ |
346 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL0, val: 0x33eb4242); |
347 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL3, val: 0x2ab0ff3b); |
348 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL5, val: 0x00000003); |
349 | } |
350 | } |
351 | } |
352 | |
353 | static inline void meson_dw_hdmi_phy_reset(struct meson_dw_hdmi *dw_hdmi) |
354 | { |
355 | struct meson_drm *priv = dw_hdmi->priv; |
356 | |
357 | /* Enable and software reset */ |
358 | regmap_update_bits(map: priv->hhi, HHI_HDMI_PHY_CNTL1, mask: 0xf, val: 0xf); |
359 | |
360 | mdelay(2); |
361 | |
362 | /* Enable and unreset */ |
363 | regmap_update_bits(map: priv->hhi, HHI_HDMI_PHY_CNTL1, mask: 0xf, val: 0xe); |
364 | |
365 | mdelay(2); |
366 | } |
367 | |
368 | static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, |
369 | const struct drm_display_info *display, |
370 | const struct drm_display_mode *mode) |
371 | { |
372 | struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; |
373 | bool is_hdmi2_sink = display->hdmi.scdc.supported; |
374 | struct meson_drm *priv = dw_hdmi->priv; |
375 | unsigned int wr_clk = |
376 | readl_relaxed(priv->io_base + _REG(VPU_HDMI_SETTING)); |
377 | bool mode_is_420 = false; |
378 | |
379 | DRM_DEBUG_DRIVER("\"%s\" div%d\n" , mode->name, |
380 | mode->clock > 340000 ? 40 : 10); |
381 | |
382 | if (drm_mode_is_420_only(display, mode) || |
383 | (!is_hdmi2_sink && drm_mode_is_420_also(display, mode)) || |
384 | dw_hdmi_bus_fmt_is_420(hdmi)) |
385 | mode_is_420 = true; |
386 | |
387 | /* Enable clocks */ |
388 | regmap_update_bits(map: priv->hhi, HHI_HDMI_CLK_CNTL, mask: 0xffff, val: 0x100); |
389 | |
390 | /* Bring HDMITX MEM output of power down */ |
391 | regmap_update_bits(map: priv->hhi, HHI_MEM_PD_REG0, mask: 0xff << 8, val: 0); |
392 | |
393 | /* Bring out of reset */ |
394 | dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_SW_RESET, 0); |
395 | |
396 | /* Enable internal pixclk, tmds_clk, spdif_clk, i2s_clk, cecclk */ |
397 | dw_hdmi_top_write_bits(dw_hdmi, HDMITX_TOP_CLK_CNTL, |
398 | mask: 0x3, val: 0x3); |
399 | |
400 | /* Enable cec_clk and hdcp22_tmdsclk_en */ |
401 | dw_hdmi_top_write_bits(dw_hdmi, HDMITX_TOP_CLK_CNTL, |
402 | mask: 0x3 << 4, val: 0x3 << 4); |
403 | |
404 | /* Enable normal output to PHY */ |
405 | dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_BIST_CNTL, BIT(12)); |
406 | |
407 | /* TMDS pattern setup */ |
408 | if (mode->clock > 340000 && !mode_is_420) { |
409 | dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, |
410 | 0); |
411 | dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, |
412 | 0x03ff03ff); |
413 | } else { |
414 | dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, |
415 | 0x001f001f); |
416 | dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, |
417 | 0x001f001f); |
418 | } |
419 | |
420 | /* Load TMDS pattern */ |
421 | dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x1); |
422 | msleep(msecs: 20); |
423 | dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x2); |
424 | |
425 | /* Setup PHY parameters */ |
426 | meson_hdmi_phy_setup_mode(dw_hdmi, mode, mode_is_420); |
427 | |
428 | /* Setup PHY */ |
429 | regmap_update_bits(map: priv->hhi, HHI_HDMI_PHY_CNTL1, |
430 | mask: 0xffff << 16, val: 0x0390 << 16); |
431 | |
432 | /* BIT_INVERT */ |
433 | if (dw_hdmi_is_compatible(dw_hdmi, compat: "amlogic,meson-gxl-dw-hdmi" ) || |
434 | dw_hdmi_is_compatible(dw_hdmi, compat: "amlogic,meson-gxm-dw-hdmi" ) || |
435 | dw_hdmi_is_compatible(dw_hdmi, compat: "amlogic,meson-g12a-dw-hdmi" )) |
436 | regmap_update_bits(map: priv->hhi, HHI_HDMI_PHY_CNTL1, |
437 | BIT(17), val: 0); |
438 | else |
439 | regmap_update_bits(map: priv->hhi, HHI_HDMI_PHY_CNTL1, |
440 | BIT(17), BIT(17)); |
441 | |
442 | /* Disable clock, fifo, fifo_wr */ |
443 | regmap_update_bits(map: priv->hhi, HHI_HDMI_PHY_CNTL1, mask: 0xf, val: 0); |
444 | |
445 | dw_hdmi_set_high_tmds_clock_ratio(hdmi, display); |
446 | |
447 | msleep(msecs: 100); |
448 | |
449 | /* Reset PHY 3 times in a row */ |
450 | meson_dw_hdmi_phy_reset(dw_hdmi); |
451 | meson_dw_hdmi_phy_reset(dw_hdmi); |
452 | meson_dw_hdmi_phy_reset(dw_hdmi); |
453 | |
454 | /* Temporary Disable VENC video stream */ |
455 | if (priv->venc.hdmi_use_enci) |
456 | writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN)); |
457 | else |
458 | writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN)); |
459 | |
460 | /* Temporary Disable HDMI video stream to HDMI-TX */ |
461 | writel_bits_relaxed(0x3, 0, |
462 | priv->io_base + _REG(VPU_HDMI_SETTING)); |
463 | writel_bits_relaxed(0xf << 8, 0, |
464 | priv->io_base + _REG(VPU_HDMI_SETTING)); |
465 | |
466 | /* Re-Enable VENC video stream */ |
467 | if (priv->venc.hdmi_use_enci) |
468 | writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN)); |
469 | else |
470 | writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN)); |
471 | |
472 | /* Push back HDMI clock settings */ |
473 | writel_bits_relaxed(0xf << 8, wr_clk & (0xf << 8), |
474 | priv->io_base + _REG(VPU_HDMI_SETTING)); |
475 | |
476 | /* Enable and Select HDMI video source for HDMI-TX */ |
477 | if (priv->venc.hdmi_use_enci) |
478 | writel_bits_relaxed(0x3, MESON_VENC_SOURCE_ENCI, |
479 | priv->io_base + _REG(VPU_HDMI_SETTING)); |
480 | else |
481 | writel_bits_relaxed(0x3, MESON_VENC_SOURCE_ENCP, |
482 | priv->io_base + _REG(VPU_HDMI_SETTING)); |
483 | |
484 | return 0; |
485 | } |
486 | |
487 | static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi, |
488 | void *data) |
489 | { |
490 | struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; |
491 | struct meson_drm *priv = dw_hdmi->priv; |
492 | |
493 | DRM_DEBUG_DRIVER("\n" ); |
494 | |
495 | regmap_write(map: priv->hhi, HHI_HDMI_PHY_CNTL0, val: 0); |
496 | } |
497 | |
498 | static enum drm_connector_status dw_hdmi_read_hpd(struct dw_hdmi *hdmi, |
499 | void *data) |
500 | { |
501 | struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; |
502 | |
503 | return !!dw_hdmi->data->top_read(dw_hdmi, HDMITX_TOP_STAT0) ? |
504 | connector_status_connected : connector_status_disconnected; |
505 | } |
506 | |
507 | static void dw_hdmi_setup_hpd(struct dw_hdmi *hdmi, |
508 | void *data) |
509 | { |
510 | struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; |
511 | |
512 | /* Setup HPD Filter */ |
513 | dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_HPD_FILTER, |
514 | (0xa << 12) | 0xa0); |
515 | |
516 | /* Clear interrupts */ |
517 | dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_INTR_STAT_CLR, |
518 | HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL); |
519 | |
520 | /* Unmask interrupts */ |
521 | dw_hdmi_top_write_bits(dw_hdmi, HDMITX_TOP_INTR_MASKN, |
522 | HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL, |
523 | HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL); |
524 | } |
525 | |
526 | static const struct dw_hdmi_phy_ops meson_dw_hdmi_phy_ops = { |
527 | .init = dw_hdmi_phy_init, |
528 | .disable = dw_hdmi_phy_disable, |
529 | .read_hpd = dw_hdmi_read_hpd, |
530 | .setup_hpd = dw_hdmi_setup_hpd, |
531 | }; |
532 | |
533 | static irqreturn_t dw_hdmi_top_irq(int irq, void *dev_id) |
534 | { |
535 | struct meson_dw_hdmi *dw_hdmi = dev_id; |
536 | u32 stat; |
537 | |
538 | stat = dw_hdmi->data->top_read(dw_hdmi, HDMITX_TOP_INTR_STAT); |
539 | dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_INTR_STAT_CLR, stat); |
540 | |
541 | /* HPD Events, handle in the threaded interrupt handler */ |
542 | if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL)) { |
543 | dw_hdmi->irq_stat = stat; |
544 | return IRQ_WAKE_THREAD; |
545 | } |
546 | |
547 | /* HDMI Controller Interrupt */ |
548 | if (stat & 1) |
549 | return IRQ_NONE; |
550 | |
551 | /* TOFIX Handle HDCP Interrupts */ |
552 | |
553 | return IRQ_HANDLED; |
554 | } |
555 | |
556 | /* Threaded interrupt handler to manage HPD events */ |
557 | static irqreturn_t dw_hdmi_top_thread_irq(int irq, void *dev_id) |
558 | { |
559 | struct meson_dw_hdmi *dw_hdmi = dev_id; |
560 | u32 stat = dw_hdmi->irq_stat; |
561 | |
562 | /* HPD Events */ |
563 | if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL)) { |
564 | bool hpd_connected = false; |
565 | |
566 | if (stat & HDMITX_TOP_INTR_HPD_RISE) |
567 | hpd_connected = true; |
568 | |
569 | dw_hdmi_setup_rx_sense(hdmi: dw_hdmi->hdmi, hpd: hpd_connected, |
570 | rx_sense: hpd_connected); |
571 | |
572 | drm_helper_hpd_irq_event(dev: dw_hdmi->bridge->dev); |
573 | drm_bridge_hpd_notify(bridge: dw_hdmi->bridge, |
574 | status: hpd_connected ? connector_status_connected |
575 | : connector_status_disconnected); |
576 | } |
577 | |
578 | return IRQ_HANDLED; |
579 | } |
580 | |
581 | /* DW HDMI Regmap */ |
582 | |
583 | static int meson_dw_hdmi_reg_read(void *context, unsigned int reg, |
584 | unsigned int *result) |
585 | { |
586 | struct meson_dw_hdmi *dw_hdmi = context; |
587 | |
588 | *result = dw_hdmi->data->dwc_read(dw_hdmi, reg); |
589 | |
590 | return 0; |
591 | |
592 | } |
593 | |
594 | static int meson_dw_hdmi_reg_write(void *context, unsigned int reg, |
595 | unsigned int val) |
596 | { |
597 | struct meson_dw_hdmi *dw_hdmi = context; |
598 | |
599 | dw_hdmi->data->dwc_write(dw_hdmi, reg, val); |
600 | |
601 | return 0; |
602 | } |
603 | |
604 | static const struct regmap_config meson_dw_hdmi_regmap_config = { |
605 | .reg_bits = 32, |
606 | .val_bits = 8, |
607 | .reg_read = meson_dw_hdmi_reg_read, |
608 | .reg_write = meson_dw_hdmi_reg_write, |
609 | .max_register = 0x10000, |
610 | .fast_io = true, |
611 | }; |
612 | |
613 | static const struct meson_dw_hdmi_data meson_dw_hdmi_gx_data = { |
614 | .top_read = dw_hdmi_top_read, |
615 | .top_write = dw_hdmi_top_write, |
616 | .dwc_read = dw_hdmi_dwc_read, |
617 | .dwc_write = dw_hdmi_dwc_write, |
618 | }; |
619 | |
620 | static const struct meson_dw_hdmi_data meson_dw_hdmi_g12a_data = { |
621 | .top_read = dw_hdmi_g12a_top_read, |
622 | .top_write = dw_hdmi_g12a_top_write, |
623 | .dwc_read = dw_hdmi_g12a_dwc_read, |
624 | .dwc_write = dw_hdmi_g12a_dwc_write, |
625 | }; |
626 | |
627 | static void meson_dw_hdmi_init(struct meson_dw_hdmi *meson_dw_hdmi) |
628 | { |
629 | struct meson_drm *priv = meson_dw_hdmi->priv; |
630 | |
631 | /* Enable clocks */ |
632 | regmap_update_bits(map: priv->hhi, HHI_HDMI_CLK_CNTL, mask: 0xffff, val: 0x100); |
633 | |
634 | /* Bring HDMITX MEM output of power down */ |
635 | regmap_update_bits(map: priv->hhi, HHI_MEM_PD_REG0, mask: 0xff << 8, val: 0); |
636 | |
637 | /* Reset HDMITX APB & TX & PHY */ |
638 | reset_control_reset(rstc: meson_dw_hdmi->hdmitx_apb); |
639 | reset_control_reset(rstc: meson_dw_hdmi->hdmitx_ctrl); |
640 | reset_control_reset(rstc: meson_dw_hdmi->hdmitx_phy); |
641 | |
642 | /* Enable APB3 fail on error */ |
643 | if (!meson_vpu_is_compatible(priv, family: VPU_COMPATIBLE_G12A)) { |
644 | writel_bits_relaxed(BIT(15), BIT(15), |
645 | meson_dw_hdmi->hdmitx + HDMITX_TOP_CTRL_REG); |
646 | writel_bits_relaxed(BIT(15), BIT(15), |
647 | meson_dw_hdmi->hdmitx + HDMITX_DWC_CTRL_REG); |
648 | } |
649 | |
650 | /* Bring out of reset */ |
651 | meson_dw_hdmi->data->top_write(meson_dw_hdmi, |
652 | HDMITX_TOP_SW_RESET, 0); |
653 | |
654 | msleep(msecs: 20); |
655 | |
656 | meson_dw_hdmi->data->top_write(meson_dw_hdmi, |
657 | HDMITX_TOP_CLK_CNTL, 0xff); |
658 | |
659 | /* Enable HDMI-TX Interrupt */ |
660 | meson_dw_hdmi->data->top_write(meson_dw_hdmi, HDMITX_TOP_INTR_STAT_CLR, |
661 | HDMITX_TOP_INTR_CORE); |
662 | |
663 | meson_dw_hdmi->data->top_write(meson_dw_hdmi, HDMITX_TOP_INTR_MASKN, |
664 | HDMITX_TOP_INTR_CORE); |
665 | |
666 | } |
667 | |
668 | static void meson_disable_clk(void *data) |
669 | { |
670 | clk_disable_unprepare(clk: data); |
671 | } |
672 | |
673 | static int meson_enable_clk(struct device *dev, char *name) |
674 | { |
675 | struct clk *clk; |
676 | int ret; |
677 | |
678 | clk = devm_clk_get(dev, id: name); |
679 | if (IS_ERR(ptr: clk)) { |
680 | dev_err(dev, "Unable to get %s pclk\n" , name); |
681 | return PTR_ERR(ptr: clk); |
682 | } |
683 | |
684 | ret = clk_prepare_enable(clk); |
685 | if (!ret) |
686 | ret = devm_add_action_or_reset(dev, meson_disable_clk, clk); |
687 | |
688 | return ret; |
689 | } |
690 | |
691 | static int meson_dw_hdmi_bind(struct device *dev, struct device *master, |
692 | void *data) |
693 | { |
694 | struct platform_device *pdev = to_platform_device(dev); |
695 | const struct meson_dw_hdmi_data *match; |
696 | struct meson_dw_hdmi *meson_dw_hdmi; |
697 | struct drm_device *drm = data; |
698 | struct meson_drm *priv = drm->dev_private; |
699 | struct dw_hdmi_plat_data *dw_plat_data; |
700 | int irq; |
701 | int ret; |
702 | |
703 | DRM_DEBUG_DRIVER("\n" ); |
704 | |
705 | match = of_device_get_match_data(dev: &pdev->dev); |
706 | if (!match) { |
707 | dev_err(&pdev->dev, "failed to get match data\n" ); |
708 | return -ENODEV; |
709 | } |
710 | |
711 | meson_dw_hdmi = devm_kzalloc(dev, size: sizeof(*meson_dw_hdmi), |
712 | GFP_KERNEL); |
713 | if (!meson_dw_hdmi) |
714 | return -ENOMEM; |
715 | |
716 | meson_dw_hdmi->priv = priv; |
717 | meson_dw_hdmi->dev = dev; |
718 | meson_dw_hdmi->data = match; |
719 | dw_plat_data = &meson_dw_hdmi->dw_plat_data; |
720 | |
721 | ret = devm_regulator_get_enable_optional(dev, id: "hdmi" ); |
722 | if (ret < 0 && ret != -ENODEV) |
723 | return ret; |
724 | |
725 | meson_dw_hdmi->hdmitx_apb = devm_reset_control_get_exclusive(dev, |
726 | id: "hdmitx_apb" ); |
727 | if (IS_ERR(ptr: meson_dw_hdmi->hdmitx_apb)) { |
728 | dev_err(dev, "Failed to get hdmitx_apb reset\n" ); |
729 | return PTR_ERR(ptr: meson_dw_hdmi->hdmitx_apb); |
730 | } |
731 | |
732 | meson_dw_hdmi->hdmitx_ctrl = devm_reset_control_get_exclusive(dev, |
733 | id: "hdmitx" ); |
734 | if (IS_ERR(ptr: meson_dw_hdmi->hdmitx_ctrl)) { |
735 | dev_err(dev, "Failed to get hdmitx reset\n" ); |
736 | return PTR_ERR(ptr: meson_dw_hdmi->hdmitx_ctrl); |
737 | } |
738 | |
739 | meson_dw_hdmi->hdmitx_phy = devm_reset_control_get_exclusive(dev, |
740 | id: "hdmitx_phy" ); |
741 | if (IS_ERR(ptr: meson_dw_hdmi->hdmitx_phy)) { |
742 | dev_err(dev, "Failed to get hdmitx_phy reset\n" ); |
743 | return PTR_ERR(ptr: meson_dw_hdmi->hdmitx_phy); |
744 | } |
745 | |
746 | meson_dw_hdmi->hdmitx = devm_platform_ioremap_resource(pdev, index: 0); |
747 | if (IS_ERR(ptr: meson_dw_hdmi->hdmitx)) |
748 | return PTR_ERR(ptr: meson_dw_hdmi->hdmitx); |
749 | |
750 | ret = meson_enable_clk(dev, name: "isfr" ); |
751 | if (ret) |
752 | return ret; |
753 | |
754 | ret = meson_enable_clk(dev, name: "iahb" ); |
755 | if (ret) |
756 | return ret; |
757 | |
758 | ret = meson_enable_clk(dev, name: "venci" ); |
759 | if (ret) |
760 | return ret; |
761 | |
762 | dw_plat_data->regm = devm_regmap_init(dev, NULL, meson_dw_hdmi, |
763 | &meson_dw_hdmi_regmap_config); |
764 | if (IS_ERR(ptr: dw_plat_data->regm)) |
765 | return PTR_ERR(ptr: dw_plat_data->regm); |
766 | |
767 | irq = platform_get_irq(pdev, 0); |
768 | if (irq < 0) |
769 | return irq; |
770 | |
771 | ret = devm_request_threaded_irq(dev, irq, handler: dw_hdmi_top_irq, |
772 | thread_fn: dw_hdmi_top_thread_irq, IRQF_SHARED, |
773 | devname: "dw_hdmi_top_irq" , dev_id: meson_dw_hdmi); |
774 | if (ret) { |
775 | dev_err(dev, "Failed to request hdmi top irq\n" ); |
776 | return ret; |
777 | } |
778 | |
779 | meson_dw_hdmi_init(meson_dw_hdmi); |
780 | |
781 | /* Bridge / Connector */ |
782 | |
783 | dw_plat_data->priv_data = meson_dw_hdmi; |
784 | dw_plat_data->phy_ops = &meson_dw_hdmi_phy_ops; |
785 | dw_plat_data->phy_name = "meson_dw_hdmi_phy" ; |
786 | dw_plat_data->phy_data = meson_dw_hdmi; |
787 | dw_plat_data->input_bus_encoding = V4L2_YCBCR_ENC_709; |
788 | dw_plat_data->ycbcr_420_allowed = true; |
789 | dw_plat_data->disable_cec = true; |
790 | dw_plat_data->output_port = 1; |
791 | |
792 | if (dw_hdmi_is_compatible(dw_hdmi: meson_dw_hdmi, compat: "amlogic,meson-gxl-dw-hdmi" ) || |
793 | dw_hdmi_is_compatible(dw_hdmi: meson_dw_hdmi, compat: "amlogic,meson-gxm-dw-hdmi" ) || |
794 | dw_hdmi_is_compatible(dw_hdmi: meson_dw_hdmi, compat: "amlogic,meson-g12a-dw-hdmi" )) |
795 | dw_plat_data->use_drm_infoframe = true; |
796 | |
797 | platform_set_drvdata(pdev, data: meson_dw_hdmi); |
798 | |
799 | meson_dw_hdmi->hdmi = dw_hdmi_probe(pdev, plat_data: &meson_dw_hdmi->dw_plat_data); |
800 | if (IS_ERR(ptr: meson_dw_hdmi->hdmi)) |
801 | return PTR_ERR(ptr: meson_dw_hdmi->hdmi); |
802 | |
803 | meson_dw_hdmi->bridge = of_drm_find_bridge(np: pdev->dev.of_node); |
804 | |
805 | DRM_DEBUG_DRIVER("HDMI controller initialized\n" ); |
806 | |
807 | return 0; |
808 | } |
809 | |
810 | static void meson_dw_hdmi_unbind(struct device *dev, struct device *master, |
811 | void *data) |
812 | { |
813 | struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev); |
814 | |
815 | dw_hdmi_unbind(hdmi: meson_dw_hdmi->hdmi); |
816 | } |
817 | |
818 | static const struct component_ops meson_dw_hdmi_ops = { |
819 | .bind = meson_dw_hdmi_bind, |
820 | .unbind = meson_dw_hdmi_unbind, |
821 | }; |
822 | |
823 | static int __maybe_unused meson_dw_hdmi_pm_suspend(struct device *dev) |
824 | { |
825 | struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev); |
826 | |
827 | if (!meson_dw_hdmi) |
828 | return 0; |
829 | |
830 | /* Reset TOP */ |
831 | meson_dw_hdmi->data->top_write(meson_dw_hdmi, |
832 | HDMITX_TOP_SW_RESET, 0); |
833 | |
834 | return 0; |
835 | } |
836 | |
837 | static int __maybe_unused meson_dw_hdmi_pm_resume(struct device *dev) |
838 | { |
839 | struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev); |
840 | |
841 | if (!meson_dw_hdmi) |
842 | return 0; |
843 | |
844 | meson_dw_hdmi_init(meson_dw_hdmi); |
845 | |
846 | dw_hdmi_resume(hdmi: meson_dw_hdmi->hdmi); |
847 | |
848 | return 0; |
849 | } |
850 | |
851 | static int meson_dw_hdmi_probe(struct platform_device *pdev) |
852 | { |
853 | return component_add(&pdev->dev, &meson_dw_hdmi_ops); |
854 | } |
855 | |
856 | static void meson_dw_hdmi_remove(struct platform_device *pdev) |
857 | { |
858 | component_del(&pdev->dev, &meson_dw_hdmi_ops); |
859 | } |
860 | |
861 | static const struct dev_pm_ops meson_dw_hdmi_pm_ops = { |
862 | SET_SYSTEM_SLEEP_PM_OPS(meson_dw_hdmi_pm_suspend, |
863 | meson_dw_hdmi_pm_resume) |
864 | }; |
865 | |
866 | static const struct of_device_id meson_dw_hdmi_of_table[] = { |
867 | { .compatible = "amlogic,meson-gxbb-dw-hdmi" , |
868 | .data = &meson_dw_hdmi_gx_data }, |
869 | { .compatible = "amlogic,meson-gxl-dw-hdmi" , |
870 | .data = &meson_dw_hdmi_gx_data }, |
871 | { .compatible = "amlogic,meson-gxm-dw-hdmi" , |
872 | .data = &meson_dw_hdmi_gx_data }, |
873 | { .compatible = "amlogic,meson-g12a-dw-hdmi" , |
874 | .data = &meson_dw_hdmi_g12a_data }, |
875 | { } |
876 | }; |
877 | MODULE_DEVICE_TABLE(of, meson_dw_hdmi_of_table); |
878 | |
879 | static struct platform_driver meson_dw_hdmi_platform_driver = { |
880 | .probe = meson_dw_hdmi_probe, |
881 | .remove_new = meson_dw_hdmi_remove, |
882 | .driver = { |
883 | .name = DRIVER_NAME, |
884 | .of_match_table = meson_dw_hdmi_of_table, |
885 | .pm = &meson_dw_hdmi_pm_ops, |
886 | }, |
887 | }; |
888 | module_platform_driver(meson_dw_hdmi_platform_driver); |
889 | |
890 | MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>" ); |
891 | MODULE_DESCRIPTION(DRIVER_DESC); |
892 | MODULE_LICENSE("GPL" ); |
893 | |