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>
This commit is contained in:
parent
518cef20f8
commit
75481607c7
@ -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 */
|
||||
|
@ -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];
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user