1296064Sjmcneill/*-
2296064Sjmcneill * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
3296064Sjmcneill * All rights reserved.
4296064Sjmcneill *
5296064Sjmcneill * Redistribution and use in source and binary forms, with or without
6296064Sjmcneill * modification, are permitted provided that the following conditions
7296064Sjmcneill * are met:
8296064Sjmcneill * 1. Redistributions of source code must retain the above copyright
9296064Sjmcneill *    notice, this list of conditions and the following disclaimer.
10296064Sjmcneill * 2. Redistributions in binary form must reproduce the above copyright
11296064Sjmcneill *    notice, this list of conditions and the following disclaimer in the
12296064Sjmcneill *    documentation and/or other materials provided with the distribution.
13296064Sjmcneill *
14296064Sjmcneill * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15296064Sjmcneill * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16296064Sjmcneill * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17296064Sjmcneill * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18296064Sjmcneill * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19296064Sjmcneill * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20296064Sjmcneill * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21296064Sjmcneill * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22296064Sjmcneill * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23296064Sjmcneill * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24296064Sjmcneill * SUCH DAMAGE.
25296064Sjmcneill *
26296064Sjmcneill * $FreeBSD: stable/11/sys/arm/allwinner/a10_hdmi.c 308324 2016-11-05 04:17:32Z mmel $
27296064Sjmcneill */
28296064Sjmcneill
29296064Sjmcneill/*
30296064Sjmcneill * Allwinner A10/A20 HDMI TX
31296064Sjmcneill */
32296064Sjmcneill
33296064Sjmcneill#include <sys/cdefs.h>
34296064Sjmcneill__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/a10_hdmi.c 308324 2016-11-05 04:17:32Z mmel $");
35296064Sjmcneill
36296064Sjmcneill#include <sys/param.h>
37296064Sjmcneill#include <sys/systm.h>
38296064Sjmcneill#include <sys/bus.h>
39296064Sjmcneill#include <sys/rman.h>
40296064Sjmcneill#include <sys/condvar.h>
41296064Sjmcneill#include <sys/kernel.h>
42296064Sjmcneill#include <sys/module.h>
43296064Sjmcneill
44296064Sjmcneill#include <machine/bus.h>
45296064Sjmcneill
46296064Sjmcneill#include <dev/ofw/ofw_bus.h>
47296064Sjmcneill#include <dev/ofw/ofw_bus_subr.h>
48296064Sjmcneill
49296064Sjmcneill#include <dev/videomode/videomode.h>
50296064Sjmcneill#include <dev/videomode/edidvar.h>
51296064Sjmcneill
52297627Sjmcneill#include <dev/extres/clk/clk.h>
53296064Sjmcneill
54296064Sjmcneill#include "hdmi_if.h"
55296064Sjmcneill
56296064Sjmcneill#define	HDMI_CTRL		0x004
57296064Sjmcneill#define	CTRL_MODULE_EN		(1 << 31)
58296064Sjmcneill#define	HDMI_INT_STATUS		0x008
59296064Sjmcneill#define	HDMI_HPD		0x00c
60296064Sjmcneill#define	HPD_DET			(1 << 0)
61296064Sjmcneill#define	HDMI_VID_CTRL		0x010
62296064Sjmcneill#define	VID_CTRL_VIDEO_EN	(1 << 31)
63296064Sjmcneill#define	VID_CTRL_HDMI_MODE	(1 << 30)
64296064Sjmcneill#define	VID_CTRL_INTERLACE	(1 << 4)
65296064Sjmcneill#define	VID_CTRL_REPEATER_2X	(1 << 0)
66296064Sjmcneill#define	HDMI_VID_TIMING0	0x014
67296064Sjmcneill#define	VID_ACT_V(v)		(((v) - 1) << 16)
68296064Sjmcneill#define	VID_ACT_H(h)		(((h) - 1) << 0)
69296064Sjmcneill#define	HDMI_VID_TIMING1	0x018
70296064Sjmcneill#define	VID_VBP(vbp)		(((vbp) - 1) << 16)
71296064Sjmcneill#define	VID_HBP(hbp)		(((hbp) - 1) << 0)
72296064Sjmcneill#define	HDMI_VID_TIMING2	0x01c
73296064Sjmcneill#define	VID_VFP(vfp)		(((vfp) - 1) << 16)
74296064Sjmcneill#define	VID_HFP(hfp)		(((hfp) - 1) << 0)
75296064Sjmcneill#define	HDMI_VID_TIMING3	0x020
76296064Sjmcneill#define	VID_VSPW(vspw)		(((vspw) - 1) << 16)
77296064Sjmcneill#define	VID_HSPW(hspw)		(((hspw) - 1) << 0)
78296064Sjmcneill#define	HDMI_VID_TIMING4	0x024
79296064Sjmcneill#define	TX_CLOCK_NORMAL		0x03e00000
80296064Sjmcneill#define	VID_VSYNC_ACTSEL	(1 << 1)
81296064Sjmcneill#define	VID_HSYNC_ACTSEL	(1 << 0)
82296064Sjmcneill#define	HDMI_AUD_CTRL		0x040
83296064Sjmcneill#define	AUD_CTRL_EN		(1 << 31)
84296064Sjmcneill#define	AUD_CTRL_RST		(1 << 30)
85296064Sjmcneill#define	HDMI_ADMA_CTRL		0x044
86296064Sjmcneill#define	HDMI_ADMA_MODE		(1 << 31)
87296064Sjmcneill#define	HDMI_ADMA_MODE_DDMA	(0 << 31)
88296064Sjmcneill#define	HDMI_ADMA_MODE_NDMA	(1 << 31)
89296064Sjmcneill#define	HDMI_AUD_FMT		0x048
90296064Sjmcneill#define	AUD_FMT_CH(n)		((n) - 1)
91296064Sjmcneill#define	HDMI_PCM_CTRL		0x04c
92296064Sjmcneill#define	HDMI_AUD_CTS		0x050
93296064Sjmcneill#define	HDMI_AUD_N		0x054
94296064Sjmcneill#define	HDMI_AUD_CH_STATUS0	0x058
95296064Sjmcneill#define	CH_STATUS0_FS_FREQ	(0xf << 24)
96296064Sjmcneill#define	CH_STATUS0_FS_FREQ_48	(2 << 24)
97296064Sjmcneill#define	HDMI_AUD_CH_STATUS1	0x05c
98296064Sjmcneill#define	CH_STATUS1_WORD_LEN	(0x7 << 1)
99296064Sjmcneill#define	CH_STATUS1_WORD_LEN_16	(1 << 1)
100296064Sjmcneill#define	HDMI_AUDIO_RESET_RETRY	1000
101296064Sjmcneill#define	HDMI_AUDIO_CHANNELS	2
102296064Sjmcneill#define	HDMI_AUDIO_CHANNELMAP	0x76543210
103296064Sjmcneill#define	HDMI_AUDIO_N		6144	/* 48 kHz */
104296064Sjmcneill#define	HDMI_AUDIO_CTS(r, n)	((((r) * 10) * ((n) / 128)) / 480)
105296064Sjmcneill#define	HDMI_PADCTRL0		0x200
106296064Sjmcneill#define	PADCTRL0_BIASEN		(1 << 31)
107296064Sjmcneill#define	PADCTRL0_LDOCEN		(1 << 30)
108296064Sjmcneill#define	PADCTRL0_LDODEN		(1 << 29)
109296064Sjmcneill#define	PADCTRL0_PWENC		(1 << 28)
110296064Sjmcneill#define	PADCTRL0_PWEND		(1 << 27)
111296064Sjmcneill#define	PADCTRL0_PWENG		(1 << 26)
112296064Sjmcneill#define	PADCTRL0_CKEN		(1 << 25)
113296064Sjmcneill#define	PADCTRL0_SEN		(1 << 24)
114296064Sjmcneill#define	PADCTRL0_TXEN		(1 << 23)
115296064Sjmcneill#define	HDMI_PADCTRL1		0x204
116296064Sjmcneill#define	PADCTRL1_AMP_OPT	(1 << 23)
117296064Sjmcneill#define	PADCTRL1_AMPCK_OPT	(1 << 22)
118296064Sjmcneill#define	PADCTRL1_DMP_OPT	(1 << 21)
119296064Sjmcneill#define	PADCTRL1_EMP_OPT	(1 << 20)
120296064Sjmcneill#define	PADCTRL1_EMPCK_OPT	(1 << 19)
121296064Sjmcneill#define	PADCTRL1_PWSCK		(1 << 18)
122296064Sjmcneill#define	PADCTRL1_PWSDT		(1 << 17)
123296064Sjmcneill#define	PADCTRL1_REG_CSMPS	(1 << 16)
124296064Sjmcneill#define	PADCTRL1_REG_DEN	(1 << 15)
125296064Sjmcneill#define	PADCTRL1_REG_DENCK	(1 << 14)
126296064Sjmcneill#define	PADCTRL1_REG_PLRCK	(1 << 13)
127296064Sjmcneill#define	PADCTRL1_REG_EMP	(0x7 << 10)
128296064Sjmcneill#define	PADCTRL1_REG_EMP_EN	(0x2 << 10)
129296064Sjmcneill#define	PADCTRL1_REG_CD		(0x3 << 8)
130296064Sjmcneill#define	PADCTRL1_REG_CKSS	(0x3 << 6)
131296064Sjmcneill#define	PADCTRL1_REG_CKSS_1X	(0x1 << 6)
132296064Sjmcneill#define	PADCTRL1_REG_CKSS_2X	(0x0 << 6)
133296064Sjmcneill#define	PADCTRL1_REG_AMP	(0x7 << 3)
134296064Sjmcneill#define	PADCTRL1_REG_AMP_EN	(0x6 << 3)
135296064Sjmcneill#define	PADCTRL1_REG_PLR	(0x7 << 0)
136296064Sjmcneill#define	HDMI_PLLCTRL0		0x208
137296064Sjmcneill#define	PLLCTRL0_PLL_EN		(1 << 31)
138296064Sjmcneill#define	PLLCTRL0_BWS		(1 << 30)
139296064Sjmcneill#define	PLLCTRL0_HV_IS_33	(1 << 29)
140296064Sjmcneill#define	PLLCTRL0_LDO1_EN	(1 << 28)
141296064Sjmcneill#define	PLLCTRL0_LDO2_EN	(1 << 27)
142296064Sjmcneill#define	PLLCTRL0_SDIV2		(1 << 25)
143296064Sjmcneill#define	PLLCTRL0_VCO_GAIN	(0x1 << 22)
144296064Sjmcneill#define	PLLCTRL0_S		(0x7 << 17)
145296064Sjmcneill#define	PLLCTRL0_CP_S		(0xf << 12)
146296064Sjmcneill#define	PLLCTRL0_CS		(0x7 << 8)
147296064Sjmcneill#define	PLLCTRL0_PREDIV(x)	((x) << 4)
148296064Sjmcneill#define	PLLCTRL0_VCO_S		(0x8 << 0)
149296064Sjmcneill#define	HDMI_PLLDBG0		0x20c
150296064Sjmcneill#define	PLLDBG0_CKIN_SEL	(1 << 21)
151296064Sjmcneill#define	PLLDBG0_CKIN_SEL_PLL3	(0 << 21)
152296064Sjmcneill#define	PLLDBG0_CKIN_SEL_PLL7	(1 << 21)
153296064Sjmcneill#define	HDMI_PKTCTRL0		0x2f0
154296064Sjmcneill#define	HDMI_PKTCTRL1		0x2f4
155296064Sjmcneill#define	PKTCTRL_PACKET(n,t)	((t) << ((n) << 2))
156296064Sjmcneill#define	PKT_NULL		0
157296064Sjmcneill#define	PKT_GC			1
158296064Sjmcneill#define	PKT_AVI			2
159296064Sjmcneill#define	PKT_AI			3
160296064Sjmcneill#define	PKT_SPD			5
161296064Sjmcneill#define	PKT_END			15
162296064Sjmcneill#define	DDC_CTRL		0x500
163296064Sjmcneill#define	CTRL_DDC_EN		(1 << 31)
164296064Sjmcneill#define	CTRL_DDC_ACMD_START	(1 << 30)
165296064Sjmcneill#define	CTRL_DDC_FIFO_DIR	(1 << 8)
166296064Sjmcneill#define	CTRL_DDC_FIFO_DIR_READ	(0 << 8)
167296064Sjmcneill#define	CTRL_DDC_FIFO_DIR_WRITE	(1 << 8)
168296064Sjmcneill#define	CTRL_DDC_SWRST		(1 << 0)
169296064Sjmcneill#define	DDC_SLAVE_ADDR		0x504
170296064Sjmcneill#define	SLAVE_ADDR_SEG_SHIFT	24
171296064Sjmcneill#define	SLAVE_ADDR_EDDC_SHIFT	16
172296064Sjmcneill#define	SLAVE_ADDR_OFFSET_SHIFT	8
173296064Sjmcneill#define	SLAVE_ADDR_SHIFT	0
174296064Sjmcneill#define	DDC_INT_STATUS		0x50c
175296064Sjmcneill#define	INT_STATUS_XFER_DONE	(1 << 0)
176296064Sjmcneill#define	DDC_FIFO_CTRL		0x510
177296064Sjmcneill#define	FIFO_CTRL_CLEAR		(1 << 31)
178296064Sjmcneill#define	DDC_BYTE_COUNTER	0x51c
179296064Sjmcneill#define	DDC_COMMAND		0x520
180296064Sjmcneill#define	COMMAND_EOREAD		(4 << 0)
181296064Sjmcneill#define	DDC_CLOCK		0x528
182296064Sjmcneill#define	DDC_CLOCK_M		(1 << 3)
183296064Sjmcneill#define	DDC_CLOCK_N		(5 << 0)
184296064Sjmcneill#define	DDC_FIFO		0x518
185296064Sjmcneill#define	SWRST_DELAY		1000
186296064Sjmcneill#define	DDC_DELAY		1000
187296064Sjmcneill#define	DDC_RETRY		1000
188296064Sjmcneill#define	DDC_BLKLEN		16
189296064Sjmcneill#define	DDC_ADDR		0x50
190296064Sjmcneill#define	EDDC_ADDR		0x60
191296064Sjmcneill#define	EDID_LENGTH		128
192296064Sjmcneill#define	HDMI_ENABLE_DELAY	50000
193296064Sjmcneill#define	DDC_READ_RETRY		4
194296064Sjmcneill#define	EXT_TAG			0x00
195296064Sjmcneill#define	CEA_TAG_ID		0x02
196296064Sjmcneill#define	CEA_DTD			0x03
197296064Sjmcneill#define	DTD_BASIC_AUDIO		(1 << 6)
198297514Sjmcneill#define	CEA_REV			0x02
199297514Sjmcneill#define	CEA_DATA_OFF		0x03
200297514Sjmcneill#define	CEA_DATA_START		4
201297514Sjmcneill#define	BLOCK_TAG(x)		(((x) >> 5) & 0x7)
202297514Sjmcneill#define	BLOCK_TAG_VSDB		3
203297514Sjmcneill#define	BLOCK_LEN(x)		((x) & 0x1f)
204297514Sjmcneill#define	HDMI_VSDB_MINLEN	5
205297514Sjmcneill#define	HDMI_OUI		"\x03\x0c\x00"
206297514Sjmcneill#define	HDMI_OUI_LEN		3
207297627Sjmcneill#define	HDMI_DEFAULT_FREQ	297000000
208296064Sjmcneill
209296064Sjmcneillstruct a10hdmi_softc {
210296064Sjmcneill	struct resource		*res;
211296064Sjmcneill
212296064Sjmcneill	struct intr_config_hook	mode_hook;
213296064Sjmcneill
214296064Sjmcneill	uint8_t			edid[EDID_LENGTH];
215296064Sjmcneill
216296789Sjmcneill	int			has_hdmi;
217296064Sjmcneill	int			has_audio;
218297627Sjmcneill
219297627Sjmcneill	clk_t			clk_ahb;
220297627Sjmcneill	clk_t			clk_hdmi;
221297627Sjmcneill	clk_t			clk_lcd;
222296064Sjmcneill};
223296064Sjmcneill
224296064Sjmcneillstatic struct resource_spec a10hdmi_spec[] = {
225296064Sjmcneill	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
226296064Sjmcneill	{ -1, 0 }
227296064Sjmcneill};
228296064Sjmcneill
229296064Sjmcneill#define	HDMI_READ(sc, reg)		bus_read_4((sc)->res, (reg))
230296064Sjmcneill#define	HDMI_WRITE(sc, reg, val)	bus_write_4((sc)->res, (reg), (val))
231296064Sjmcneill
232296064Sjmcneillstatic void
233296064Sjmcneilla10hdmi_init(struct a10hdmi_softc *sc)
234296064Sjmcneill{
235296064Sjmcneill	/* Enable the HDMI module */
236296064Sjmcneill	HDMI_WRITE(sc, HDMI_CTRL, CTRL_MODULE_EN);
237296064Sjmcneill
238296064Sjmcneill	/* Configure PLL/DRV settings */
239296064Sjmcneill	HDMI_WRITE(sc, HDMI_PADCTRL0, PADCTRL0_BIASEN | PADCTRL0_LDOCEN |
240296064Sjmcneill	    PADCTRL0_LDODEN | PADCTRL0_PWENC | PADCTRL0_PWEND |
241296064Sjmcneill	    PADCTRL0_PWENG | PADCTRL0_CKEN | PADCTRL0_TXEN);
242296064Sjmcneill	HDMI_WRITE(sc, HDMI_PADCTRL1, PADCTRL1_AMP_OPT | PADCTRL1_AMPCK_OPT |
243296064Sjmcneill	    PADCTRL1_EMP_OPT | PADCTRL1_EMPCK_OPT | PADCTRL1_REG_DEN |
244296064Sjmcneill	    PADCTRL1_REG_DENCK | PADCTRL1_REG_EMP_EN | PADCTRL1_REG_AMP_EN);
245296064Sjmcneill
246296064Sjmcneill	/* Select PLL3 as input clock */
247296064Sjmcneill	HDMI_WRITE(sc, HDMI_PLLDBG0, PLLDBG0_CKIN_SEL_PLL3);
248296064Sjmcneill
249296064Sjmcneill	DELAY(HDMI_ENABLE_DELAY);
250296064Sjmcneill}
251296064Sjmcneill
252296064Sjmcneillstatic void
253296064Sjmcneilla10hdmi_hpd(void *arg)
254296064Sjmcneill{
255296064Sjmcneill	struct a10hdmi_softc *sc;
256296064Sjmcneill	device_t dev;
257296064Sjmcneill	uint32_t hpd;
258296064Sjmcneill
259296064Sjmcneill	dev = arg;
260296064Sjmcneill	sc = device_get_softc(dev);
261296064Sjmcneill
262296064Sjmcneill	hpd = HDMI_READ(sc, HDMI_HPD);
263296064Sjmcneill	if ((hpd & HPD_DET) == HPD_DET)
264296064Sjmcneill		EVENTHANDLER_INVOKE(hdmi_event, dev, HDMI_EVENT_CONNECTED);
265296064Sjmcneill
266296064Sjmcneill	config_intrhook_disestablish(&sc->mode_hook);
267296064Sjmcneill}
268296064Sjmcneill
269296064Sjmcneillstatic int
270296064Sjmcneilla10hdmi_probe(device_t dev)
271296064Sjmcneill{
272296064Sjmcneill	if (!ofw_bus_status_okay(dev))
273296064Sjmcneill		return (ENXIO);
274296064Sjmcneill
275296064Sjmcneill	if (!ofw_bus_is_compatible(dev, "allwinner,sun7i-a20-hdmi"))
276296064Sjmcneill		return (ENXIO);
277296064Sjmcneill
278296064Sjmcneill	device_set_desc(dev, "Allwinner HDMI TX");
279296064Sjmcneill	return (BUS_PROBE_DEFAULT);
280296064Sjmcneill}
281296064Sjmcneill
282296064Sjmcneillstatic int
283296064Sjmcneilla10hdmi_attach(device_t dev)
284296064Sjmcneill{
285296064Sjmcneill	struct a10hdmi_softc *sc;
286296064Sjmcneill	int error;
287296064Sjmcneill
288296064Sjmcneill	sc = device_get_softc(dev);
289296064Sjmcneill
290296064Sjmcneill	if (bus_alloc_resources(dev, a10hdmi_spec, &sc->res)) {
291296064Sjmcneill		device_printf(dev, "cannot allocate resources for device\n");
292296064Sjmcneill		return (ENXIO);
293296064Sjmcneill	}
294296064Sjmcneill
295297627Sjmcneill	/* Setup clocks */
296308324Smmel	error = clk_get_by_ofw_name(dev, 0, "ahb", &sc->clk_ahb);
297297627Sjmcneill	if (error != 0) {
298297627Sjmcneill		device_printf(dev, "cannot find ahb clock\n");
299297627Sjmcneill		return (error);
300297627Sjmcneill	}
301308324Smmel	error = clk_get_by_ofw_name(dev, 0, "hdmi", &sc->clk_hdmi);
302297627Sjmcneill	if (error != 0) {
303297627Sjmcneill		device_printf(dev, "cannot find hdmi clock\n");
304297627Sjmcneill		return (error);
305297627Sjmcneill	}
306308324Smmel	error = clk_get_by_ofw_name(dev, 0, "lcd", &sc->clk_lcd);
307297627Sjmcneill	if (error != 0) {
308297627Sjmcneill		device_printf(dev, "cannot find lcd clock\n");
309297627Sjmcneill	}
310297627Sjmcneill	/* Enable HDMI clock */
311297627Sjmcneill	error = clk_enable(sc->clk_hdmi);
312297627Sjmcneill	if (error != 0) {
313297627Sjmcneill		device_printf(dev, "cannot enable hdmi clock\n");
314297627Sjmcneill		return (error);
315297627Sjmcneill	}
316297627Sjmcneill	/* Gating AHB clock for HDMI */
317297627Sjmcneill	error = clk_enable(sc->clk_ahb);
318297627Sjmcneill	if (error != 0) {
319297627Sjmcneill		device_printf(dev, "cannot enable ahb gate\n");
320297627Sjmcneill		return (error);
321297627Sjmcneill	}
322296064Sjmcneill
323296064Sjmcneill	a10hdmi_init(sc);
324296064Sjmcneill
325296064Sjmcneill	sc->mode_hook.ich_func = a10hdmi_hpd;
326296064Sjmcneill	sc->mode_hook.ich_arg = dev;
327296064Sjmcneill
328296064Sjmcneill	error = config_intrhook_establish(&sc->mode_hook);
329296064Sjmcneill	if (error != 0)
330296064Sjmcneill		return (error);
331296064Sjmcneill
332296064Sjmcneill	return (0);
333296064Sjmcneill}
334296064Sjmcneill
335296064Sjmcneillstatic int
336296064Sjmcneilla10hdmi_ddc_xfer(struct a10hdmi_softc *sc, uint16_t addr, uint8_t seg,
337296064Sjmcneill    uint8_t off, int len)
338296064Sjmcneill{
339296064Sjmcneill	uint32_t val;
340296064Sjmcneill	int retry;
341296064Sjmcneill
342296064Sjmcneill	/* Set FIFO direction to read */
343296064Sjmcneill	val = HDMI_READ(sc, DDC_CTRL);
344296064Sjmcneill	val &= ~CTRL_DDC_FIFO_DIR;
345296064Sjmcneill	val |= CTRL_DDC_FIFO_DIR_READ;
346296064Sjmcneill	HDMI_WRITE(sc, DDC_CTRL, val);
347296064Sjmcneill
348296064Sjmcneill	/* Setup DDC slave address */
349296064Sjmcneill	val = (addr << SLAVE_ADDR_SHIFT) | (seg << SLAVE_ADDR_SEG_SHIFT) |
350296064Sjmcneill	    (EDDC_ADDR << SLAVE_ADDR_EDDC_SHIFT) |
351296064Sjmcneill	    (off << SLAVE_ADDR_OFFSET_SHIFT);
352296064Sjmcneill	HDMI_WRITE(sc, DDC_SLAVE_ADDR, val);
353296064Sjmcneill
354296064Sjmcneill	/* Clear FIFO */
355296064Sjmcneill	val = HDMI_READ(sc, DDC_FIFO_CTRL);
356296064Sjmcneill	val |= FIFO_CTRL_CLEAR;
357296064Sjmcneill	HDMI_WRITE(sc, DDC_FIFO_CTRL, val);
358296064Sjmcneill
359296064Sjmcneill	/* Set transfer length */
360296064Sjmcneill	HDMI_WRITE(sc, DDC_BYTE_COUNTER, len);
361296064Sjmcneill
362296064Sjmcneill	/* Set command to "Explicit Offset Address Read" */
363296064Sjmcneill	HDMI_WRITE(sc, DDC_COMMAND, COMMAND_EOREAD);
364296064Sjmcneill
365296064Sjmcneill	/* Start transfer */
366296064Sjmcneill	val = HDMI_READ(sc, DDC_CTRL);
367296064Sjmcneill	val |= CTRL_DDC_ACMD_START;
368296064Sjmcneill	HDMI_WRITE(sc, DDC_CTRL, val);
369296064Sjmcneill
370296064Sjmcneill	/* Wait for command to start */
371296064Sjmcneill	retry = DDC_RETRY;
372296064Sjmcneill	while (--retry > 0) {
373296064Sjmcneill		val = HDMI_READ(sc, DDC_CTRL);
374296064Sjmcneill		if ((val & CTRL_DDC_ACMD_START) == 0)
375296064Sjmcneill			break;
376296064Sjmcneill		DELAY(DDC_DELAY);
377296064Sjmcneill	}
378296064Sjmcneill	if (retry == 0)
379296064Sjmcneill		return (ETIMEDOUT);
380296064Sjmcneill
381296064Sjmcneill	/* Ensure that the transfer completed */
382296064Sjmcneill	val = HDMI_READ(sc, DDC_INT_STATUS);
383296064Sjmcneill	if ((val & INT_STATUS_XFER_DONE) == 0)
384296064Sjmcneill		return (EIO);
385296064Sjmcneill
386296064Sjmcneill	return (0);
387296064Sjmcneill}
388296064Sjmcneill
389296064Sjmcneillstatic int
390296064Sjmcneilla10hdmi_ddc_read(struct a10hdmi_softc *sc, int block, uint8_t *edid)
391296064Sjmcneill{
392296064Sjmcneill	int resid, off, len, error;
393296064Sjmcneill	uint8_t *pbuf;
394296064Sjmcneill
395296064Sjmcneill	pbuf = edid;
396296064Sjmcneill	resid = EDID_LENGTH;
397296064Sjmcneill	off = (block & 1) ? EDID_LENGTH : 0;
398296064Sjmcneill
399296064Sjmcneill	while (resid > 0) {
400296064Sjmcneill		len = min(resid, DDC_BLKLEN);
401296064Sjmcneill		error = a10hdmi_ddc_xfer(sc, DDC_ADDR, block >> 1, off, len);
402296064Sjmcneill		if (error != 0)
403296064Sjmcneill			return (error);
404296064Sjmcneill
405296064Sjmcneill		bus_read_multi_1(sc->res, DDC_FIFO, pbuf, len);
406296064Sjmcneill
407296064Sjmcneill		pbuf += len;
408296064Sjmcneill		off += len;
409296064Sjmcneill		resid -= len;
410296064Sjmcneill	}
411296064Sjmcneill
412296064Sjmcneill	return (0);
413296064Sjmcneill}
414296064Sjmcneill
415297514Sjmcneillstatic int
416297514Sjmcneilla10hdmi_detect_hdmi_vsdb(uint8_t *edid)
417297514Sjmcneill{
418297514Sjmcneill	int off, p, btag, blen;
419297514Sjmcneill
420297514Sjmcneill	if (edid[EXT_TAG] != CEA_TAG_ID)
421297514Sjmcneill		return (0);
422297514Sjmcneill
423297514Sjmcneill	off = edid[CEA_DATA_OFF];
424297514Sjmcneill
425297514Sjmcneill	/* CEA data block collection starts at byte 4 */
426297514Sjmcneill	if (off <= CEA_DATA_START)
427297514Sjmcneill		return (0);
428297514Sjmcneill
429297514Sjmcneill	/* Parse the CEA data blocks */
430297514Sjmcneill	for (p = CEA_DATA_START; p < off;) {
431297514Sjmcneill		btag = BLOCK_TAG(edid[p]);
432297514Sjmcneill		blen = BLOCK_LEN(edid[p]);
433297514Sjmcneill
434297514Sjmcneill		/* Make sure the length is sane */
435297514Sjmcneill		if (p + blen + 1 > off)
436297514Sjmcneill			break;
437297514Sjmcneill
438297514Sjmcneill		/* Look for a VSDB with the HDMI 24-bit IEEE registration ID */
439297514Sjmcneill		if (btag == BLOCK_TAG_VSDB && blen >= HDMI_VSDB_MINLEN &&
440297514Sjmcneill		    memcmp(&edid[p + 1], HDMI_OUI, HDMI_OUI_LEN) == 0)
441297514Sjmcneill			return (1);
442297514Sjmcneill
443297514Sjmcneill		/* Next data block */
444297514Sjmcneill		p += (1 + blen);
445297514Sjmcneill	}
446297514Sjmcneill
447297514Sjmcneill	return (0);
448297514Sjmcneill}
449297514Sjmcneill
450296789Sjmcneillstatic void
451296789Sjmcneilla10hdmi_detect_hdmi(struct a10hdmi_softc *sc, int *phdmi, int *paudio)
452296064Sjmcneill{
453296064Sjmcneill	struct edid_info ei;
454296064Sjmcneill	uint8_t edid[EDID_LENGTH];
455296064Sjmcneill	int block;
456296064Sjmcneill
457296789Sjmcneill	*phdmi = *paudio = 0;
458296789Sjmcneill
459296064Sjmcneill	if (edid_parse(sc->edid, &ei) != 0)
460296789Sjmcneill		return;
461296064Sjmcneill
462296064Sjmcneill	/* Scan through extension blocks, looking for a CEA-861 block. */
463296064Sjmcneill	for (block = 1; block <= ei.edid_ext_block_count; block++) {
464296064Sjmcneill		if (a10hdmi_ddc_read(sc, block, edid) != 0)
465296789Sjmcneill			return;
466296064Sjmcneill
467297514Sjmcneill		if (a10hdmi_detect_hdmi_vsdb(edid) != 0) {
468296789Sjmcneill			*phdmi = 1;
469296789Sjmcneill			*paudio = ((edid[CEA_DTD] & DTD_BASIC_AUDIO) != 0);
470296789Sjmcneill			return;
471296789Sjmcneill		}
472296064Sjmcneill	}
473296064Sjmcneill}
474296064Sjmcneill
475296064Sjmcneillstatic int
476296064Sjmcneilla10hdmi_get_edid(device_t dev, uint8_t **edid, uint32_t *edid_len)
477296064Sjmcneill{
478296064Sjmcneill	struct a10hdmi_softc *sc;
479296064Sjmcneill	int error, retry;
480296064Sjmcneill
481296064Sjmcneill	sc = device_get_softc(dev);
482296064Sjmcneill	retry = DDC_READ_RETRY;
483296064Sjmcneill
484296064Sjmcneill	while (--retry > 0) {
485296064Sjmcneill		/* I2C software reset */
486296064Sjmcneill		HDMI_WRITE(sc, DDC_FIFO_CTRL, 0);
487296064Sjmcneill		HDMI_WRITE(sc, DDC_CTRL, CTRL_DDC_EN | CTRL_DDC_SWRST);
488296064Sjmcneill		DELAY(SWRST_DELAY);
489296064Sjmcneill		if (HDMI_READ(sc, DDC_CTRL) & CTRL_DDC_SWRST) {
490296064Sjmcneill			device_printf(dev, "DDC software reset failed\n");
491296064Sjmcneill			return (ENXIO);
492296064Sjmcneill		}
493296064Sjmcneill
494296064Sjmcneill		/* Configure DDC clock */
495296064Sjmcneill		HDMI_WRITE(sc, DDC_CLOCK, DDC_CLOCK_M | DDC_CLOCK_N);
496296064Sjmcneill
497296064Sjmcneill		/* Read EDID block */
498296064Sjmcneill		error = a10hdmi_ddc_read(sc, 0, sc->edid);
499296064Sjmcneill		if (error == 0) {
500296064Sjmcneill			*edid = sc->edid;
501296064Sjmcneill			*edid_len = sizeof(sc->edid);
502296064Sjmcneill			break;
503296064Sjmcneill		}
504296064Sjmcneill	}
505296064Sjmcneill
506296064Sjmcneill	if (error == 0)
507296789Sjmcneill		a10hdmi_detect_hdmi(sc, &sc->has_hdmi, &sc->has_audio);
508296064Sjmcneill	else
509296789Sjmcneill		sc->has_hdmi = sc->has_audio = 0;
510296064Sjmcneill
511296064Sjmcneill	return (error);
512296064Sjmcneill}
513296064Sjmcneill
514296064Sjmcneillstatic void
515296064Sjmcneilla10hdmi_set_audiomode(device_t dev, const struct videomode *mode)
516296064Sjmcneill{
517296064Sjmcneill	struct a10hdmi_softc *sc;
518296064Sjmcneill	uint32_t val;
519296064Sjmcneill	int retry;
520296064Sjmcneill
521296064Sjmcneill	sc = device_get_softc(dev);
522296064Sjmcneill
523296064Sjmcneill	/* Disable and reset audio module and wait for reset bit to clear */
524296064Sjmcneill	HDMI_WRITE(sc, HDMI_AUD_CTRL, AUD_CTRL_RST);
525296064Sjmcneill	for (retry = HDMI_AUDIO_RESET_RETRY; retry > 0; retry--) {
526296064Sjmcneill		val = HDMI_READ(sc, HDMI_AUD_CTRL);
527296064Sjmcneill		if ((val & AUD_CTRL_RST) == 0)
528296064Sjmcneill			break;
529296064Sjmcneill	}
530296064Sjmcneill	if (retry == 0) {
531296064Sjmcneill		device_printf(dev, "timeout waiting for audio module\n");
532296064Sjmcneill		return;
533296064Sjmcneill	}
534296064Sjmcneill
535296064Sjmcneill	if (!sc->has_audio)
536296064Sjmcneill		return;
537296064Sjmcneill
538296064Sjmcneill	/* DMA and FIFO control */
539296064Sjmcneill	HDMI_WRITE(sc, HDMI_ADMA_CTRL, HDMI_ADMA_MODE_DDMA);
540296064Sjmcneill
541296064Sjmcneill	/* Audio format control (LPCM, S16LE, stereo) */
542296064Sjmcneill	HDMI_WRITE(sc, HDMI_AUD_FMT, AUD_FMT_CH(HDMI_AUDIO_CHANNELS));
543296064Sjmcneill
544296064Sjmcneill	/* Channel mappings */
545296064Sjmcneill	HDMI_WRITE(sc, HDMI_PCM_CTRL, HDMI_AUDIO_CHANNELMAP);
546296064Sjmcneill
547296064Sjmcneill	/* Clocks */
548296064Sjmcneill	HDMI_WRITE(sc, HDMI_AUD_CTS,
549296064Sjmcneill	    HDMI_AUDIO_CTS(mode->dot_clock, HDMI_AUDIO_N));
550296064Sjmcneill	HDMI_WRITE(sc, HDMI_AUD_N, HDMI_AUDIO_N);
551296064Sjmcneill
552296064Sjmcneill	/* Set sampling frequency to 48 kHz, word length to 16-bit */
553296064Sjmcneill	HDMI_WRITE(sc, HDMI_AUD_CH_STATUS0, CH_STATUS0_FS_FREQ_48);
554296064Sjmcneill	HDMI_WRITE(sc, HDMI_AUD_CH_STATUS1, CH_STATUS1_WORD_LEN_16);
555296064Sjmcneill
556296064Sjmcneill	/* Enable */
557296064Sjmcneill	HDMI_WRITE(sc, HDMI_AUD_CTRL, AUD_CTRL_EN);
558296064Sjmcneill}
559296064Sjmcneill
560296064Sjmcneillstatic int
561297627Sjmcneilla10hdmi_get_tcon_config(struct a10hdmi_softc *sc, int *div, int *dbl)
562297627Sjmcneill{
563297627Sjmcneill	uint64_t lcd_fin, lcd_fout;
564297627Sjmcneill	clk_t clk_lcd_parent;
565297627Sjmcneill	const char *pname;
566297627Sjmcneill	int error;
567297627Sjmcneill
568297627Sjmcneill	error = clk_get_parent(sc->clk_lcd, &clk_lcd_parent);
569297627Sjmcneill	if (error != 0)
570297627Sjmcneill		return (error);
571297627Sjmcneill
572297627Sjmcneill	/* Get the LCD CH1 special clock 2 divider */
573297627Sjmcneill	error = clk_get_freq(sc->clk_lcd, &lcd_fout);
574297627Sjmcneill	if (error != 0)
575297627Sjmcneill		return (error);
576297627Sjmcneill	error = clk_get_freq(clk_lcd_parent, &lcd_fin);
577297627Sjmcneill	if (error != 0)
578297627Sjmcneill		return (error);
579297627Sjmcneill	*div = lcd_fin / lcd_fout;
580297627Sjmcneill
581297627Sjmcneill	/* Detect LCD CH1 special clock using a 1X or 2X source */
582297627Sjmcneill	/* XXX */
583297627Sjmcneill	pname = clk_get_name(clk_lcd_parent);
584297627Sjmcneill	if (strcmp(pname, "pll3-1x") == 0 || strcmp(pname, "pll7-1x") == 0)
585297627Sjmcneill		*dbl = 0;
586297627Sjmcneill	else
587297627Sjmcneill		*dbl = 1;
588297627Sjmcneill
589297627Sjmcneill	return (0);
590297627Sjmcneill}
591297627Sjmcneill
592297627Sjmcneillstatic int
593296064Sjmcneilla10hdmi_set_videomode(device_t dev, const struct videomode *mode)
594296064Sjmcneill{
595296064Sjmcneill	struct a10hdmi_softc *sc;
596296064Sjmcneill	int error, clk_div, clk_dbl;
597296064Sjmcneill	int dblscan, hfp, hspw, hbp, vfp, vspw, vbp;
598296064Sjmcneill	uint32_t val;
599296064Sjmcneill
600296064Sjmcneill	sc = device_get_softc(dev);
601296064Sjmcneill	dblscan = !!(mode->flags & VID_DBLSCAN);
602296064Sjmcneill	hfp = mode->hsync_start - mode->hdisplay;
603296064Sjmcneill	hspw = mode->hsync_end - mode->hsync_start;
604296064Sjmcneill	hbp = mode->htotal - mode->hsync_start;
605296064Sjmcneill	vfp = mode->vsync_start - mode->vdisplay;
606296064Sjmcneill	vspw = mode->vsync_end - mode->vsync_start;
607296064Sjmcneill	vbp = mode->vtotal - mode->vsync_start;
608296064Sjmcneill
609297627Sjmcneill	error = a10hdmi_get_tcon_config(sc, &clk_div, &clk_dbl);
610297627Sjmcneill	if (error != 0) {
611297627Sjmcneill		device_printf(dev, "couldn't get tcon config: %d\n", error);
612296064Sjmcneill		return (error);
613297627Sjmcneill	}
614296064Sjmcneill
615296064Sjmcneill	/* Clear interrupt status */
616296064Sjmcneill	HDMI_WRITE(sc, HDMI_INT_STATUS, HDMI_READ(sc, HDMI_INT_STATUS));
617296064Sjmcneill
618296064Sjmcneill	/* Clock setup */
619296064Sjmcneill	val = HDMI_READ(sc, HDMI_PADCTRL1);
620296064Sjmcneill	val &= ~PADCTRL1_REG_CKSS;
621296064Sjmcneill	val |= (clk_dbl ? PADCTRL1_REG_CKSS_2X : PADCTRL1_REG_CKSS_1X);
622296064Sjmcneill	HDMI_WRITE(sc, HDMI_PADCTRL1, val);
623296064Sjmcneill	HDMI_WRITE(sc, HDMI_PLLCTRL0, PLLCTRL0_PLL_EN | PLLCTRL0_BWS |
624296064Sjmcneill	    PLLCTRL0_HV_IS_33 | PLLCTRL0_LDO1_EN | PLLCTRL0_LDO2_EN |
625296064Sjmcneill	    PLLCTRL0_SDIV2 | PLLCTRL0_VCO_GAIN | PLLCTRL0_S |
626296064Sjmcneill	    PLLCTRL0_CP_S | PLLCTRL0_CS | PLLCTRL0_PREDIV(clk_div) |
627296064Sjmcneill	    PLLCTRL0_VCO_S);
628296064Sjmcneill
629296064Sjmcneill	/* Setup display settings */
630297514Sjmcneill	if (bootverbose)
631297514Sjmcneill		device_printf(dev, "HDMI: %s, Audio: %s\n",
632297514Sjmcneill		    sc->has_hdmi ? "yes" : "no", sc->has_audio ? "yes" : "no");
633296789Sjmcneill	val = 0;
634296789Sjmcneill	if (sc->has_hdmi)
635296789Sjmcneill		val |= VID_CTRL_HDMI_MODE;
636296064Sjmcneill	if (mode->flags & VID_INTERLACE)
637296064Sjmcneill		val |= VID_CTRL_INTERLACE;
638296064Sjmcneill	if (mode->flags & VID_DBLSCAN)
639296064Sjmcneill		val |= VID_CTRL_REPEATER_2X;
640296064Sjmcneill	HDMI_WRITE(sc, HDMI_VID_CTRL, val);
641296064Sjmcneill
642296064Sjmcneill	/* Setup display timings */
643296064Sjmcneill	HDMI_WRITE(sc, HDMI_VID_TIMING0,
644296064Sjmcneill	    VID_ACT_V(mode->vdisplay) | VID_ACT_H(mode->hdisplay << dblscan));
645296064Sjmcneill	HDMI_WRITE(sc, HDMI_VID_TIMING1,
646296064Sjmcneill	    VID_VBP(vbp) | VID_HBP(hbp << dblscan));
647296064Sjmcneill	HDMI_WRITE(sc, HDMI_VID_TIMING2,
648296064Sjmcneill	    VID_VFP(vfp) | VID_HFP(hfp << dblscan));
649296064Sjmcneill	HDMI_WRITE(sc, HDMI_VID_TIMING3,
650296064Sjmcneill	    VID_VSPW(vspw) | VID_HSPW(hspw << dblscan));
651296064Sjmcneill	val = TX_CLOCK_NORMAL;
652296064Sjmcneill	if (mode->flags & VID_PVSYNC)
653296064Sjmcneill		val |= VID_VSYNC_ACTSEL;
654296064Sjmcneill	if (mode->flags & VID_PHSYNC)
655296064Sjmcneill		val |= VID_HSYNC_ACTSEL;
656296064Sjmcneill	HDMI_WRITE(sc, HDMI_VID_TIMING4, val);
657296064Sjmcneill
658296064Sjmcneill	/* This is an ordered list of infoframe packets that the HDMI
659296064Sjmcneill	 * transmitter will send. Transmit packets in the following order:
660296064Sjmcneill	 *  1. General control packet
661296064Sjmcneill	 *  2. AVI infoframe
662296064Sjmcneill	 *  3. Audio infoframe
663296064Sjmcneill	 * There are 2 registers with 4 slots each. The list is terminated
664296064Sjmcneill	 * with the special PKT_END marker.
665296064Sjmcneill	 */
666296064Sjmcneill	HDMI_WRITE(sc, HDMI_PKTCTRL0,
667296064Sjmcneill	    PKTCTRL_PACKET(0, PKT_GC) | PKTCTRL_PACKET(1, PKT_AVI) |
668296064Sjmcneill	    PKTCTRL_PACKET(2, PKT_AI) | PKTCTRL_PACKET(3, PKT_END));
669296064Sjmcneill	HDMI_WRITE(sc, HDMI_PKTCTRL1, 0);
670296064Sjmcneill
671296064Sjmcneill	/* Setup audio */
672296064Sjmcneill	a10hdmi_set_audiomode(dev, mode);
673296064Sjmcneill
674296064Sjmcneill	return (0);
675296064Sjmcneill}
676296064Sjmcneill
677296064Sjmcneillstatic int
678296064Sjmcneilla10hdmi_enable(device_t dev, int onoff)
679296064Sjmcneill{
680296064Sjmcneill	struct a10hdmi_softc *sc;
681296064Sjmcneill	uint32_t val;
682296064Sjmcneill
683296064Sjmcneill	sc = device_get_softc(dev);
684296064Sjmcneill
685296064Sjmcneill	/* Enable or disable video output */
686296064Sjmcneill	val = HDMI_READ(sc, HDMI_VID_CTRL);
687296064Sjmcneill	if (onoff)
688296064Sjmcneill		val |= VID_CTRL_VIDEO_EN;
689296064Sjmcneill	else
690296064Sjmcneill		val &= ~VID_CTRL_VIDEO_EN;
691296064Sjmcneill	HDMI_WRITE(sc, HDMI_VID_CTRL, val);
692296064Sjmcneill
693296064Sjmcneill	return (0);
694296064Sjmcneill}
695296064Sjmcneill
696296064Sjmcneillstatic device_method_t a10hdmi_methods[] = {
697296064Sjmcneill	/* Device interface */
698296064Sjmcneill	DEVMETHOD(device_probe,		a10hdmi_probe),
699296064Sjmcneill	DEVMETHOD(device_attach,	a10hdmi_attach),
700296064Sjmcneill
701296064Sjmcneill	/* HDMI interface */
702296064Sjmcneill	DEVMETHOD(hdmi_get_edid,	a10hdmi_get_edid),
703296064Sjmcneill	DEVMETHOD(hdmi_set_videomode,	a10hdmi_set_videomode),
704296064Sjmcneill	DEVMETHOD(hdmi_enable,		a10hdmi_enable),
705296064Sjmcneill
706296064Sjmcneill	DEVMETHOD_END
707296064Sjmcneill};
708296064Sjmcneill
709296064Sjmcneillstatic driver_t a10hdmi_driver = {
710296064Sjmcneill	"a10hdmi",
711296064Sjmcneill	a10hdmi_methods,
712296064Sjmcneill	sizeof(struct a10hdmi_softc),
713296064Sjmcneill};
714296064Sjmcneill
715296064Sjmcneillstatic devclass_t a10hdmi_devclass;
716296064Sjmcneill
717296064SjmcneillDRIVER_MODULE(a10hdmi, simplebus, a10hdmi_driver, a10hdmi_devclass, 0, 0);
718296064SjmcneillMODULE_VERSION(a10hdmi, 1);
719