硬件上,高通平台有一个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);

高通通信是如何架构的 高通平台架构_初始化


高通通信是如何架构的 高通平台架构_初始化_02

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)