大致描述一下fb的初始化流程。
主要通过hdmi的注册来分析,提了一下lvds的注册。
hdmi的注册代码路径为/drivers/video/mxc/mxc_hdmi.c
"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";
};
是用来读取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;
}
这是具体的显示设备,过程与上面类似。代码如下。
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);
首先检查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显示设备注册完成。
代码主要文件为/drivers/video/mxc/mxc_ipuv3_fb.c
在/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";
};
与上面类似。最后调用mxcfb_probe函数。
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;
}
/* Initialize FB structures */
fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops);
if (!fbi) {
ret = -ENOMEM;
goto init_fbinfo_failed;
}
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';
}
...
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);
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);
}
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
INIT_DELAYED_WORK(&hdmi->hotplug_work, hotplug_worker);
INIT_DELAYED_WORK(&hdmi->hdcp_hdp_work, hdcp_hdp_worker);
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;
}