Skip to contentSkip to conversation

sunxi: display: Add DDC & EDID support

PATCH New Hans de Goede 3 participants 12 messages Nov 24, 2014 – Nov 25, 2014

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

cover sunxi: display: Add DDC & EDID support

Hi Ian and Anatolij,

The first 2 patches here are minor fixes to the existing sunxi display code, a quick review would be appreciated, so that I can queue them up for the next pull-req.

The other 3 patches are for next, so you can take your time reviewing them.

Anatolij, patches 3 and 4 add 2 new edid utility functions, so a review from you would be appreciated. Patch 5 uses them, so your input there would be appreciated too.

Regards,

Hans

#1 lore raw

PATCH 1/5 sunxi: display: Remove unused HDMI register addr defines

All hdmi code uses struct sunxi_hdmi_reg, so these are not needed.

---
 arch/arm/include/asm/arch-sunxi/display.h | 13 -------------
 1 file changed, 13 deletions(-)
arch/arm/include/asm/arch-sunxi/display.h +0 -13
index c17c3c3..ddb71c1 100644
@@ -91,19 +91,6 @@ struct sunxi_lcdc_reg {
 	u32 tcon1_io_tristate;		/* 0xf4 */
 };
 
-#define SUNXI_HDMI_CTRL			0x004
-#define SUNXI_HDMI_INT_CTRL		0x008
-#define SUNXI_HDMI_HPD			0x00c
-#define SUNXI_HDMI_VIDEO_CTRL		0x010
-#define SUNXI_HDMI_VIDEO_SIZE		0x014
-#define SUNXI_HDMI_VIDEO_BP		0x018
-#define SUNXI_HDMI_VIDEO_FP		0x01c
-#define SUNXI_HDMI_VIDEO_SPW		0x020
-#define SUNXI_HDMI_VIDEO_POLARITY	0x024
-#define SUNXI_HDMI_TX_DRIVER0		0x200
-#define SUNXI_HDMI_TX_DRIVER1		0x204
-#define SUNXI_HDMI_TX_DRIVER2		0x208
-#define SUNXI_HDMI_TX_DRIVER3		0x20C
 struct sunxi_hdmi_reg {
 	u32 version_id;			/* 0x000 */
 	u32 ctrl;			/* 0x004 */
Signature · 1 line
2.1.0
#4 lore raw

PATCH 2/5 sunxi: display: Do not use eprintf in early boot

In reply to Hans de Goede

stderr is not hooked up yet as this point, so use a regular printf.

---
 drivers/video/sunxi_display.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/video/sunxi_display.c +1 -1
index fb28c23..5154084 100644
@@ -351,7 +351,7 @@ retry:
 	    (SUNXI_HDMI_IRQ_STATUS_FIFO_UF | SUNXI_HDMI_IRQ_STATUS_FIFO_OF)) {
 		if (retries--)
 			goto retry;
-		eprintf("HDMI fifo under or overrun\n");
+		printf("HDMI fifo under or overrun\n");
 	}
 }
 
Signature · 1 line
2.1.0
#7 lore raw

PATCH 3/5 edid: Add an edid_dtd_to_fbmode() helper function

In reply to Hans de Goede

Various u-boot video drivers use fb_videomode structs to store timing info, add a helper function to convert an EDID detailed timing into a fb_videomode struct.

---
 common/edid.c  | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 include/edid.h | 18 ++++++++++++++++
 2 files changed, 83 insertions(+)
common/edid.c +65 -0
index e66108f..e41cd3e 100644
@@ -12,6 +12,7 @@
 
 #include <common.h>
 #include <edid.h>
+#include <errno.h>
 #include <linux/ctype.h>
 #include <linux/string.h>
 
@@ -288,3 +289,67 @@ void edid_print_info(struct edid1_info *edid_info)
 	if (!have_timing)
 		printf("\tNone\n");
 }
+
+int edid_dtd_to_fbmode(struct edid_detailed_timing *t,
+		       struct fb_videomode *mode, char *name, int name_len)
+{
+	int margin, h_total, v_total;
+
+	if (EDID_DETAILED_TIMING_PIXEL_CLOCK(*t) == 0 ||
+	    EDID_DETAILED_TIMING_HORIZONTAL_ACTIVE(*t) == 0 ||
+	    EDID_DETAILED_TIMING_HORIZONTAL_BLANKING(*t) == 0 ||
+	    EDID_DETAILED_TIMING_VERTICAL_ACTIVE(*t) == 0 ||
+	    EDID_DETAILED_TIMING_VERTICAL_BLANKING(*t) == 0 ||
+	    EDID_DETAILED_TIMING_HSYNC_OFFSET(*t) == 0 ||
+	    EDID_DETAILED_TIMING_HSYNC_PULSE_WIDTH(*t) == 0 ||
+	    EDID_DETAILED_TIMING_VSYNC_OFFSET(*t) == 0 ||
+	    EDID_DETAILED_TIMING_VSYNC_PULSE_WIDTH(*t) == 0)
+		return -EINVAL;
+
+	mode->name = name;
+	mode->xres = EDID_DETAILED_TIMING_HORIZONTAL_ACTIVE(*t);
+	mode->yres = EDID_DETAILED_TIMING_VERTICAL_ACTIVE(*t);
+	mode->pixclock = (EDID_DETAILED_TIMING_PIXEL_CLOCK(*t) + 500) /
+			 1000;
+
+	mode->left_margin = EDID_DETAILED_TIMING_HSYNC_OFFSET(*t);
+	mode->hsync_len = EDID_DETAILED_TIMING_HSYNC_PULSE_WIDTH(*t);
+	margin = EDID_DETAILED_TIMING_HORIZONTAL_BLANKING(*t) -
+			(mode->left_margin + mode->hsync_len);
+	if (margin <= 0)
+		return -EINVAL;
+
+	mode->right_margin = margin;
+
+	mode->lower_margin = EDID_DETAILED_TIMING_VSYNC_OFFSET(*t);
+	mode->vsync_len = EDID_DETAILED_TIMING_VSYNC_PULSE_WIDTH(*t);
+	margin = EDID_DETAILED_TIMING_VERTICAL_BLANKING(*t) -
+			(mode->lower_margin + mode->vsync_len);
+	if (margin <= 0)
+		return -EINVAL;
+
+	mode->upper_margin = margin;
+
+	if (EDID_DETAILED_TIMING_FLAG_INTERLEAVED(*t))
+		mode->vmode = FB_VMODE_INTERLACED;
+	else
+		mode->vmode = FB_VMODE_NONINTERLACED;
+
+	mode->sync = 0;
+	if (EDID_DETAILED_TIMING_FLAG_HSYNC_POLARITY(*t))
+		mode->sync |= FB_SYNC_HOR_HIGH_ACT;
+	if (EDID_DETAILED_TIMING_FLAG_VSYNC_POLARITY(*t))
+		mode->sync |= FB_SYNC_VERT_HIGH_ACT;
+
+	mode->flag = 0;
+
+	h_total = mode->xres + EDID_DETAILED_TIMING_HORIZONTAL_BLANKING(*t);
+	v_total = mode->yres + EDID_DETAILED_TIMING_VERTICAL_BLANKING(*t);
+	mode->refresh = EDID_DETAILED_TIMING_PIXEL_CLOCK(*t) /
+			(h_total * v_total);
+
+	snprintf(name, name_len, "%dx%d@%d", mode->xres, mode->yres,
+		 mode->refresh);
+
+	return 0;
+}
include/edid.h +18 -0
index 480a773..d66f76b 100644
@@ -13,6 +13,7 @@
 #ifndef __EDID_H_
 #define __EDID_H_
 
+#include <linux/fb.h>
 #include <linux/types.h>
 
 #define GET_BIT(_x, _pos) \
@@ -86,6 +87,10 @@ struct edid_detailed_timing {
 	GET_BITS((_x).flags, 4, 3)
 #define EDID_DETAILED_TIMING_FLAG_POLARITY(_x) \
 	GET_BITS((_x).flags, 2, 1)
+#define EDID_DETAILED_TIMING_FLAG_VSYNC_POLARITY(_x) \
+	GET_BIT((_x).flags, 2)
+#define EDID_DETAILED_TIMING_FLAG_HSYNC_POLARITY(_x) \
+	GET_BIT((_x).flags, 1)
 #define EDID_DETAILED_TIMING_FLAG_INTERLEAVED(_x) \
 	GET_BIT((_x).flags, 0)
 } __attribute__ ((__packed__));
@@ -255,4 +260,17 @@ int edid_get_ranges(struct edid1_info *edid, unsigned int *hmin,
 		    unsigned int *hmax, unsigned int *vmin,
 		    unsigned int *vmax);
 
+/**
+ * Convert an EDID detailed timing to a fb_videomode
+ *
+ * @param t		The EDID detailed timing to be converted
+ * @param mode		Returns the converted timing
+ * @param name		Buffer for the mode name mode->name will be set to this
+ * @param name_len	Length of name
+ *
+ * @return 0 on success, or a negative errno on error
+ */
+int edid_dtd_to_fbmode(struct edid_detailed_timing *t,
+		       struct fb_videomode *mode, char *name, int name_len);
+
 #endif /* __EDID_H_ */
Signature · 1 line
2.1.0
#8 lore raw

PATCH 3/5 edid: Add an edid_dtd_to_fbmode() helper function

Hi,

Quoted 47 lines · Hans de Goede

Various u-boot video drivers use fb_videomode structs to store timing info, add a helper function to convert an EDID detailed timing into a fb_videomode struct.

---
  common/edid.c  | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  include/edid.h | 18 ++++++++++++++++
  2 files changed, 83 insertions(+)
common/edid.c +22 -0
index e66108f..e41cd3e 100644
@@ -12,6 +12,7 @@
 
  #include <common.h>
  #include <edid.h>
+#include <errno.h>
  #include <linux/ctype.h>
  #include <linux/string.h>
 
@@ -288,3 +289,67 @@ void edid_print_info(struct edid1_info *edid_info)
  	if (!have_timing)
  		printf("\tNone\n");
  }
+
+int edid_dtd_to_fbmode(struct edid_detailed_timing *t,
+		       struct fb_videomode *mode, char *name, int name_len)
+{
+	int margin, h_total, v_total;
+
+	if (EDID_DETAILED_TIMING_PIXEL_CLOCK(*t) == 0 ||
+	    EDID_DETAILED_TIMING_HORIZONTAL_ACTIVE(*t) == 0 ||
+	    EDID_DETAILED_TIMING_HORIZONTAL_BLANKING(*t) == 0 ||
+	    EDID_DETAILED_TIMING_VERTICAL_ACTIVE(*t) == 0 ||
+	    EDID_DETAILED_TIMING_VERTICAL_BLANKING(*t) == 0 ||
+	    EDID_DETAILED_TIMING_HSYNC_OFFSET(*t) == 0 ||
+	    EDID_DETAILED_TIMING_HSYNC_PULSE_WIDTH(*t) == 0 ||
+	    EDID_DETAILED_TIMING_VSYNC_OFFSET(*t) == 0 ||
+	    EDID_DETAILED_TIMING_VSYNC_PULSE_WIDTH(*t) == 0)
+		return -EINVAL;
+
+	mode->name = name;
+	mode->xres = EDID_DETAILED_TIMING_HORIZONTAL_ACTIVE(*t);
+	mode->yres = EDID_DETAILED_TIMING_VERTICAL_ACTIVE(*t);
+	mode->pixclock = (EDID_DETAILED_TIMING_PIXEL_CLOCK(*t) + 500) /

common/edid.c void edid_print_info(struct edid1_info *edid_info) · line 322

Quoted 10 lines
common/edid.c +10 -0
+			 1000;
+
+	mode->left_margin = EDID_DETAILED_TIMING_HSYNC_OFFSET(*t);
+	mode->hsync_len = EDID_DETAILED_TIMING_HSYNC_PULSE_WIDTH(*t);
+	margin = EDID_DETAILED_TIMING_HORIZONTAL_BLANKING(*t) -
+			(mode->left_margin + mode->hsync_len);
+	if (margin <= 0)
+		return -EINVAL;
+
+	mode->right_margin = margin;

Note left and right margin are swapped here, I've this fixed in my local tree.

Regards,

Hans

Quoted 75 lines
common/edid.c +33 -0
+
+	mode->lower_margin = EDID_DETAILED_TIMING_VSYNC_OFFSET(*t);
+	mode->vsync_len = EDID_DETAILED_TIMING_VSYNC_PULSE_WIDTH(*t);
+	margin = EDID_DETAILED_TIMING_VERTICAL_BLANKING(*t) -
+			(mode->lower_margin + mode->vsync_len);
+	if (margin <= 0)
+		return -EINVAL;
+
+	mode->upper_margin = margin;
+
+	if (EDID_DETAILED_TIMING_FLAG_INTERLEAVED(*t))
+		mode->vmode = FB_VMODE_INTERLACED;
+	else
+		mode->vmode = FB_VMODE_NONINTERLACED;
+
+	mode->sync = 0;
+	if (EDID_DETAILED_TIMING_FLAG_HSYNC_POLARITY(*t))
+		mode->sync |= FB_SYNC_HOR_HIGH_ACT;
+	if (EDID_DETAILED_TIMING_FLAG_VSYNC_POLARITY(*t))
+		mode->sync |= FB_SYNC_VERT_HIGH_ACT;
+
+	mode->flag = 0;
+
+	h_total = mode->xres + EDID_DETAILED_TIMING_HORIZONTAL_BLANKING(*t);
+	v_total = mode->yres + EDID_DETAILED_TIMING_VERTICAL_BLANKING(*t);
+	mode->refresh = EDID_DETAILED_TIMING_PIXEL_CLOCK(*t) /
+			(h_total * v_total);
+
+	snprintf(name, name_len, "%dx%d@%d", mode->xres, mode->yres,
+		 mode->refresh);
+
+	return 0;
+}
include/edid.h +18 -0
index 480a773..d66f76b 100644
@@ -13,6 +13,7 @@
  #ifndef __EDID_H_
  #define __EDID_H_
 
+#include <linux/fb.h>
  #include <linux/types.h>
 
  #define GET_BIT(_x, _pos) \
@@ -86,6 +87,10 @@ struct edid_detailed_timing {
  	GET_BITS((_x).flags, 4, 3)
  #define EDID_DETAILED_TIMING_FLAG_POLARITY(_x) \
  	GET_BITS((_x).flags, 2, 1)
+#define EDID_DETAILED_TIMING_FLAG_VSYNC_POLARITY(_x) \
+	GET_BIT((_x).flags, 2)
+#define EDID_DETAILED_TIMING_FLAG_HSYNC_POLARITY(_x) \
+	GET_BIT((_x).flags, 1)
  #define EDID_DETAILED_TIMING_FLAG_INTERLEAVED(_x) \
  	GET_BIT((_x).flags, 0)
  } __attribute__ ((__packed__));
@@ -255,4 +260,17 @@ int edid_get_ranges(struct edid1_info *edid, unsigned int *hmin,
  		    unsigned int *hmax, unsigned int *vmin,
  		    unsigned int *vmax);
 
+/**
+ * Convert an EDID detailed timing to a fb_videomode
+ *
+ * @param t		The EDID detailed timing to be converted
+ * @param mode		Returns the converted timing
+ * @param name		Buffer for the mode name mode->name will be set to this
+ * @param name_len	Length of name
+ *
+ * @return 0 on success, or a negative errno on error
+ */
+int edid_dtd_to_fbmode(struct edid_detailed_timing *t,
+		       struct fb_videomode *mode, char *name, int name_len);
+
  #endif /* __EDID_H_ */
#9 lore raw

PATCH 4/5 edid: Add an edid_check_checksum() helper function

In reply to Hans de Goede

Add a helper function to check the checksum of an EDID data block.

---
 common/edid.c  | 11 +++++++++++
 include/edid.h |  9 +++++++++
 2 files changed, 20 insertions(+)
common/edid.c +11 -0
index e41cd3e..e5d35b2 100644
@@ -353,3 +353,14 @@ int edid_dtd_to_fbmode(struct edid_detailed_timing *t,
 
 	return 0;
 }
+
+int edid_check_checksum(u8 *edid_block)
+{
+	u8 checksum = 0;
+	int i;
+
+	for (i = 0; i < 128; i++)
+		checksum += edid_block[i];
+
+	return (checksum == 0) ? 0 : -EINVAL;
+}
include/edid.h +9 -0
index d66f76b..b32f42c 100644
@@ -273,4 +273,13 @@ int edid_get_ranges(struct edid1_info *edid, unsigned int *hmin,
 int edid_dtd_to_fbmode(struct edid_detailed_timing *t,
 		       struct fb_videomode *mode, char *name, int name_len);
 
+/**
+ * Check checksum of a 128 bytes EDID data block
+ *
+ * @param edid_block	EDID block data
+ *
+ * @return 0 on success, or a negative errno on error
+ */
+int edid_check_checksum(u8 *edid_block);
+
 #endif /* __EDID_H_ */
Signature · 1 line
2.1.0
#10 lore raw

PATCH 5/5 sunxi: display: Add DDC & EDID support

In reply to Hans de Goede

Add DDC & EDID support and use it to automatically select the native mode of the attached monitor. This can be overriden through the hdmi_mode env. variable.

---
 arch/arm/include/asm/arch-sunxi/display.h |  85 ++++++++++++++++
 drivers/video/sunxi_display.c             | 164 +++++++++++++++++++++++++++++-
 include/configs/sunxi-common.h            |   1 +
 3 files changed, 249 insertions(+), 1 deletion(-)
arch/arm/include/asm/arch-sunxi/display.h +85 -0
index ddb71c1..8c4835e 100644
@@ -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 */
drivers/video/sunxi_display.c +163 -1

194 lines not on this page · raw message

include/configs/sunxi-common.h +1 -0
index a6cdffb..cf56b64 100644
@@ -212,6 +212,7 @@
 #define CONFIG_CFB_CONSOLE
 #define CONFIG_VIDEO_SW_CURSOR
 #define CONFIG_VIDEO_LOGO
+#define CONFIG_I2C_EDID
 
 /* allow both serial and cfb console. */
 #define CONFIG_CONSOLE_MUX
Signature · 1 line
2.1.0
#11 lore raw

PATCH 5/5 sunxi: display: Add DDC & EDID support

Quoted 2 lines · Hans de Goede

This can be overriden through the hdmi_mode env.
variable.

Apart from the question regarding the variable name I asked on "sunxi:
video: Add extra modes and allow selecting the mode via hdmi_mode env" the code here looks fine to me.