[U-Boot] [PATCH v2 11/11] sunxi: video: Add DDC & EDID support

Hans de Goede hdegoede at redhat.com
Fri Dec 19 18:10:41 CET 2014


Add DDC & EDID support and use it to automatically select the native mode of
the attached monitor. This can be disabled by adding edid=0 as option
to the video-mode env. variable.

Signed-off-by: Hans de Goede <hdegoede at redhat.com>
---
 arch/arm/include/asm/arch-sunxi/display.h |  85 +++++++++++++++++
 drivers/video/sunxi_display.c             | 154 +++++++++++++++++++++++++++++-
 include/configs/sunxi-common.h            |   1 +
 3 files changed, 239 insertions(+), 1 deletion(-)

diff --git a/arch/arm/include/asm/arch-sunxi/display.h b/arch/arm/include/asm/arch-sunxi/display.h
index ddb71c1..8c4835e 100644
--- a/arch/arm/include/asm/arch-sunxi/display.h
+++ b/arch/arm/include/asm/arch-sunxi/display.h
@@ -107,6 +107,48 @@ struct sunxi_hdmi_reg {
 	u32 pad_ctrl1;			/* 0x204 */
 	u32 pll_ctrl;			/* 0x208 */
 	u32 pll_dbg0;			/* 0x20c */
+	u32 pll_dbg1;			/* 0x210 */
+	u32 hpd_cec;			/* 0x214 */
+	u8 res1[0x28];			/* 0x218 */
+	u32 spd_pkt;			/* 0x240 */
+	u8 res2[0xac];			/* 0x244 */
+	u32 pkt_ctrl0;			/* 0x2f0 */
+	u32 pkt_ctrl1;			/* 0x2f4 */
+	u8 res3[0x18];			/* 0x2f8 */
+	u32 audio_sample_count;		/* 0x310 */
+	u8 res4[0xec];			/* 0x314 */
+	u32 audio_tx_fifo;		/* 0x400 */
+	u8 res5[0xfc];			/* 0x404 */
+#ifndef CONFIG_MACH_SUN6I
+	u32 ddc_ctrl;			/* 0x500 */
+	u32 ddc_addr;			/* 0x504 */
+	u32 ddc_int_mask;		/* 0x508 */
+	u32 ddc_int_status;		/* 0x50c */
+	u32 ddc_fifo_ctrl;		/* 0x510 */
+	u32 ddc_fifo_status;		/* 0x514 */
+	u32 ddc_fifo_data;		/* 0x518 */
+	u32 ddc_byte_count;		/* 0x51c */
+	u32 ddc_cmnd;			/* 0x520 */
+	u32 ddc_exreg;			/* 0x524 */
+	u32 ddc_clock;			/* 0x528 */
+	u8 res6[0x14];			/* 0x52c */
+	u32 ddc_line_ctrl;		/* 0x540 */
+#else
+	u32 ddc_ctrl;			/* 0x500 */
+	u32 ddc_exreg;			/* 0x504 */
+	u32 ddc_cmnd;			/* 0x508 */
+	u32 ddc_addr;			/* 0x50c */
+	u32 ddc_int_mask;		/* 0x510 */
+	u32 ddc_int_status;		/* 0x514 */
+	u32 ddc_fifo_ctrl;		/* 0x518 */
+	u32 ddc_fifo_status;		/* 0x51c */
+	u32 ddc_clock;			/* 0x520 */
+	u32 ddc_timeout;		/* 0x524 */
+	u8 res6[0x18];			/* 0x528 */
+	u32 ddc_dbg;			/* 0x540 */
+	u8 res7[0x3c];			/* 0x544 */
+	u32 ddc_fifo_data;		/* 0x580 */
+#endif
 };
 
 /*
@@ -182,6 +224,49 @@ struct sunxi_hdmi_reg {
 #define SUNXI_HDMI_PLL_DBG0_PLL3		(0 << 21)
 #define SUNXI_HDMI_PLL_DBG0_PLL7		(1 << 21)
 
+#ifdef CONFIG_MACH_SUN6I
+#define SUNXI_HMDI_DDC_CTRL_ENABLE		(1 << 0)
+#define SUNXI_HMDI_DDC_CTRL_SCL_ENABLE		(1 << 4)
+#define SUNXI_HMDI_DDC_CTRL_SDA_ENABLE		(1 << 6)
+#define SUNXI_HMDI_DDC_CTRL_START		(1 << 27)
+#define SUNXI_HMDI_DDC_CTRL_RESET		(1 << 31)
+#else
+#define SUNXI_HMDI_DDC_CTRL_RESET		(1 << 0)
+/* sun4i / sun5i / sun7i do not have a separate line_ctrl reg */
+#define SUNXI_HMDI_DDC_CTRL_SDA_ENABLE		0
+#define SUNXI_HMDI_DDC_CTRL_SCL_ENABLE		0
+#define SUNXI_HMDI_DDC_CTRL_START		(1 << 30)
+#define SUNXI_HMDI_DDC_CTRL_ENABLE		(1 << 31)
+#endif
+
+#ifdef CONFIG_MACH_SUN6I
+#define SUNXI_HMDI_DDC_ADDR_SLAVE_ADDR		(0xa0 << 0)
+#else
+#define SUNXI_HMDI_DDC_ADDR_SLAVE_ADDR		(0x50 << 0)
+#endif
+#define SUNXI_HMDI_DDC_ADDR_OFFSET(n)		(((n) & 0xff) << 8)
+#define SUNXI_HMDI_DDC_ADDR_EDDC_ADDR		(0x60 << 16)
+#define SUNXI_HMDI_DDC_ADDR_EDDC_SEGMENT(n)	((n) << 24)
+
+#ifdef CONFIG_MACH_SUN6I
+#define SUNXI_HDMI_DDC_FIFO_CTRL_CLEAR		(1 << 15)
+#else
+#define SUNXI_HDMI_DDC_FIFO_CTRL_CLEAR		(1 << 31)
+#endif
+
+#define SUNXI_HDMI_DDC_CMND_EXPLICIT_EDDC_READ	6
+#define SUNXI_HDMI_DDC_CMND_IMPLICIT_EDDC_READ	7
+
+#ifdef CONFIG_MACH_SUN6I
+#define SUNXI_HDMI_DDC_CLOCK			0x61
+#else
+/* N = 5,M=1 Fscl= Ftmds/2/10/2^N/(M+1) */
+#define SUNXI_HDMI_DDC_CLOCK			0x0d
+#endif
+
+#define SUNXI_HMDI_DDC_LINE_CTRL_SCL_ENABLE	(1 << 8)
+#define SUNXI_HMDI_DDC_LINE_CTRL_SDA_ENABLE	(1 << 9)
+
 int sunxi_simplefb_setup(void *blob);
 
 #endif /* _SUNXI_DISPLAY_H */
diff --git a/drivers/video/sunxi_display.c b/drivers/video/sunxi_display.c
index 18fa83d..0997740 100644
--- a/drivers/video/sunxi_display.c
+++ b/drivers/video/sunxi_display.c
@@ -13,6 +13,7 @@
 #include <asm/arch/display.h>
 #include <asm/global_data.h>
 #include <asm/io.h>
+#include <errno.h>
 #include <fdtdec.h>
 #include <fdt_support.h>
 #include <video_fb.h>
@@ -25,6 +26,22 @@ struct sunxi_display {
 	bool enabled;
 } sunxi_display;
 
+/*
+ * Wait up to 200ms for value to be set in given part of reg.
+ */
+static int await_completion(u32 *reg, u32 mask, u32 val)
+{
+	unsigned long tmo = timer_get_us() + 200000;
+
+	while ((readl(reg) & mask) != val) {
+		if (timer_get_us() > tmo) {
+			printf("DDC: timeout reading EDID\n");
+			return -ETIME;
+		}
+	}
+	return 0;
+}
+
 static int sunxi_hdmi_hpd_detect(void)
 {
 	struct sunxi_ccm_reg * const ccm =
@@ -72,6 +89,133 @@ static void sunxi_hdmi_shutdown(void)
 	clock_set_pll3(0);
 }
 
+static int sunxi_hdmi_ddc_do_command(u32 cmnd, int offset, int n)
+{
+	struct sunxi_hdmi_reg * const hdmi =
+		(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
+
+	setbits_le32(&hdmi->ddc_fifo_ctrl, SUNXI_HDMI_DDC_FIFO_CTRL_CLEAR);
+	writel(SUNXI_HMDI_DDC_ADDR_EDDC_SEGMENT(offset >> 8) |
+	       SUNXI_HMDI_DDC_ADDR_EDDC_ADDR |
+	       SUNXI_HMDI_DDC_ADDR_OFFSET(offset) |
+	       SUNXI_HMDI_DDC_ADDR_SLAVE_ADDR, &hdmi->ddc_addr);
+#ifndef CONFIG_MACH_SUN6I
+	writel(n, &hdmi->ddc_byte_count);
+	writel(cmnd, &hdmi->ddc_cmnd);
+#else
+	writel(n << 16 | cmnd, &hdmi->ddc_cmnd);
+#endif
+	setbits_le32(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_START);
+
+	return await_completion(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_START, 0);
+}
+
+static int sunxi_hdmi_ddc_read(int offset, u8 *buf, int count)
+{
+	struct sunxi_hdmi_reg * const hdmi =
+		(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
+	int i, n;
+
+	while (count > 0) {
+		if (count > 16)
+			n = 16;
+		else
+			n = count;
+
+		if (sunxi_hdmi_ddc_do_command(
+				SUNXI_HDMI_DDC_CMND_EXPLICIT_EDDC_READ,
+				offset, n))
+			return -ETIME;
+
+		for (i = 0; i < n; i++)
+			*buf++ = readb(&hdmi->ddc_fifo_data);
+
+		offset += n;
+		count -= n;
+	}
+
+	return 0;
+}
+
+static int sunxi_hdmi_edid_get_mode(struct ctfb_res_modes *mode)
+{
+	struct edid1_info edid1;
+	struct edid_detailed_timing *t =
+		(struct edid_detailed_timing *)edid1.monitor_details.timing;
+	struct sunxi_hdmi_reg * const hdmi =
+		(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
+	struct sunxi_ccm_reg * const ccm =
+		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+	int i, r, retries = 2;
+
+	/* SUNXI_HDMI_CTRL_ENABLE & PAD_CTRL0 are already set by hpd_detect */
+	writel(SUNXI_HDMI_PAD_CTRL1 | SUNXI_HDMI_PAD_CTRL1_HALVE,
+	       &hdmi->pad_ctrl1);
+	writel(SUNXI_HDMI_PLL_CTRL | SUNXI_HDMI_PLL_CTRL_DIV(15),
+	       &hdmi->pll_ctrl);
+	writel(SUNXI_HDMI_PLL_DBG0_PLL3, &hdmi->pll_dbg0);
+
+	/* Reset i2c controller */
+	setbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_DDC_GATE);
+	writel(SUNXI_HMDI_DDC_CTRL_ENABLE |
+	       SUNXI_HMDI_DDC_CTRL_SDA_ENABLE |
+	       SUNXI_HMDI_DDC_CTRL_SCL_ENABLE |
+	       SUNXI_HMDI_DDC_CTRL_RESET, &hdmi->ddc_ctrl);
+	if (await_completion(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_RESET, 0))
+		return -EIO;
+
+	writel(SUNXI_HDMI_DDC_CLOCK, &hdmi->ddc_clock);
+#ifndef CONFIG_MACH_SUN6I
+	writel(SUNXI_HMDI_DDC_LINE_CTRL_SDA_ENABLE |
+	       SUNXI_HMDI_DDC_LINE_CTRL_SCL_ENABLE, &hdmi->ddc_line_ctrl);
+#endif
+
+	do {
+		r = sunxi_hdmi_ddc_read(0, (u8 *)&edid1, 128);
+		if (r)
+			continue;
+		r = edid_check_checksum((u8 *)&edid1);
+		if (r) {
+			printf("EDID: checksum error%s\n",
+			       retries ? ", retrying" : "");
+		}
+	} while (r && retries--);
+
+	/* Disable DDC engine, no longer needed */
+	clrbits_le32(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_ENABLE);
+	clrbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_DDC_GATE);
+
+	if (r)
+		return r;
+
+	r = edid_check_info(&edid1);
+	if (r) {
+		printf("EDID: invalid EDID data\n");
+		return -EINVAL;
+	}
+
+	/* We want version 1.3 or 1.2 with detailed timing info */
+	if (edid1.version != 1 || (edid1.revision < 3 &&
+			!EDID1_INFO_FEATURE_PREFERRED_TIMING_MODE(edid1))) {
+		printf("EDID: unsupported version %d.%d\n",
+		       edid1.version, edid1.revision);
+		return -EINVAL;
+	}
+
+	/* Take the first usable detailed timing */
+	for (i = 0; i < 4; i++, t++) {
+		r = video_edid_dtd_to_ctfb_res_modes(t, mode);
+		if (r == 0)
+			break;
+	}
+	if (i == 4) {
+		printf("EDID: no usable detailed timing found\n");
+		return -ENOENT;
+	}
+
+	return 0;
+}
+
 /*
  * This is the entity that mixes and matches the different layers and inputs.
  * Allwinner calls it the back-end, but i like composer better.
@@ -363,9 +507,10 @@ void *video_hw_init(void)
 {
 	static GraphicDevice *graphic_device = &sunxi_display.graphic_device;
 	const struct ctfb_res_modes *mode;
+	struct ctfb_res_modes edid_mode;
 	const char *options;
 	unsigned int depth;
-	int ret, hpd;
+	int ret, hpd, edid;
 
 	memset(&sunxi_display, 0, sizeof(struct sunxi_display));
 
@@ -375,6 +520,7 @@ void *video_hw_init(void)
 
 	video_get_ctfb_res_modes(RES_MODE_1024x768, 24, &mode, &depth, &options);
 	hpd = video_get_option_int(options, "hpd", 1);
+	edid = video_get_option_int(options, "edid", 1);
 
 	/* Always call hdp_detect, as it also enables various clocks, etc. */
 	ret = sunxi_hdmi_hpd_detect();
@@ -385,6 +531,12 @@ void *video_hw_init(void)
 	if (ret)
 		printf("HDMI connected: ");
 
+	/* Check edid if requested and we've a cable plugged in */
+	if (edid && ret) {
+		if (sunxi_hdmi_edid_get_mode(&edid_mode) == 0)
+			mode = &edid_mode;
+	}
+
 	if (mode->vmode != FB_VMODE_NONINTERLACED) {
 		printf("Only non-interlaced modes supported, falling back to 1024x768\n");
 		mode = &res_mode_init[RES_MODE_1024x768];
diff --git a/include/configs/sunxi-common.h b/include/configs/sunxi-common.h
index 77965f7..30b7ee4 100644
--- a/include/configs/sunxi-common.h
+++ b/include/configs/sunxi-common.h
@@ -213,6 +213,7 @@
 #define CONFIG_VIDEO_SW_CURSOR
 #define CONFIG_VIDEO_LOGO
 #define CONFIG_VIDEO_STD_TIMINGS
+#define CONFIG_I2C_EDID
 
 /* allow both serial and cfb console. */
 #define CONFIG_CONSOLE_MUX
-- 
2.1.0



More information about the U-Boot mailing list