[U-Boot] [PATCH 0/5] 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

All hdmi code uses struct sunxi_hdmi_reg, so these are not needed.
Signed-off-by: Hans de Goede hdegoede@redhat.com --- arch/arm/include/asm/arch-sunxi/display.h | 13 ------------- 1 file changed, 13 deletions(-)
diff --git a/arch/arm/include/asm/arch-sunxi/display.h b/arch/arm/include/asm/arch-sunxi/display.h index c17c3c3..ddb71c1 100644 --- a/arch/arm/include/asm/arch-sunxi/display.h +++ b/arch/arm/include/asm/arch-sunxi/display.h @@ -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 */

On Mon, 2014-11-24 at 17:14 +0100, Hans de Goede wrote:
All hdmi code uses struct sunxi_hdmi_reg, so these are not needed.
Signed-off-by: Hans de Goede hdegoede@redhat.com
Acked-by: Ian Campbell ijc@hellion.org.uk

On Mon, 24 Nov 2014 17:14:15 +0100 Hans de Goede hdegoede@redhat.com wrote:
All hdmi code uses struct sunxi_hdmi_reg, so these are not needed.
Signed-off-by: Hans de Goede hdegoede@redhat.com
Acked-by: Anatolij Gustschin agust@denx.de

stderr is not hooked up yet as this point, so use a regular printf.
Signed-off-by: Hans de Goede hdegoede@redhat.com --- drivers/video/sunxi_display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/video/sunxi_display.c b/drivers/video/sunxi_display.c index fb28c23..5154084 100644 --- a/drivers/video/sunxi_display.c +++ b/drivers/video/sunxi_display.c @@ -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"); } }

On Mon, 2014-11-24 at 17:14 +0100, Hans de Goede wrote:
stderr is not hooked up yet as this point, so use a regular printf.
Signed-off-by: Hans de Goede hdegoede@redhat.com
Acked-by: Ian Campbell ijc@hellion.org.uk

On Mon, 24 Nov 2014 17:14:16 +0100 Hans de Goede hdegoede@redhat.com wrote:
stderr is not hooked up yet as this point, so use a regular printf.
Signed-off-by: Hans de Goede hdegoede@redhat.com
Acked-by: Anatolij Gustschin agust@denx.de

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.
Signed-off-by: Hans de Goede hdegoede@redhat.com --- common/edid.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ include/edid.h | 18 ++++++++++++++++ 2 files changed, 83 insertions(+)
diff --git a/common/edid.c b/common/edid.c index e66108f..e41cd3e 100644 --- a/common/edid.c +++ b/common/edid.c @@ -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; +} diff --git a/include/edid.h b/include/edid.h index 480a773..d66f76b 100644 --- a/include/edid.h +++ b/include/edid.h @@ -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_ */

Hi,
On 11/24/2014 05:14 PM, Hans de Goede wrote:
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.
Signed-off-by: Hans de Goede hdegoede@redhat.com
common/edid.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ include/edid.h | 18 ++++++++++++++++ 2 files changed, 83 insertions(+)
diff --git a/common/edid.c b/common/edid.c index e66108f..e41cd3e 100644 --- a/common/edid.c +++ b/common/edid.c @@ -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;
Note left and right margin are swapped here, I've this fixed in my local tree.
Regards,
Hans
- 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;
+} diff --git a/include/edid.h b/include/edid.h index 480a773..d66f76b 100644 --- a/include/edid.h +++ b/include/edid.h @@ -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_ */

Add a helper function to check the checksum of an EDID data block.
Signed-off-by: Hans de Goede hdegoede@redhat.com --- common/edid.c | 11 +++++++++++ include/edid.h | 9 +++++++++ 2 files changed, 20 insertions(+)
diff --git a/common/edid.c b/common/edid.c index e41cd3e..e5d35b2 100644 --- a/common/edid.c +++ b/common/edid.c @@ -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; +} diff --git a/include/edid.h b/include/edid.h index d66f76b..b32f42c 100644 --- a/include/edid.h +++ b/include/edid.h @@ -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_ */

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.
Signed-off-by: Hans de Goede hdegoede@redhat.com --- 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(-)
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 5154084..9f31ac5 100644 --- a/drivers/video/sunxi_display.c +++ b/drivers/video/sunxi_display.c @@ -13,6 +13,8 @@ #include <asm/arch/display.h> #include <asm/global_data.h> #include <asm/io.h> +#include <edid.h> +#include <errno.h> #include <fdtdec.h> #include <fdt_support.h> #include <linux/fb.h> @@ -25,6 +27,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 = @@ -68,6 +86,140 @@ static int sunxi_hdmi_hpd_detect(void) return 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 fb_videomode *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; + static char name[16]; + 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++) { + /* Skip interlaced (not implemented) or stereo modes. */ + if (EDID_DETAILED_TIMING_FLAG_INTERLACED(*t) || + EDID_DETAILED_TIMING_FLAG_STEREO(*t) || + EDID_DETAILED_TIMING_FLAG_INTERLEAVED(*t)) + continue; + + r = edid_dtd_to_fbmode(t, mode, name, sizeof(name)); + 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. @@ -446,6 +598,7 @@ void *video_hw_init(void) { static GraphicDevice *graphic_device = &sunxi_display.graphic_device; struct fb_videomode mode; + char *modestr; int ret;
memset(&sunxi_display, 0, sizeof(struct sunxi_display)); @@ -459,7 +612,16 @@ void *video_hw_init(void) return NULL;
sunxi_display.enabled = true; - video_get_mode(getenv("hdmi_mode"), &mode); + + modestr = getenv("hdmi_mode"); + + if (modestr) { + video_get_mode(modestr, &mode); + } else { + ret = sunxi_hdmi_edid_get_mode(&mode); + if (ret) + video_get_mode(NULL, &mode); + }
printf("HDMI connected, setting up a %s console.\n", mode.name);
diff --git a/include/configs/sunxi-common.h b/include/configs/sunxi-common.h index a6cdffb..cf56b64 100644 --- a/include/configs/sunxi-common.h +++ b/include/configs/sunxi-common.h @@ -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

On Mon, 2014-11-24 at 17:14 +0100, Hans de Goede wrote:
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.
participants (3)
-
Anatolij Gustschin
-
Hans de Goede
-
Ian Campbell