imx6q mxc 的 framebuffer 初始化过程

基于3.14.28内核。

大致描述一下fb的初始化流程。

hdmi、lcd等驱动注册。

主要通过hdmi的注册来分析,提了一下lvds的注册。
hdmi的注册代码路径为/drivers/video/mxc/mxc_hdmi.c

1 设备树中的各hdmi节点。

"fsl,imx6q-hdmi-core"为hdmi的控制驱动。
"fsl,imx6q-hdmi-audio"为音频设备。
"fsl,imx6q-hdmi-video"为fb提供hdmi的设备,是fb的具体设备。在/arch/arm/boot/dts/imx6qdl.dtsi中。
"fsl,imx6-hdmi-i2c"是i2c设备,用来读取edid信息。在/arch/arm/boot/dts/imx6qdl-sabresd.dtsi中。
下面只列出了了video跟i2c两个的设备树信息。

hdmi: edid@50 {
	compatible = "fsl,imx6-hdmi-i2c";
	reg = <0x50>;
};
...
hdmi_video: hdmi_video@020e0000 {
	compatible = "fsl,imx6q-hdmi-video";
	reg = <0x020e0000 0x1000>;
	reg-names = "hdmi_gpr";
	interrupts = <0 115 IRQ_TYPE_LEVEL_HIGH>;
	clocks = <&clks IMX6QDL_CLK_HDMI_ISFR>,
			<&clks IMX6QDL_CLK_HDMI_IAHB>,
			<&clks IMX6QDL_CLK_HSI_TX>;
	clock-names = "hdmi_isfr", "hdmi_iahb", "mipi_core";
	status = "disabled";
};

2. 注册hdmi-i2c设备驱动。

是用来读取edid信息,匹配的设备为"fsl,imx6-hdmi-i2c"。
module_init(mxc_hdmi_i2c_init);模块入口,调用mxc_hdmi_i2c_init函数。mxc_hdmi_i2c_init中注册了mxc_hdmi_i2c_driver i2c设备驱动。主要代码如下。

static const struct of_device_id imx_hdmi_i2c_match[] = {
	{ .compatible = "fsl,imx6-hdmi-i2c", },
	{ /* sentinel */ }
};

static const struct i2c_device_id mxc_hdmi_i2c_id[] = {
	{ "mxc_hdmi_i2c", 0 },
	{},
};
MODULE_DEVICE_TABLE(i2c, mxc_hdmi_i2c_id);

static struct i2c_driver mxc_hdmi_i2c_driver = {
	.driver = {
		   .name = "mxc_hdmi_i2c",
			.of_match_table	= imx_hdmi_i2c_match,
		   },
	.probe = mxc_hdmi_i2c_probe,
	.remove = mxc_hdmi_i2c_remove,
	.id_table = mxc_hdmi_i2c_id,
};

static int __init mxc_hdmi_i2c_init(void)
{
	return i2c_add_driver(&mxc_hdmi_i2c_driver);
}

static void __exit mxc_hdmi_i2c_exit(void)
{
	i2c_del_driver(&mxc_hdmi_i2c_driver);
}

module_init(mxc_hdmi_i2c_init);
module_exit(mxc_hdmi_i2c_exit);

在mxc_hdmi_i2c_probe函数中,可以看到比较简单,先检查i2c_adapter的特性,并将该设备赋值到全局变量中hdmi_i2c 。代码如下。

static int mxc_hdmi_i2c_probe(struct i2c_client *client,
	const struct i2c_device_id *id)
{
	if (!i2c_check_functionality(client->adapter,
				I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C))
		return -ENODEV;

	hdmi_i2c = client;

	return 0;
}

3. 注册hdmi-video hdmi显示设备

这是具体的显示设备,过程与上面类似。代码如下。

static const struct of_device_id imx_hdmi_dt_ids[] = {
	{ .compatible = "fsl,imx6dl-hdmi-video", .data = &imx_hdmi_devtype[IMX6DL_HDMI],},
	{ .compatible = "fsl,imx6q-hdmi-video", .data = &imx_hdmi_devtype[IMX6Q_HDMI],},
	{ /* sentinel */ }
};
static struct platform_driver mxc_hdmi_driver = {
	.probe = mxc_hdmi_probe,
	.remove = mxc_hdmi_remove,
	.driver = {
		.name = "mxc_hdmi",
		.of_match_table	= imx_hdmi_dt_ids,
		.owner = THIS_MODULE,
	},
};

static int __init mxc_hdmi_init(void)
{
	return platform_driver_register(&mxc_hdmi_driver);
}
module_init(mxc_hdmi_init);

4. mxc_hdmi_probe函数。

  1. 首先检查hdcp是否在设备树中打开。这里没有设置打开。故hdcp_init为null。如果hdmi_i2c也为null会报错。这里hdmi_i2c已经初始化。代码如下。

    /* Check I2C driver is loaded and available
     * check hdcp function is enable by dts */
    hdmi_hdcp_get_property(pdev);
    if (!hdmi_i2c && !hdcp_init)
    	return -ENODEV;
    
  2. 获取platform资源 res,该资源为reg = <0x20e0000 0x1000>,申请platform设备 mxc_hdmi 内存,并赋值全局变量。创建字符设备mxc_hdmi。 hdmi_major 主设备号为249。创建 hdmi_class,/sys/class/mxc_hdmi 目录。根据 hdmi_major 在 hdmi_class 创建子目录 mxc_hdmi。申请core_pdev内存。通过 ioremap 映射 res 。代码就不贴了。

  3. 调用mxc_dispdrv_register函数注册mxc_hdmi_drv,为初始化重要变量。

    hdmi->disp_mxc_hdmi = mxc_dispdrv_register(&mxc_hdmi_drv);
    if (IS_ERR(hdmi->disp_mxc_hdmi)) {
    	dev_err(&pdev->dev, "Failed to register dispdrv - 0x%x\n",
    		(int)hdmi->disp_mxc_hdmi);
    	ret = (int)hdmi->disp_mxc_hdmi;
    	goto edispdrv;
    }
    

    mxc_hdmi_drv保存了初始化等回调函数。

    static struct mxc_dispdrv_driver mxc_hdmi_drv = {
    	.name	= DISPDRV_HDMI,
    	.init	= mxc_hdmi_disp_init,
    	.deinit	= mxc_hdmi_disp_deinit,
    	.enable = mxc_hdmi_power_on,
    	.disable = mxc_hdmi_power_off,
    };
    
  4. mxc_dispdrv_register函数在/drivers/video/mxc/mxc_dispdrv.c中,该文件有3组函数,用来保存具体显显示设备的回调信息,获取回调信息并调用初始化等。
    mxc_dispdrv_register首先申请了一个mxc_dispdrv_entry的节点new,将mxc_hdmi_drv保存在这个节点的drv成员中,然后将这个节点加入dispdrv_list链表中,最后返回该节点给调用者。代码如下:

    struct mxc_dispdrv_handle *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv)
    {
    	struct mxc_dispdrv_entry *new;
    
    	mutex_lock(&dispdrv_lock);
    
    	new = kzalloc(sizeof(struct mxc_dispdrv_entry), GFP_KERNEL);
    	if (!new) {
    		mutex_unlock(&dispdrv_lock);
    		return ERR_PTR(-ENOMEM);
    	}
    
    	new->drv = drv;
    	list_add_tail(&new->list, &dispdrv_list);
    
    	mutex_unlock(&dispdrv_lock);
    
    	return (struct mxc_dispdrv_handle *)new;
    }
    
  5. 设置变量。代码如下。

    mxc_dispdrv_setdata(hdmi->disp_mxc_hdmi, hdmi);
    platform_set_drvdata(pdev, hdmi);
    

    至此,hdmi-video hdmi显示设备注册完成。

5. lvds显示设备的流程与hdmi类似,代码在/drivers/video/mxc/ldb.c中。暂不添加具体分析了。

mxc framebuffer初始化过程

代码主要文件为/drivers/video/mxc/mxc_ipuv3_fb.c

1 设备树节点

在/arch/arm/boot/dts/imx6qdl-sabresd.dtsi中。共有mxcfb1:fb@0到mxcfb4: fb@3共4个节点。这里只列出第一个节点。该节点的disp_dev为"ldb",在驱动初始化时不使用此字段。

mxcfb1: fb@0 {
	compatible = "fsl,mxc_sdc_fb";
	disp_dev = "ldb";
	interface_pix_fmt = "RGB666";
	default_bpp = <16>;
	int_clk = <0>;
	late_init = <0>;
	status = "disabled";
};

2 模块注册。

与上面类似。最后调用mxcfb_probe函数。

3 mxcfb_probe分析

  1. 获取fb的id号,申请plat_data内存,mxcfb_get_of_property获取设备树参数。在mxcfb_get_of_property中plat_data->disp_dev读取了"disp_dev"节点参数,后续会修改此参数。
    pdev->id = of_alias_get_id(pdev->dev.of_node, "mxcfb");
    if (pdev->id < 0) {
    	dev_err(&pdev->dev, "can not get alias id\n");
    	return pdev->id;
    }
    
    plat_data = devm_kzalloc(&pdev->dev, sizeof(struct
    				ipuv3_fb_platform_data), GFP_KERNEL);
    if (!plat_data)
    	return -ENOMEM;
    pdev->dev.platform_data = plat_data;
    
    ret = mxcfb_get_of_property(pdev, plat_data);
    if (ret < 0) {
    	dev_err(&pdev->dev, "get mxcfb of property fail\n");
    	return ret;
    }
    
  2. 申请 fb_infomxcfb_info 内存并填充mxcfb_ops回调函数。
    /* Initialize FB structures */
    fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops);
    if (!fbi) {
    	ret = -ENOMEM;
    	goto init_fbinfo_failed;
    }
    
  3. 调用mxcfb_option_setup函数。在fb_get_options函数中,获取 kernel cmdline 中的显示参数。更新 disp_dev 字段。这里pdata->disp_dev被更新为hdmi。不能解析的参数放在pdata->mode_str中。
    name[5] += pdev->id;
    if (fb_get_options(name, &options)) {
    	dev_err(&pdev->dev, "Can't get fb option for %s!\n", name);
    	return -ENODEV;
    }
    ...
    if (!strncmp(opt, "dev=", 4)) {
    	memcpy(pdata->disp_dev, opt + 4, strlen(opt) - 4);
    	pdata->disp_dev[strlen(opt) - 4] = '\0';
    } 
    ...
    
  4. 调用mxcfb_dispdrv_init函数。由pdata->disp_dev构建 disp_dev 字符串,调用mxc_dispdrv_gethandle函数。
    if (!strlen(plat_data->disp_dev)) {
    	memcpy(disp_dev, default_dev, strlen(default_dev));
    	disp_dev[strlen(default_dev)] = '\0';
    } else {
    	memcpy(disp_dev, plat_data->disp_dev,
    			strlen(plat_data->disp_dev));
    	disp_dev[strlen(plat_data->disp_dev)] = '\0';
    }
    
    mxcfbi->dispdrv = mxc_dispdrv_gethandle(disp_dev, &setting);
    
  5. mxc_dispdrv_gethandle函数在/drivers/video/mxc/mxc_dispdrv.c中,与上面mxc_dispdrv_register是一系列函数。mxc_dispdrv_gethandle根据disp_dev参数name,遍历dispdrv_list列表,对比drv->name(回调变量中的name字段)与name,找到对应的drv,并调用drv->init。
    struct mxc_dispdrv_handle *mxc_dispdrv_gethandle(char *name,
    	struct mxc_dispdrv_setting *setting)
    {
    	int ret, found = 0;
    	struct mxc_dispdrv_entry *entry;
    
    	mutex_lock(&dispdrv_lock);
    	list_for_each_entry(entry, &dispdrv_list, list) {
    		if (!strcmp(entry->drv->name, name) && (entry->drv->init)) {
    			ret = entry->drv->init((struct mxc_dispdrv_handle *)
    				entry, setting);
    			if (ret >= 0) {
    				entry->active = true;
    				found = 1;
    				break;
    			}
    		}
    	}
    	mutex_unlock(&dispdrv_lock);
    
    	return found ? (struct mxc_dispdrv_handle *)entry:ERR_PTR(-ENODEV);
    }
    
  6. drv->init函数为mxc_hdmi.c中的mxc_hdmi_disp_init函数。获取irq中断号,获取设备树参数。设置ipu,设置时钟等。
    struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp);
    int irq = platform_get_irq(hdmi->pdev, 0);
    ...
    hdmi_get_of_property(hdmi);
    ...
    hdmi_init_route(hdmi);
    ...
    /* Setting HDMI default to blank state */
    ...
    //Enabled HDMI clocks
    
  7. 初始化modelist。选出最接近的分辨率设置为默认参数。
  8. 初始化热插拔工作队列hotplug_worker及hdcp队列。hotplug_worker工作队列在中断处理后工作,放在以后分析。
    INIT_DELAYED_WORK(&hdmi->hotplug_work, hotplug_worker);
    INIT_DELAYED_WORK(&hdmi->hdcp_hdp_work, hdcp_hdp_worker);
    
  9. 注册中断函数mxc_hdmi_hotplug, 处理hdmi热插拔事件。mxc_hdmi_hotplug以后分析。
    ret = devm_request_irq(&hdmi->pdev->dev, irq, mxc_hdmi_hotplug, IRQF_SHARED,
    		dev_name(&hdmi->pdev->dev), hdmi);
    if (ret < 0) {
    	dev_err(&hdmi->pdev->dev,
    		"Unable to request irq: %d\n", ret);
    	goto ereqirq;
    }
    
  10. 创建sys虚拟文件,hdmi_inited置位true表使已初始化。至此mxc_hdmi_disp_init运行结束。
  11. 回到mxcfb_probe函数。ipu设置,资源申请等。mxcfb_register函数注册fb_info。注册ipu中断函数。注册到framebuffer模块中。mxcfb_setup_overlay初始化overlay设备,调用mxcfb_register。创建sys虚拟文件。
  12. 至此,fb初始化完成。最后几步没有进行详细分析,可直接参考源代码。

你可能感兴趣的:(imx6q内核代码分析)