]>
Commit | Line | Data |
---|---|---|
3bed4220 NA |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (C) 2018 BayLibre, SAS | |
4 | * Author: Jorge Ramirez-Ortiz <jramirez@baylibre.com> | |
5 | */ | |
6 | ||
7 | #include <common.h> | |
8 | #include <display.h> | |
9 | #include <dm.h> | |
10 | #include <edid.h> | |
11 | #include <asm/io.h> | |
12 | #include <dw_hdmi.h> | |
13 | #include <dm/device-internal.h> | |
14 | #include <dm/uclass-internal.h> | |
15 | #include <power/regulator.h> | |
16 | #include <clk.h> | |
17 | #include <linux/delay.h> | |
18 | #include <reset.h> | |
19 | #include <media_bus_format.h> | |
20 | #include "meson_dw_hdmi.h" | |
21 | #include "meson_vpu.h" | |
22 | ||
23 | /* TOP Block Communication Channel */ | |
24 | #define HDMITX_TOP_ADDR_REG 0x0 | |
25 | #define HDMITX_TOP_DATA_REG 0x4 | |
26 | #define HDMITX_TOP_CTRL_REG 0x8 | |
27 | ||
28 | /* Controller Communication Channel */ | |
29 | #define HDMITX_DWC_ADDR_REG 0x10 | |
30 | #define HDMITX_DWC_DATA_REG 0x14 | |
31 | #define HDMITX_DWC_CTRL_REG 0x18 | |
32 | ||
33 | /* HHI Registers */ | |
34 | #define HHI_MEM_PD_REG0 0x100 /* 0x40 */ | |
35 | #define HHI_HDMI_CLK_CNTL 0x1cc /* 0x73 */ | |
36 | #define HHI_HDMI_PHY_CNTL0 0x3a0 /* 0xe8 */ | |
37 | #define HHI_HDMI_PHY_CNTL1 0x3a4 /* 0xe9 */ | |
38 | #define HHI_HDMI_PHY_CNTL2 0x3a8 /* 0xea */ | |
39 | #define HHI_HDMI_PHY_CNTL3 0x3ac /* 0xeb */ | |
40 | ||
41 | struct meson_dw_hdmi { | |
42 | struct udevice *dev; | |
43 | struct dw_hdmi hdmi; | |
44 | void __iomem *hhi_base; | |
45 | }; | |
46 | ||
47 | enum hdmi_compatible { | |
48 | HDMI_COMPATIBLE_GXBB = 0, | |
49 | HDMI_COMPATIBLE_GXL = 1, | |
50 | HDMI_COMPATIBLE_GXM = 2, | |
51 | }; | |
52 | ||
53 | static inline bool meson_hdmi_is_compatible(struct meson_dw_hdmi *priv, | |
54 | enum hdmi_compatible family) | |
55 | { | |
56 | enum hdmi_compatible compat = dev_get_driver_data(priv->dev); | |
57 | ||
58 | return compat == family; | |
59 | } | |
60 | ||
61 | static unsigned int dw_hdmi_top_read(struct dw_hdmi *hdmi, unsigned int addr) | |
62 | { | |
63 | unsigned int data; | |
64 | ||
65 | /* ADDR must be written twice */ | |
66 | writel(addr & 0xffff, hdmi->ioaddr + HDMITX_TOP_ADDR_REG); | |
67 | writel(addr & 0xffff, hdmi->ioaddr + HDMITX_TOP_ADDR_REG); | |
68 | ||
69 | /* Read needs a second DATA read */ | |
70 | data = readl(hdmi->ioaddr + HDMITX_TOP_DATA_REG); | |
71 | data = readl(hdmi->ioaddr + HDMITX_TOP_DATA_REG); | |
72 | ||
73 | return data; | |
74 | } | |
75 | ||
76 | static inline void dw_hdmi_top_write(struct dw_hdmi *hdmi, | |
77 | unsigned int addr, unsigned int data) | |
78 | { | |
79 | /* ADDR must be written twice */ | |
80 | writel(addr & 0xffff, hdmi->ioaddr + HDMITX_TOP_ADDR_REG); | |
81 | writel(addr & 0xffff, hdmi->ioaddr + HDMITX_TOP_ADDR_REG); | |
82 | ||
83 | /* Write needs single DATA write */ | |
84 | writel(data, hdmi->ioaddr + HDMITX_TOP_DATA_REG); | |
85 | } | |
86 | ||
87 | static inline void dw_hdmi_top_write_bits(struct dw_hdmi *hdmi, | |
88 | unsigned int addr, | |
89 | unsigned int mask, | |
90 | unsigned int val) | |
91 | { | |
92 | unsigned int data = dw_hdmi_top_read(hdmi, addr); | |
93 | ||
94 | data &= ~mask; | |
95 | data |= val; | |
96 | dw_hdmi_top_write(hdmi, addr, data); | |
97 | } | |
98 | ||
99 | static u8 dw_hdmi_dwc_read(struct dw_hdmi *hdmi, int addr) | |
100 | { | |
101 | unsigned int data; | |
102 | ||
103 | /* ADDR must be written twice */ | |
104 | writel(addr & 0xffff, hdmi->ioaddr + HDMITX_DWC_ADDR_REG); | |
105 | writel(addr & 0xffff, hdmi->ioaddr + HDMITX_DWC_ADDR_REG); | |
106 | ||
107 | /* Read needs a second DATA read */ | |
108 | data = readl(hdmi->ioaddr + HDMITX_DWC_DATA_REG); | |
109 | data = readl(hdmi->ioaddr + HDMITX_DWC_DATA_REG); | |
110 | ||
111 | return data; | |
112 | } | |
113 | ||
114 | static inline void dw_hdmi_dwc_write(struct dw_hdmi *hdmi, u8 data, int addr) | |
115 | { | |
116 | /* ADDR must be written twice */ | |
117 | writel(addr & 0xffff, hdmi->ioaddr + HDMITX_DWC_ADDR_REG); | |
118 | writel(addr & 0xffff, hdmi->ioaddr + HDMITX_DWC_ADDR_REG); | |
119 | ||
120 | /* Write needs single DATA write */ | |
121 | writel(data, hdmi->ioaddr + HDMITX_DWC_DATA_REG); | |
122 | } | |
123 | ||
124 | static inline void dw_hdmi_dwc_write_bits(struct dw_hdmi *hdmi, | |
125 | unsigned int addr, | |
126 | unsigned int mask, | |
127 | unsigned int val) | |
128 | { | |
129 | u8 data = dw_hdmi_dwc_read(hdmi, addr); | |
130 | ||
131 | data &= ~mask; | |
132 | data |= val; | |
133 | ||
134 | dw_hdmi_dwc_write(hdmi, data, addr); | |
135 | } | |
136 | ||
137 | static inline void dw_hdmi_hhi_write(struct meson_dw_hdmi *priv, | |
138 | unsigned int addr, unsigned int data) | |
139 | { | |
140 | hhi_write(addr, data); | |
141 | } | |
142 | ||
143 | __attribute__((unused)) | |
144 | static unsigned int dw_hdmi_hhi_read(struct meson_dw_hdmi *priv, | |
145 | unsigned int addr) | |
146 | { | |
147 | return hhi_read(addr); | |
148 | } | |
149 | ||
150 | static inline void dw_hdmi_hhi_update_bits(struct meson_dw_hdmi *priv, | |
151 | unsigned int addr, | |
152 | unsigned int mask, | |
153 | unsigned int val) | |
154 | { | |
155 | hhi_update_bits(addr, mask, val); | |
156 | } | |
157 | ||
158 | static int meson_dw_hdmi_read_edid(struct udevice *dev, u8 *buf, int buf_size) | |
159 | { | |
160 | #if defined DEBUG | |
161 | struct display_timing timing; | |
162 | int panel_bits_per_colour; | |
163 | #endif | |
164 | struct meson_dw_hdmi *priv = dev_get_priv(dev); | |
165 | int ret; | |
166 | ||
167 | ret = dw_hdmi_read_edid(&priv->hdmi, buf, buf_size); | |
168 | ||
169 | #if defined DEBUG | |
170 | if (!ret) | |
171 | return ret; | |
172 | ||
173 | edid_print_info((struct edid1_info *)buf); | |
174 | edid_get_timing(buf, ret, &timing, &panel_bits_per_colour); | |
175 | debug("Display timing:\n"); | |
176 | debug(" hactive %04d, hfrontp %04d, hbackp %04d hsync %04d\n" | |
177 | " vactive %04d, vfrontp %04d, vbackp %04d vsync %04d\n", | |
178 | timing.hactive.typ, timing.hfront_porch.typ, | |
179 | timing.hback_porch.typ, timing.hsync_len.typ, | |
180 | timing.vactive.typ, timing.vfront_porch.typ, | |
181 | timing.vback_porch.typ, timing.vsync_len.typ); | |
182 | debug(" flags: "); | |
183 | if (timing.flags & DISPLAY_FLAGS_INTERLACED) | |
184 | debug("interlaced "); | |
185 | if (timing.flags & DISPLAY_FLAGS_DOUBLESCAN) | |
186 | debug("doublescan "); | |
187 | if (timing.flags & DISPLAY_FLAGS_DOUBLECLK) | |
188 | debug("doubleclk "); | |
189 | if (timing.flags & DISPLAY_FLAGS_HSYNC_LOW) | |
190 | debug("hsync_low "); | |
191 | if (timing.flags & DISPLAY_FLAGS_HSYNC_HIGH) | |
192 | debug("hsync_high "); | |
193 | if (timing.flags & DISPLAY_FLAGS_VSYNC_LOW) | |
194 | debug("vsync_low "); | |
195 | if (timing.flags & DISPLAY_FLAGS_VSYNC_HIGH) | |
196 | debug("vsync_high "); | |
197 | debug("\n"); | |
198 | #endif | |
199 | ||
200 | return ret; | |
201 | } | |
202 | ||
203 | static inline void meson_dw_hdmi_phy_reset(struct meson_dw_hdmi *priv) | |
204 | { | |
205 | /* Enable and software reset */ | |
206 | dw_hdmi_hhi_update_bits(priv, HHI_HDMI_PHY_CNTL1, 0xf, 0xf); | |
207 | ||
208 | mdelay(2); | |
209 | ||
210 | /* Enable and unreset */ | |
211 | dw_hdmi_hhi_update_bits(priv, HHI_HDMI_PHY_CNTL1, 0xf, 0xe); | |
212 | ||
213 | mdelay(2); | |
214 | } | |
215 | ||
216 | static void meson_dw_hdmi_phy_setup_mode(struct meson_dw_hdmi *priv, | |
217 | uint pixel_clock) | |
218 | { | |
219 | pixel_clock = pixel_clock / 1000; | |
220 | ||
221 | if (meson_hdmi_is_compatible(priv, HDMI_COMPATIBLE_GXL) || | |
222 | meson_hdmi_is_compatible(priv, HDMI_COMPATIBLE_GXM)) { | |
223 | if (pixel_clock >= 371250) { | |
224 | /* 5.94Gbps, 3.7125Gbps */ | |
225 | hhi_write(HHI_HDMI_PHY_CNTL0, 0x333d3282); | |
226 | hhi_write(HHI_HDMI_PHY_CNTL3, 0x2136315b); | |
227 | } else if (pixel_clock >= 297000) { | |
228 | /* 2.97Gbps */ | |
229 | hhi_write(HHI_HDMI_PHY_CNTL0, 0x33303382); | |
230 | hhi_write(HHI_HDMI_PHY_CNTL3, 0x2036315b); | |
231 | } else if (pixel_clock >= 148500) { | |
232 | /* 1.485Gbps */ | |
233 | hhi_write(HHI_HDMI_PHY_CNTL0, 0x33303362); | |
234 | hhi_write(HHI_HDMI_PHY_CNTL3, 0x2016315b); | |
235 | } else { | |
236 | /* 742.5Mbps, and below */ | |
237 | hhi_write(HHI_HDMI_PHY_CNTL0, 0x33604142); | |
238 | hhi_write(HHI_HDMI_PHY_CNTL3, 0x0016315b); | |
239 | } | |
240 | } else { | |
241 | if (pixel_clock >= 371250) { | |
242 | /* 5.94Gbps, 3.7125Gbps */ | |
243 | hhi_write(HHI_HDMI_PHY_CNTL0, 0x33353245); | |
244 | hhi_write(HHI_HDMI_PHY_CNTL3, 0x2100115b); | |
245 | } else if (pixel_clock >= 297000) { | |
246 | /* 2.97Gbps */ | |
247 | hhi_write(HHI_HDMI_PHY_CNTL0, 0x33634283); | |
248 | hhi_write(HHI_HDMI_PHY_CNTL3, 0xb000115b); | |
249 | } else { | |
250 | /* 1.485Gbps, and below */ | |
251 | hhi_write(HHI_HDMI_PHY_CNTL0, 0x33632122); | |
252 | hhi_write(HHI_HDMI_PHY_CNTL3, 0x2000115b); | |
253 | } | |
254 | } | |
255 | } | |
256 | ||
257 | static int meson_dw_hdmi_phy_init(struct dw_hdmi *hdmi, uint pixel_clock) | |
258 | { | |
259 | struct meson_dw_hdmi *priv = container_of(hdmi, struct meson_dw_hdmi, | |
260 | hdmi); | |
261 | /* Enable clocks */ | |
262 | dw_hdmi_hhi_update_bits(priv, HHI_HDMI_CLK_CNTL, 0xffff, 0x100); | |
263 | ||
264 | /* Bring HDMITX MEM output of power down */ | |
265 | dw_hdmi_hhi_update_bits(priv, HHI_MEM_PD_REG0, 0xff << 8, 0); | |
266 | ||
267 | /* Bring out of reset */ | |
268 | dw_hdmi_top_write(hdmi, HDMITX_TOP_SW_RESET, 0); | |
269 | ||
270 | /* Enable internal pixclk, tmds_clk, spdif_clk, i2s_clk, cecclk */ | |
271 | dw_hdmi_top_write_bits(hdmi, HDMITX_TOP_CLK_CNTL, 0x3, 0x3); | |
272 | dw_hdmi_top_write_bits(hdmi, HDMITX_TOP_CLK_CNTL, 0x3 << 4, 0x3 << 4); | |
273 | ||
274 | /* Enable normal output to PHY */ | |
275 | dw_hdmi_top_write(hdmi, HDMITX_TOP_BIST_CNTL, BIT(12)); | |
276 | ||
277 | /* TMDS pattern setup (TOFIX pattern for 4k2k scrambling) */ | |
278 | dw_hdmi_top_write(hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, 0x001f001f); | |
279 | dw_hdmi_top_write(hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, 0x001f001f); | |
280 | ||
281 | /* Load TMDS pattern */ | |
282 | dw_hdmi_top_write(hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x1); | |
283 | mdelay(20); | |
284 | dw_hdmi_top_write(hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x2); | |
285 | ||
286 | /* Setup PHY parameters */ | |
287 | meson_dw_hdmi_phy_setup_mode(priv, pixel_clock); | |
288 | ||
289 | /* Setup PHY */ | |
290 | dw_hdmi_hhi_update_bits(priv, HHI_HDMI_PHY_CNTL1, | |
291 | 0xffff << 16, 0x0390 << 16); | |
292 | ||
293 | /* BIT_INVERT */ | |
294 | if (meson_hdmi_is_compatible(priv, HDMI_COMPATIBLE_GXL) || | |
295 | meson_hdmi_is_compatible(priv, HDMI_COMPATIBLE_GXM)) | |
296 | dw_hdmi_hhi_update_bits(priv, HHI_HDMI_PHY_CNTL1, BIT(17), 0); | |
297 | else | |
298 | dw_hdmi_hhi_update_bits(priv, HHI_HDMI_PHY_CNTL1, | |
299 | BIT(17), BIT(17)); | |
300 | ||
301 | /* Disable clock, fifo, fifo_wr */ | |
302 | dw_hdmi_hhi_update_bits(priv, HHI_HDMI_PHY_CNTL1, 0xf, 0); | |
303 | ||
304 | mdelay(100); | |
305 | ||
306 | /* Reset PHY 3 times in a row */ | |
307 | meson_dw_hdmi_phy_reset(priv); | |
308 | meson_dw_hdmi_phy_reset(priv); | |
309 | meson_dw_hdmi_phy_reset(priv); | |
310 | ||
311 | return 0; | |
312 | } | |
313 | ||
314 | static int meson_dw_hdmi_enable(struct udevice *dev, int panel_bpp, | |
315 | const struct display_timing *edid) | |
316 | { | |
317 | struct meson_dw_hdmi *priv = dev_get_priv(dev); | |
318 | ||
319 | /* will back into meson_dw_hdmi_phy_init */ | |
320 | return dw_hdmi_enable(&priv->hdmi, edid); | |
321 | } | |
322 | ||
323 | static int meson_dw_hdmi_wait_hpd(struct dw_hdmi *hdmi) | |
324 | { | |
325 | int i; | |
326 | ||
327 | /* Poll 1 second for HPD signal */ | |
328 | for (i = 0; i < 10; ++i) { | |
329 | if (dw_hdmi_top_read(hdmi, HDMITX_TOP_STAT0)) | |
330 | return 0; | |
331 | ||
332 | mdelay(100); | |
333 | } | |
334 | ||
335 | return -ETIMEDOUT; | |
336 | } | |
337 | ||
338 | static int meson_dw_hdmi_probe(struct udevice *dev) | |
339 | { | |
340 | struct meson_dw_hdmi *priv = dev_get_priv(dev); | |
341 | struct reset_ctl_bulk resets; | |
342 | struct clk_bulk clocks; | |
343 | struct udevice *supply; | |
344 | int ret; | |
345 | ||
346 | priv->dev = dev; | |
347 | ||
348 | priv->hdmi.ioaddr = (ulong)dev_remap_addr_index(dev, 0); | |
349 | if (!priv->hdmi.ioaddr) | |
350 | return -EINVAL; | |
351 | ||
352 | priv->hhi_base = dev_remap_addr_index(dev, 1); | |
353 | if (!priv->hhi_base) | |
354 | return -EINVAL; | |
355 | ||
356 | priv->hdmi.hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; | |
357 | priv->hdmi.hdmi_data.enc_in_bus_format = MEDIA_BUS_FMT_YUV8_1X24; | |
358 | priv->hdmi.phy_set = meson_dw_hdmi_phy_init; | |
359 | priv->hdmi.write_reg = dw_hdmi_dwc_write; | |
360 | priv->hdmi.read_reg = dw_hdmi_dwc_read; | |
361 | priv->hdmi.i2c_clk_high = 0x67; | |
362 | priv->hdmi.i2c_clk_low = 0x78; | |
363 | ||
f944b159 | 364 | #if CONFIG_IS_ENABLED(DM_REGULATOR) |
3bed4220 | 365 | ret = device_get_supply_regulator(dev, "hdmi-supply", &supply); |
f944b159 MJ |
366 | if (ret && ret != -ENOENT) { |
367 | pr_err("Failed to get HDMI regulator\n"); | |
3bed4220 | 368 | return ret; |
f944b159 | 369 | } |
3bed4220 | 370 | |
f944b159 MJ |
371 | if (!ret) { |
372 | ret = regulator_set_enable(supply, true); | |
373 | if (ret) | |
374 | return ret; | |
375 | } | |
376 | #endif | |
3bed4220 NA |
377 | |
378 | ret = reset_get_bulk(dev, &resets); | |
379 | if (ret) | |
380 | return ret; | |
381 | ||
382 | ret = clk_get_bulk(dev, &clocks); | |
383 | if (ret) | |
384 | return ret; | |
385 | ||
386 | ret = clk_enable_bulk(&clocks); | |
387 | if (ret) | |
388 | return ret; | |
389 | ||
390 | /* Enable clocks */ | |
391 | dw_hdmi_hhi_update_bits(priv, HHI_HDMI_CLK_CNTL, 0xffff, 0x100); | |
392 | ||
393 | /* Bring HDMITX MEM output of power down */ | |
394 | dw_hdmi_hhi_update_bits(priv, HHI_MEM_PD_REG0, 0xff << 8, 0); | |
395 | ||
396 | /* Reset HDMITX APB & TX & PHY: cycle needed for EDID */ | |
397 | ret = reset_deassert_bulk(&resets); | |
398 | if (ret) | |
399 | return ret; | |
400 | ||
401 | ret = reset_assert_bulk(&resets); | |
402 | if (ret) | |
403 | return ret; | |
404 | ||
405 | ret = reset_deassert_bulk(&resets); | |
406 | if (ret) | |
407 | return ret; | |
408 | ||
409 | /* Enable APB3 fail on error */ | |
410 | writel_bits(BIT(15), BIT(15), priv->hdmi.ioaddr + HDMITX_TOP_CTRL_REG); | |
411 | writel_bits(BIT(15), BIT(15), priv->hdmi.ioaddr + HDMITX_DWC_CTRL_REG); | |
412 | ||
413 | /* Bring out of reset */ | |
414 | dw_hdmi_top_write(&priv->hdmi, HDMITX_TOP_SW_RESET, 0); | |
415 | mdelay(20); | |
416 | dw_hdmi_top_write(&priv->hdmi, HDMITX_TOP_CLK_CNTL, 0xff); | |
417 | ||
418 | dw_hdmi_init(&priv->hdmi); | |
419 | dw_hdmi_phy_init(&priv->hdmi); | |
420 | ||
421 | /* wait for connector */ | |
422 | ret = meson_dw_hdmi_wait_hpd(&priv->hdmi); | |
423 | if (ret) | |
424 | debug("hdmi can not get hpd signal\n"); | |
425 | ||
426 | return ret; | |
427 | } | |
428 | ||
429 | static const struct dm_display_ops meson_dw_hdmi_ops = { | |
430 | .read_edid = meson_dw_hdmi_read_edid, | |
431 | .enable = meson_dw_hdmi_enable, | |
432 | }; | |
433 | ||
434 | static const struct udevice_id meson_dw_hdmi_ids[] = { | |
435 | { .compatible = "amlogic,meson-gxbb-dw-hdmi", | |
436 | .data = HDMI_COMPATIBLE_GXBB }, | |
437 | { .compatible = "amlogic,meson-gxl-dw-hdmi", | |
438 | .data = HDMI_COMPATIBLE_GXL }, | |
439 | { .compatible = "amlogic,meson-gxm-dw-hdmi", | |
440 | .data = HDMI_COMPATIBLE_GXM }, | |
441 | { } | |
442 | }; | |
443 | ||
444 | U_BOOT_DRIVER(meson_dw_hdmi) = { | |
445 | .name = "meson_dw_hdmi", | |
446 | .id = UCLASS_DISPLAY, | |
447 | .of_match = meson_dw_hdmi_ids, | |
448 | .ops = &meson_dw_hdmi_ops, | |
449 | .probe = meson_dw_hdmi_probe, | |
450 | .priv_auto_alloc_size = sizeof(struct meson_dw_hdmi), | |
451 | }; |