Android Qcom Display学习(四)

该系列文章总目录链接与各部分简介: Android Qcom Display学习(零)

DRM

Linux DRM(二)基本概念和特性
libdrm:对底层定义在drm_ioctl.c 中各种IOCTL接口进行封装,向上层提供通用的API接口
GEM:Graphic Execution Manager,主要负责显示buffer的分配和释放,也是GPU唯一用到DRM的地方。
KMS:Kernel Mode Setting,负责显示buffer的切换,多图层的合成方式,显示位置。设置分辨率、刷新率等等。
Android Qcom Display学习(四)_第1张图片
KMS和DRM两者都是通过libdrm来进行调用的,但两者是具有相对独立性的,DPU对应的KMS对应于系统侧的HWC Composer,其重点是在于送显,而GPU对应DRM则侧重应用侧相关的渲染绘制。最初的版本DRM中没有KMS的,Mode-setting(包含更新画面、屏幕分辨、刷新率等等)都是在Userspace中实现的,这样会与Rendering发现竞争,并且两个硬件之间工作不统一的问题,因此就引入了KMS。

KMS

Android Qcom Display学习(四)_第2张图片

FrameBuffer

FrameBuffer,单个图层的显示内容(包含图像格式和大小),唯一一个和硬件无关的基本元素

我们知道在SurfaceFlinger中处理多个Layers的时候,这些Layers都会通过libdrm调用到GPU模块完成渲染,渲染后的buffer会通过dma_buf共享内存的方式放入GraphciBuffer。不同的Layers的合成方式不同,分为Device和Client,Client的类型还会通过GPU来进行图层的合成,HWC在收集所有的GraphicBuffer后,通过libdrm交给KMS的FrameBuffer,Plane再从drm_framebuffer中获取数据。

SurfaceFlinger传入HWC的过程,都会调用beginCommand,最终都会通过QtiComposerClient::CommandReader::parseCommonCmd来解析,如果没有Client合成的话,会尝试parsePresentOrValidateDisplay(PRESENT_OR_VALIDATE_DISPLAY)送显,如果无法接受显示则会调用valide,后续还会处理Client的合成,最终再通过parsePresentDisplay(PRESENT_DISPLAY)送显,两者都是调用PresentDisplay

HWCSession::PresentDisplay
	HWCDisplayBuiltIn::Present
		HWCDisplay::CommitLayerStack  // DumpInputBuffers() 高通dump inputbuffer的地方
			DisplayBuiltIn::Commit
				DisplayBase::Commit

	HWPeripheralDRM::Commit
		HWDeviceDRM::AtomicCommit
		    HWDeviceDRM::SetupAtomic // fb id用于对应framebuffer
			DRMAtomicReq::Commit
				drmModeAtomicCommit
					drm_atomic_commit
			

PLANE

PLANE代表显示图层,每个 CRTC 需要定义一个 primary plane 以及可选的 overlay plane、cursor plane

enum drm_plane_type {
   DRM_PLANE_TYPE_OVERLAY,
   DRM_PLANE_TYPE_PRIMARY,
   DRM_PLANE_TYPE_CURSOR,
}

PLANE 存在的意义主要有两点:增强系统灵活性、提高系统性能
(1)对于背景和光标等变化不频繁的基本图显输出,可用通用 plane 来实现。而那些频繁变化则可由专用的 plane 来实现。
(2)plane 具备图像缩放、剪裁、多图层叠加等基本的图像处理功能,因此,可以让 GPU 来将更多的精力放在图形渲染上。

PLANE对应的SSPP(Source Surface Processor Pipes)是DPU的硬件模块,功能如上就是支持缩放,格式转换,锐化质量改进等。

plane所支持的Format定义在drm_fourcc.h
#define fourcc_code(a, b, c, d) ((__u32)(a) | ((__u32)(b) << 8) | \
  				   ((__u32)(c) << 16) | ((__u32)(d) << 24)) 
#define DRM_FORMAT_RGBA8888	fourcc_code('R', 'A', '2', '4') /* [31:0] R:G:B:A 8:8:8:8 little endian */
'R', 'A', '2', '4' = 0x85 | 0x65 << 8 | 0x32 << 16 | 0x34 << 24 = 0x34326582 = 875718018

需要注意的是调用的驱动接口基本都使用atmoic的方式,例如atomic_set_property对应的应用接口drmModeAtomicCommit,之前的legacy的方式,例如drm_atomic_helper_update_plane对应的应用接口drmModeSetPlane已被弃用,新接口代替老接口的使用方式(atomic-plane)应用

static const struct drm_plane_funcs sde_plane_funcs = {
    .update_plane = drm_atomic_helper_update_plane,
    .disable_plane = drm_atomic_helper_disable_plane,
    .destroy = sde_plane_destroy,
    .atomic_set_property = sde_plane_atomic_set_property,

static const struct drm_plane_helper_funcs sde_plane_helper_funcs = {
 	.prepare_fb = sde_plane_prepare_fb, //预先对图层进行处理,为扫描准备帧缓冲区
  	.cleanup_fb = sde_plane_cleanup_fb, // 图层显示完成后的处理,清楚prepare_fb中分配的资源
  	.atomic_check = sde_plane_atomic_check,// 检查参数是否支持当前的format、区域大小越界等
  	.atomic_update = sde_plane_atomic_update,// 更新plane状态(注意FrameBuffer和crtc长宽区别)

  
atmoic: drmModeAtomicCommit  + drmModeAtomicAddProperty = legacy: drmModeSetPlane
drmModeAtomicCommit 
	DRM_IOCTL_MODE_ATOMIC
	drm_mode_atomic_ioctl
		drm_atomic_set_property
			case DRM_MODE_OBJECT_PLANE:
			drm_atomic_plane_set_property
				.atomic_set_property = sde_plane_atomic_set_property
				
drmModeSetPlane
	DRM_IOCTL_MODE_SETPLANE
	drm_mode_setplane
	.update_plane = drm_atomic_helper_update_plane,

CTRC

CRTC从drm_plane 接收RGB像素数据并将其混合到一起,传输给下级显示设备drm_encoder。
CRTC 模块存在的意义主要有以下三点:
(1)统一协调 FB、DRM、用户空间代码之间对图显输出的控制
(2)图显模式的配置权回收到 kernel 空间,避免用户空间代码直接控制图显控制器引起 kernel panic
(3)kernel 空间实现的图显输出暂停与恢复相关代码,可以方便的调用图显控制器的配置函数

CRTC对应的Layer Mixer以及DSPP(Destination Surface Processor Pipes)同样是DPU的一部分,Layer Mixer很好理解,就是将上述PANEL中提到的图层进行融合,DSPP则是基于面板特性进行Gamma校正和亮度,对比度,饱和度等调整。所以Android中护眼模式,色彩模式不应用于每个Layers而是应用于屏幕上就是指生成的颜色矩阵最终都是通过DSPP来实现的对应的模式效果吧,部分不开源封装在libsdm-color.so

static const struct drm_crtc_funcs sde_crtc_funcs = {
  	.set_config = drm_atomic_helper_set_config,
  	.page_flip = drm_atomic_helper_page_flip,
  	.atomic_set_property = sde_crtc_atomic_set_property,
  	.atomic_duplicate_state = sde_crtc_duplicate_state,

static const struct drm_crtc_helper_funcs sde_crtc_helper_funcs = {
    .mode_fixup = sde_crtc_mode_fixup, // 在一帧显示前验证模式,是否支持seamless
    .atomic_check = sde_crtc_atomic_check,// 当mix没有分配,需刷新一个关闭状态的crtc
    .atomic_flush = sde_crtc_atomic_flush, //完成crtc上多个平面的更新

ENCODER

DRM Encoder 和 Connector 模块由图显外设抽象而来,Encoder 属于控制器部分,将内存的像素编码转换为显示器所需要的信号例如VGA、MIPI;Virutal Encoder管理一个逻辑显示,包含一个或多个物理encoder,每个物理encoder对应一个INTF硬件模块。

static const struct drm_encoder_funcs sde_encoder_funcs = {
  		.destroy = sde_encoder_destroy,
  		.late_register = sde_encoder_late_register,
  		.early_unregister = sde_encoder_early_unregister,

static const struct drm_encoder_helper_funcs sde_encoder_helper_funcs = {
        .mode_set = sde_encoder_virt_mode_set, //ModeSetting
        .disable = sde_encoder_virt_disable,
        .enable = sde_encoder_virt_enable,
        .atomic_check = sde_encoder_virt_atomic_check,
};

CONNECTOR

Connector 包含外设 PHY 或者显示器参数,代表连接的显示设备连接状态,支持的视频模式等。

static const struct drm_connector_funcs sde_connector_ops = {
        .reset =                  sde_connector_atomic_reset,
        .detect =                 sde_connector_detect,
        .destroy =                sde_connector_destroy,
        .fill_modes =             sde_connector_fill_modes,

static const struct drm_connector_helper_funcs sde_connector_helper_ops_v2 = {
  	    .get_modes =    sde_connector_get_modes, //调用到dsi connector的get_modes
     	.mode_valid =   sde_connector_mode_valid,
  	    .best_encoder = sde_connector_best_encoder,
  	    .atomic_best_encoder = sde_connector_atomic_best_encoder,
  	    .atomic_check = sde_connector_atomic_check,
  };

_sde_kms_setup_displays中创建encoders和connectors时,赋予了sde_connector_ops,使得connector能对应到dsi

static const struct sde_connector_ops dsi_ops = {
	    .get_modes =  dsi_connector_get_modes,//标准话的EDID交换LCD和DSI的数据
	    .get_info =   dsi_display_get_info,//获取primary或者secondary的宽、高、Video模式等
 	    .set_backlight = dsi_display_set_backlight,//调用背光支持WLED、PWD、DCS等
	    .get_mode_info = dsi_conn_get_mode_info,//获取timings、刷新率、时钟速率等
	    .get_dst_format = dsi_display_get_dst_format,//获取format,通过bpp来确定是RGB8888或其他
	    .cmd_transfer = dsi_display_cmd_transfer,//传输panel的cmd参数

DRM Component

kernel图显系统里DRM模块的注册与绑定
内核加载每个模块时间不定,component框架能保证在最后初始化的设备加载前,所需设备全部加载完毕。注册主要分为两类设备:
(1)master即超级设备,执行probe使用component_master_add_with_match函数注册自己到component框架中。
(2)component即普通设备,执行probe使用component_add函数注册自己到component框架中。
component子系统component_master_add_with_match/component_add,每个设备加入到框架中,框架就尝试进行匹配,当master匹配上所有component后,会调用master的bind回调,开始按顺序进行初始化,保证当所有子设备全部probe成功后再执行初始化操作

kernel/msm-4.19/techpack/display/msm/msm_drv.c DRM top level control
kernel/msm-4.19/techpack/display/msm/sde/sde_kms.c Adreno DPU 显示图像处理器

msm_pdev_probe
	add_display_components(&pdev->dev, &match); /*添加组件用于匹配  master = sde-kms commponent = sde_dsi*/
		component_match_add(dev, matchptr, compare_of, node);/*qcom,sde-kms  connectors = <&sde_dsi>;*/
	component_master_add_with_match(&pdev->dev, &msm_drm_ops, match);
    /* master在component框架中component_list链表找到适配于自己的component组件,注册自己为master到component框架 
     dsi_display调用component_add 把自己添加到component_list, crtc+plane+encoder+connector 都是在DSI Controller驱动中 */

static const struct component_master_ops msm_drm_ops = {
        .bind = msm_drm_bind,
        .unbind = msm_drm_unbind,
};

msm_drm_bind	/* 当该master下的所有支持的设备(dsi wb dp等)都初始化完成后,调用该回调的bind */
	msm_drm_init(dev, &msm_driver);
		msm_mdss_init(ddev);
		msm_component_bind_all(dev, ddev);/* Bind all our sub-components: */
			component_bind_all(dev, drm_dev);/* 调用所有从设备的bind 调用dsi_display_bind*/
		_msm_drm_init_helper(priv, ddev, dev, pdev);
			sde_kms_init(ddev);/* .compatible = "qcom,sde-kms" */
				msm_kms_init(&sde_kms->base, &kms_funcs);/*.hw_init  .prepare_commit kms_func函数注册
			(kms)->funcs->hw_init(kms); /* 上述init中注册*/
				_sde_kms_hw_init_blocks(sde_kms, dev, priv);
					_sde_kms_drm_obj_init(sde_kms); /* create the DRM related objects */
						_sde_kms_get_displays(sde_kms) /* 这里对应到Interface了 */
							dsi_display_get_active_displays(sde_kms->dsi_displays,sde_kms->dsi_display_count);/* dsi */
							wb_display_get_displays(sde_kms->wb_displays,sde_kms->wb_display_count);/* wb */
							dp_display_get_displays(sde_kms->dp_displays,sde_kms->dp_display_count);/* dp */
						_sde_kms_setup_displays(dev, priv, sde_kms)
							 sde_encoder_init(dev, &info);
							 	drm_encoder_init(dev, drm_enc, &sde_encoder_funcs, drm_enc_mode, NULL); /*encoder初始化*/
							 sde_connector_init(dev,encoder,dsi_display_get_drm_panel(display),display,
                                        &dsi_ops,DRM_CONNECTOR_POLL_HPD, DRM_MODE_CONNECTOR_DSI);
                             	drm_connector_init(dev,&c_conn->base, &sde_connector_ops, connector_type);/*connector初始化*/
						sde_plane_init(dev, catalog->sspp[i].id, primary,(1UL << max_crtc_count) - 1, 0);
							drm_universal_plane_init(dev, plane, 0xff, &sde_plane_funcs, psde->formats,/* plane初始化 */
													psde->nformats,NULL, type, NULL);
						sde_crtc_init(dev, primary_planes[i]); /* Create one CRTC per encoder  ctrc初始化*/
							drm_crtc_init_with_planes(dev, crtc, plane, NULL, &sde_crtc_funcs, NULL);
							drm_crtc_helper_add(crtc, &sde_crtc_helper_funcs);
						priv->planes[i]->possible_crtcs =(1 << priv->num_crtcs) - 1; //plane to crtcs
						priv->encoders[i]->possible_crtcs = (1 << priv->num_crtcs) - 1;//crtcs to encoder 
						/* 一个CRTC 可以连接多个 Encoder 用于实现多屏显示 */

kernel/msm-4.19/techpack/display/msm/dsi/dsi_display.c
kernel/msm-4.19/techpack/display/msm/dsi/dsi_ctrl.c
kernel/msm-4.19/techpack/display/msm/dsi/dsi_phy.c
kernel/msm-4.19/techpack/display/msm/dsi/dsi_panel.c

module_param_string(dsi_display0, dsi_display_primary, MAX_CMDLINE_PARAM_LEN, 0600);
module_param_string(name, string, len, perm);    内核把字符串直接复制到程序中的字符数组
name是外部的参数名,string是内部的变量名,len是以string命名的buffer大小,perm表示sysfs的访问权限

MODULE_PARM_DESC(dsi_display0,      参数格式的说明
   "msm_drm.dsi_display0=<display node>:<configX> where <display node> is 'primary dsi display node name' and
   <configX> where x represents index in the topology list");
cat /proc/cmdline:   msm_drm.dsi_display0=qcom,mdss_dsi_hx8399_1080_video

dsi_display_parse_boot_display_selection();  /* 解析得到 boot_display.name boot_disp_en = true */

通过XBL-> ABL -> Kernel的Command Line来引导LCD驱动的加载或者使用default的panel型号

dsi_display_dev_probe
	boot_disp_en = true  /* 当前dsi控制器支持的panel列表中查找uefi传递的panel */
		of_find_node_by_name(mdp_node, boot_disp->name);
	else
		display_panel = getDisplayPanel();
			display_panel_name = strstr ( saved_command_line, "hx8399");
			panel_node = of_parse_phandle(node,"qcom,dsi-default-panel1", 0);
	    dtsi:
	     qcom,dsi-default-panel1 = <&dsi_hx8399_1080_video>;
	dsi_display_init(display);
		dsi_display_dev_init(display); 
			dsi_display_parse_dt(display);/* 解析dtsi包括TE,初始化一些handel 包括dsi_ctrl dsi_phy */
			dsi_display_res_init(display);
				dsi_panel_get(&display->pdev->dev, display->panel_node, display->parser_node,
							  display->display_type, display->cmdline_topology); ↓↓↓
					dsi_panel_parse_power_cfg(panel);
					dsi_panel_parse_bl_config(panel); /* parse dtsi */
					drm_panel_add(&panel->drm_panel); /* Let DRM can get panel name */ 
		component_add(&pdev->dev, &dsi_display_comp_ops); 
		/* component框架中通过component_add添加管理多个组件的加载,保证都正常工作
 		其中注册了两个函数 .bind = dsi_display_bind, .unbind = dsi_display_unbind */

DRM NotifierChain

Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notification chain),而对应Display来说紧密相关就是TP,通过内核通知链通知TP进行Resume和Suspend等操作。
在msm_kms_init初始化的时候注册的函数,通过内核通知链去通知TP resume和suspend流程

.prepare_commit  = sde_kms_prepare_commit,
 	_sde_kms_drm_check_dpms(state, DRM_PANEL_EARLY_EVENT_BLANK);
.complete_commit = sde_kms_complete_commit,
	_sde_kms_drm_check_dpms(old_state, DRM_PANEL_EVENT_BLANK);

	_sde_kms_drm_check_dpms
		sde_kms_get_blank(crtc->state, connector->state);
        	case SDE_MODE_DPMS_ON:
                blank = DRM_PANEL_BLANK_UNBLANK; //new mode:亮屏操作
                break;
        	case SDE_MODE_DPMS_LP1:
       		case SDE_MODE_DPMS_LP2:
                blank = DRM_PANEL_BLANK_LP;
                break;
        	case SDE_MODE_DPMS_OFF:
        	default:
                blank = DRM_PANEL_BLANK_POWERDOWN;//new mode:灭屏操作
                break;
        notifier_data.data = &new_mode;
		drm_panel_notifier_call_chain(connector->panel,event, &notifier_data); 

tp driver:
	data->fb_notif.notifier_call = fb_notifier_callback;
	drm_panel_notifier_register(data->active_panel,&data->fb_notif);
fb_notifier_callback
    if (action == DRM_PANEL_EARLY_EVENT_BLANK && *transition == DRM_PANEL_BLANK_POWERDOWN)
    	mxt_secure_touch_stop(mxt_dev_data, 0); 
    else if (action == DRM_PANEL_EVENT_BLANK) {
    	if (*transition == DRM_PANEL_BLANK_UNBLANK){
        	mxt_resume(&mxt_dev_data->client->dev); /* resume */
        }
        else if (*transition == DRM_PANEL_BLANK_POWERDOWN){
        	mxt_suspend(&mxt_dev_data->client->dev);/* suspend */
        }
    }

你可能感兴趣的:(Android_Display,lcd,drm,qcom)