MDP主要对使用的硬件资源进行初始化,同时在fb设备中注册mdp的使用接口。
MDP使用的设备树在:
msm8909-mdss.dtsi
主要代码:
static int mdp3_probe(struct platform_device *pdev)
{
int rc;
/* 创建msm_mdp_interface结构体,里边包含mdp的初始化等,这些函数会在fb初始化时调用 */
static struct msm_mdp_interface mdp3_interface = {
.init_fnc = mdp3_init,
.fb_mem_get_iommu_domain = mdp3_fb_mem_get_iommu_domain,
.panel_register_done = mdp3_panel_register_done,
.fb_stride = mdp3_fb_stride,
.check_dsi_status = mdp3_check_dsi_ctrl_status,
};
/* 初始化mdp3_res */
mdp3_res = devm_kzalloc(&pdev->dev, sizeof(struct mdp3_hw_resource),
GFP_KERNEL);
/* mdp3_res->mdss_util执行全局变量mdss_util,初始化之,fb注册时会用到 */
mdp3_res->mdss_util = mdss_get_util_intf();
mdp3_res->mdss_util->get_iommu_domain = mdp3_get_iommu_domain;
mdp3_res->mdss_util->iommu_attached = is_mdss_iommu_attached;
mdp3_res->mdss_util->iommu_ctrl = mdp3_iommu_ctrl;
mdp3_res->mdss_util->bus_scale_set_quota = mdp3_bus_scale_set_quota;
mdp3_res->mdss_util->panel_intf_type = mdp3_panel_intf_type;
mdp3_res->mdss_util->dyn_clk_gating_ctrl =
mdp3_dynamic_clock_gating_ctrl;
/* 调用改函数,将mdp3_interface赋值给全局变量mdp_instance,用fb注册时初始化mdp等操作 */
rc = mdss_fb_register_mdp_instance(&mdp3_interface);
/* 标志mdp已经执行probe,dsi probe会检测 */
mdp3_res->mdss_util->mdp_probe_done = true;
}
看下mdp3_interface.init_fnc mdp3_init实现:
static int mdp3_init(struct msm_fb_data_type *mfd)
{
int rc;
rc = mdp3_ctrl_init(mfd);
if (rc) {
pr_err("mdp3 ctl init fail\n");
return rc;
}
rc = mdp3_ppp_res_init(mfd);
if (rc)
pr_err("mdp3 ppp res init fail\n");
return rc;
}
主要看mdp3_ctrl_init()的实现:
int mdp3_ctrl_init(struct msm_fb_data_type *mfd)
{
struct device *dev = mfd->fbi->dev;
struct msm_mdp_interface *mdp3_interface = &mfd->mdp;
struct mdp3_session_data *mdp3_session = NULL;
u32 intf_type = MDP3_DMA_OUTPUT_SEL_DSI_VIDEO;
int rc;
int splash_mismatch = 0;
pr_info("mdp3_ctrl_init\n");
rc = mdp3_parse_dt_splash(mfd);
if (rc)
splash_mismatch = 1;
mdp3_interface->on_fnc = mdp3_ctrl_on;
mdp3_interface->off_fnc = mdp3_ctrl_off;
mdp3_interface->do_histogram = NULL;
mdp3_interface->cursor_update = NULL;
mdp3_interface->dma_fnc = mdp3_ctrl_pan_display;
mdp3_interface->ioctl_handler = mdp3_ctrl_ioctl_handler;
mdp3_interface->kickoff_fnc = mdp3_ctrl_display_commit_kickoff;
mdp3_interface->lut_update = NULL;
mdp3_interface->configure_panel = mdp3_update_panel_info;
...
rc = mdp3_intf_init(mdp3_session->intf);
return rc;
}
在msm_mdp_interface的init_fnc中,为mdp3_interface添加了更多的操作,包括上下电操作,ioctl控制函数,mdp屏幕信息获取等。
解析模组厂商提供的panel的dtsi文件,从哪个文件中能够获取到panel的mode,分辨率,还有Driver IC的初始化command;
DSI驱动在mdss_dsi.c中,其中注册了两个驱动,第一个是
static struct platform_driver mdss_dsi_driver = {
.probe = mdss_dsi_probe,
.remove = mdss_dsi_remove,
.shutdown = NULL,
.driver = {
.name = "mdss_dsi",
.of_match_table = mdss_dsi_dt_match,
},
};
static int mdss_dsi_register_driver(void)
{
return platform_driver_register(&mdss_dsi_driver);
}
另一个是:
static struct platform_driver mdss_dsi_ctrl_driver = {
.probe = mdss_dsi_ctrl_probe,
.remove = mdss_dsi_ctrl_remove,
.shutdown = NULL,
.driver = {
.name = "mdss_dsi_ctrl",
.of_match_table = mdss_dsi_ctrl_dt_match,
},
};
static int mdss_dsi_ctrl_register_driver(void)
{
return platform_driver_register(&mdss_dsi_ctrl_driver);
}
先看第一个驱动的probe函数:
static int mdss_dsi_probe(struct platform_device *pdev)
{
struct mdss_panel_cfg *pan_cfg = NULL;
struct mdss_util_intf *util;
/* 获得全局变量mdp_instance,在mdp probe时注册 */
util = mdss_get_util_intf();
/* 将dsi资源信息保存到全局变量mdss_dsi_res,初始化dsi时钟 */
rc = mdss_dsi_res_init(pdev);
/* 创建qcom,mdss-dsi-ctrl设备,我们这里说的第二个驱动就是该设备对应的驱动 */
of_platform_populate(pdev->dev.of_node, mdss_dsi_ctrl_dt_match,
NULL, &pdev->dev);
return rc;
}
在dis_driver中,主要获取对了dsi时钟等信息,初始化了mdss_dsi_res.然后创建了dsi_ctrl的平台设备,让其可以和之后注册的dsi_ctrl驱动相匹配。
接下来看dsi_ctrl驱动的注册:
@mdss_dsi.c
static int mdss_dsi_ctrl_probe(struct platform_device *pdev)
{
int rc = 0;
struct mdss_dsi_ctrl_pdata *ctrl_pdata = NULL;
const char *ctrl_name;
ctrl_name = of_get_property(pdev->dev.of_node, "label", NULL);
/* panel配置 */
dsi_pan_node = mdss_dsi_config_panel(pdev, index);
rc = dsi_panel_device_register(pdev, dsi_pan_node, ctrl_pdata);
return 0;
}
首先看mdss_dsi_config_panel()配置函数中的mdss_dsi_panel_init()的实现:
@mdss_dsi_panel.c
int mdss_dsi_panel_init(struct device_node *node,
struct mdss_dsi_ctrl_pdata *ctrl_pdata,
int ndx)
{
int rc = 0;
static const char *panel_name;
struct mdss_panel_info *pinfo;
pinfo = &ctrl_pdata->panel_data.panel_info;
pinfo->panel_name[0] = '\0';
/* 设置panel名字 */
panel_name = of_get_property(node, "qcom,mdss-dsi-panel-name", NULL);
/* 找到dsi panel节点,保存在dsi_pan_node */
dsi_pan_node = mdss_dsi_find_panel_of_node(pdev, panel_cfg);
/* 从dsi节点读取panel信息,并保存到结果在ctrl_pdata->panel_data.panel_info中 */
rc = mdss_panel_parse_dt(node, ctrl_pdata);
pinfo->dynamic_switch_pending = false;
pinfo->is_lpm_mode = false;
pinfo->esd_rdy = false;
// 屏上电操作
ctrl_pdata->on = mdss_dsi_panel_on;
ctrl_pdata->post_panel_on = mdss_dsi_post_panel_on;
// 屏下电操作
ctrl_pdata->off = mdss_dsi_panel_off;
ctrl_pdata->low_power_config = mdss_dsi_panel_low_power_config;
// 背光设置
ctrl_pdata->panel_data.set_backlight = mdss_dsi_panel_bl_ctrl;
// 切换模式
ctrl_pdata->switch_mode = mdss_dsi_panel_switch_mode;
ctrl_pdata->panel_data.set_idle = mdss_dsi_panel_set_idle_mode;
ctrl_pdata->panel_data.get_idle = mdss_dsi_panel_get_idle_mode;
return 0;
}
在配置中,首先通过mdss_panel_parse_dt()函数解析dsi和屏信息的设备设,屏的信息,在fb注册时,将使用到。然后初始化了mdss_dsi_ctrl_pdata结构体中的操作函数指针,这些函数指针将用在fb中的熄灭屏,设置屏幕亮度等操作。
主要工作在dsi_panel_device_register()函数中完成。现在看起实现:
@mdss_dsi.c
int dsi_panel_device_register(struct platform_device *ctrl_pdev,
struct device_node *pan_node, struct mdss_dsi_ctrl_pdata *ctrl_pdata)
{
struct mdss_panel_info *pinfo = &(ctrl_pdata->panel_data.panel_info);
mipi = &(pinfo->mipi);
/* mipi模式 */
pinfo->type =
((mipi->mode == DSI_VIDEO_MODE)
? MIPI_VIDEO_PANEL : MIPI_CMD_PANEL);
/* 后面会有event发送给dsi,会在mdss_dsi_event_handler中处理 */
ctrl_pdata->panel_data.event_handler = mdss_dsi_event_handler;
ctrl_pdata->panel_data.get_fb_node = mdss_dsi_get_fb_node_cb;
mdss_dsi_ctrl_init(&ctrl_pdev->dev, ctrl_pdata);
rc = mdss_register_panel(ctrl_pdev, &(ctrl_pdata->panel_data));
}
FB驱动在mdss_fb.c中完成,实现Linux Framebuffer的注册已经相关操作。
int __init mdss_fb_init(void)
{
int rc = -ENODEV;
if (fb_get_options("msmfb", NULL))
return rc;
if (platform_driver_register(&mdss_fb_driver))
return rc;
return 0;
}
module_init(mdss_fb_init);
mdss_fb_driver的实现:
static const struct of_device_id mdss_fb_dt_match[] = {
{ .compatible = "qcom,mdss-fb",},
{}
};
EXPORT_COMPAT("qcom,mdss-fb");
static struct platform_driver mdss_fb_driver = {
.probe = mdss_fb_probe,
.remove = mdss_fb_remove,
.suspend = mdss_fb_suspend,
.resume = mdss_fb_resume,
.shutdown = mdss_fb_shutdown,
.driver = {
.name = "mdss_fb",
.of_match_table = mdss_fb_dt_match,
.pm = &mdss_fb_pm_ops,
},
};
compatible为"qcom,mdss-fb"在msm8909-mdss.dtsi中有定义。因此platform_driver的probe将被调用。整个fb注册过程大都也在这其中完成。
static int mdss_fb_probe(struct platform_device *pdev)
{
struct msm_fb_data_type *mfd = NULL;
struct mdss_panel_data *pdata;
struct fb_info *fbi;
int rc;
/* 获取plat_data,含有在mdss_dsi中解析出的屏信息 */
pdata = dev_get_platdata(&pdev->dev);
/* 分配fbi结构体,用来注册fb */
fbi = framebuffer_alloc(sizeof(struct msm_fb_data_type), NULL);
/* fbi->par执行mfd,保存私有数据 mfd保存了屏的所以信息 */
mfd = (struct msm_fb_data_type *)fbi->par;
mfd->key = MFD_KEY;
mfd->fbi = fbi;
mfd->panel_info = &pdata->panel_info;
mfd->panel.type = pdata->panel_info.type;
mfd->fb_imgType = MDP_RGBA_8888;
...
platform_set_drvdata(pdev, mfd);
/* 完成fb的注册,从mfd中取出屏信息放到fbi中,进行fb注册,并包含fb给用户空间的的操作函数 */
rc = mdss_fb_register(mfd);
/* 初始化mdp */
if (mfd->mdp.init_fnc) {
rc = mfd->mdp.init_fnc(mfd);
if (rc) {
pr_err("init_fnc failed\n");
return rc;
}
}
/* 注册背光灯设备 */
if (!lcd_backlight_registered) {
backlight_led.brightness = mfd->panel_info->brightness_max;
backlight_led.max_brightness = mfd->panel_info->brightness_max;
if (led_classdev_register(&pdev->dev, &backlight_led))
pr_err("led_classdev_register failed\n");
else
lcd_backlight_registered = 1;
}
mdss_fb_create_sysfs(mfd);
/* 发送注册事件,处理的hanlder在mdss_dsi中定义 */
mdss_fb_send_panel_event(mfd, MDSS_EVENT_FB_REGISTERED, fbi);
}
在上面代码中,首先通过dev_get_platdata()获取屏的信息,然后创建一个msm_fb_data_type结构体,将屏信息全部放在了mfd中。调用mdss_fb_register()进行fb的注册。