sunxi_hdmi.c revision 1.1 1 /* $NetBSD: sunxi_hdmi.c,v 1.1 2018/04/03 12:52:16 bouyer Exp $ */
2
3 /*-
4 * Copyright (c) 2014 Jared D. McNeill <jmcneill (at) invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include "opt_ddb.h"
30
31 #include <sys/cdefs.h>
32 __KERNEL_RCSID(0, "$NetBSD: sunxi_hdmi.c,v 1.1 2018/04/03 12:52:16 bouyer Exp $");
33
34 #include <sys/param.h>
35 #include <sys/bus.h>
36 #include <sys/device.h>
37 #include <sys/intr.h>
38 #include <sys/systm.h>
39 #include <sys/kernel.h>
40 #include <sys/proc.h>
41 #include <sys/mutex.h>
42 #include <sys/kthread.h>
43
44 #include <dev/fdt/fdtvar.h>
45 #include <dev/fdt/fdt_port.h>
46
47 #include <dev/i2c/i2cvar.h>
48 #include <dev/i2c/ddcvar.h>
49 #include <dev/i2c/ddcreg.h>
50 #include <dev/videomode/videomode.h>
51 #include <dev/videomode/edidvar.h>
52
53 #include <arm/sunxi/sunxi_hdmireg.h>
54 #include <arm/sunxi/sunxi_display.h>
55
56 enum sunxi_hdmi_type {
57 HDMI_A10 = 1,
58 HDMI_A20,
59 HDMI_A31,
60 };
61
62 struct sunxi_hdmi_softc {
63 device_t sc_dev;
64 int sc_phandle;
65 enum sunxi_hdmi_type sc_type;
66 bus_space_tag_t sc_bst;
67 bus_space_handle_t sc_bsh;
68 struct clk *sc_clk_ahb;
69 struct clk *sc_clk_mod;
70 struct clk *sc_clk_pll0;
71 struct clk *sc_clk_pll1;
72 void *sc_ih;
73 lwp_t *sc_thread;
74
75 struct i2c_controller sc_ic;
76 kmutex_t sc_ic_lock;
77
78 bool sc_display_connected;
79 char sc_display_vendor[16];
80 char sc_display_product[16];
81
82 u_int sc_display_mode;
83 u_int sc_current_display_mode;
84 #define DISPLAY_MODE_AUTO 0
85 #define DISPLAY_MODE_HDMI 1
86 #define DISPLAY_MODE_DVI 2
87
88 kmutex_t sc_pwr_lock;
89 int sc_pwr_refcount; /* reference who needs HDMI */
90
91 uint32_t sc_ver;
92 unsigned int sc_i2c_blklen;
93
94 struct fdt_device_ports sc_ports;
95 struct fdt_endpoint *sc_in_ep;
96 struct fdt_endpoint *sc_in_rep;
97 struct fdt_endpoint *sc_out_ep;
98 };
99
100 #define HDMI_READ(sc, reg) \
101 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
102 #define HDMI_WRITE(sc, reg, val) \
103 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val));
104
105 #define HDMI_1_3_P(sc) ((sc)->sc_ver == 0x00010003)
106 #define HDMI_1_4_P(sc) ((sc)->sc_ver == 0x00010004)
107
108 static const struct of_compat_data compat_data[] = {
109 {"allwinner,sun4i-a10-hdmi", HDMI_A10},
110 {"allwinner,sun7i-a20-hdmi", HDMI_A20},
111 {NULL}
112 };
113
114 static int sunxi_hdmi_match(device_t, cfdata_t, void *);
115 static void sunxi_hdmi_attach(device_t, device_t, void *);
116 static void sunxi_hdmi_i2c_init(struct sunxi_hdmi_softc *);
117 static int sunxi_hdmi_i2c_acquire_bus(void *, int);
118 static void sunxi_hdmi_i2c_release_bus(void *, int);
119 static int sunxi_hdmi_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *,
120 size_t, void *, size_t, int);
121 static int sunxi_hdmi_i2c_xfer(void *, i2c_addr_t, uint8_t, uint8_t,
122 size_t, int, int);
123 static int sunxi_hdmi_i2c_reset(struct sunxi_hdmi_softc *, int);
124
125 static int sunxi_hdmi_ep_activate(device_t, struct fdt_endpoint *, bool);
126 static int sunxi_hdmi_ep_enable(device_t, struct fdt_endpoint *, bool);
127 static void sunxi_hdmi_do_enable(struct sunxi_hdmi_softc *);
128 static void sunxi_hdmi_read_edid(struct sunxi_hdmi_softc *);
129 static int sunxi_hdmi_read_edid_block(struct sunxi_hdmi_softc *, uint8_t *,
130 uint8_t);
131 static u_int sunxi_hdmi_get_display_mode(struct sunxi_hdmi_softc *,
132 const struct edid_info *);
133 static void sunxi_hdmi_video_enable(struct sunxi_hdmi_softc *, bool);
134 static void sunxi_hdmi_set_videomode(struct sunxi_hdmi_softc *,
135 const struct videomode *, u_int);
136 static void sunxi_hdmi_set_audiomode(struct sunxi_hdmi_softc *,
137 const struct videomode *, u_int);
138 static void sunxi_hdmi_hpd(struct sunxi_hdmi_softc *);
139 static void sunxi_hdmi_thread(void *);
140 static int sunxi_hdmi_poweron(struct sunxi_hdmi_softc *, bool);
141 #if 0
142 static int sunxi_hdmi_intr(void *);
143 #endif
144
145 #if defined(DDB)
146 void sunxi_hdmi_dump_regs(void);
147 #endif
148
149 CFATTACH_DECL_NEW(sunxi_hdmi, sizeof(struct sunxi_hdmi_softc),
150 sunxi_hdmi_match, sunxi_hdmi_attach, NULL, NULL);
151
152 static int
153 sunxi_hdmi_match(device_t parent, cfdata_t cf, void *aux)
154 {
155 struct fdt_attach_args * const faa = aux;
156
157 return of_match_compat_data(faa->faa_phandle, compat_data);
158 }
159
160 static void
161 sunxi_hdmi_attach(device_t parent, device_t self, void *aux)
162 {
163 struct sunxi_hdmi_softc *sc = device_private(self);
164 struct fdt_attach_args * const faa = aux;
165 const int phandle = faa->faa_phandle;
166 bus_addr_t addr;
167 bus_size_t size;
168 uint32_t ver;
169 int error;
170
171 sc->sc_dev = self;
172 sc->sc_phandle = phandle;
173 sc->sc_bst = faa->faa_bst;
174
175 sc->sc_type = of_search_compatible(faa->faa_phandle, compat_data)->data;
176
177 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
178 aprint_error(": couldn't get registers\n");
179 }
180 if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
181 aprint_error(": couldn't map registers\n");
182 return;
183 }
184
185 sc->sc_clk_ahb = fdtbus_clock_get(phandle, "ahb");
186 sc->sc_clk_mod = fdtbus_clock_get(phandle, "mod");
187 sc->sc_clk_pll0 = fdtbus_clock_get(phandle, "pll-0");
188 sc->sc_clk_pll1 = fdtbus_clock_get(phandle, "pll-1");
189
190 if (sc->sc_clk_ahb == NULL || sc->sc_clk_mod == NULL
191 || sc->sc_clk_pll0 == NULL || sc->sc_clk_pll1 == NULL) {
192 aprint_error(": couldn't get clocks\n");
193 aprint_debug_dev(self, "clk ahb %s mod %s pll-0 %s pll-1 %s\n",
194 sc->sc_clk_ahb == NULL ? "missing" : "present",
195 sc->sc_clk_mod == NULL ? "missing" : "present",
196 sc->sc_clk_pll0 == NULL ? "missing" : "present",
197 sc->sc_clk_pll1 == NULL ? "missing" : "present");
198 return;
199 }
200
201 error = clk_disable(sc->sc_clk_mod);
202 if (error) {
203 aprint_error(": couldn't disable mod clock\n");
204 return;
205 }
206
207 if (clk_enable(sc->sc_clk_ahb) != 0) {
208 aprint_error(": couldn't enable ahb clock\n");
209 return;
210 }
211 #if defined(SUNXI_HDMI_DEBUG)
212 sunxi_hdmi_dump_regs();
213 #endif
214
215 /*
216 * reset device, in case it has been setup by firmware in an
217 * incompatible way
218 */
219 for (int i = 0; i <= 0x500; i += 4) {
220 HDMI_WRITE(sc, i, 0);
221 }
222
223 ver = HDMI_READ(sc, SUNXI_HDMI_VERSION_ID_REG);
224
225 const int vmaj = __SHIFTOUT(ver, SUNXI_HDMI_VERSION_ID_H);
226 const int vmin = __SHIFTOUT(ver, SUNXI_HDMI_VERSION_ID_L);
227
228 aprint_naive("\n");
229 aprint_normal(": HDMI %d.%d\n", vmaj, vmin);
230
231 sc->sc_ver = ver;
232 sc->sc_i2c_blklen = 16;
233
234 sc->sc_ports.dp_ep_activate = sunxi_hdmi_ep_activate;
235 sc->sc_ports.dp_ep_enable = sunxi_hdmi_ep_enable;
236 fdt_ports_register(&sc->sc_ports, self, phandle, EP_OTHER);
237
238 mutex_init(&sc->sc_pwr_lock, MUTEX_DEFAULT, IPL_NONE);
239 sunxi_hdmi_i2c_init(sc);
240
241 }
242
243 static void
244 sunxi_hdmi_i2c_init(struct sunxi_hdmi_softc *sc)
245 {
246 struct i2c_controller *ic = &sc->sc_ic;
247
248 mutex_init(&sc->sc_ic_lock, MUTEX_DEFAULT, IPL_NONE);
249
250 ic->ic_cookie = sc;
251 ic->ic_acquire_bus = sunxi_hdmi_i2c_acquire_bus;
252 ic->ic_release_bus = sunxi_hdmi_i2c_release_bus;
253 ic->ic_exec = sunxi_hdmi_i2c_exec;
254 }
255
256 static int
257 sunxi_hdmi_i2c_acquire_bus(void *priv, int flags)
258 {
259 struct sunxi_hdmi_softc *sc = priv;
260
261 if (flags & I2C_F_POLL) {
262 if (!mutex_tryenter(&sc->sc_ic_lock))
263 return EBUSY;
264 } else {
265 mutex_enter(&sc->sc_ic_lock);
266 }
267
268 return 0;
269 }
270
271 static void
272 sunxi_hdmi_i2c_release_bus(void *priv, int flags)
273 {
274 struct sunxi_hdmi_softc *sc = priv;
275
276 mutex_exit(&sc->sc_ic_lock);
277 }
278
279 static int
280 sunxi_hdmi_i2c_exec(void *priv, i2c_op_t op, i2c_addr_t addr,
281 const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
282 {
283 struct sunxi_hdmi_softc *sc = priv;
284 uint8_t *pbuf;
285 uint8_t block;
286 int resid;
287 off_t off;
288 int err;
289
290 KASSERT(mutex_owned(&sc->sc_ic_lock));
291 KASSERT(op == I2C_OP_READ_WITH_STOP);
292 KASSERT(addr == DDC_ADDR);
293 KASSERT(cmdlen > 0);
294 KASSERT(buf != NULL);
295
296 err = sunxi_hdmi_i2c_reset(sc, flags);
297 if (err)
298 goto done;
299
300 block = *(const uint8_t *)cmdbuf;
301 off = (block & 1) ? 128 : 0;
302
303 pbuf = buf;
304 resid = len;
305 while (resid > 0) {
306 size_t blklen = min(resid, sc->sc_i2c_blklen);
307
308 err = sunxi_hdmi_i2c_xfer(sc, addr, block >> 1, off, blklen,
309 SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_EOREAD, flags);
310 if (err)
311 goto done;
312
313 if (HDMI_1_3_P(sc)) {
314 bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh,
315 SUNXI_HDMI_DDC_FIFO_ACCESS_REG, pbuf, blklen);
316 } else {
317 bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh,
318 SUNXI_A31_HDMI_DDC_FIFO_ACCESS_REG, pbuf, blklen);
319 }
320
321 #ifdef SUNXI_HDMI_DEBUG
322 printf("off=%d:", (int)off);
323 for (int i = 0; i < blklen; i++)
324 printf(" %02x", pbuf[i]);
325 printf("\n");
326 #endif
327
328 pbuf += blklen;
329 off += blklen;
330 resid -= blklen;
331 }
332
333 done:
334 return err;
335 }
336
337 static int
338 sunxi_hdmi_i2c_xfer_1_3(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg,
339 size_t len, int type, int flags)
340 {
341 struct sunxi_hdmi_softc *sc = priv;
342 uint32_t val;
343 int retry;
344
345 val = HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG);
346 val &= ~SUNXI_HDMI_DDC_CTRL_FIFO_DIR;
347 HDMI_WRITE(sc, SUNXI_HDMI_DDC_CTRL_REG, val);
348
349 val |= __SHIFTIN(block, SUNXI_HDMI_DDC_SLAVE_ADDR_0);
350 val |= __SHIFTIN(0x60, SUNXI_HDMI_DDC_SLAVE_ADDR_1);
351 val |= __SHIFTIN(reg, SUNXI_HDMI_DDC_SLAVE_ADDR_2);
352 val |= __SHIFTIN(addr, SUNXI_HDMI_DDC_SLAVE_ADDR_3);
353 HDMI_WRITE(sc, SUNXI_HDMI_DDC_SLAVE_ADDR_REG, val);
354
355 val = HDMI_READ(sc, SUNXI_HDMI_DDC_FIFO_CTRL_REG);
356 val |= SUNXI_HDMI_DDC_FIFO_CTRL_ADDR_CLEAR;
357 HDMI_WRITE(sc, SUNXI_HDMI_DDC_FIFO_CTRL_REG, val);
358
359 HDMI_WRITE(sc, SUNXI_HDMI_DDC_BYTE_COUNTER_REG, len);
360
361 HDMI_WRITE(sc, SUNXI_HDMI_DDC_COMMAND_REG, type);
362
363 val = HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG);
364 val |= SUNXI_HDMI_DDC_CTRL_ACCESS_CMD_START;
365 HDMI_WRITE(sc, SUNXI_HDMI_DDC_CTRL_REG, val);
366
367 retry = 1000;
368 while (--retry > 0) {
369 val = HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG);
370 if ((val & SUNXI_HDMI_DDC_CTRL_ACCESS_CMD_START) == 0)
371 break;
372 delay(1000);
373 }
374 if (retry == 0)
375 return ETIMEDOUT;
376
377 val = HDMI_READ(sc, SUNXI_HDMI_DDC_INT_STATUS_REG);
378 if ((val & SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE) == 0) {
379 device_printf(sc->sc_dev, "xfer failed, status=%08x\n", val);
380 return EIO;
381 }
382
383 return 0;
384 }
385
386 static int
387 sunxi_hdmi_i2c_xfer_1_4(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg,
388 size_t len, int type, int flags)
389 {
390 struct sunxi_hdmi_softc *sc = priv;
391 uint32_t val;
392 int retry;
393
394 val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_FIFO_CTRL_REG);
395 val |= SUNXI_A31_HDMI_DDC_FIFO_CTRL_RST;
396 HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_FIFO_CTRL_REG, val);
397
398 val = __SHIFTIN(block, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_SEG_PTR);
399 val |= __SHIFTIN(0x60, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_DDC_CMD);
400 val |= __SHIFTIN(reg, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_OFF_ADR);
401 val |= __SHIFTIN(addr, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_DEV_ADR);
402 HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_REG, val);
403
404 HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_COMMAND_REG,
405 __SHIFTIN(len, SUNXI_A31_HDMI_DDC_COMMAND_DTC) |
406 __SHIFTIN(type, SUNXI_A31_HDMI_DDC_COMMAND_CMD));
407
408 val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_CTRL_REG);
409 val |= SUNXI_A31_HDMI_DDC_CTRL_ACCESS_CMD_START;
410 HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_CTRL_REG, val);
411
412 retry = 1000;
413 while (--retry > 0) {
414 val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_CTRL_REG);
415 if ((val & SUNXI_A31_HDMI_DDC_CTRL_ACCESS_CMD_START) == 0)
416 break;
417 if (cold)
418 delay(1000);
419 else
420 kpause("hdmiddc", false, mstohz(10), &sc->sc_ic_lock);
421 }
422 if (retry == 0)
423 return ETIMEDOUT;
424
425 return 0;
426 }
427
428 static int
429 sunxi_hdmi_i2c_xfer(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg,
430 size_t len, int type, int flags)
431 {
432 struct sunxi_hdmi_softc *sc = priv;
433 int rv;
434
435 if (HDMI_1_3_P(sc)) {
436 rv = sunxi_hdmi_i2c_xfer_1_3(priv, addr, block, reg, len,
437 type, flags);
438 } else {
439 rv = sunxi_hdmi_i2c_xfer_1_4(priv, addr, block, reg, len,
440 type, flags);
441 }
442
443 return rv;
444 }
445
446 static int
447 sunxi_hdmi_i2c_reset(struct sunxi_hdmi_softc *sc, int flags)
448 {
449 uint32_t hpd, ctrl;
450
451 hpd = HDMI_READ(sc, SUNXI_HDMI_HPD_REG);
452 if ((hpd & SUNXI_HDMI_HPD_HOTPLUG_DET) == 0) {
453 device_printf(sc->sc_dev, "no device detected\n");
454 return ENODEV; /* no device plugged in */
455 }
456
457 if (HDMI_1_3_P(sc)) {
458 HDMI_WRITE(sc, SUNXI_HDMI_DDC_FIFO_CTRL_REG, 0);
459 HDMI_WRITE(sc, SUNXI_HDMI_DDC_CTRL_REG,
460 SUNXI_HDMI_DDC_CTRL_EN | SUNXI_HDMI_DDC_CTRL_SW_RST);
461
462 delay(1000);
463
464 ctrl = HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG);
465 if (ctrl & SUNXI_HDMI_DDC_CTRL_SW_RST) {
466 device_printf(sc->sc_dev, "reset failed (1.3)\n");
467 return EBUSY;
468 }
469
470 /* N=5,M=1 */
471 HDMI_WRITE(sc, SUNXI_HDMI_DDC_CLOCK_REG,
472 __SHIFTIN(5, SUNXI_HDMI_DDC_CLOCK_N) |
473 __SHIFTIN(1, SUNXI_HDMI_DDC_CLOCK_M));
474
475 HDMI_WRITE(sc, SUNXI_HDMI_DDC_DBG_REG, 0x300);
476 } else {
477 HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_CTRL_REG,
478 SUNXI_A31_HDMI_DDC_CTRL_SW_RST);
479
480 /* N=1,M=12 */
481 HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_CLOCK_REG,
482 __SHIFTIN(1, SUNXI_HDMI_DDC_CLOCK_N) |
483 __SHIFTIN(12, SUNXI_HDMI_DDC_CLOCK_M));
484
485 HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_CTRL_REG,
486 SUNXI_A31_HDMI_DDC_CTRL_SDA_PAD_EN |
487 SUNXI_A31_HDMI_DDC_CTRL_SCL_PAD_EN |
488 SUNXI_A31_HDMI_DDC_CTRL_EN);
489 }
490
491 return 0;
492 }
493
494 static int
495 sunxi_hdmi_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
496 {
497 struct sunxi_hdmi_softc *sc = device_private(dev);
498 struct fdt_endpoint *in_ep, *out_ep;
499 int error;
500
501 /* our input is activated by tcon, we activate our output */
502 if (fdt_endpoint_port_index(ep) != SUNXI_PORT_INPUT) {
503 panic("sunxi_hdmi_ep_activate: port %d",
504 fdt_endpoint_port_index(ep));
505 }
506
507 if (!activate)
508 return EOPNOTSUPP;
509
510 /* check that out other input is not active */
511 switch (fdt_endpoint_index(ep)) {
512 case 0:
513 in_ep = fdt_endpoint_get_from_index(&sc->sc_ports,
514 SUNXI_PORT_INPUT, 1);
515 break;
516 case 1:
517 in_ep = fdt_endpoint_get_from_index(&sc->sc_ports,
518 SUNXI_PORT_INPUT, 0);
519 break;
520 default:
521 in_ep = NULL;
522 panic("sunxi_hdmi_ep_activate: input index %d",
523 fdt_endpoint_index(ep));
524 }
525 if (in_ep != NULL) {
526 if (fdt_endpoint_is_active(in_ep))
527 return EBUSY;
528 }
529 /* only one output */
530 out_ep = fdt_endpoint_get_from_index(&sc->sc_ports,
531 SUNXI_PORT_OUTPUT, 0);
532 if (out_ep == NULL) {
533 aprint_error_dev(dev, "no output endpoint\n");
534 return ENODEV;
535 }
536 error = fdt_endpoint_activate(out_ep, activate);
537 if (error == 0) {
538 sc->sc_in_ep = ep;
539 sc->sc_in_rep = fdt_endpoint_remote(ep);
540 sc->sc_out_ep = out_ep;
541 sunxi_hdmi_do_enable(sc);
542 return 0;
543 }
544 return error;
545 }
546
547 static int
548 sunxi_hdmi_ep_enable(device_t dev, struct fdt_endpoint *ep, bool enable)
549 {
550 struct sunxi_hdmi_softc *sc = device_private(dev);
551 int error;
552
553 if (fdt_endpoint_port_index(ep) == SUNXI_PORT_INPUT) {
554 KASSERT(ep == sc->sc_in_ep);
555 if (sc->sc_thread == NULL) {
556 if (enable) {
557 delay(50000);
558 mutex_enter(&sc->sc_pwr_lock);
559 sunxi_hdmi_hpd(sc);
560 mutex_exit(&sc->sc_pwr_lock);
561 kthread_create(PRI_NONE, KTHREAD_MPSAFE, NULL,
562 sunxi_hdmi_thread, sc, &sc->sc_thread, "%s",
563 device_xname(dev));
564 }
565 return 0;
566 } else {
567 mutex_enter(&sc->sc_pwr_lock);
568 error = sunxi_hdmi_poweron(sc, enable);
569 mutex_exit(&sc->sc_pwr_lock);
570 return error;
571 }
572 }
573 panic("sunxi_hdmi_ep_enable");
574 }
575
576 static void
577 sunxi_hdmi_do_enable(struct sunxi_hdmi_softc *sc)
578 {
579 /* complete attach */
580 struct clk *clk;
581 int error;
582 uint32_t dbg0_reg;
583
584 /* assume tcon0 uses pll3, tcon1 uses pll7 */
585 switch(fdt_endpoint_index(sc->sc_in_ep)) {
586 case 0:
587 clk = sc->sc_clk_pll0;
588 dbg0_reg = (0<<21);
589 break;
590 case 1:
591 clk = sc->sc_clk_pll1;
592 dbg0_reg = (1<<21);
593 break;
594 default:
595 panic("sunxi_hdmi pll");
596 }
597 error = clk_set_rate(clk, 270000000);
598 if (error) {
599 clk = clk_get_parent(clk);
600 /* probably because this is pllx2 */
601 error = clk_set_rate(clk, 270000000);
602 }
603 if (error) {
604 aprint_error_dev(sc->sc_dev, ": couldn't init pll clock\n");
605 return;
606 }
607 error = clk_set_parent(sc->sc_clk_mod, clk);
608 if (error) {
609 aprint_error_dev(sc->sc_dev, ": couldn't set mod clock parent\n");
610 return;
611 }
612 error = clk_enable(sc->sc_clk_mod);
613 if (error) {
614 aprint_error_dev(sc->sc_dev, ": couldn't enable mod clock\n");
615 return;
616 }
617 delay(1000);
618
619 HDMI_WRITE(sc, SUNXI_HDMI_CTRL_REG, SUNXI_HDMI_CTRL_MODULE_EN);
620 delay(1000);
621 if (sc->sc_type == HDMI_A20) {
622 HDMI_WRITE(sc, SUNXI_HDMI_PAD_CTRL0_REG, 0xfe800000);
623 HDMI_WRITE(sc, SUNXI_HDMI_PAD_CTRL1_REG, 0x00d8c830);
624 } else if (sc->sc_type == HDMI_A31) {
625 HDMI_WRITE(sc, SUNXI_HDMI_PAD_CTRL0_REG, 0x7e80000f);
626 HDMI_WRITE(sc, SUNXI_HDMI_PAD_CTRL1_REG, 0x01ded030);
627 }
628 HDMI_WRITE(sc, SUNXI_HDMI_PLL_DBG0_REG, dbg0_reg);
629 delay(1000);
630 }
631
632 static int
633 sunxi_hdmi_read_edid_block(struct sunxi_hdmi_softc *sc, uint8_t *data,
634 uint8_t block)
635 {
636 i2c_tag_t tag = &sc->sc_ic;
637 uint8_t wbuf[2];
638 int error;
639
640 if ((error = iic_acquire_bus(tag, I2C_F_POLL)) != 0)
641 return error;
642
643 wbuf[0] = block; /* start address */
644
645 if ((error = iic_exec(tag, I2C_OP_READ_WITH_STOP, DDC_ADDR, wbuf, 1,
646 data, 128, I2C_F_POLL)) != 0) {
647 iic_release_bus(tag, I2C_F_POLL);
648 return error;
649 }
650 iic_release_bus(tag, I2C_F_POLL);
651
652 return 0;
653 }
654
655 static void
656 sunxi_hdmi_read_edid(struct sunxi_hdmi_softc *sc)
657 {
658 const struct videomode *mode;
659 char edid[128];
660 struct edid_info ei;
661 int retry = 4;
662 u_int display_mode;
663
664 memset(edid, 0, sizeof(edid));
665 memset(&ei, 0, sizeof(ei));
666
667 while (--retry > 0) {
668 if (!sunxi_hdmi_read_edid_block(sc, edid, 0))
669 break;
670 }
671 if (retry == 0) {
672 device_printf(sc->sc_dev, "failed to read EDID\n");
673 } else {
674 if (edid_parse(edid, &ei) != 0) {
675 device_printf(sc->sc_dev, "failed to parse EDID\n");
676 }
677 #ifdef SUNXI_HDMI_DEBUG
678 else {
679 edid_print(&ei);
680 }
681 #endif
682 }
683
684 if (sc->sc_display_mode == DISPLAY_MODE_AUTO)
685 display_mode = sunxi_hdmi_get_display_mode(sc, &ei);
686 else
687 display_mode = sc->sc_display_mode;
688
689 const char *forced = sc->sc_display_mode == DISPLAY_MODE_AUTO ?
690 "auto-detected" : "forced";
691 device_printf(sc->sc_dev, "%s mode (%s)\n",
692 display_mode == DISPLAY_MODE_HDMI ? "HDMI" : "DVI", forced);
693
694 strlcpy(sc->sc_display_vendor, ei.edid_vendorname,
695 sizeof(sc->sc_display_vendor));
696 strlcpy(sc->sc_display_product, ei.edid_productname,
697 sizeof(sc->sc_display_product));
698 sc->sc_current_display_mode = display_mode;
699
700 mode = ei.edid_preferred_mode;
701 if (mode == NULL)
702 mode = pick_mode_by_ref(640, 480, 60);
703
704 if (mode != NULL) {
705 sunxi_hdmi_video_enable(sc, false);
706 fdt_endpoint_enable(sc->sc_in_ep, false);
707 delay(20000);
708
709 sunxi_tcon1_set_videomode(
710 fdt_endpoint_device(sc->sc_in_rep), mode);
711 sunxi_hdmi_set_videomode(sc, mode, display_mode);
712 sunxi_hdmi_set_audiomode(sc, mode, display_mode);
713 fdt_endpoint_enable(sc->sc_in_ep, true);
714 delay(20000);
715 sunxi_hdmi_video_enable(sc, true);
716 }
717 }
718
719 static u_int
720 sunxi_hdmi_get_display_mode(struct sunxi_hdmi_softc *sc,
721 const struct edid_info *ei)
722 {
723 char edid[128];
724 bool found_hdmi = false;
725 unsigned int n, p;
726
727 /*
728 * Scan through extension blocks, looking for a CEA-861-D v3
729 * block. If an HDMI Vendor-Specific Data Block (HDMI VSDB) is
730 * found in that, assume HDMI mode.
731 */
732 for (n = 1; n <= MIN(ei->edid_ext_block_count, 4); n++) {
733 if (sunxi_hdmi_read_edid_block(sc, edid, n)) {
734 #ifdef SUNXI_HDMI_DEBUG
735 device_printf(sc->sc_dev,
736 "Failed to read EDID block %d\n", n);
737 #endif
738 break;
739 }
740
741 #ifdef SUNXI_HDMI_DEBUG
742 device_printf(sc->sc_dev, "EDID block #%d:\n", n);
743 #endif
744
745 const uint8_t tag = edid[0];
746 const uint8_t rev = edid[1];
747 const uint8_t off = edid[2];
748
749 #ifdef SUNXI_HDMI_DEBUG
750 device_printf(sc->sc_dev, " Tag %d, Revision %d, Offset %d\n",
751 tag, rev, off);
752 device_printf(sc->sc_dev, " Flags: 0x%02x\n", edid[3]);
753 #endif
754
755 /* We are looking for a CEA-861-D tag (02h) with revision 3 */
756 if (tag != 0x02 || rev != 3)
757 continue;
758 /*
759 * CEA data block collection starts at byte 4, so the
760 * DTD blocks must start after it.
761 */
762 if (off <= 4)
763 continue;
764
765 /* Parse the CEA data blocks */
766 for (p = 4; p < off;) {
767 const uint8_t btag = (edid[p] >> 5) & 0x7;
768 const uint8_t blen = edid[p] & 0x1f;
769
770 #ifdef SUNXI_HDMI_DEBUG
771 device_printf(sc->sc_dev, " CEA data block @ %d\n", p);
772 device_printf(sc->sc_dev, " Tag %d, Length %d\n",
773 btag, blen);
774 #endif
775
776 /* Make sure the length is sane */
777 if (p + blen + 1 > off)
778 break;
779 /* Looking for a VSDB tag */
780 if (btag != 3)
781 goto next_block;
782 /* HDMI VSDB is at least 5 bytes long */
783 if (blen < 5)
784 goto next_block;
785
786 #ifdef SUNXI_HDMI_DEBUG
787 device_printf(sc->sc_dev, " ID: %02x%02x%02x\n",
788 edid[p + 1], edid[p + 2], edid[p + 3]);
789 #endif
790
791 /* HDMI 24-bit IEEE registration ID is 0x000C03 */
792 if (memcmp(&edid[p + 1], "\x03\x0c\x00", 3) == 0)
793 found_hdmi = true;
794
795 next_block:
796 p += (1 + blen);
797 }
798 }
799
800 return found_hdmi ? DISPLAY_MODE_HDMI : DISPLAY_MODE_DVI;
801 }
802
803 static void
804 sunxi_hdmi_video_enable(struct sunxi_hdmi_softc *sc, bool enable)
805 {
806 uint32_t val;
807
808 fdt_endpoint_enable(sc->sc_out_ep, enable);
809 val = HDMI_READ(sc, SUNXI_HDMI_VID_CTRL_REG);
810 val &= ~SUNXI_HDMI_VID_CTRL_SRC_SEL;
811 #ifdef SUNXI_HDMI_CBGEN
812 val |= __SHIFTIN(SUNXI_HDMI_VID_CTRL_SRC_SEL_CBGEN,
813 SUNXI_HDMI_VID_CTRL_SRC_SEL);
814 #else
815 val |= __SHIFTIN(SUNXI_HDMI_VID_CTRL_SRC_SEL_RGB,
816 SUNXI_HDMI_VID_CTRL_SRC_SEL);
817 #endif
818 if (enable) {
819 val |= SUNXI_HDMI_VID_CTRL_VIDEO_EN;
820 } else {
821 val &= ~SUNXI_HDMI_VID_CTRL_VIDEO_EN;
822 }
823 HDMI_WRITE(sc, SUNXI_HDMI_VID_CTRL_REG, val);
824
825 #if defined(SUNXI_HDMI_DEBUG)
826 sunxi_hdmi_dump_regs();
827 #endif
828 }
829
830 static void
831 sunxi_hdmi_set_videomode(struct sunxi_hdmi_softc *sc,
832 const struct videomode *mode, u_int display_mode)
833 {
834 uint32_t val;
835 const u_int dblscan_p = !!(mode->flags & VID_DBLSCAN);
836 const u_int interlace_p = !!(mode->flags & VID_INTERLACE);
837 const u_int phsync_p = !!(mode->flags & VID_PHSYNC);
838 const u_int pvsync_p = !!(mode->flags & VID_PVSYNC);
839 const u_int hfp = mode->hsync_start - mode->hdisplay;
840 const u_int hspw = mode->hsync_end - mode->hsync_start;
841 const u_int hbp = mode->htotal - mode->hsync_start;
842 const u_int vfp = mode->vsync_start - mode->vdisplay;
843 const u_int vspw = mode->vsync_end - mode->vsync_start;
844 const u_int vbp = mode->vtotal - mode->vsync_start;
845 struct clk *clk_pll;
846 int parent_rate;
847 int best_div, best_dbl, best_diff;
848 int target_rate = mode->dot_clock * 1000;
849
850 #ifdef SUNXI_HDMI_DEBUG
851 device_printf(sc->sc_dev,
852 "dblscan %d, interlace %d, phsync %d, pvsync %d\n",
853 dblscan_p, interlace_p, phsync_p, pvsync_p);
854 device_printf(sc->sc_dev, "h: %u %u %u %u\n",
855 mode->hdisplay, hbp, hfp, hspw);
856 device_printf(sc->sc_dev, "v: %u %u %u %u\n",
857 mode->vdisplay, vbp, vfp, vspw);
858 #endif
859
860 HDMI_WRITE(sc, SUNXI_HDMI_INT_STATUS_REG, 0xffffffff);
861
862 /* assume tcon0 uses pll3, tcon1 uses pll7 */
863 switch(fdt_endpoint_index(sc->sc_in_ep)) {
864 case 0:
865 clk_pll = sc->sc_clk_pll0;
866 break;
867 case 1:
868 clk_pll = sc->sc_clk_pll1;
869 break;
870 default:
871 panic("sunxi_hdmi pll");
872 }
873 parent_rate = clk_get_rate(clk_pll);
874 KASSERT(parent_rate > 0);
875 best_div = best_dbl = 0;
876 best_diff = INT_MAX;
877 for (int d = 2; d > 0 && best_diff != 0; d--) {
878 for (int m = 1; m <= 16 && best_diff != 0; m++) {
879 int cur_rate = parent_rate / m / d;
880 int diff = abs(target_rate - cur_rate);
881 if (diff >= 0 && diff < best_diff) {
882 best_diff = diff;
883 best_div = m;
884 best_dbl = d;
885 }
886 }
887 }
888
889 #ifdef SUNXI_HDMI_DEBUG
890 device_printf(sc->sc_dev, "parent rate: %d\n", parent_rate);
891 device_printf(sc->sc_dev, "dot_clock: %d\n", mode->dot_clock);
892 device_printf(sc->sc_dev, "clkdiv: %d\n", best_div);
893 device_printf(sc->sc_dev, "clkdbl: %c\n", (best_dbl == 1) ? 'Y' : 'N');
894 #endif
895
896 if (best_div == 0) {
897 device_printf(sc->sc_dev, "ERROR: TCON clk not configured\n");
898 return;
899 }
900
901 uint32_t pll_ctrl, pad_ctrl0, pad_ctrl1;
902 if (HDMI_1_4_P(sc)) {
903 pad_ctrl0 = 0x7e8000ff;
904 pad_ctrl1 = 0x01ded030;
905 pll_ctrl = 0xba48a308;
906 pll_ctrl |= __SHIFTIN(best_div - 1, SUNXI_HDMI_PLL_CTRL_PREDIV);
907 } else {
908 pad_ctrl0 = 0xfe800000;
909 pad_ctrl1 = 0x00d8c830;
910 pll_ctrl = 0xfa4ef708;
911 pll_ctrl |= __SHIFTIN(best_div, SUNXI_HDMI_PLL_CTRL_PREDIV);
912 }
913 if (best_dbl == 2)
914 pad_ctrl1 |= 0x40;
915
916 HDMI_WRITE(sc, SUNXI_HDMI_PAD_CTRL0_REG, pad_ctrl0);
917 HDMI_WRITE(sc, SUNXI_HDMI_PAD_CTRL1_REG, pad_ctrl1);
918 HDMI_WRITE(sc, SUNXI_HDMI_PLL_CTRL_REG, pll_ctrl);
919 /* assume tcon0 uses pll3, tcon1 uses pll7 */
920 switch(fdt_endpoint_index(sc->sc_in_ep)) {
921 case 0:
922 HDMI_WRITE(sc, SUNXI_HDMI_PLL_DBG0_REG, (0<<21));
923 break;
924 case 1:
925 HDMI_WRITE(sc, SUNXI_HDMI_PLL_DBG0_REG, (1<<21));
926 break;
927 default:
928 panic("sunxi_hdmi pll");
929 }
930
931 val = HDMI_READ(sc, SUNXI_HDMI_VID_CTRL_REG);
932 val &= ~SUNXI_HDMI_VID_CTRL_HDMI_MODE;
933 if (display_mode == DISPLAY_MODE_DVI) {
934 val |= __SHIFTIN(SUNXI_HDMI_VID_CTRL_HDMI_MODE_DVI,
935 SUNXI_HDMI_VID_CTRL_HDMI_MODE);
936 } else {
937 val |= __SHIFTIN(SUNXI_HDMI_VID_CTRL_HDMI_MODE_HDMI,
938 SUNXI_HDMI_VID_CTRL_HDMI_MODE);
939 }
940 val &= ~SUNXI_HDMI_VID_CTRL_REPEATER_SEL;
941 if (dblscan_p) {
942 val |= __SHIFTIN(SUNXI_HDMI_VID_CTRL_REPEATER_SEL_2X,
943 SUNXI_HDMI_VID_CTRL_REPEATER_SEL);
944 }
945 val &= ~SUNXI_HDMI_VID_CTRL_OUTPUT_FMT;
946 if (interlace_p) {
947 val |= __SHIFTIN(SUNXI_HDMI_VID_CTRL_OUTPUT_FMT_INTERLACE,
948 SUNXI_HDMI_VID_CTRL_OUTPUT_FMT);
949 }
950 HDMI_WRITE(sc, SUNXI_HDMI_VID_CTRL_REG, val);
951
952 val = __SHIFTIN((mode->hdisplay << dblscan_p) - 1,
953 SUNXI_HDMI_VID_TIMING_0_ACT_H);
954 val |= __SHIFTIN(mode->vdisplay - 1,
955 SUNXI_HDMI_VID_TIMING_0_ACT_V);
956 HDMI_WRITE(sc, SUNXI_HDMI_VID_TIMING_0_REG, val);
957
958 val = __SHIFTIN((hbp << dblscan_p) - 1,
959 SUNXI_HDMI_VID_TIMING_1_HBP);
960 val |= __SHIFTIN(vbp - 1,
961 SUNXI_HDMI_VID_TIMING_1_VBP);
962 HDMI_WRITE(sc, SUNXI_HDMI_VID_TIMING_1_REG, val);
963
964 val = __SHIFTIN((hfp << dblscan_p) - 1,
965 SUNXI_HDMI_VID_TIMING_2_HFP);
966 val |= __SHIFTIN(vfp - 1,
967 SUNXI_HDMI_VID_TIMING_2_VFP);
968 HDMI_WRITE(sc, SUNXI_HDMI_VID_TIMING_2_REG, val);
969
970 val = __SHIFTIN((hspw << dblscan_p) - 1,
971 SUNXI_HDMI_VID_TIMING_3_HSPW);
972 val |= __SHIFTIN(vspw - 1,
973 SUNXI_HDMI_VID_TIMING_3_VSPW);
974 HDMI_WRITE(sc, SUNXI_HDMI_VID_TIMING_3_REG, val);
975
976 val = 0;
977 if (phsync_p) {
978 val |= SUNXI_HDMI_VID_TIMING_4_HSYNC_ACTIVE_SEL;
979 }
980 if (pvsync_p) {
981 val |= SUNXI_HDMI_VID_TIMING_4_VSYNC_ACTIVE_SEL;
982 }
983 val |= __SHIFTIN(SUNXI_HDMI_VID_TIMING_4_TX_CLOCK_NORMAL,
984 SUNXI_HDMI_VID_TIMING_4_TX_CLOCK);
985 HDMI_WRITE(sc, SUNXI_HDMI_VID_TIMING_4_REG, val);
986
987 /* Packet control */
988 HDMI_WRITE(sc, SUNXI_HDMI_GP_PKT0_REG, 0);
989 HDMI_WRITE(sc, SUNXI_HDMI_GP_PKT1_REG, 0);
990 HDMI_WRITE(sc, SUNXI_HDMI_PKT_CTRL0_REG, 0x00005321);
991 HDMI_WRITE(sc, SUNXI_HDMI_PKT_CTRL1_REG, 0x0000000f);
992 }
993
994 static void
995 sunxi_hdmi_set_audiomode(struct sunxi_hdmi_softc *sc,
996 const struct videomode *mode, u_int display_mode)
997 {
998 uint32_t cts, n, val;
999
1000 /*
1001 * Before changing audio parameters, disable and reset the
1002 * audio module. Wait for the soft reset bit to clear before
1003 * configuring the audio parameters.
1004 */
1005 val = HDMI_READ(sc, SUNXI_HDMI_AUD_CTRL_REG);
1006 val &= ~SUNXI_HDMI_AUD_CTRL_EN;
1007 val |= SUNXI_HDMI_AUD_CTRL_RST;
1008 HDMI_WRITE(sc, SUNXI_HDMI_AUD_CTRL_REG, val);
1009 do {
1010 val = HDMI_READ(sc, SUNXI_HDMI_AUD_CTRL_REG);
1011 } while (val & SUNXI_HDMI_AUD_CTRL_RST);
1012
1013 /* No audio support in DVI mode */
1014 if (display_mode != DISPLAY_MODE_HDMI) {
1015 return;
1016 }
1017
1018 /* DMA & FIFO control */
1019 val = HDMI_READ(sc, SUNXI_HDMI_ADMA_CTRL_REG);
1020 if (sc->sc_type == HDMI_A31) {
1021 val |= SUNXI_HDMI_ADMA_CTRL_SRC_DMA_MODE; /* NDMA */
1022 } else {
1023 val &= ~SUNXI_HDMI_ADMA_CTRL_SRC_DMA_MODE; /* DDMA */
1024 }
1025 val &= ~SUNXI_HDMI_ADMA_CTRL_SRC_DMA_SAMPLE_RATE;
1026 val &= ~SUNXI_HDMI_ADMA_CTRL_SRC_SAMPLE_LAYOUT;
1027 val &= ~SUNXI_HDMI_ADMA_CTRL_SRC_WORD_LEN;
1028 val &= ~SUNXI_HDMI_ADMA_CTRL_DATA_SEL;
1029 HDMI_WRITE(sc, SUNXI_HDMI_ADMA_CTRL_REG, val);
1030
1031 /* Audio format control */
1032 val = HDMI_READ(sc, SUNXI_HDMI_AUD_FMT_REG);
1033 val &= ~SUNXI_HDMI_AUD_FMT_SRC_SEL;
1034 val &= ~SUNXI_HDMI_AUD_FMT_SEL;
1035 val &= ~SUNXI_HDMI_AUD_FMT_DSD_FMT;
1036 val &= ~SUNXI_HDMI_AUD_FMT_LAYOUT;
1037 val &= ~SUNXI_HDMI_AUD_FMT_SRC_CH_CFG;
1038 val |= __SHIFTIN(1, SUNXI_HDMI_AUD_FMT_SRC_CH_CFG);
1039 HDMI_WRITE(sc, SUNXI_HDMI_AUD_FMT_REG, val);
1040
1041 /* PCM control (channel map) */
1042 HDMI_WRITE(sc, SUNXI_HDMI_AUD_PCM_CTRL_REG, 0x76543210);
1043
1044 /* Clock setup */
1045 n = 6144; /* 48 kHz */
1046 cts = ((mode->dot_clock * 10) * (n / 128)) / 480;
1047 HDMI_WRITE(sc, SUNXI_HDMI_AUD_CTS_REG, cts);
1048 HDMI_WRITE(sc, SUNXI_HDMI_AUD_N_REG, n);
1049
1050 /* Audio PCM channel status 0 */
1051 val = __SHIFTIN(SUNXI_HDMI_AUD_CH_STATUS0_FS_FREQ_48,
1052 SUNXI_HDMI_AUD_CH_STATUS0_FS_FREQ);
1053 HDMI_WRITE(sc, SUNXI_HDMI_AUD_CH_STATUS0_REG, val);
1054
1055 /* Audio PCM channel status 1 */
1056 val = HDMI_READ(sc, SUNXI_HDMI_AUD_CH_STATUS1_REG);
1057 val &= ~SUNXI_HDMI_AUD_CH_STATUS1_CGMS_A;
1058 val &= ~SUNXI_HDMI_AUD_CH_STATUS1_ORIGINAL_FS;
1059 val &= ~SUNXI_HDMI_AUD_CH_STATUS1_WORD_LEN;
1060 val |= __SHIFTIN(5, SUNXI_HDMI_AUD_CH_STATUS1_WORD_LEN);
1061 val |= SUNXI_HDMI_AUD_CH_STATUS1_WORD_LEN_MAX;
1062 HDMI_WRITE(sc, SUNXI_HDMI_AUD_CH_STATUS1_REG, val);
1063
1064 /* Re-enable */
1065 val = HDMI_READ(sc, SUNXI_HDMI_AUD_CTRL_REG);
1066 val |= SUNXI_HDMI_AUD_CTRL_EN;
1067 HDMI_WRITE(sc, SUNXI_HDMI_AUD_CTRL_REG, val);
1068
1069 #if defined(SUNXI_HDMI_DEBUG)
1070 sunxi_hdmi_dump_regs();
1071 #endif
1072 }
1073
1074 static void
1075 sunxi_hdmi_hpd(struct sunxi_hdmi_softc *sc)
1076 {
1077 uint32_t hpd = HDMI_READ(sc, SUNXI_HDMI_HPD_REG);
1078 bool con = !!(hpd & SUNXI_HDMI_HPD_HOTPLUG_DET);
1079
1080 KASSERT(mutex_owned(&sc->sc_pwr_lock));
1081 if (sc->sc_display_connected == con)
1082 return;
1083
1084 if (con) {
1085 device_printf(sc->sc_dev, "display connected\n");
1086 sc->sc_pwr_refcount = 1;
1087 sunxi_hdmi_read_edid(sc);
1088 } else {
1089 device_printf(sc->sc_dev, "display disconnected\n");
1090 sc->sc_pwr_refcount = 0;
1091 sunxi_hdmi_video_enable(sc, false);
1092 fdt_endpoint_enable(sc->sc_in_ep, false);
1093 sunxi_tcon1_set_videomode(
1094 fdt_endpoint_device(sc->sc_in_rep), NULL);
1095 }
1096
1097 sc->sc_display_connected = con;
1098 }
1099
1100 static void
1101 sunxi_hdmi_thread(void *priv)
1102 {
1103 struct sunxi_hdmi_softc *sc = priv;
1104
1105 for (;;) {
1106 mutex_enter(&sc->sc_pwr_lock);
1107 sunxi_hdmi_hpd(sc);
1108 mutex_exit(&sc->sc_pwr_lock);
1109 kpause("hdmihotplug", false, mstohz(1000), NULL);
1110 }
1111 }
1112
1113 static int
1114 sunxi_hdmi_poweron(struct sunxi_hdmi_softc *sc, bool enable)
1115 {
1116 int error = 0;
1117 KASSERT(mutex_owned(&sc->sc_pwr_lock));
1118 if (!sc->sc_display_connected)
1119 return EOPNOTSUPP;
1120 if (enable) {
1121 KASSERT(sc->sc_pwr_refcount >= 0);
1122 if (sc->sc_pwr_refcount == 0) {
1123 error = fdt_endpoint_enable(sc->sc_in_ep, true);
1124 if (error)
1125 return error;
1126 sunxi_hdmi_video_enable(sc, true);
1127 }
1128 sc->sc_pwr_refcount++;
1129 } else {
1130 sc->sc_pwr_refcount--;
1131 KASSERT(sc->sc_pwr_refcount >= 0);
1132 if (sc->sc_pwr_refcount == 0) {
1133 sunxi_hdmi_video_enable(sc, false);
1134 error = fdt_endpoint_enable(sc->sc_in_ep, false);
1135 }
1136 }
1137 return error;
1138 }
1139 #if 0
1140 static int
1141 sunxi_hdmi_intr(void *priv)
1142 {
1143 struct sunxi_hdmi_softc *sc = priv;
1144 uint32_t intsts;
1145
1146 intsts = HDMI_READ(sc, SUNXI_HDMI_INT_STATUS_REG);
1147 if (!(intsts & 0x73))
1148 return 0;
1149
1150 HDMI_WRITE(sc, SUNXI_HDMI_INT_STATUS_REG, intsts);
1151
1152 device_printf(sc->sc_dev, "INT_STATUS %08X\n", intsts);
1153
1154 return 1;
1155 }
1156 #endif
1157
1158 #if 0 /* XXX audio */
1159 void
1160 sunxi_hdmi_get_info(struct sunxi_hdmi_info *info)
1161 {
1162 struct sunxi_hdmi_softc *sc;
1163 device_t dev;
1164
1165 memset(info, 0, sizeof(*info));
1166
1167 dev = device_find_by_driver_unit("sunxihdmi", 0);
1168 if (dev == NULL) {
1169 info->display_connected = false;
1170 return;
1171 }
1172 sc = device_private(dev);
1173
1174 info->display_connected = sc->sc_display_connected;
1175 if (info->display_connected) {
1176 strlcpy(info->display_vendor, sc->sc_display_vendor,
1177 sizeof(info->display_vendor));
1178 strlcpy(info->display_product, sc->sc_display_product,
1179 sizeof(info->display_product));
1180 info->display_hdmimode =
1181 sc->sc_current_display_mode == DISPLAY_MODE_HDMI;
1182 }
1183 }
1184 #endif
1185
1186 #if defined(SUNXI_HDMI_DEBUG)
1187 void
1188 sunxi_hdmi_dump_regs(void)
1189 {
1190 static const struct {
1191 const char *name;
1192 uint16_t reg;
1193 } regs[] = {
1194 { "CTRL", SUNXI_HDMI_CTRL_REG },
1195 { "INT_STATUS", SUNXI_HDMI_INT_STATUS_REG },
1196 { "VID_CTRL", SUNXI_HDMI_VID_CTRL_REG },
1197 { "VID_TIMING_0", SUNXI_HDMI_VID_TIMING_0_REG },
1198 { "VID_TIMING_1", SUNXI_HDMI_VID_TIMING_1_REG },
1199 { "VID_TIMING_2", SUNXI_HDMI_VID_TIMING_2_REG },
1200 { "VID_TIMING_3", SUNXI_HDMI_VID_TIMING_3_REG },
1201 { "VID_TIMING_4", SUNXI_HDMI_VID_TIMING_4_REG },
1202 { "PAD_CTRL0", SUNXI_HDMI_PAD_CTRL0_REG },
1203 { "PAD_CTRL1", SUNXI_HDMI_PAD_CTRL1_REG },
1204 { "PLL_CTRL", SUNXI_HDMI_PLL_CTRL_REG },
1205 { "PLL_DBG0", SUNXI_HDMI_PLL_DBG0_REG },
1206 { "PLL_DBG1", SUNXI_HDMI_PLL_DBG1_REG },
1207 };
1208 struct sunxi_hdmi_softc *sc;
1209 device_t dev;
1210
1211 dev = device_find_by_driver_unit("sunxihdmi", 0);
1212 if (dev == NULL)
1213 return;
1214 sc = device_private(dev);
1215
1216 for (int i = 0; i < __arraycount(regs); i++) {
1217 printf("%s: 0x%08x\n", regs[i].name,
1218 HDMI_READ(sc, regs[i].reg));
1219 }
1220 }
1221 #endif
1222