一.设备树配置
设备节点vopb
下的子节点vopb_out_hdmi
通过hdmi_in_vopb
(由remote-endpoint
属性指定)和hdmi
显示接口组成一个连接通路;
设备节点vopl
下的子节点vopl_out_hdmi
通过hdmi_in_vopl
(由remote-endpoint
属性指定)和hdmi
显示接口组成一个连接通路;
hdmi: hdmi@ff940000 {
compatible = "rockchip,rk3399-dw-hdmi";
reg = <0x0 0xff940000 0x0 0x20000>;
interrupts = <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH 0>;
clocks = <&cru PCLK_HDMI_CTRL>,
<&cru SCLK_HDMI_SFR>,
<&cru SCLK_HDMI_CEC>,
<&cru PCLK_VIO_GRF>,
<&cru PLL_VPLL>;
clock-names = "iahb", "isfr", "cec", "grf", "ref";
power-domains = <&power RK3399_PD_HDCP>;
reg-io-width = <4>;
rockchip,grf = <&grf>;
#sound-dai-cells = <0>;
status = "disabled";
ports {
hdmi_in: port {
#address-cells = <1>;
#size-cells = <0>;
hdmi_in_vopb: endpoint@0 {
reg = <0>;
remote-endpoint = <&vopb_out_hdmi>;
};
hdmi_in_vopl: endpoint@1 {
reg = <1>;
remote-endpoint = <&vopl_out_hdmi>;
};
};
};
};
Rochchip DRM
驱动中与hdmi
相关的实现,具体实现文件:
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c;
drivers/gpu/drm/rockchip/inno_hdmi.c;
drivers/gpu/drm/bridge/synopsys/;
二.HDMI驱动分析
dw_hdmi_rockchip_bind
函数定义在drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
,该函数的代码虽然看着那么多,实际上主要就做了以下几件事;
- 解析
hdmi
设备节点,涉及到clocks
、rockchip,grf
、avdd-0v9
、avdd-1v8
、以及endpoint
子节点; - 初始化
encoder
; - 构造
dw_hdmi_bind
函数需要的参数,尤其是第三个参数rk3399_hdmi_drv_data
,最后调用dw_hdmi_bind
进入到DesignWare hdmi
驱动;
static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct dw_hdmi_plat_data *plat_data;
const struct of_device_id *match;
struct drm_device *drm = data;
struct drm_encoder *encoder;
struct rockchip_hdmi *hdmi;
int ret;
if (!pdev->dev.of_node)
return -ENODEV;
// 动态分配内存,指向struct rockchip_hdmi
hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return -ENOMEM;
// 根据设备的设备节点和匹配表进行匹配,并返回匹配项
match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node);
// 分配内存,指向一个struct dw_hdmi_plat_data,并复制rk3399_hdmi_drv_data数据
plat_data = devm_kmemdup(&pdev->dev, match->data,
sizeof(*plat_data), GFP_KERNEL);
if (!plat_data)
return -ENOMEM;
// 设置device设备
hdmi->dev = &pdev->dev;
// 设置数据
hdmi->chip_data = plat_data->phy_data;
plat_data->phy_data = hdmi;
encoder = &hdmi->encoder.encoder;
// 基于hdmi设备节点的信息,确定特定encoder端口可能连接的CRTC
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
// 获取设备节点hdmi子节点hdmi_in_vopb的属性remote-endpoint指定vopb_out_hdmi节点的reg的值,用来初始化encoder->crtc_endpoint_id
rockchip_drm_encoder_set_crtc_endpoint_id(&hdmi->encoder,
dev->of_node, 0, 0);
/*
* If we failed to find the CRTC(s) which this encoder is
* supposed to be connected to, it's because the CRTC has
* not been registered yet. Defer probing, and hope that
* the required CRTC is added later.
*/
if (encoder->possible_crtcs == 0)
return -EPROBE_DEFER;
// 解析hdmi设备节点,比如clocks 、rockchip,grf、avdd-0v9、avdd-1v8等属性;
ret = rockchip_hdmi_parse_dt(hdmi);
if (ret) {
if (ret != -EPROBE_DEFER)
DRM_DEV_ERROR(hdmi->dev, "Unable to parse OF data\n");
return ret;
}
// 查找并获取一个可选的 PHY(物理层设备)的引用
hdmi->phy = devm_phy_optional_get(dev, "hdmi");
if (IS_ERR(hdmi->phy)) {
ret = PTR_ERR(hdmi->phy);
if (ret != -EPROBE_DEFER)
DRM_DEV_ERROR(hdmi->dev, "failed to get phy\n");
return ret;
}
// 使能AVDD_0V9电源
ret = regulator_enable(hdmi->avdd_0v9);
if (ret) {
DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd0v9: %d\n", ret);
goto err_avdd_0v9;
}
// 使能AVDD_1V8电源
ret = regulator_enable(hdmi->avdd_1v8);
if (ret) {
DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd1v8: %d\n", ret);
goto err_avdd_1v8;
}
// 准备和使能时钟
ret = clk_prepare_enable(hdmi->ref_clk);
if (ret) {
DRM_DEV_ERROR(hdmi->dev, "Failed to enable HDMI reference clock: %d\n",
ret);
goto err_clk;
}
// 不匹配
if (hdmi->chip_data == &rk3568_chip_data) {
regmap_write(hdmi->regmap, RK3568_GRF_VO_CON1,
HIWORD_UPDATE(RK3568_HDMI_SDAIN_MSK |
RK3568_HDMI_SCLIN_MSK,
RK3568_HDMI_SDAIN_MSK |
RK3568_HDMI_SCLIN_MSK));
}
// 设置encoder的辅助函数helper_private为dw_hdmi_rockchip_encoder_helper_funcs
drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs);
// encoder初始化
drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
// 设置驱动私有数据 pdev->dev.driver_data = hdmi
platform_set_drvdata(pdev, hdmi);
// 初始化HDMI接口
hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data);
/*
* If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
* which would have called the encoder cleanup. Do it manually.
*/
if (IS_ERR(hdmi->hdmi)) {
ret = PTR_ERR(hdmi->hdmi);
goto err_bind;
}
return 0;
err_bind:
drm_encoder_cleanup(encoder);
clk_disable_unprepare(hdmi->ref_clk);
err_clk:
regulator_disable(hdmi->avdd_1v8);
err_avdd_1v8:
regulator_disable(hdmi->avdd_0v9);
err_avdd_0v9:
return ret;
}
dw_hdmi_bind
初始化HDMI接口:
定义在drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
/* -----------------------------------------------------------------------------
* Bind/unbind API, used from platforms based on the component framework.
*/
struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev,
struct drm_encoder *encoder,
const struct dw_hdmi_plat_data *plat_data)
{
struct dw_hdmi *hdmi;
int ret;
// dw hdmi探测
hdmi = dw_hdmi_probe(pdev, plat_data);
if (IS_ERR(hdmi))
return hdmi;
// 将bridge连接到encoder的链中
ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, 0);
if (ret) {
dw_hdmi_remove(hdmi);
return ERR_PTR(ret);
}
return hdmi;
}
dw hdmi探测 dw_hdmi_probe:
dw_hdmi_probe
函数定义在drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, // 传入hdmi设备节点所属的platform device
const struct dw_hdmi_plat_data *plat_data) // 传入rk3399_hdmi_drv_data
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct platform_device_info pdevinfo;
struct device_node *ddc_node;
struct dw_hdmi_cec_data cec;
struct dw_hdmi *hdmi;
struct resource *iores = NULL;
int irq;
int ret;
u32 val = 1;
u8 prod_id0;
u8 prod_id1;
u8 config0;
u8 config3;
// 1. 动态分配内存,指向struct dw_hdmi,并进行成员的初始化
hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return ERR_PTR(-ENOMEM);
hdmi->plat_data = plat_data;
hdmi->dev = dev;
hdmi->sample_rate = 48000;
hdmi->channels = 2;
hdmi->disabled = true;
hdmi->rxsense = true;
hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE);
hdmi->mc_clkdis = 0x7f;
hdmi->last_connector_result = connector_status_disconnected;
mutex_init(&hdmi->mutex);
mutex_init(&hdmi->audio_mutex);
mutex_init(&hdmi->cec_notifier_mutex);
spin_lock_init(&hdmi->audio_lock);
// 2. 解析hdmi设备节点,初始化hdmi成员
ret = dw_hdmi_parse_dt(hdmi);
if (ret < 0)
return ERR_PTR(ret);
// 3. 获取ddc-i2c-bus设备节点 ddc-i2c-bus = <&i2c7>
ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0);
if (ddc_node) {
// 获取i2c总线适配器
hdmi->ddc = of_get_i2c_adapter_by_node(ddc_node);
of_node_put(ddc_node);
if (!hdmi->ddc) {
dev_dbg(hdmi->dev, "failed to read ddc node\n");
return ERR_PTR(-EPROBE_DEFER);
}
} else {
dev_dbg(hdmi->dev, "no ddc property found\n");
}
// 4. 为HDMI相关寄存器注册regmap,采用regmap模型访问HDMI相关寄存器
if (!plat_data->regm) {
const struct regmap_config *reg_config;
// 获取hdmi设备节点reg-io-width属性,描述hdmi相关寄存器位宽 reg-io-width = <4>
of_property_read_u32(np, "reg-io-width", &val);
switch (val) {
case 4:
// regmap 配置信息
reg_config = &hdmi_regmap_32bit_config;
hdmi->reg_shift = 2;
break;
case 1:
reg_config = &hdmi_regmap_8bit_config;
break;
default:
dev_err(dev, "reg-io-width must be 1 or 4\n");
return ERR_PTR(-EINVAL);
}
// 获取第一个内存资源,即reg = <0x0 0xff940000 0x0 0x20000> HDMI相关寄存器基地址
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
hdmi->regs = devm_ioremap_resource(dev, iores);
if (IS_ERR(hdmi->regs)) {
ret = PTR_ERR(hdmi->regs);
goto err_res;
}
// 为内存映射I/O注册regmap
hdmi->regm = devm_regmap_init_mmio(dev, hdmi->regs, reg_config);
if (IS_ERR(hdmi->regm)) {
dev_err(dev, "Failed to configure regmap\n");
ret = PTR_ERR(hdmi->regm);
goto err_res;
}
} else {
hdmi->regm = plat_data->regm;
}
// 根据时钟名称isfr获取时钟,设备节点属性clock-names、clocks,指定了名字为isfr对应的时钟为<&cru SCLK_HDMI_SFR>
hdmi->isfr_clk = devm_clk_get(hdmi->dev, "isfr");
if (IS_ERR(hdmi->isfr_clk)) {
ret = PTR_ERR(hdmi->isfr_clk);
dev_err(hdmi->dev, "Unable to get HDMI isfr clk: %d\n", ret);
goto err_res;
}
// 准备和使能时钟
ret = clk_prepare_enable(hdmi->isfr_clk);
if (ret) {
dev_err(hdmi->dev, "Cannot enable HDMI isfr clock: %d\n", ret);
goto err_res;
}
// 根据时钟名称iahb获取时钟,设备节点属性clock-names、clocks,指定了名字为iahb对应的时钟为<&cru PCLK_HDMI_CTRL>
hdmi->iahb_clk = devm_clk_get(hdmi->dev, "iahb");
if (IS_ERR(hdmi->iahb_clk)) {
ret = PTR_ERR(hdmi->iahb_clk);
dev_err(hdmi->dev, "Unable to get HDMI iahb clk: %d\n", ret);
goto err_isfr;
}
// 准备和使能时钟
ret = clk_prepare_enable(hdmi->iahb_clk);
if (ret) {
dev_err(hdmi->dev, "Cannot enable HDMI iahb clock: %d\n", ret);
goto err_isfr;
}
// 根据时钟名称cec获取时钟,设备节点属性clock-names、clocks,指定了名字为cec对应的时钟为<&cru SCLK_HDMI_CEC>
hdmi->cec_clk = devm_clk_get(hdmi->dev, "cec");
if (PTR_ERR(hdmi->cec_clk) == -ENOENT) {
hdmi->cec_clk = NULL;
} else if (IS_ERR(hdmi->cec_clk)) {
ret = PTR_ERR(hdmi->cec_clk);
if (ret != -EPROBE_DEFER)
dev_err(hdmi->dev, "Cannot get HDMI cec clock: %d\n",
ret);
hdmi->cec_clk = NULL;
goto err_iahb;
} else {
// 准备和使能时钟
ret = clk_prepare_enable(hdmi->cec_clk);
if (ret) {
dev_err(hdmi->dev, "Cannot enable HDMI cec clock: %d\n",
ret);
goto err_iahb;
}
}
/* Product and revision IDs, 获取产品和版本标识信息 */
hdmi->version = (hdmi_readb(hdmi, HDMI_DESIGN_ID) << 8)
| (hdmi_readb(hdmi, HDMI_REVISION_ID) << 0);
prod_id0 = hdmi_readb(hdmi, HDMI_PRODUCT_ID0);
prod_id1 = hdmi_readb(hdmi, HDMI_PRODUCT_ID1);
// 如果发现不支持的HDMI控制器类型,则会打印错误信息并返回-ENODEV错误
if (prod_id0 != HDMI_PRODUCT_ID0_HDMI_TX ||
(prod_id1 & ~HDMI_PRODUCT_ID1_HDCP) != HDMI_PRODUCT_ID1_HDMI_TX) {
dev_err(dev, "Unsupported HDMI controller (%04x:%02x:%02x)\n",
hdmi->version, prod_id0, prod_id1);
ret = -ENODEV;
goto err_iahb;
}
// 5. 检测HDMI的物理层接口
ret = dw_hdmi_detect_phy(hdmi);
if (ret < 0)
goto err_iahb;
dev_info(dev, "Detected HDMI TX controller v%x.%03x %s HDCP (%s)\n",
hdmi->version >> 12, hdmi->version & 0xfff,
prod_id1 & HDMI_PRODUCT_ID1_HDCP ? "with" : "without",
hdmi->phy.name);
// 6. HDMI硬件初始化
dw_hdmi_init_hw(hdmi);
// 7. 获取第1个IRQ编号 interrupts = <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH 0>
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
ret = irq;
goto err_iahb;
}
// 8. 申请中断,中断处理函数设置为dw_hdmi_hardirq,中断线程化的处理函数设置为dw_hdmi_irq
ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq,
dw_hdmi_irq, IRQF_SHARED,
dev_name(dev), hdmi);
if (ret)
goto err_iahb;
/*
* To prevent overflows in HDMI_IH_FC_STAT2, set the clk regenerator
* N and cts values before enabling phy
*/
hdmi_init_clk_regenerator(hdmi);
/* If DDC bus is not specified, try to register HDMI I2C bus,不会进入 */
if (!hdmi->ddc) {
/* Look for (optional) stuff related to unwedging */
hdmi->pinctrl = devm_pinctrl_get(dev);
if (!IS_ERR(hdmi->pinctrl)) {
hdmi->unwedge_state =
pinctrl_lookup_state(hdmi->pinctrl, "unwedge");
hdmi->default_state =
pinctrl_lookup_state(hdmi->pinctrl, "default");
if (IS_ERR(hdmi->default_state) ||
IS_ERR(hdmi->unwedge_state)) {
if (!IS_ERR(hdmi->unwedge_state))
dev_warn(dev,
"Unwedge requires default pinctrl\n");
hdmi->default_state = NULL;
hdmi->unwedge_state = NULL;
}
}
hdmi->ddc = dw_hdmi_i2c_adapter(hdmi);
if (IS_ERR(hdmi->ddc))
hdmi->ddc = NULL;
}
// 初始化桥接设备
hdmi->bridge.driver_private = hdmi;
hdmi->bridge.funcs = &dw_hdmi_bridge_funcs;
hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID
| DRM_BRIDGE_OP_HPD;
hdmi->bridge.interlace_allowed = true;
#ifdef CONFIG_OF
hdmi->bridge.of_node = pdev->dev.of_node;
#endif
memset(&pdevinfo, 0, sizeof(pdevinfo));
pdevinfo.parent = dev;
pdevinfo.id = PLATFORM_DEVID_AUTO;
// 这看起来应该是获取HDMI的配置信息,具体是啥咱也不知道
config0 = hdmi_readb(hdmi, HDMI_CONFIG0_ID);
config3 = hdmi_readb(hdmi, HDMI_CONFIG3_ID);
// AHB DMA音频?
if (iores && config3 & HDMI_CONFIG3_AHBAUDDMA) {
struct dw_hdmi_audio_data audio;
audio.phys = iores->start;
audio.base = hdmi->regs;
audio.irq = irq;
audio.hdmi = hdmi;
audio.get_eld = hdmi_audio_get_eld;
hdmi->enable_audio = dw_hdmi_ahb_audio_enable;
hdmi->disable_audio = dw_hdmi_ahb_audio_disable;
pdevinfo.name = "dw-hdmi-ahb-audio";
pdevinfo.data = &audio;
pdevinfo.size_data = sizeof(audio);
pdevinfo.dma_mask = DMA_BIT_MASK(32);
hdmi->audio = platform_device_register_full(&pdevinfo);
} else if (config0 & HDMI_CONFIG0_I2S) { // I2S音频?
struct dw_hdmi_i2s_audio_data audio;
audio.hdmi = hdmi;
audio.get_eld = hdmi_audio_get_eld;
audio.write = hdmi_writeb;
audio.read = hdmi_readb;
hdmi->enable_audio = dw_hdmi_i2s_audio_enable;
hdmi->disable_audio = dw_hdmi_i2s_audio_disable;
pdevinfo.name = "dw-hdmi-i2s-audio";
pdevinfo.data = &audio;
pdevinfo.size_data = sizeof(audio);
pdevinfo.dma_mask = DMA_BIT_MASK(32);
hdmi->audio = platform_device_register_full(&pdevinfo);
} else if (iores && config3 & HDMI_CONFIG3_GPAUD) { // GP Audiou音频
struct dw_hdmi_audio_data audio;
audio.phys = iores->start;
audio.base = hdmi->regs;
audio.irq = irq;
audio.hdmi = hdmi;
audio.get_eld = hdmi_audio_get_eld;
hdmi->enable_audio = dw_hdmi_gp_audio_enable;
hdmi->disable_audio = dw_hdmi_gp_audio_disable;
pdevinfo.name = "dw-hdmi-gp-audio";
pdevinfo.id = PLATFORM_DEVID_NONE;
pdevinfo.data = &audio;
pdevinfo.size_data = sizeof(audio);
pdevinfo.dma_mask = DMA_BIT_MASK(32);
hdmi->audio = platform_device_register_full(&pdevinfo);
}
// 如果没有禁用CEC,并且HDMI控制器支持CEC
if (!plat_data->disable_cec && (config0 & HDMI_CONFIG0_CEC)) {
cec.hdmi = hdmi;
cec.ops = &dw_hdmi_cec_ops;
cec.irq = irq;
pdevinfo.name = "dw-hdmi-cec";
pdevinfo.data = &cec;
pdevinfo.size_data = sizeof(cec);
pdevinfo.dma_mask = 0;
hdmi->cec = platform_device_register_full(&pdevinfo);
}
// 当前桥接设备到全局链表`bridge_list中
drm_bridge_add(&hdmi->bridge);
return hdmi;
err_iahb:
clk_disable_unprepare(hdmi->iahb_clk);
clk_disable_unprepare(hdmi->cec_clk);
err_isfr:
clk_disable_unprepare(hdmi->isfr_clk);
err_res:
i2c_put_adapter(hdmi->ddc);
return ERR_PTR(ret);
}
这个代码的长度一眼望过去令人窒息。我们也不用去一一解读这段代码干了什么,我们只关注我们想了解的东西,比如与edid
相关的内容,以及connector
初始化相关的内容;
- 动态分配
struct dw_hdmi
对象,并进行hdmi
成员的初始化; - 调用
dw_hdmi_parse_dt
解析hdmi
设备节点,初始化hdmi
成员;实际上由于没有指定hdmi->plat_data->output_port
所以这个函数会直接返回; - 如果指定了
ddc-i2c-bus
属性,则 获取i2c
总线适配器; - 为
HDMI
相关寄存器注册regmap
,采用regmap
模型访问hdmi
相关寄存器; - 获取并使能时钟
isfr_clk
、iahb_clk
、cec_clk
; - 调用
dw_hdmi_detect_phy
检测hdmi
的物理层接口; - 调用
dw_hdmi_init_hw
进行HDMI
硬件初始化; - 注册中断
interrupts = <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH 0>
,中断处理函数设置为dw_hdmi_hardirq
,中断线程化的处理函数设置为dw_hdmi_irq
; - 初始化桥接设备,设置回调
funcs
为dw_hdmi_bridge_funcs
; - 调用
drm_bridge_add
添加hdmi
桥接设备;
这个的重点是dw_hdmi_bridge_funcs函数:
dw_hdmi_bridge_funcs
定义了bridge
的控制函数,位于drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
文件;
static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
.attach = dw_hdmi_bridge_attach, // 桥接设备连接到encoder时被调用
.detach = dw_hdmi_bridge_detach,
.atomic_check = dw_hdmi_bridge_atomic_check,
.atomic_get_output_bus_fmts = dw_hdmi_bridge_atomic_get_output_bus_fmts,
.atomic_get_input_bus_fmts = dw_hdmi_bridge_atomic_get_input_bus_fmts,
.atomic_enable = dw_hdmi_bridge_atomic_enable,
.atomic_disable = dw_hdmi_bridge_atomic_disable,
.mode_set = dw_hdmi_bridge_mode_set,
.mode_valid = dw_hdmi_bridge_mode_valid, // 用于校验显示模式是否有效,最终调用dw_hdmi_rockchip_mode_valid
.detect = dw_hdmi_bridge_detect,
.get_edid = dw_hdmi_bridge_get_edid, // 用于获取edid信息
};
其中:
attach
:回调函数在桥接设备连接到encoder
时被调用;detach
:回调函数在桥接设备从encoder
断开时被调用;mode_valid
:用于校验显示模式是否有效,最终调用dw_hdmi_plat_data
的成员mode_valid
,也就是dw_hdmi_rockchip_mode_valid
函数;get_edid
:用于读取连接显示器的edid
数据的首选方法。如果桥接设备支持读取edid
的话,应当实现这个回调函数,并不实现get_modes
回调;
这里的重点是dw_hdmi_bridge_get_edid函数:
用于获取edid
信息;
static struct edid *dw_hdmi_bridge_get_edid(struct drm_bridge *bridge,
struct drm_connector *connector)
{
struct dw_hdmi *hdmi = bridge->driver_private;
// 获取edid信息
return dw_hdmi_get_edid(hdmi, connector);
}
dw_hdmi_get_edid
定义在drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
,用于获取connector
的edid
信息;
static struct edid *dw_hdmi_get_edid(struct dw_hdmi *hdmi,
struct drm_connector *connector)
{
struct edid *edid;
if (!hdmi->ddc)
return NULL;
// 通过I2C通信获取edid信息
edid = drm_get_edid(connector, hdmi->ddc);
if (!edid) {
dev_dbg(hdmi->dev, "failed to get edid\n");
return NULL;
}
dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n",
edid->width_cm, edid->height_cm);
hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid);
hdmi->sink_has_audio = drm_detect_monitor_audio(edid);
return edid;
}
drm_get_edid
函数位于drivers/gpu/drm/drm_edid.c
;
/**
* drm_get_edid - get EDID data, if available
* @connector: connector we're probing
* @adapter: I2C adapter to use for DDC
*
* Poke the given I2C channel to grab EDID data if possible. If found,
* attach it to the connector.
*
* Return: Pointer to valid EDID or NULL if we couldn't find any.
*/
struct edid *drm_get_edid(struct drm_connector *connector,
struct i2c_adapter *adapter)
{
struct edid *edid;
if (connector->force == DRM_FORCE_OFF)
return NULL;
if (connector->force == DRM_FORCE_UNSPECIFIED && !drm_probe_ddc(adapter))
return NULL;
// 调用drm_do_probe_ddc_edid函数实现通过I2C总线读取edid信息,有兴趣可以看一下i2c_transfer,I2C从设备地址为0x50
edid = _drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter, NULL);
drm_connector_update_edid_property(connector, edid);
return edid;
}
drm_add_edid_modes
函数用于解析edid
信息,并将其转换为显示模式,添加到connector
的 probed_modes
链表。
drm_add_edid_modes
其实现位于drivers/gpu/drm/drm_edid.c
;
/**
* drm_add_edid_modes - add modes from EDID data, if available
* @connector: connector we're probing
* @edid: EDID data
*
* Add the specified modes to the connector's mode list. Also fills out the
* &drm_display_info structure and ELD in @connector with any information which
* can be derived from the edid.
*
* This function is deprecated. Use drm_edid_connector_add_modes() instead.
*
* Return: The number of modes added or 0 if we couldn't find any.
*/
int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid)
{
struct drm_edid _drm_edid;
const struct drm_edid *drm_edid;
// edid有效性校验
if (edid && !drm_edid_is_valid(edid)) {
drm_warn(connector->dev, "[CONNECTOR:%d:%s] EDID invalid.\n",
connector->base.id, connector->name);
edid = NULL;
}
drm_edid = drm_edid_legacy_init(&_drm_edid, edid);
update_display_info(connector, drm_edid);
return _drm_edid_connector_add_modes(connector, drm_edid);
}
函数首先检查edid
数据的有效性,然后使用提供的edid
数据初始化 drm_edid
结构,接着更新显示信息,最后调用 _drm_edid_connector_add_modes
函数来添加模式。
update_display_info
用于更新HDMI
显示设备的显示信息(即connector->display_info
),该函数接收两个参数,第一个是connector
,第二个参数为edid
信息;下面是我使用的一款HDMI
显示器的edid
信息;
0 1 2 3 4 5 6 7 8 9
000 | 00 FF FF FF FF FF FF 00 35 34
010 | 00 00 01 01 01 01 00 20 01 03
020 | 81 00 00 78 EE 8C 75 A9 54 45
030 | 98 22 1E 50 54 2F CF 00 71 40
040 | 81 C0 81 80 95 00 A9 C0 B3 00
050 | D1 C0 D1 00 D3 BC 00 A0 A0 A0
060 | 29 50 30 20 35 00 B9 88 21 00
070 | 00 1A 56 5E 00 A0 A0 A0 29 50
080 | 30 20 35 00 B9 88 21 00 00 1A
090 | 67 E2 00 A0 A0 A0 29 50 30 20
100 | 35 00 B9 88 21 00 00 1A 00 00
110 | 00 FC 00 4D 45 49 54 49 41 4E
120 | 48 41 4F 0A 20 20 01 0B
(8-9) ID Manufacture Name : MIT
(10-11) ID Product Code : 0000
(12-15) ID Serial Number : N/A
(16) Week of Manufacture : 0
(17) Year of Manufacture : 2022
(18) EDID Version Number : 1
(19) EDID Revision Number: 3
(20) Video Input Definition : Digital
DFP 1.x Compatible
(21) Maximum Horizontal Image Size: 0 cm
(22) Maximum Vertical Image Size : 0 cm
(23) Display Gamma : 2.20
(24) Power Management and Supported Feature(s):
Standby, Suspend, Active Off/Very Low Power, RGB Color, sRGB, Preferred Timing Mode
(25-34) Color Characteristics
Red Chromaticity : Rx = 0.658 Ry = 0.328
Green Chromaticity : Gx = 0.269 Gy = 0.594
Blue Chromaticity : Bx = 0.134 By = 0.120
Default White Point: Wx = 0.313 Wy = 0.329
......
(126-127) Extension Flag and Checksum
Extension Block(s) : 1 # 表明有扩展块
Checksum Value : 11
___________________________________________________________________
Block 1 ( CEA-861 Extension Block), Bytes 128 - 255, 128 BYTES OF EDID CODE:
0 1 2 3 4 5 6 7 8 9
128 | 02 03 3A F2 4F 04 05 10 13 14
138 | 1F 6C 6C 6C 27 6C 6C 6C 4B 4C
148 | E2 00 D5 E3 05 C0 00 23 09 7F
158 | 07 83 01 00 00 67 03 0C 00 10
168 | 00 38 78 E6 06 05 01 69 69 4F
178 | 67 D8 5D C4 01 76 C0 00 02 3A
188 | 80 18 71 38 2D 40 58 2C 25 00
198 | 58 C3 10 00 00 1E D4 BC 00 A0
208 | A0 A0 29 50 30 20 35 00 B9 88
218 | 21 00 00 1E 98 E2 00 A0 A0 A0
228 | 29 50 30 20 35 00 B9 88 21 00
238 | 00 1E 00 00 00 00 00 00 00 00
248 | 00 00 00 00 00 00 00 C4
(128-130) Extension Header
Revision Number : 3
DTD Starting Offset: 58
(131) Display Support
DTV Underscan, Basic Audio, YCbCr 4:4:4, YCbCr 4:2:2
Number of Native Formats: 2
(132-147) Video Data Block
1280x720p @ 59.94/60Hz - HDTV (16:9, 1:1)
1920x1080i @ 59.94/60Hz - HDTV (16:9, 1:1)
1920x1080p @ 59.94/60Hz - HDTV (16:9, 1:1)
1280x720p @ 50Hz - HDTV (16:9, 1:1)
1920x1080i @ 50Hz - HDTV (16:9, 1:1)
1920x1080p @ 50Hz - HDTV (16:9, 1:1)
Reserved for the Future
Reserved for the Future
Reserved for the Future
1920x1080i (1250 total) - HDTV 50Hz (16:9, 1:1)
Reserved for the Future
Reserved for the Future
Reserved for the Future
Reserved for the Future
Reserved for the Future
(148-150) Video Capability Data Block (VCDB)
(151-154) Colorimetry Data Block
update_display_info
代码如下:
static void update_display_info(struct drm_connector *connector,
const struct drm_edid *drm_edid)
{
struct drm_display_info *info = &connector->display_info;
const struct edid *edid;
// 复位info各个成员的值
drm_reset_display_info(connector);
clear_eld(connector);
if (!drm_edid)
return;
edid = drm_edid->edid;
// 从edit中获取面板ID(由制造商名称、产品代码构成的一个u32类型),然后根据面板ID到edid_quirk_list列表匹配来获取相应的quirk标志;比如我使用的一款HDMI显示器的面板ID就在edid_quirk_list中不到匹配项,所以quirks设置为0
info->quirks = edid_get_quirks(drm_edid);
// 从edid中获取最大水平图像尺寸 0x15H
info->width_mm = edid->width_cm * 10;
// 从edid中获取最大垂直图像尺寸 0x16H
info->height_mm = edid->height_cm * 10;
// 对于edit revision版本号小于4,该函数直接返回
drm_get_monitor_range(connector, drm_edid);
if (edid->revision < 3)
goto out;
// 如果是模拟信号,进入
if (!(edid->input & DRM_EDID_INPUT_DIGITAL))
goto out;
// 设置HDMI的颜色格式
info->color_formats |= DRM_COLOR_FORMAT_RGB444;
// 解析EDID扩展块(CEA-861D)
drm_parse_cea_ext(connector, drm_edid);
/*
* Digital sink with "DFP 1.x compliant TMDS" according to EDID 1.3?
*
* For such displays, the DFP spec 1.0, section 3.10 "EDID support"
* tells us to assume 8 bpc color depth if the EDID doesn't have
* extensions which tell otherwise.
*/
if (info->bpc == 0 && edid->revision == 3 &&
edid->input & DRM_EDID_DIGITAL_DFP_1_X) { // 进入 DRM_EDID_DIGITAL_DFP_1_X = 1<<0
info->bpc = 8;
drm_dbg_kms(connector->dev,
"[CONNECTOR:%d:%s] Assigning DFP sink color depth as %d bpc.\n",
connector->base.id, connector->name, info->bpc);
}
/* Only defined for 1.4 with digital displays,对于EDID 1.4以下版本跳转到out */
if (edid->revision < 4)
goto out;
switch (edid->input & DRM_EDID_DIGITAL_DEPTH_MASK) {
case DRM_EDID_DIGITAL_DEPTH_6:
info->bpc = 6;
break;
case DRM_EDID_DIGITAL_DEPTH_8:
info->bpc = 8;
break;
case DRM_EDID_DIGITAL_DEPTH_10:
info->bpc = 10;
break;
case DRM_EDID_DIGITAL_DEPTH_12:
info->bpc = 12;
break;
case DRM_EDID_DIGITAL_DEPTH_14:
info->bpc = 14;
break;
case DRM_EDID_DIGITAL_DEPTH_16:
info->bpc = 16;
break;
case DRM_EDID_DIGITAL_DEPTH_UNDEF:
default:
info->bpc = 0;
break;
}
drm_dbg_kms(connector->dev,
"[CONNECTOR:%d:%s] Assigning EDID-1.4 digital sink color depth as %d bpc.\n",
connector->base.id, connector->name, info->bpc);
if (edid->features & DRM_EDID_FEATURE_RGB_YCRCB444)
info->color_formats |= DRM_COLOR_FORMAT_YCBCR444;
if (edid->features & DRM_EDID_FEATURE_RGB_YCRCB422)
info->color_formats |= DRM_COLOR_FORMAT_YCBCR422;
drm_update_mso(connector, drm_edid);
out:
// 跳过
if (info->quirks & EDID_QUIRK_NON_DESKTOP) { // 1<<12
drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Non-desktop display%s\n",
connector->base.id, connector->name,
info->non_desktop ? " (redundant quirk)" : "");
info->non_desktop = true;
}
// 跳过
if (info->quirks & EDID_QUIRK_CAP_DSC_15BPP) // 1<<13
info->max_dsc_bpp = 15;
// 跳过
if (info->quirks & EDID_QUIRK_FORCE_6BPC) // 1<<10
info->bpc = 6;
// 跳过
if (info->quirks & EDID_QUIRK_FORCE_8BPC) // 1<<8
info->bpc = 8;
// 跳过
if (info->quirks & EDID_QUIRK_FORCE_10BPC) // 1<<11
info->bpc = 10;
// 跳过
if (info->quirks & EDID_QUIRK_FORCE_12BPC) // 1<<9
info->bpc = 12;
/* Depends on info->cea_rev set by drm_parse_cea_ext() above */
drm_edid_to_eld(connector, drm_edid);
}
在该函数的执行流程中同时会解析edid
扩展块,由drm_parse_cea_ext
函数实现,以我是用的HDMI
显示器为例分析如下代码:
static void drm_parse_cea_ext(struct drm_connector *connector,
const struct drm_edid *drm_edid)
{
struct drm_display_info *info = &connector->display_info;
struct drm_edid_iter edid_iter;
const struct cea_db *db;
struct cea_db_iter iter;
const u8 *edid_ext;
u64 y420cmdb_map = 0;
drm_edid_iter_begin(drm_edid, &edid_iter);
// edid信息由edid主块和edid扩展块组成,这里是一个遍历操作,edid_ext依次指向主块的首地址、edid扩展块的首地址
drm_edid_iter_for_each(edid_ext, &edid_iter) {
// 通过标记位判定是不是扩展块,如果不是跳过
if (edid_ext[0] != CEA_EXT)
continue;
// 设置版本号,未设置则进入,cea_rev设置为03H,
if (!info->cea_rev)
info->cea_rev = edid_ext[1];
if (info->cea_rev != edid_ext[1])
drm_dbg_kms(connector->dev,
"[CONNECTOR:%d:%s] CEA extension version mismatch %u != %u\n",
connector->base.id, connector->name,
info->cea_rev, edid_ext[1]);
/* The existence of a CTA extension should imply RGB support */
info->color_formats = DRM_COLOR_FORMAT_RGB444;
// 是否支持YCbCr 4:4:4 支持
if (edid_ext[3] & EDID_CEA_YCRCB444)
info->color_formats |= DRM_COLOR_FORMAT_YCBCR444;
// 是否支持YCbCr 4:2:2 支持
if (edid_ext[3] & EDID_CEA_YCRCB422)
info->color_formats |= DRM_COLOR_FORMAT_YCBCR422;
}
drm_edid_iter_end(&edid_iter);
cea_db_iter_edid_begin(drm_edid, &iter);
// 遍历edid扩展块的Data Blocks
cea_db_iter_for_each(db, &iter) {
/* FIXME: convert parsers to use struct cea_db */
const u8 *data = (const u8 *)db;
// Vendor Specific类型的Data Block 不会进入
if (cea_db_is_hdmi_vsdb(db))
drm_parse_hdmi_vsdb_video(connector, data);
// Forum Vendor Specific类型的Data Block 不会进入
else if (cea_db_is_hdmi_forum_vsdb(db) ||
cea_db_is_hdmi_forum_scdb(db))
drm_parse_hdmi_forum_scds(connector, data);
// Microsoft Vendor Specific类型的Data Block 不会进入
else if (cea_db_is_microsoft_vsdb(db))
drm_parse_microsoft_vsdb(connector, data);
else if (cea_db_is_y420cmdb(db))
parse_cta_y420cmdb(connector, db, &y420cmdb_map);
else if (cea_db_is_y420vdb(db))
parse_cta_y420vdb(connector, db);
else if (cea_db_is_vcdb(db))
drm_parse_vcdb(connector, data);
else if (cea_db_is_hdmi_hdr_metadata_block(db))
drm_parse_hdr_metadata_block(connector, data);
else if (cea_db_tag(db) == CTA_DB_VIDEO)
parse_cta_vdb(connector, db);
}
cea_db_iter_end(&iter);
if (y420cmdb_map)
update_cta_y420cmdb(connector, y420cmdb_map);
}
_drm_edid_connector_add_modes
函数是今天我们学习的重点,这个函数会从edid
中解析HDMI
显示设备支持的所有显示模式,在解析edid
时,是按照edid
规范中定义的优先级顺序来解析的;
static int _drm_edid_connector_add_modes(struct drm_connector *connector,
const struct drm_edid *drm_edid)
{
const struct drm_display_info *info = &connector->display_info;
int num_modes = 0;
if (!drm_edid)
return 0;
/*
* EDID spec says modes should be preferred in this order:
* - preferred detailed mode
* - other detailed modes from base block
* - detailed modes from extension blocks
* - CVT 3-byte code modes
* - standard timing codes
* - established timing codes
* - modes inferred from GTF or CVT range information
*
* We get this pretty much right.
*
* XXX order for additional mode types in extension blocks?
*/
num_modes += add_detailed_modes(connector, drm_edid);
num_modes += add_cvt_modes(connector, drm_edid);
num_modes += add_standard_modes(connector, drm_edid);
num_modes += add_established_modes(connector, drm_edid);
num_modes += add_cea_modes(connector, drm_edid);
num_modes += add_alternate_cea_modes(connector, drm_edid);
num_modes += add_displayid_detailed_modes(connector, drm_edid);
if (drm_edid->edid->features & DRM_EDID_FEATURE_CONTINUOUS_FREQ)
num_modes += add_inferred_modes(connector, drm_edid);
if (info->quirks & (EDID_QUIRK_PREFER_LARGE_60 | EDID_QUIRK_PREFER_LARGE_75))
edid_fixup_preferred(connector);
return num_modes;
}
add_detailed_modes
函数用于从Detailed Timings
中获取显示模式,并添加到connector
的 probed_modes
链表;
/*
* add_detailed_modes - Add modes from detailed timings
* @connector: attached connector
* @drm_edid: EDID block to scan
*/
static int add_detailed_modes(struct drm_connector *connector,
const struct drm_edid *drm_edid)
{
struct detailed_mode_closure closure = {
.connector = connector,
.drm_edid = drm_edid,
};
// 我使用的HDMI显示器revision为3,因此不会进入
if (drm_edid->edid->revision >= 4)
closure.preferred = true; /* first detailed timing is always preferred */
else
// 0x18 位[1]:如果置1,推荐分辨率为第一个Detailed Timing
closure.preferred =
drm_edid->edid->features & DRM_EDID_FEATURE_PREFERRED_TIMING; // 1<<1
// 遍历Detailed Timings,依次执行do_detailed_mode
drm_for_each_detailed_block(drm_edid, do_detailed_mode, &closure);
return closure.modes;
}
do_detailed_mode
函数用于解析Timing Descriptor
,创建显示模式并添加到connector
的 probed_modes
链表;
static void
do_detailed_mode(const struct detailed_timing *timing, void *c)
{
struct detailed_mode_closure *closure = c;
struct drm_display_mode *newmode;
// 如果不是Timing Descriptor,将跳过
if (!is_detailed_timing_descriptor(timing))
return;
// 解析Timing Descriptor 此对于我使用的HDMI显示器,只有前3个块可以走到这一步
newmode = drm_mode_detailed(closure->connector,
closure->drm_edid, timing);
if (!newmode)
return;
// 第一个Timing Descriptor会进入,其他的不会进入
if (closure->preferred)
newmode->type |= DRM_MODE_TYPE_PREFERRED;
/*
* Detailed modes are limited to 10kHz pixel clock resolution,
* so fix up anything that looks like CEA/HDMI mode, but the clock
* is just slightly off.
*/
fixup_detailed_cea_mode_clock(closure->connector, newmode);
// list_add_tail(&mode->head, &connector->probed_modes);
drm_mode_probed_add(closure->connector, newmode);
// 计数
closure->modes++;
// 清除标志位
closure->preferred = false;
}
整个函数的核心为drm_mode_detailed
。
drm_mode_detailed
函数定义如下,下面我们以第一个Timing Descriptor
为例进行分析该函数;
/*
* Create a new mode from an EDID detailed timing section. An EDID detailed
* timing block contains enough info for us to create and return a new struct
* drm_display_mode.
*/
static struct drm_display_mode *drm_mode_detailed(struct drm_connector *connector,
const struct drm_edid *drm_edid,
const struct detailed_timing *timing)
{
// 获取显示信息
const struct drm_display_info *info = &connector->display_info;
// 获取drm设备
struct drm_device *dev = connector->dev;
struct drm_display_mode *mode;
// 时序参数
const struct detailed_pixel_timing *pt = &timing->data.pixel_data;
// 水平活动像素高4位 | 水平活动像素低8位 = 2560 Pixels
unsigned hactive = (pt->hactive_hblank_hi & 0xf0) << 4 | pt->hactive_lo;
// 垂直活动像素高4位 | 水平活动像素低8位 = 1440 Lines
unsigned vactive = (pt->vactive_vblank_hi & 0xf0) << 4 | pt->vactive_lo;
// 水平blanking高4位 | 水平blanking低8位 = 160 Pixels
unsigned hblank = (pt->hactive_hblank_hi & 0xf) << 8 | pt->hblank_lo;
// 垂直blanking高4位 | 水平blanking低8位 = 41 Lines
unsigned vblank = (pt->vactive_vblank_hi & 0xf) << 8 | pt->vblank_lo;
// 水平同步信号偏移量 = 48 Pixels
unsigned hsync_offset = (pt->hsync_vsync_offset_pulse_width_hi & 0xc0) << 2 | pt->hsync_offset_lo;
// 水平同步信号脉冲宽度 = 32 Pixels
unsigned hsync_pulse_width = (pt->hsync_vsync_offset_pulse_width_hi & 0x30) << 4 | pt->hsync_pulse_width_lo;
// 垂直同步信号偏移量 = 3 Lines
unsigned vsync_offset = (pt->hsync_vsync_offset_pulse_width_hi & 0xc) << 2 | pt->vsync_offset_pulse_width_lo >> 4;
// 垂直同步信号脉冲宽度 = 5 Lines
unsigned vsync_pulse_width = (pt->hsync_vsync_offset_pulse_width_hi & 0x3) << 4 | (pt->vsync_offset_pulse_width_lo & 0xf);
/* ignore tiny modes,跳过 */
if (hactive < 64 || vactive < 64)
return NULL;
// 标志位 配置了立体 0x1A&1<<5=0, 所以跳过
if (pt->misc & DRM_EDID_PT_STEREO) { // 1<<5
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] Stereo mode not supported\n",
connector->base.id, connector->name);
return NULL;
}
// 标志位 配置了数字分离信号 0x1A&0x3<<3=0x3<<3, 所以进入
if (!(pt->misc & DRM_EDID_PT_SEPARATE_SYNC)) { // 3<<3
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] Composite sync not supported\n",
connector->base.id, connector->name);
}
/* it is incorrect if hsync/vsync width is zero,跳过 */
if (!hsync_pulse_width || !vsync_pulse_width) {
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] Incorrect Detailed timing. Wrong Hsync/Vsync pulse width\n",
connector->base.id, connector->name);
return NULL;
}
// 跳过
if (info->quirks & EDID_QUIRK_FORCE_REDUCED_BLANKING) { // 1<<7
mode = drm_cvt_mode(dev, hactive, vactive, 60, true, false, false);
if (!mode)
return NULL;
goto set_size;
}
// 创建显示模式,动态分配内存
mode = drm_mode_create(dev);
if (!mode)
return NULL;
// 跳过
if (info->quirks & EDID_QUIRK_135_CLOCK_TOO_HIGH) // 1<<1
mode->clock = 1088 * 10;
else
// 为TMDS时钟频率:串行数据速率是实际像素时钟速率的10倍 单位10KHz
mode->clock = le16_to_cpu(timing->pixel_clock) * 10;
// 行有效像素 2560 Pixels
mode->hdisplay = hactive;
// 行同步起始像素 2560 + 48 = 2608 Pixels
mode->hsync_start = mode->hdisplay + hsync_offset;
// 水平同步结束 2608 + 32 = 2640 Pixels
mode->hsync_end = mode->hsync_start + hsync_pulse_width;
// 水平总大小 2560 + 160 = 2720 Pixels
mode->htotal = mode->hdisplay + hblank;
// 垂直显示大小 1440 Lines
mode->vdisplay = vactive;
// 垂直同步起始 1440 + 3 = 1443 Lines
mode->vsync_start = mode->vdisplay + vsync_offset;
// 帧同步结束行 1443 + 5 = 1448 Lines
mode->vsync_end = mode->vsync_start + vsync_pulse_width;
// 一帧总行数 1440 + 41 = 1481 Lines
mode->vtotal = mode->vdisplay + vblank;
/* Some EDIDs have bogus h/vtotal values,跳过 */
if (mode->hsync_end > mode->htotal)
mode->htotal = mode->hsync_end + 1;
/* 跳过 */
if (mode->vsync_end > mode->vtotal)
mode->vtotal = mode->vsync_end + 1;
// 处理交叉模式,对于当前Timing Descriptor 0x47位[7]为0,该函数直接返回
drm_mode_do_interlace_quirk(mode, pt);
// 跳过
if (info->quirks & EDID_QUIRK_DETAILED_SYNC_PP) { // 1<<6
mode->flags |= DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC;
} else {
mode->flags |= (pt->misc & DRM_EDID_PT_HSYNC_POSITIVE) ? // 1<<1 DRM_MODE_FLAG_PHSYNC
DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC;
mode->flags |= (pt->misc & DRM_EDID_PT_VSYNC_POSITIVE) ? // 1<<2 DRM_MODE_FLAG_NVSYNC
DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC;
}
set_size:
// 水平图像尺寸 697mm
mode->width_mm = pt->width_mm_lo | (pt->width_height_mm_hi & 0xf0) << 4;
// 垂直图像尺寸 392mm
mode->height_mm = pt->height_mm_lo | (pt->width_height_mm_hi & 0xf) << 8;
// 跳过
if (info->quirks & EDID_QUIRK_DETAILED_IN_CM) { // 1<<3
mode->width_mm *= 10;
mode->height_mm *= 10;
}
// 跳过
if (info->quirks & EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE) { // 1<<4
mode->width_mm = drm_edid->edid->width_cm * 10;
mode->height_mm = drm_edid->edid->height_cm * 10;
}
// 一个标志位的位掩码,主要用于表示显示模式的来源;DRM_MODE_TYPE_DRIVER标识驱动程序创建的模式
mode->type = DRM_MODE_TYPE_DRIVER;
// 设置显示模式的名称 %dx%d%s 第一个参数为:mode->hdisplay 第二个参数为:mode->vdisplay 第三个参数为:i/''(取决于mode->flags是否设置了DRM_MODE_FLAG_INTERLACE)
drm_mode_set_name(mode);
return mode;
}