基于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函数。
-
首先检查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;
-
获取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 。代码就不贴了。
-
调用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, };
-
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; }
-
设置变量。代码如下。
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分析
- 获取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; }
- 申请 fb_info 和 mxcfb_info 内存并填充mxcfb_ops回调函数。
/* Initialize FB structures */ fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); if (!fbi) { ret = -ENOMEM; goto init_fbinfo_failed; }
- 调用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'; } ...
- 调用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);
- 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); }
- 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
- 初始化modelist。选出最接近的分辨率设置为默认参数。
- 初始化热插拔工作队列hotplug_worker及hdcp队列。hotplug_worker工作队列在中断处理后工作,放在以后分析。
INIT_DELAYED_WORK(&hdmi->hotplug_work, hotplug_worker); INIT_DELAYED_WORK(&hdmi->hdcp_hdp_work, hdcp_hdp_worker);
- 注册中断函数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; }
- 创建sys虚拟文件,hdmi_inited置位true表使已初始化。至此mxc_hdmi_disp_init运行结束。
- 回到mxcfb_probe函数。ipu设置,资源申请等。mxcfb_register函数注册fb_info。注册ipu中断函数。注册到framebuffer模块中。mxcfb_setup_overlay初始化overlay设备,调用mxcfb_register。创建sys虚拟文件。
- 至此,fb初始化完成。最后几步没有进行详细分析,可直接参考源代码。