From: Hans de Goede Date: Fri, 19 Dec 2014 15:05:12 +0000 (+0100) Subject: sunxi: video: Add DDC & EDID support X-Git-Url: http://git.dujemihanovic.xyz/?a=commitdiff_plain;h=75481607c74188ff3c194baea92b75dddba8530b;p=u-boot.git sunxi: video: Add DDC & EDID support 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 Acked-by: Ian Campbell Acked-by: Anatolij Gustschin --- diff --git a/arch/arm/include/asm/arch-sunxi/display.h b/arch/arm/include/asm/arch-sunxi/display.h index ddb71c1bbd..8c4835e49e 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 18fa83dced..0997740127 100644 --- a/drivers/video/sunxi_display.c +++ b/drivers/video/sunxi_display.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -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 77e37a8425..7e264ba7df 100644 --- a/include/configs/sunxi-common.h +++ b/include/configs/sunxi-common.h @@ -216,6 +216,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