diff options
author | Hans de Goede <hdegoede@redhat.com> | 2014-12-19 16:05:12 +0100 |
---|---|---|
committer | Hans de Goede <hdegoede@redhat.com> | 2015-01-14 14:56:38 +0100 |
commit | 75481607c74188ff3c194baea92b75dddba8530b (patch) | |
tree | c6cbb09ab55db50efca2136e1b424caea502c7b9 /drivers/video | |
parent | 518cef20f88d1c020a57febf69ed220bdda746df (diff) |
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 <hdegoede@redhat.com>
Acked-by: Ian Campbell <ijc@hellion.org.uk>
Acked-by: Anatolij Gustschin <agust@denx.de>
Diffstat (limited to 'drivers/video')
-rw-r--r-- | drivers/video/sunxi_display.c | 154 |
1 files changed, 153 insertions, 1 deletions
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 <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]; |