DDC获取EDID的入口函数是drm_do_get_edid(),其调用DDC通讯接口drm_do_probe_ddc_edid()。
struct edid *drm_get_edid(struct drm_connector *connector,
struct i2c_adapter *adapter)
{
...
edid = drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter);
...
}
drm_do_probe_ddc_edid()作为参数传入drm_do_get_edid()中。
struct edid *drm_do_get_edid(struct drm_connector *connector,
int (*get_edid_block)(void *data, u8 *buf, unsigned int block,
size_t len),
void *data)
{
...
/* base block fetch */
for (i = 0; i < 4; i++) {
if (get_edid_block(data, edid, 0, EDID_LENGTH))
goto out;
...
}
...
DDC的消息传输机制
HDMI的EDID最多包含256Bytes,其余的如VGA、DVI是128Bytes。对于E-EDID来讲,由512Bytes组成,分2个segment。每次i2c传输128bytes,分4次传输完成。因此每个segment由两部分组成,这体现在DDC的word addr上。
下面是读取E-EDID的时序图。
|------- 1st-----|------2st-------|------3st-------|------4st-------|
| word addr wr 0 |word addr 0x80 |word addr wr 0 |word addr 0x80--|
|----128Bytes----|----128bytes----|----128bytes----|----128bytes----|
|------------segment0-------------|------------segment1-------------|
|------- segment addr wr 0 -------|------- segment addr wr 1 -------|
在描述了DDC消息机制后,继续。
DRM架构定义了3个i2c消息头,包括两次i2c write和一次i2c read。
i2c 消息头定义如下。
do {
struct i2c_msg msgs[] = {
{
.addr = DDC_SEGMENT_ADDR,
.flags = 0,
.len = 1,
.buf = &segment,
}, {
.addr = DDC_ADDR,
.flags = 0,
.len = 1,
.buf = &start,
}, {
.addr = DDC_ADDR,
.flags = I2C_M_RD,
.len = len,
.buf = buf,
}
};
kernel代码中也是规定了每次只能读取128Bytes的EDID信息。当获取到EDID之后,对EDID进行解析,再决定是否还要继续读取额外长度的信息。
#define EDID_LENGTH 128
#define DDC_ADDR 0x50
#define DDC_ADDR2 0x52
...
/* 获取EDID时共进行4次i2c传输,每次读取128Bytes data */
for (i = 0; i < 4; i++) {
if (get_edid_block(data, edid, 0, EDID_LENGTH))
goto out;
if (drm_edid_block_valid(edid, 0, false,
&connector->edid_corrupt))
break;
if (i == 0 && drm_edid_is_zero(edid, EDID_LENGTH)) {
connector->null_edid_counter++;
goto carp;
}
}
例如下面的ZTE HDMI驱动中,根据DRM定义的i2c头信息实现了DDC word_addr和segment_addr的配置。
static int zx_hdmi_i2c_write(struct zx_hdmi *hdmi, struct i2c_msg *msg)
{
...
if (msg->addr == DDC_SEGMENT_ADDR)
hdmi_writeb(hdmi, ZX_DDC_SEGM, msg->addr << 1);
else if (msg->addr == DDC_ADDR)
hdmi_writeb(hdmi, ZX_DDC_ADDR, msg->addr << 1);
...
}