RK3568平台(显示篇)DRM HDMI驱动程序

一.设备树配置

设备节点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,grfavdd-0v9avdd-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_clkiahb_clkcec_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
  • 初始化桥接设备,设置回调funcsdw_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,用于获取connectoredid信息;

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;
}

  • 14
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式_笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值