硬件上,高通平台有一个mipi-dsi接口连接LCM,由MDP(mobile display processor)进行管理,就是一般说的LCD控制器
软件上,高通平台提供MDSS(Multimedia Display Sub-system)进行管理
软件驱动目录:kernel/msm-4.9/drivers/video/fbdev/msm
主要分为三部分:
MDP驱动:对使用的硬件资源进行初始化,同时在fb设备中注册mdp的使用接口,文件mdss_mdp3.c
DSI驱动:解析模组厂商提供的panel的dtsi文件,从哪个文件中能够获取到panel的mode,分辨率,还有Driver IC的初始化command;文件mdss_dsi_panel.c
FB驱动:实现Linux Framebuffer的注册和为上层用户提供访问控制接口;文件mdss_fb.c
一般的执行顺序是: MDP probe → DSI probe → FB probe
下面分析每部分的代码
LCD控制器、I2C控制器等一般都是平台设备,设备用platform_device表示,驱动用platform_driver表示。
MDP设备在dts文件中定义:
msm8953-mdss.dtsi
&soc {
mdss_mdp: qcom,mdss_mdp@1a00000 {
compatible = "qcom,mdss_mdp";
reg = <0x01a00000 0x90000>,
<0x01ab0000 0x1040>;
reg-names = "mdp_phys", "vbif_phys";
interrupts = <0 72 0>;
vdd-supply = <&gdsc_mdss>;
/* Bus Scale Settings */
qcom,msm-bus,name = "mdss_mdp";
qcom,msm-bus,num-cases = <3>;
qcom,msm-bus,num-paths = <1>;
qcom,msm-bus,vectors-KBps =
<22 512 0 0>,
<22 512 0 6400000>,
<22 512 0 6400000>;
/* Fudge factors */
qcom,mdss-ab-factor = <1 1>; /* 1 time */
qcom,mdss-ib-factor = <1 1>; /* 1 time */
qcom,mdss-clk-factor = <105 100>; /* 1.05 times */
qcom,max-mixer-width = <2048>;
qcom,max-pipe-width = <2048>;
MDP驱动的注册:
static const struct of_device_id mdss_mdp_dt_match[] = {
{ .compatible = "qcom,mdss_mdp",},
{}
};
MODULE_DEVICE_TABLE(of, mdss_mdp_dt_match);
static struct platform_driver mdss_mdp_driver = {
.probe = mdss_mdp_probe,
.remove = mdss_mdp_remove,
.suspend = mdss_mdp_suspend,
.resume = mdss_mdp_resume,
.shutdown = NULL,
.driver = {
/*
* Driver name must match the device name added in
* platform.c.
*/
.name = "mdp",
.of_match_table = mdss_mdp_dt_match,
.pm = &mdss_mdp_pm_ops,
},
};
static int mdss_mdp_register_driver(void)
{
return platform_driver_register(&mdss_mdp_driver);
}
static int __init mdss_mdp_driver_init(void)
{
int ret;
ret = mdss_mdp_register_driver();
if (ret) {
pr_err("mdp_register_driver() failed!\n");
return ret;
}
return 0;
}
老套路,设备和驱动匹配之后,调用mdss_mdp_probe函数,这个函数首先分配mdss_data_type结构体内存,然后对成员进行初始化:
static int mdss_mdp_probe(struct platform_device *pdev)
{
struct resource *res;
int rc;
struct mdss_data_type *mdata;
uint32_t intf_sel = 0;
uint32_t split_display = 0;
int num_of_display_on = 0;
int i = 0;
if (!pdev->dev.of_node) {
pr_err("MDP driver only supports device tree probe\n");
return -ENOTSUPP;
}
if (mdss_res) {
pr_err("MDP already initialized\n");
return -EINVAL;
}
mdata = devm_kzalloc(&pdev->dev, sizeof(*mdata), GFP_KERNEL);
if (mdata == NULL)
return -ENOMEM;
pdev->id = 0;
mdata->pdev = pdev;
platform_set_drvdata(pdev, mdata);
mdss_res = mdata;
mutex_init(&mdata->reg_lock);
mutex_init(&mdata->reg_bus_lock);
mutex_init(&mdata->bus_lock);
INIT_LIST_HEAD(&mdata->reg_bus_clist);
atomic_set(&mdata->sd_client_count, 0);
atomic_set(&mdata->active_intf_cnt, 0);
mdss_res->mdss_util = mdss_get_util_intf();
if (mdss_res->mdss_util == NULL) {
pr_err("Failed to get mdss utility functions\n");
return -ENODEV;
}
mdss_res->mdss_util->get_iommu_domain = mdss_smmu_get_domain_id;
mdss_res->mdss_util->iommu_attached = is_mdss_iommu_attached;
mdss_res->mdss_util->iommu_ctrl = mdss_iommu_ctrl;
mdss_res->mdss_util->bus_scale_set_quota = mdss_bus_scale_set_quota;
mdss_res->mdss_util->bus_bandwidth_ctrl = mdss_bus_bandwidth_ctrl;
mdss_res->mdss_util->panel_intf_type = mdss_panel_intf_type;
mdss_res->mdss_util->panel_intf_status = mdss_panel_get_intf_status;
rc = msm_mdss_ioremap_byname(pdev, &mdata->mdss_io, "mdp_phys");
if (rc) {
pr_err("unable to map MDP base\n");
goto probe_done;
}
pr_debug("MDSS HW Base addr=0x%x len=0x%x\n",
(int) (unsigned long) mdata->mdss_io.base,
mdata->mdss_io.len);
rc = msm_mdss_ioremap_byname(pdev, &mdata->vbif_io, "vbif_phys");
if (rc) {
pr_err("unable to map MDSS VBIF base\n");
goto probe_done;
}
pr_debug("MDSS VBIF HW Base addr=0x%x len=0x%x\n",
(int) (unsigned long) mdata->vbif_io.base,
mdata->vbif_io.len);
rc = msm_mdss_ioremap_byname(pdev, &mdata->vbif_nrt_io,
"vbif_nrt_phys");
if (rc)
pr_debug("unable to map MDSS VBIF non-realtime base\n");
else
pr_debug("MDSS VBIF NRT HW Base addr=%pK len=0x%x\n",
mdata->vbif_nrt_io.base, mdata->vbif_nrt_io.len);
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
pr_err("unable to get MDSS irq\n");
rc = -ENOMEM;
goto probe_done;
}
mdss_mdp_hw.irq_info = kcalloc(1, sizeof(struct irq_info), GFP_KERNEL);
if (!mdss_mdp_hw.irq_info)
return -ENOMEM;
mdss_mdp_hw.irq_info->irq = res->start;
mdss_mdp_hw.ptr = mdata;
/* export misc. interrupts to external driver */
mdata->irq_domain = irq_domain_add_linear(pdev->dev.of_node, 32,
&mdss_irq_domain_ops, mdata);
if (!mdata->irq_domain) {
pr_err("unable to add linear domain\n");
rc = -ENOMEM;
goto probe_done;
}
mdss_misc_hw.irq_info = mdss_intr_line();
rc = mdss_res->mdss_util->register_irq(&mdss_misc_hw);
if (rc)
pr_err("mdss_register_irq failed.\n");
rc = mdss_mdp_res_init(mdata);
if (rc) {
pr_err("unable to initialize mdss mdp resources\n");
goto probe_done;
}
rc = mdss_mdp_retention_init(mdata);
if (rc) {
pr_err("unable to initialize mdss mdp retention\n");
goto probe_done;
}
pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT_MS);
if (mdata->idle_pc_enabled)
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_set_suspended(&pdev->dev);
pm_runtime_enable(&pdev->dev);
if (!pm_runtime_enabled(&pdev->dev))
mdss_mdp_footswitch_ctrl(mdata, true);
rc = mdss_mdp_bus_scale_register(mdata);
if (rc) {
pr_err("unable to register bus scaling\n");
goto probe_done;
}
/*
* enable clocks and read mdp_rev as soon as possible once
* kernel is up.
*/
mdss_mdp_footswitch_ctrl_splash(true);
mdss_hw_rev_init(mdata);
/*populate hw iomem base info from device tree*/
rc = mdss_mdp_parse_dt(pdev);
if (rc) {
pr_err("unable to parse device tree\n");
goto probe_done;
}
rc = mdss_mdp_get_cmdline_config(pdev);
if (rc) {
pr_err("Error in panel override:rc=[%d]\n", rc);
goto probe_done;
}
rc = mdss_mdp_debug_init(pdev, mdata);
if (rc) {
pr_err("unable to initialize mdp debugging\n");
goto probe_done;
}
rc = mdss_mdp_scaler_init(mdata, &pdev->dev);
if (rc)
goto probe_done;
rc = mdss_mdp_register_sysfs(mdata);
if (rc)
pr_err("unable to register mdp sysfs nodes\n");
rc = mdss_fb_register_mdp_instance(&mdp5);
if (rc)
pr_err("unable to register mdp instance\n");
rc = mdss_res->mdss_util->register_irq(&mdss_mdp_hw);
if (rc)
pr_err("mdss_register_irq failed.\n");
rc = mdss_smmu_init(mdata, &pdev->dev);
if (rc)
pr_err("mdss smmu init failed\n");
mdss_mdp_set_supported_formats(mdata);
mdss_res->mdss_util->mdp_probe_done = true;
mdss_hw_init(mdata);
rc = mdss_mdp_pp_init(&pdev->dev);
if (rc)
pr_err("unable to initialize mdss pp resources\n");
/* Restoring Secure configuration during boot-up */
if (mdss_mdp_req_init_restore_cfg(mdata))
__mdss_restore_sec_cfg(mdata);
if (mdss_has_quirk(mdata, MDSS_QUIRK_BWCPANIC)) {
mdata->default_panic_lut0 = readl_relaxed(mdata->mdp_base +
MMSS_MDP_PANIC_LUT0);
mdata->default_panic_lut1 = readl_relaxed(mdata->mdp_base +
MMSS_MDP_PANIC_LUT1);
mdata->default_robust_lut = readl_relaxed(mdata->mdp_base +
MMSS_MDP_ROBUST_LUT);
}
/*
* Read the DISP_INTF_SEL register to check if display was enabled in
* bootloader or not. If yes, let handoff handle removing the extra
* clk/regulator votes else turn off clk/regulators because purpose
* here is to get mdp_rev.
*/
intf_sel = readl_relaxed(mdata->mdp_base +
MDSS_MDP_REG_DISP_INTF_SEL);
split_display = readl_relaxed(mdata->mdp_base +
MDSS_MDP_REG_SPLIT_DISPLAY_EN);
mdata->splash_intf_sel = intf_sel;
mdata->splash_split_disp = split_display;
if (intf_sel != 0) {
for (i = 0; i < 4; i++)
num_of_display_on += ((intf_sel >> i*8) & 0x000000FF);
/*
* For split display enabled - DSI0, DSI1 interfaces are
* considered as single display. So decrement
* 'num_of_display_on' by 1
*/
if (split_display)
num_of_display_on--;
}
if (!num_of_display_on) {
mdss_mdp_footswitch_ctrl_splash(false);
msm_bus_scale_client_update_request(
mdata->bus_hdl, 0);
mdata->ao_bw_uc_idx = 0;
} else {
mdata->handoff_pending = true;
/*
* If multiple displays are enabled in LK, ctrl_splash off will
* be called multiple times during splash_cleanup. Need to
* enable it symmetrically
*/
for (i = 1; i < num_of_display_on; i++)
mdss_mdp_footswitch_ctrl_splash(true);
}
mdp_intr_cb = kcalloc(ARRAY_SIZE(mdp_irq_map),
sizeof(struct intr_callback), GFP_KERNEL);
if (mdp_intr_cb == NULL)
return -ENOMEM;
mdss_res->mdp_irq_mask = kcalloc(ARRAY_SIZE(mdp_intr_reg),
sizeof(u32), GFP_KERNEL);
if (mdss_res->mdp_irq_mask == NULL)
return -ENOMEM;
pr_info("mdss version = 0x%x, bootloader display is %s, num %d, intf_sel=0x%08x\n",
mdata->mdp_rev, num_of_display_on ? "on" : "off",
num_of_display_on, intf_sel);
probe_done:
if (IS_ERR_VALUE((unsigned long)rc)) {
if (!num_of_display_on)
mdss_mdp_footswitch_ctrl_splash(false);
if (mdata->regulator_notif_register)
regulator_unregister_notifier(mdata->fs,
&(mdata->gdsc_cb));
mdss_mdp_hw.ptr = NULL;
mdss_mdp_pp_term(&pdev->dev);
mutex_destroy(&mdata->reg_lock);
mdss_res = NULL;
}
return rc;
}
这里msm_mdss_ioremap_byname(pdev, &mdata->mdss_io, “mdp_phys”)进行IO内存映射,以便可以通过内存操作直接访问外设寄存器。
static struct resource *msm_mdss_get_res_byname(struct platform_device *pdev,
unsigned int type, const char *name)
{
struct resource *res = NULL;
res = platform_get_resource_byname(pdev, type, name);
if (!res)
DEV_ERR("%s: '%s' resource not found\n", __func__, name);
return res;
} /* msm_mdss_get_res_byname */
EXPORT_SYMBOL(msm_mdss_get_res_byname);
int msm_mdss_ioremap_byname(struct platform_device *pdev,
struct mdss_io_data *io_data, const char *name)
{
struct resource *res = NULL;
if (!pdev || !io_data) {
DEV_ERR("%pS->%s: invalid input\n",
__builtin_return_address(0), __func__);
return -EINVAL;
}
res = msm_mdss_get_res_byname(pdev, IORESOURCE_MEM, name);
if (!res) {
DEV_ERR("%pS->%s: '%s' msm_mdss_get_res_byname failed\n",
__builtin_return_address(0), __func__, name);
return -ENODEV;
}
io_data->len = (u32)resource_size(res);
io_data->base = ioremap(res->start, io_data->len);
if (!io_data->base) {
DEV_ERR("%pS->%s: '%s' ioremap failed\n",
__builtin_return_address(0), __func__, name);
return -EIO;
}
return 0;
} /* msm_mdss_ioremap_byname */
首先调用platform_get_resource_byname获得resource结构体,该结构体定义如下,这个结构体的每个成员值对应dts文件中的reg和reg-names属性。该resource是在系统开机初始化时调用struct resource *request_mem_region生成。获得物理基地址后,调用ioremap将物理基地址映射到虚拟地址空间,就能在驱动中进行访问。
struct resource {
resource_size_t start; //表示资源的起始值,
resource_size_t end; //表示资源的最后一个字节的地址, 如果是中断,end和satrt相同
const char *name; // 可不写
unsigned long flags; //资源的类型
struct resource *parent, *sibling, *child;
};
reg = <0x01a00000 0x90000>,
<0x01ab0000 0x1040>;
reg-names = "mdp_phys", "vbif_phys";
DSI驱动的注册:
mdss_dsi.c 该文件中注册两个模块,dsi driver和dsi ctrl driver。mdss_dsi_probe中初始化dsi控制器,
mdss_dsi_ctrl_probe中解析模组厂提供的panel的dtsi文件。从文件中获得panel的mode、分辨率、帧率、command数据等
static const struct of_device_id mdss_dsi_dt_match[] = {
{.compatible = "qcom,mdss-dsi"},
{}
};
MODULE_DEVICE_TABLE(of, mdss_dsi_dt_match);
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 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_register_driver(void)
{
return platform_driver_register(&mdss_dsi_driver);
}
static int __init mdss_dsi_driver_init(void)
{
int ret;
ret = mdss_dsi_register_driver();
if (ret) {
pr_err("mdss_dsi_register_driver() failed!\n");
return ret;
}
return ret;
}
module_init(mdss_dsi_driver_init);
static int mdss_dsi_ctrl_register_driver(void)
{
return platform_driver_register(&mdss_dsi_ctrl_driver);
}
static int __init mdss_dsi_ctrl_driver_init(void)
{
int ret;
ret = mdss_dsi_ctrl_register_driver();
if (ret) {
pr_err("mdss_dsi_ctrl_register_driver() failed!\n");
return ret;
}
return ret;
}
module_init(mdss_dsi_ctrl_driver_init);
FB驱动的注册:
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,
},
};
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;
}
mdss-fb在设备树中配置如下,注意这个有三个compatible属性都是qcom,mdss-fb的节点
msm8953-mdss.dtsi
&soc {
mdss_fb0: qcom,mdss_fb_primary {
cell-index = <0>;
compatible = "qcom,mdss-fb";
qcom,cont-splash-memory {
linux,contiguous-region = <&cont_splash_mem>;
};
};
mdss_fb1: qcom,mdss_fb_wfd {
cell-index = <1>;
compatible = "qcom,mdss-fb";
};
mdss_fb2: qcom,mdss_fb_secondary {
cell-index = <2>;
compatible = "qcom,mdss-fb";
}
设备和驱动匹配成功后,调用mdss_fb_probe
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;
const char *data;
if (fbi_list_index >= MAX_FBI_LIST)
return -ENOMEM;
pdata = dev_get_platdata(&pdev->dev);
if (!pdata)
return -EPROBE_DEFER;
if (!mdp_instance) {
pr_err("mdss mdp resource not initialized yet\n");
return -ENODEV;
}
/*
* alloc framebuffer info + par data
*/
fbi = framebuffer_alloc(sizeof(struct msm_fb_data_type), NULL);
if (fbi == NULL) {
pr_err("can't allocate framebuffer info data!\n");
return -ENOMEM;
}
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->panel.id = mfd->index;
mfd->fb_page = MDSS_FB_NUM;
mfd->index = fbi_list_index;
mfd->mdp_fb_page_protection = MDP_FB_PAGE_PROTECTION_WRITECOMBINE;
mfd->ext_ad_ctrl = -1;
if (mfd->panel_info && mfd->panel_info->brightness_max > 0)
MDSS_BRIGHT_TO_BL(mfd->bl_level, backlight_led.brightness,
mfd->panel_info->bl_max, mfd->panel_info->brightness_max);
else
mfd->bl_level = 0;
mfd->bl_scale = 1024;
mfd->bl_min_lvl = 30;
mfd->ad_bl_level = 0;
mfd->fb_imgType = MDP_RGBA_8888;
mfd->calib_mode_bl = 0;
mfd->unset_bl_level = U32_MAX;
mfd->bl_extn_level = -1;
mfd->pdev = pdev;
if (mfd->panel.type == SPI_PANEL)
mfd->fb_imgType = MDP_RGB_565;
if (mfd->panel.type == MIPI_VIDEO_PANEL || mfd->panel.type ==
MIPI_CMD_PANEL || mfd->panel.type == SPI_PANEL){
rc = of_property_read_string(pdev->dev.of_node,
"qcom,mdss-fb-format", &data);
if (!rc) {
if (!strcmp(data, "rgb888"))
mfd->fb_imgType = MDP_RGB_888;
else if (!strcmp(data, "rgb565"))
mfd->fb_imgType = MDP_RGB_565;
else
mfd->fb_imgType = MDP_RGBA_8888;
}
}
mfd->split_fb_left = mfd->split_fb_right = 0;
mdss_fb_set_split_mode(mfd, pdata);
pr_info("fb%d: split_mode:%d left:%d right:%d\n", mfd->index,
mfd->split_mode, mfd->split_fb_left, mfd->split_fb_right);
mfd->mdp = *mdp_instance;
rc = of_property_read_bool(pdev->dev.of_node,
"qcom,boot-indication-enabled");
if (rc) {
led_trigger_register_simple("boot-indication",
&(mfd->boot_notification_led));
}
INIT_LIST_HEAD(&mfd->file_list);
mutex_init(&mfd->bl_lock);
mutex_init(&mfd->mdss_sysfs_lock);
mutex_init(&mfd->switch_lock);
fbi_list[fbi_list_index++] = fbi;
platform_set_drvdata(pdev, mfd);
rc = mdss_fb_register(mfd);
if (rc)
return rc;
mdss_fb_create_sysfs(mfd);
mdss_fb_send_panel_event(mfd, MDSS_EVENT_FB_REGISTERED, fbi);
if (mfd->mdp.init_fnc) {
rc = mfd->mdp.init_fnc(mfd);
if (rc) {
pr_err("init_fnc failed\n");
return rc;
}
}
mdss_fb_init_fps_info(mfd);
rc = pm_runtime_set_active(mfd->fbi->dev);
if (rc < 0)
pr_err("pm_runtime: fail to set active.\n");
pm_runtime_enable(mfd->fbi->dev);
/* android supports only one lcd-backlight/lcd for now */
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_init_panel_modes(mfd, pdata);
mfd->mdp_sync_pt_data.fence_name = "mdp-fence";
if (mfd->mdp_sync_pt_data.timeline == NULL) {
char timeline_name[32];
snprintf(timeline_name, sizeof(timeline_name),
"mdss_fb_%d", mfd->index);
mfd->mdp_sync_pt_data.timeline =
mdss_create_timeline(timeline_name);
if (mfd->mdp_sync_pt_data.timeline == NULL) {
pr_err("cannot create release fence time line\n");
return -ENOMEM;
}
mfd->mdp_sync_pt_data.notifier.notifier_call =
__mdss_fb_sync_buf_done_callback;
}
mdss_fb_set_mdp_sync_pt_threshold(mfd, mfd->panel.type);
if (mfd->mdp.splash_init_fnc)
mfd->mdp.splash_init_fnc(mfd);
/*
* Register with input driver for a callback for command mode panels.
* When there is an input event, mdp clocks will be turned on to reduce
* latency when a frame update happens.
* For video mode panels, idle timeout will be delayed so that userspace
* does not get an idle event while new frames are expected. In case of
* an idle event, user space tries to fall back to GPU composition which
* can lead to increased load when there are new frames.
*/
if (mfd->mdp.input_event_handler &&
((mfd->panel_info->type == MIPI_CMD_PANEL) ||
(mfd->panel_info->type == MIPI_VIDEO_PANEL)))
if (mdss_fb_register_input_handler(mfd))
pr_err("failed to register input handler\n");
INIT_DELAYED_WORK(&mfd->idle_notify_work, __mdss_fb_idle_notify_work);
return rc;
}
调用framebuffer_alloc分配一个fbi内存,该函数将struct fb_info和私有数据的内存info->par一起分配,并且按sizeof(long)对齐。这种方法分配了连续的内存,减少内存的碎片化,是一种常用的方法。
struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
{
#define BYTES_PER_LONG (BITS_PER_LONG/8)
#define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG))
int fb_info_size = sizeof(struct fb_info);
struct fb_info *info;
char *p;
if (size)
fb_info_size += PADDING;
p = kzalloc(fb_info_size + size, GFP_KERNEL);
if (!p)
return NULL;
info = (struct fb_info *) p;
if (size)
info->par = p + fb_info_size;
info->device = dev;
info->fbcon_rotate_hint = -1;
#if IS_ENABLED(CONFIG_FB_BACKLIGHT)
mutex_init(&info->bl_curve_mutex);
#endif
return info;
#undef PADDING
#undef BYTES_PER_LONG
}
EXPORT_SYMBOL(framebuffer_alloc);
接下来调用mdss_fb_register对fb_info结构体进行填充,调用register_framebuffer完成fb的注册
static int mdss_fb_register(struct msm_fb_data_type *mfd)
{
int ret = -ENODEV;
int bpp;
char panel_name[20];
struct mdss_panel_info *panel_info = mfd->panel_info;
struct fb_info *fbi = mfd->fbi;
struct fb_fix_screeninfo *fix;
struct fb_var_screeninfo *var;
int *id;
/*
* fb info initialization
*/
fix = &fbi->fix;
var = &fbi->var;
fix->type_aux = 0; /* if type == FB_TYPE_INTERLEAVED_PLANES */
fix->visual = FB_VISUAL_TRUECOLOR; /* True Color */
fix->ywrapstep = 0; /* No support */
fix->mmio_start = 0; /* No MMIO Address */
fix->mmio_len = 0; /* No MMIO Address */
fix->accel = FB_ACCEL_NONE;/* FB_ACCEL_MSM needes to be added in fb.h */
var->xoffset = 0, /* Offset from virtual to visible */
var->yoffset = 0, /* resolution */
var->grayscale = 0, /* No graylevels */
var->nonstd = 0, /* standard pixel format */
var->activate = FB_ACTIVATE_VBL, /* activate it at vsync */
var->height = -1, /* height of picture in mm */
var->width = -1, /* width of picture in mm */
var->accel_flags = 0, /* acceleration flags */
var->sync = 0, /* see FB_SYNC_* */
var->rotate = 0, /* angle we rotate counter clockwise */
mfd->op_enable = false;
mdss_panelinfo_to_fb_var(panel_info, var);
fix->type = panel_info->is_3d_panel;
if (mfd->mdp.fb_stride)
fix->line_length = mfd->mdp.fb_stride(mfd->index, var->xres,
bpp);
else
fix->line_length = var->xres * bpp;
var->xres_virtual = var->xres;
var->yres_virtual = panel_info->yres * mfd->fb_page;
var->bits_per_pixel = bpp * 8; /* FrameBuffer color depth */
/*
* Populate smem length here for uspace to get the
* Framebuffer size when FBIO_FSCREENINFO ioctl is called.
*/
fix->smem_len = PAGE_ALIGN(fix->line_length * var->yres) * mfd->fb_page;
/* id field for fb app */
id = (int *)&mfd->panel;
snprintf(fix->id, sizeof(fix->id), "mdssfb_%x", (u32) *id);
fbi->fbops = &mdss_fb_ops;
fbi->flags = FBINFO_FLAG_DEFAULT;
fbi->pseudo_palette = mdss_fb_pseudo_palette;
mfd->ref_cnt = 0;
mfd->panel_power_state = MDSS_PANEL_POWER_OFF;
mfd->dcm_state = DCM_UNINIT;
if (mdss_fb_alloc_fbmem(mfd))
pr_warn("unable to allocate fb memory in fb register\n");
mfd->op_enable = true;
mutex_init(&mfd->update.lock);
mutex_init(&mfd->no_update.lock);
mutex_init(&mfd->mdp_sync_pt_data.sync_mutex);
atomic_set(&mfd->mdp_sync_pt_data.commit_cnt, 0);
atomic_set(&mfd->commits_pending, 0);
atomic_set(&mfd->ioctl_ref_cnt, 0);
atomic_set(&mfd->kickoff_pending, 0);
init_timer(&mfd->no_update.timer);
mfd->no_update.timer.function = mdss_fb_no_update_notify_timer_cb;
mfd->no_update.timer.data = (unsigned long)mfd;
mfd->update.ref_count = 0;
mfd->no_update.ref_count = 0;
mfd->update.init_done = false;
init_completion(&mfd->update.comp);
init_completion(&mfd->no_update.comp);
init_completion(&mfd->power_off_comp);
init_completion(&mfd->power_set_comp);
init_waitqueue_head(&mfd->commit_wait_q);
init_waitqueue_head(&mfd->idle_wait_q);
init_waitqueue_head(&mfd->ioctl_q);
init_waitqueue_head(&mfd->kickoff_wait_q);
ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
if (ret)
pr_err("fb_alloc_cmap() failed!\n");
if (register_framebuffer(fbi) < 0) {
fb_dealloc_cmap(&fbi->cmap);
mfd->op_enable = false;
return -EPERM;
}
snprintf(panel_name, ARRAY_SIZE(panel_name), "mdss_panel_fb%d",
mfd->index);
mdss_panel_debugfs_init(panel_info, panel_name);
pr_info("FrameBuffer[%d] %dx%d registered successfully!\n", mfd->index,
fbi->var.xres, fbi->var.yres);
return 0;
}
fbi->fbops = &mdss_fb_ops;
static struct fb_ops mdss_fb_ops = {
.owner = THIS_MODULE,
.fb_open = mdss_fb_open,
.fb_release = mdss_fb_release,
.fb_check_var = mdss_fb_check_var, /* vinfo check */
.fb_set_par = mdss_fb_set_par, /* set the video mode */
.fb_blank = mdss_fb_blank, /* blank display */
.fb_pan_display = mdss_fb_pan_display, /* pan display */
.fb_ioctl_v2 = mdss_fb_ioctl, /* perform fb specific ioctl */
#ifdef CONFIG_COMPAT
.fb_compat_ioctl_v2 = mdss_fb_compat_ioctl,
#endif
.fb_mmap = mdss_fb_mmap,
};
fb_mmap 用于应用层和framebuffer的内存映射
static int mdss_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
int rc = -EINVAL;
if (mfd->fb_mmap_type == MDP_FB_MMAP_ION_ALLOC) {
rc = mdss_fb_fbmem_ion_mmap(info, vma);
} else if (mfd->fb_mmap_type == MDP_FB_MMAP_PHYSICAL_ALLOC) {
rc = mdss_fb_physical_mmap(info, vma);
} else {
if (!info->fix.smem_start && !mfd->fb_ion_handle) {
rc = mdss_fb_fbmem_ion_mmap(info, vma);
mfd->fb_mmap_type = MDP_FB_MMAP_ION_ALLOC;
} else {
rc = mdss_fb_physical_mmap(info, vma);
mfd->fb_mmap_type = MDP_FB_MMAP_PHYSICAL_ALLOC;
}
}
if (rc < 0)
pr_err("fb mmap failed with rc = %d\n", rc);
return rc;
}
这里调用led_classdev_register注册背光灯设备
/* android supports only one lcd-backlight/lcd for now */
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;
}
开机亮屏灭屏流程(fb_ioctl)