我们知道,在android上,本就支持mipi(primary display)、HDMI(external display)、wifi display、virtual display这四种屏,但是并不支持双mipi屏。如果需要做到集成双mipi屏,外面普通的作法有两个:
1.)在一套主板上用两个cpu、两套android代码,然后中间用一条USB数据线连接起来,实现两个display之间的数据交互。
2.)使用一个桥接芯片。
第1个方法,不仅在软硬件上相当的繁锁,而且成本极高,显然不合算。第2个方法,这个需要看主板上是否有这个桥接转换芯片,如果没有的话,也就没办法了。
目前我接手的这个项目上面,客户就要求在一个不带桥接芯片的主板上面,集成两个mipi屏,用来做同显和异显。针对这个要求和客观情况,我仔细分析后发现,其实这个需求并不难实现。
首先,我们android是运行在linux内核上的,无论我们用的是什么lcd,最终对应到linux内核上,无非就是fb0、fb1这样的设备节点而已。
明白了这一点后,我们就会发现,其实我们可以利用android系统本身就有的hdmi屏的接口来稍作修改,让fb0对应到主屏,fb1对应到副屏,也就是第二块mipi屏即可。
下面来说说具体的代码实现,我这个项目是基于高通平台8953芯片来做的来做的,高通的驱动代码在dtsi文件里来配置。对应的,我们的dtsi文件为msm8953-mdss.dtsi。在这个文件里,对lcd的驱动进行了配置。比如:
mdss_fb0: qcom,mdss_fb_primary {
cell-index = <0>;
compatible = "qcom,mdss-fb";
qcom,cont-splash-memory {
linux,contiguous-region = <&cont_splash_mem>;
};
};
mdss_fb2: qcom,mdss_fb_wfd {
cell-index = <2>;
compatible = "qcom,mdss-fb";
};
mdss_fb1: qcom,mdss_fb_secondary {
cell-index = <1>;
compatible = "qcom,mdss-fb";
};
};
qcom,mdss-fb-map-prim = <&mdss_fb0>;
qcom,mdss-fb-map-sec = <&mdss_fb1>;
mdss_dsi0: qcom,mdss_dsi_ctrl0@1a94000 {
compatible = "qcom,mdss-dsi-ctrl";
label = "MDSS DSI CTRL->0";
qcom,display-id = "primary";
cell-index = <0>;
reg = <0x1a94000 0x400>,
<0x1a94400 0x580>,
<0x193e000 0x30>;
reg-names = "dsi_ctrl", "dsi_phy", "mmss_misc_phys";
qcom,timing-db-mode;
qcom,mdss-mdp = <&mdss_mdp>;
vdd-supply = <&pm8953_l17>;
vddio-supply = <&pm8953_l6>;
clocks = <&clock_gcc_mdss clk_gcc_mdss_byte0_clk>,
<&clock_gcc_mdss clk_gcc_mdss_pclk0_clk>,
<&clock_gcc clk_gcc_mdss_esc0_clk>,
<&clock_gcc_mdss clk_byte0_clk_src>,
<&clock_gcc_mdss clk_pclk0_clk_src>,
<&mdss_dsi0_pll clk_dsi0pll_byte_clk_mux>,
<&mdss_dsi0_pll clk_dsi0pll_pixel_clk_mux>,
<&mdss_dsi0_pll clk_dsi0pll_byte_clk_src>,
<&mdss_dsi0_pll clk_dsi0pll_pixel_clk_src>,
<&mdss_dsi0_pll
clk_dsi0pll_shadow_byte_clk_src>,
<&mdss_dsi0_pll
clk_dsi0pll_shadow_pixel_clk_src>;
clock-names = "byte_clk", "pixel_clk", "core_clk",
"byte_clk_rcg", "pixel_clk_rcg",
"pll_byte_clk_mux", "pll_pixel_clk_mux",
"pll_byte_clk_src", "pll_pixel_clk_src",
"pll_shadow_byte_clk_src",
"pll_shadow_pixel_clk_src";
qcom,platform-strength-ctrl = [ff 06
ff 06
ff 06
ff 06
ff 00];
qcom,platform-regulator-settings = [1d
1d 1d 1d 1d];
qcom,platform-lane-config = [00 00 10 0f
00 00 10 0f
00 00 10 0f
00 00 10 0f
00 00 10 8f];
};
mdss_dsi1: qcom,mdss_dsi_ctrl1@1a96000 {
compatible = "qcom,mdss-dsi-ctrl";
label = "MDSS DSI CTRL->1";
qcom,display-id = "secondary";
cell-index = <1>;
reg = <0x1a96000 0x400>,
<0x1a96400 0x588>,
<0x193e000 0x30>;
reg-names = "dsi_ctrl", "dsi_phy", "mmss_misc_phys";
qcom,mdss-mdp = <&mdss_mdp>;
vdd-supply = <&pm8953_l17>;
vddio-supply = <&pm8953_l6>;
clocks = <&clock_gcc_mdss clk_gcc_mdss_byte1_clk>,
<&clock_gcc_mdss clk_gcc_mdss_pclk1_clk>,
<&clock_gcc clk_gcc_mdss_esc1_clk>,
<&clock_gcc_mdss clk_byte1_clk_src>,
<&clock_gcc_mdss clk_pclk1_clk_src>,
<&mdss_dsi1_pll clk_dsi1pll_byte_clk_mux>,
<&mdss_dsi1_pll clk_dsi1pll_pixel_clk_mux>,
<&mdss_dsi1_pll clk_dsi1pll_byte_clk_src>,
<&mdss_dsi1_pll clk_dsi1pll_pixel_clk_src>,
<&mdss_dsi1_pll
clk_dsi1pll_shadow_byte_clk_src>,
<&mdss_dsi1_pll
clk_dsi1pll_shadow_pixel_clk_src>;
clock-names = "byte_clk", "pixel_clk", "core_clk",
"byte_clk_rcg", "pixel_clk_rcg",
"pll_byte_clk_mux", "pll_pixel_clk_mux",
"pll_byte_clk_src", "pll_pixel_clk_src",
"pll_shadow_byte_clk_src",
"pll_shadow_pixel_clk_src";
qcom,timing-db-mode;
qcom,platform-strength-ctrl = [ff 06
ff 06
ff 06
ff 06
ff 00];
qcom,platform-regulator-settings = [1d
1d 1d 1d 1d];
qcom,platform-lane-config = [00 00 10 0f
00 00 10 0f
00 00 10 0f
00 00 10 0f
00 00 10 8f];
};
};
在这里,对fb0、fb1、fb2进行了配置。android默认只有一个屏,所以默认的,fb1是配给了wifi屏,即mdss_fb_wfd。现在我们第二个屏,也即fb1要用来做第二块mipi屏,所以这里要改过来。当然这里不改的话,在内核代码里,也会mdss_dsi1强行指为第二个屏的驱动,并和fb1对应起来。不过我们做软件的,讲究的就是一个条理清晰。如果这里不改过来,看起来就相当的别扭,明明fb1指给了wifi屏,为啥实际上对应的却又是副屏呢?
这里还需要改动的一个地方是mdss_dsi: qcom,mdss_dsi@0 这块代码里,要加上hw-config = “dual_dsi”;表示我们要做双屏。另外我们还在sdm450-qrd-sku4.dtsi这个文件里,对mdss_dsi0、mdss_dsi1配置了对应的lcd厂商驱动和gpio口,当然这些也可以直接在msm8953-mdss.dtsi这个文件里配置,都一样的。
&mdss_dsi0 {
status = "ok";
lab-supply = <&lcdb_ldo_vreg>;
ibb-supply = <&lcdb_ncp_vreg>;
/delete-property/ vdd-supply;
qcom,dsi-pref-prim-pan = <&dsi_hx8394f_720p_video>;//厂商驱动代码
/delete-property/ qcom,platform-bklight-en-gpio;
pinctrl-names = "mdss_default", "mdss_sleep";
pinctrl-0 = <&mdss_dsi_active &mdss_te_active>;//gpio引脚配置
pinctrl-1 = <&mdss_dsi_suspend &mdss_te_suspend>;//gpio引脚配置
qcom,platform-te-gpio = <&tlmm 24 0>;
qcom,platform-reset-gpio = <&tlmm 61 0>;
};
&mdss_dsi1 {
status = "ok";
//lab-supply = <&lcdb_ldo_vreg>;
//ibb-supply = <&lcdb_ncp_vreg>;
/delete-property/ vdd-supply;
qcom,dsi-pref-prim-pan = <&dsi_hx8394f_720p_dsi1_video>;//厂商驱动代码
/delete-property/ qcom,platform-bklight-en-gpio;
pinctrl-names = "mdss_default", "mdss_sleep";
pinctrl-0 = <&mdss_dsi1_active &mdss_te1_active>;//gpio引脚配置
pinctrl-1 = <&mdss_dsi1_suspend &mdss_te1_suspend>;//gpio引脚配置
qcom,bridge-index = <0>;
qcom,pluggable;
qcom,platform-te-gpio = <&tlmm 25 0>;
qcom,platform-reset-gpio = <&tlmm 60 0>;
};
因为我们是两块一样的mipi屏,所以dsi_hx8394f_720p_video和dsi_hx8394f_720p_dsi1_video的代码,基本上都差不多。以dsi_hx8394f_720p_video为例,它对应的文件为dsi-panel-hx8394f-720p-video.dtsi
&mdss_mdp {
dsi_hx8394f_720p_video: qcom,mdss_dsi_hx8394f_720p_video {
qcom,mdss-dsi-panel-name = "hx8394f 720p video mode dsi panel";
qcom,mdss-dsi-panel-controller = <&mdss_dsi0>;
qcom,mdss-dsi-panel-type = "dsi_video_mode";
qcom,mdss-dsi-panel-destination = "display_1";
qcom,mdss-dsi-panel-framerate = <60>;
qcom,mdss-dsi-virtual-channel-id = <0>;
qcom,mdss-dsi-stream = <0>;
qcom,mdss-dsi-panel-width = <720>;
qcom,mdss-dsi-panel-height = <1280>;
qcom,mdss-dsi-h-front-porch = <100>;
qcom,mdss-dsi-h-back-porch = <300>;
qcom,mdss-dsi-h-pulse-width = <2>;
qcom,mdss-dsi-h-sync-skew = <0>;
qcom,mdss-dsi-v-back-porch = <15>;
qcom,mdss-dsi-v-front-porch = <25>;
qcom,mdss-dsi-v-pulse-width = <4>;
qcom,mdss-dsi-h-left-border = <0>;
qcom,mdss-dsi-h-right-border = <0>;
qcom,mdss-dsi-v-top-border = <0>;
qcom,mdss-dsi-v-bottom-border = <0>;
qcom,mdss-dsi-bpp = <24>;
qcom,mdss-dsi-color-order = "rgb_swap_rgb";
qcom,mdss-dsi-underflow-color = <0xff>;
qcom,mdss-dsi-border-color = <0>;
qcom,mdss-dsi-on-command = [39 01 00 00 00 00 04 B9 FF 83 94
39 01 00 00 00 00 07 BA 63 03 68 6B B2 C0
39 01 00 00 00 00 0B B1 50 12 72 09 33 54 B1 31 6B 2F
39 01 00 00 00 00 07 B2 00 80 64 0E 0D 2F
39 01 00 00 00 00 16 B4 73 74 73 74 73 74 01 0C 86 75 00 3F 73 74 73 74 73 74 01 0C 86
39 01 00 00 00 00 22 D3 00 00 07 07 40 07 10 00 08 10 08 00 08 54 15 0E 05 0E 02 15 06 05 06 47 44 0A 0A 4B 10 07 07 0E 40
39 01 00 00 00 00 2D D5 1A 1A 1B 1B 00 01 02 03 04 05 06 07 08 09 0A 0B 24 25 18 18 26 27 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 20 21 18 18 18 18
39 01 00 00 00 00 2D D6 1A 1A 1B 1B 0B 0A 09 08 07 06 05 04 03 02 01 00 21 20 18 18 27 26 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 25 24 18 18 18 18
39 01 00 00 00 00 3B E0 00 0C 19 20 23 26 29 28 51 61 70 6F 76 86 89 8D 99 9A 95 A1 B0 57 55 58 5C 5e 64 6B 7F 00 0C 19 20 23 26 29 28 51 61 70 6F 76 86 89 8D 99 9A 95 A1 B0 57 55 58 5C 5E 64 6B 7F
39 01 00 00 00 00 03 C0 1F 31
15 01 00 00 00 00 02 CC 0B
15 01 00 00 00 00 02 D4 02
15 01 00 00 00 00 02 BD 02
39 01 00 00 00 00 0D D8 FF FF FF FF FF FF FF FF FF FF FF FF
15 01 00 00 00 00 02 BD 00
15 01 00 00 00 00 02 BD 01
15 01 00 00 00 00 02 B1 00
15 01 00 00 00 00 02 BD 00
39 01 00 00 00 00 08 BF 40 81 50 00 1A FC 01
39 01 00 00 00 00 03 B6 7D 7D
05 01 00 00 78 00 02 11 00
39 01 00 00 00 00 0D B2 00 80 64 0E 0D 2F 00 00 00 00 C0 18
05 01 00 00 14 00 02 29 00];
qcom,mdss-dsi-off-command = [05 01 00 00 78 00 02 28 00
05 01 00 00 96 00 02 10 00];
qcom,mdss-dsi-on-command-state = "dsi_lp_mode";
qcom,mdss-dsi-off-command-state = "dsi_hs_mode";
qcom,mdss-dsi-h-sync-pulse = <1>;
qcom,mdss-dsi-traffic-mode = "burst_mode";
qcom,mdss-dsi-lane-map = "lane_map_0123";
qcom,mdss-dsi-bllp-eof-power-mode;
qcom,mdss-dsi-bllp-power-mode;
qcom,mdss-dsi-lane-0-state;
qcom,mdss-dsi-lane-1-state;
qcom,mdss-dsi-lane-2-state;
qcom,mdss-dsi-lane-3-state;
qcom,mdss-dsi-panel-timings = [1F 10 05 06 03 1F 1C 05 06 03 02 04];
qcom,mdss-dsi-t-clk-post = <0x0B>;
qcom,mdss-dsi-t-clk-pre = <0x22>;
qcom,mdss-dsi-bl-min-level = <1>;
qcom,mdss-dsi-bl-max-level = <255>;
qcom,mdss-dsi-dma-trigger = "trigger_sw";
qcom,mdss-dsi-mdp-trigger = "none";
qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_pwm";
qcom,mdss-dsi-reset-sequence = <1 80>, <0 80>, <1 80>;
};
};
gpio引脚mdss_dsi_active、mdss_dsi1_active等定义在msm8953-pinctrl.dtsi里
pmx_mdss: pmx_mdss {
mdss_dsi_active: mdss_dsi_active {
mux {
pins = "gpio61", "gpio59";
function = "gpio";
};
config {
pins = "gpio61", "gpio59";
drive-strength = <8>; /* 8 mA */
bias-disable = <0>; /* no pull */
output-high;
};
};
mdss_dsi_suspend: mdss_dsi_suspend {
mux {
pins = "gpio61", "gpio59";
function = "gpio";
};
config {
pins = "gpio61", "gpio59";
drive-strength = <2>; /* 2 mA */
bias-pull-down; /* pull down */
};
};
};
pmx_mdss1: pmx_mdss1 {
mdss_dsi1_active: mdss_dsi1_active {
mux {
pins = "gpio60";
function = "gpio";
};
config {
pins = "gpio60";
drive-strength = <8>; /* 8 mA */
bias-disable = <0>; /* no pull */
output-high;
};
};
mdss_dsi1_suspend: mdss_dsi1_suspend {
mux {
pins = "gpio60";
function = "gpio";
};
config {
pins = "gpio60";
drive-strength = <2>; /* 2 mA */
bias-pull-down; /* pull down */
};
};
};
驱动上的配置,暂时先写到这里,我们再来说说hardware层的逻辑。我们主屏的创建是在hardware\qcom\display\sdm\libs\hwc2\hwc_session.cpp这个文件里的HWCSession::Init()函数里进行的。其代码如下:
// Create and power on primary display
status = HWCDisplayPrimary::Create(core_intf_, &buffer_allocator_, &callbacks_, qservice_,
&hwc_display_[HWC_DISPLAY_PRIMARY]);
color_mgr_ = HWCColorManager::CreateColorManager(&buffer_allocator_);
这里主屏的display创建好后,会发送一个热拨插事件到frameworks\native\services\surfaceflinger\SurfaceFlinger.cpp这个文件里的processDisplayHotplugEventsLocked()。然后会创建对应的逻辑屏并保存起来,代码如下:
if (event.connection == HWC2::Connection::Connected) {
if (!mBuiltinDisplays[displayType].get()) {
ALOGV("Creating built in display %d", displayType);
mBuiltinDisplays[displayType] = new BBinder();
// All non-virtual displays are currently considered secure.
DisplayDeviceState info(displayType, true);
info.displayName = displayType == DisplayDevice::DISPLAY_PRIMARY ?
"Built-in Screen" : "External Screen";
mCurrentState.displays.add(mBuiltinDisplays[displayType], info);
mInterceptor->saveDisplayCreation(info);
}
}
将来我们创建副屏的时候,最终也要调用到这里。
现在我们的主屏创建完成后,就要考虑开始创建第二块mipi屏了。正如前面所说的,无论是mipi屏还是hdmi屏,对应到linux层,都是fb0、fb1这样的设备节点,所以上层的接口是可以借用的。
正常情况下,当我们开机完成后,当有hdmi屏插入的时候,在hardware\qcom\display\sdm\libs\hwc2\hwc_session.cpp里的UEventHandler函数,会收到一个”change@/devices/virtual/switch/hdmi”这样的事件,然后会调用HotPlugHandler(connected) 函数开始连接。因为我们没有真的hdmi屏,所以我们收不到这个事情。我们可以暂时先用”remove@/devices/platform/soc/1de0000.qcom,venus/firmware/venus.mdt”这个图形子系统事件来代替hdmi插入事件。代码如下:
#define HWC_UEVENT_SWITCH_TEST "remove@/devices/platform/soc/1de0000.qcom,venus/firmware/venus.mdt"
void HWCSession::UEventHandler(const char *uevent_data, int length) {
DLOGI("UEventHandler uevent_data = %s\n", uevent_data);
if (!strcasecmp(uevent_data, HWC_UEVENT_SWITCH_TEST)) {
// if (!strcasecmp(uevent_data, HWC_UEVENT_SWITCH_HDMI)) {
int connected = 1;//GetEventValue(uevent_data, length, "SWITCH_STATE=");
if (connected >= 0) {
DLOGI("HDMI = %s\n", connected ? "connected" : "disconnected");
if (HotPlugHandler(connected) == -1) {
DLOGE("Failed handling Hotplug = %s\n", connected ? "connected" : "disconnected");
}
}
} else if (!strcasecmp(uevent_data, HWC_UEVENT_GRAPHICS_FB0)) {
DLOGI("UEventHandler Uevent FB0 = %s\n", uevent_data);
int panel_reset = GetEventValue(uevent_data, length, "PANEL_ALIVE=");
if (panel_reset == 0) {
Refresh(0);
reset_panel_ = true;
}
}
}
进int HWCSession::HotPlugHandler(bool connected)这个函数里看一下:
int HWCSession::HotPlugHandler(bool connected) {
int status = 0;
bool notify_hotplug = false;
// To prevent sending events to client while a lock is held, acquire scope locks only within
// below scope so that those get automatically unlocked after the scope ends.
do {
// If HDMI is primary but not created yet (first time), create it and notify surfaceflinger.
// if it is already created, but got disconnected/connected again,
// just toggle display status and do not notify surfaceflinger.
// If HDMI is not primary, create/destroy external display normally.
if (hdmi_is_primary_) {
SCOPE_LOCK(locker_[HWC_DISPLAY_PRIMARY]);
if (hwc_display_[HWC_DISPLAY_PRIMARY]) {
status = hwc_display_[HWC_DISPLAY_PRIMARY]->SetState(connected);
} else {
status = CreateExternalDisplay(HWC_DISPLAY_PRIMARY);
notify_hotplug = true;
}
break;
}
{
SCOPE_LOCK(locker_[HWC_DISPLAY_PRIMARY]);
// Primary display must be connected for HDMI as secondary cases.
//如果主屏没有创建成功,则不允许创建副屏
if (!hwc_display_[HWC_DISPLAY_PRIMARY]) {
DLOGE("xuhui Primary display is not connected.\n");
return -1;
}
}
if (connected) {
SCOPE_LOCK(locker_[HWC_DISPLAY_EXTERNAL]);
Locker::ScopeLock lock_v(locker_[HWC_DISPLAY_VIRTUAL]);
// Connect external display if virtual display is not connected.
// Else, defer external display connection and process it when virtual display
// tears down; Do not notify SurfaceFlinger since connection is deferred now.
if (!hwc_display_[HWC_DISPLAY_EXTERNAL]) {
//我们副屏会走这里。
status = ConnectDisplay(HWC_DISPLAY_EXTERNAL);
DLOGE("xuhui HWCSession::HotPlugHandler status is %d\n", status);
if (status) {
return status;
}
notify_hotplug = true;
} else {
DLOGI("Virtual display is connected, pending connection\n");
external_pending_connect_ = true;
}
} else {
SEQUENCE_WAIT_SCOPE_LOCK(locker_[HWC_DISPLAY_EXTERNAL]);
// Do not return error if external display is not in connected status.
// Due to virtual display concurrency, external display connection might be still pending
// but hdmi got disconnected before pending connection could be processed.
if (hwc_display_[HWC_DISPLAY_EXTERNAL]) {
status = DisconnectDisplay(HWC_DISPLAY_EXTERNAL);
notify_hotplug = true;
}
external_pending_connect_ = false;
}
} while (0);
if (connected) {
Refresh(0);
if (!hdmi_is_primary_) {
// wait for sufficient time to ensure sufficient resources are available to process new
// new display connection.
uint32_t vsync_period = UINT32(GetVsyncPeriod(HWC_DISPLAY_PRIMARY));
usleep(vsync_period * 2 / 1000);
}
DLOGE("xuhui HWCSession::HotPlugHandler 6\n");
}
// Cache hotplug for external till first present is called
if (notify_hotplug) {
if (!hdmi_is_primary_) {
if (!first_commit_) {
notify_hotplug = false;
external_pending_hotplug_ = connected;
}
}
}
// notify client
//创建完成后,需要向framework层发送通知
if (notify_hotplug) {
HotPlug(hdmi_is_primary_ ? HWC_DISPLAY_PRIMARY : HWC_DISPLAY_EXTERNAL,
connected ? HWC2::Connection::Connected : HWC2::Connection::Disconnected);
}
qservice_->onHdmiHotplug(INT(connected));
return 0;
}
再来看看ConnectDisplay
int32_t HWCSession::ConnectDisplay(int disp) {
int status = 0;
uint32_t primary_width = 0;
uint32_t primary_height = 0;
hwc_display_[HWC_DISPLAY_PRIMARY]->GetFrameBufferResolution(&primary_width, &primary_height);
if (disp == HWC_DISPLAY_EXTERNAL) {
//我们是副屏,会走这里
status = CreateExternalDisplay(disp, primary_width, primary_height);
} else {
DLOGE("Invalid display type");
return -1;
}
if (!status) {
hwc_display_[disp]->SetSecureDisplay(secure_display_active_);
}
return status;
}
再跟进CreateExternalDisplay看看
int HWCSession::CreateExternalDisplay(int disp, uint32_t primary_width,
uint32_t primary_height, bool use_primary_res) {
uint32_t panel_bpp = 0;
uint32_t pattern_type = 0;
if (qdutils::isDPConnected()) {
qdutils::getDPTestConfig(&panel_bpp, &pattern_type);
}
if (panel_bpp && pattern_type) {
return HWCDisplayExternalTest::Create(core_intf_, &buffer_allocator_, &callbacks_,
qservice_, panel_bpp,
pattern_type, &hwc_display_[disp]);
}
if (use_primary_res) {
return HWCDisplayExternal::Create(core_intf_, &buffer_allocator_, &callbacks_,
primary_width, primary_height, qservice_,
use_primary_res, &hwc_display_[disp]);
} else {
//副屏走这里
return HWCDisplayExternal::Create(core_intf_, &buffer_allocator_, &callbacks_,
qservice_, &hwc_display_[disp]);
}
}
再看下HWCDisplayExternal::Create,它在hardware\qcom\display\sdm\libs\hwc2\hwc_display_external.cpp里,
int HWCDisplayExternal::Create(CoreInterface *core_intf, HWCBufferAllocator *buffer_allocator,
HWCCallbacks *callbacks, qService::QService *qservice,
HWCDisplay **hwc_display) {
return Create(core_intf, buffer_allocator, callbacks, 0, 0, qservice, false, hwc_display);
}
int HWCDisplayExternal::Create(CoreInterface *core_intf, HWCBufferAllocator *buffer_allocator,
HWCCallbacks *callbacks,
uint32_t primary_width, uint32_t primary_height,
qService::QService *qservice, bool use_primary_res,
HWCDisplay **hwc_display) {
uint32_t external_width = 0;
uint32_t external_height = 0;
DisplayError error = kErrorNone;
HWCDisplay *hwc_display_external = new HWCDisplayExternal(core_intf, buffer_allocator, callbacks,
qservice);
int status = hwc_display_external->Init();
......
return status;
}
我们再跟进hwc_display_external->Init();这里看看,它定义在hardware\qcom\display\sdm\libs\hwc2\hwc_display.cpp里,代码如下:
int HWCDisplay::Init() {
......
DisplayError error = core_intf_->CreateDisplay(type_, this, &display_intf_);
......
}
这个core_intf_是CoreInterface类型,而CoreImpl类继承自它,这里的CreateDisplay实际上调用到了hardware\qcom\display\sdm\libs\core\core_impl.cpp里的CreateDisplay,对应的代码如下:
DisplayError CoreImpl::CreateDisplay(DisplayType type, DisplayEventHandler *event_handler,
DisplayInterface **intf) {
SCOPE_LOCK(locker_);
if (!event_handler || !intf) {
return kErrorParameters;
}
DisplayBase *display_base = NULL;
switch (type) {
case kPrimary:
display_base = new DisplayPrimary(event_handler, hw_info_intf_, buffer_sync_handler_,
buffer_allocator_, &comp_mgr_);
break;
case kHDMI:
display_base = new DisplayPrimary(event_handler, hw_info_intf_, buffer_sync_handler_,
buffer_allocator_, &comp_mgr_, kHDMI);
// display_base = new DisplayHDMI(event_handler, hw_info_intf_, buffer_sync_handler_,
// buffer_allocator_, &comp_mgr_);
break;
}
注意,本来正常流程,当CreateDisplay里判断类型为kHDMI时,会去调用DisplayHDMI。但是我们的是mipi屏,而不是hdmi屏,所以不能走这里。从这里开始,就要走和primary屏一样的流程了,只是传进去的id不同。注意下面:
display_base = new DisplayPrimary(event_handler, hw_info_intf_, buffer_sync_handler_,
buffer_allocator_, &comp_mgr_,
kHDMI
);
本来DisplayPrimary的构造函数里,是不带这个id的,我们为了区分,所以新加了一个构造函数,将我们的id传了进来。继续看代码:
hardware\qcom\display\sdm\libs\core\display_primary.cpp
DisplayPrimary::DisplayPrimary(DisplayEventHandler *event_handler, HWInfoInterface *hw_info_intf,
BufferSyncHandler *buffer_sync_handler,
BufferAllocator *buffer_allocator, CompManager
*comp_manager, int id)
: DisplayBase(kHDMI, event_handler, kDeviceHDMI, buffer_sync_handler, buffer_allocator,
comp_manager, hw_info_intf) {
displayType = kHDMI;
}
DisplayError DisplayPrimary::Init() {
lock_guard<recursive_mutex> obj(recursive_mutex_);
DLOGW("xuhui DisplayPrimary::Init() displayType is %d\n", displayType);
DisplayError error = kErrorNone;
if(displayType == kPrimary)
{
error = HWInterface::Create(kPrimary, hw_info_intf_, buffer_sync_handler_,
buffer_allocator_, &hw_intf_);
}
else
{
//这里是新增加的,注意传进来的id和上面的不同。
error = HWInterface::Create(kHDMI, hw_info_intf_, buffer_sync_handler_,
buffer_allocator_, &hw_intf_);
}
......
}
hardware\qcom\display\sdm\libs\core\hw_interface.cpp
DisplayError HWInterface::Create(DisplayType type, HWInfoInterface *hw_info_intf,
BufferSyncHandler *buffer_sync_handler,
BufferAllocator *buffer_allocator, HWInterface **intf) {
DisplayError error = kErrorNone;
HWInterface *hw = nullptr;
DriverType driver_type = GetDriverType();
switch (type) {
case kPrimary:
if (driver_type == DriverType::FB) {
hw = new HWPrimary(buffer_sync_handler, hw_info_intf);
} else {
#ifdef COMPILE_DRM
hw = new HWDeviceDRM(buffer_sync_handler, buffer_allocator, hw_info_intf);
#endif
}
break;
case kHDMI:
if (driver_type == DriverType::FB) {
//hw = new HWHDMI(buffer_sync_handler, hw_info_intf);
//注意,这里用HWPrimary替换了原生的HWHDMI,并传进去了kHDMI这个ID
hw = new HWPrimary(buffer_sync_handler, hw_info_intf, kHDMI);
} else {
return kErrorNotSupported;
}
break;
........
}
这里新增加了一个HWPrimary的构造函数,用于传kHDMI这个id,它定义在下面:
hardware\qcom\display\sdm\libs\core\fb\hw_primary.cpp
HWPrimary::HWPrimary(BufferSyncHandler *buffer_sync_handler, HWInfoInterface *hw_info_intf)
: HWDevice(buffer_sync_handler) {
HWDevice::device_type_ = kDevicePrimary;
HWDevice::device_name_ = "Primary Display Device";
HWDevice::hw_info_intf_ = hw_info_intf;
}
HWPrimary::HWPrimary(BufferSyncHandler *buffer_sync_handler, HWInfoInterface
*hw_info_intf,DisplayType type)
//注意,这里传了一个1进来了
: HWDevice(buffer_sync_handler, 1) {
//注意,这里type又切回来了,归根结底,我们的lcd还是和主屏一样的mipi屏嘛
HWDevice::device_type_ = kDevicePrimary;
HWDevice::device_name_ = "Primary Display Device2";
HWDevice::hw_info_intf_ = hw_info_intf;
}
注意上面传进去的1,传给了HWDevice里的fb_node_index_,这个地方是最最关键的,它关系到最终lcd对应到哪一个设备节点上去,是对应到fb0还是fb1。我这里传进去的1,表示对应的为fb1节点。详细的看代码:
hardware\qcom\display\sdm\libs\core\fb\hw_device.cpp
HWDevice::HWDevice(BufferSyncHandler *buffer_sync_handler)
: fb_node_index_(-1), fb_path_("/sys/devices/virtual/graphics/fb"),
buffer_sync_handler_(buffer_sync_handler), synchronous_commit_(false) {
}
HWDevice::HWDevice(BufferSyncHandler *buffer_sync_handler, int index)
: fb_node_index_(index), fb_path_("/sys/devices/virtual/graphics/fb"),
buffer_sync_handler_(buffer_sync_handler), synchronous_commit_(false) {
}
DisplayError HWDevice::Init() {
// Read the fb node index
if(fb_node_index_ == -1)
fb_node_index_ = GetFBNodeIndex(device_type_);
if (fb_node_index_ == -1) {
DLOGE("device type = %d should be present", device_type_);
return kErrorHardware;
}
const char *dev_name = NULL;
vector<string> dev_paths = {"/dev/graphics/fb", "/dev/fb"};
for (size_t i = 0; i < dev_paths.size(); i++) {
dev_paths[i] += to_string(fb_node_index_);
if (Sys::access_(dev_paths[i].c_str(), F_OK) >= 0) {
dev_name = dev_paths[i].c_str();
DLOGI("access(%s) successful", dev_name);
break;
}
DLOGI("access(%s), errno = %d, error = %s", dev_paths[i].c_str(), errno, strerror(errno));
}
if (!dev_name) {
DLOGE("access() failed for all possible paths");
return kErrorHardware;
}
// Populate Panel Info (Used for Partial Update)
PopulateHWPanelInfo();
// Populate Bit clk levels.
PopulateBitClkRates();
// Populate HW Capabilities
hw_resource_ = HWResourceInfo();
hw_info_intf_->GetHWResourceInfo(&hw_resource_);
device_fd_ = Sys::open_(dev_name, O_RDWR);
if (device_fd_ < 0) {
DLOGE("open %s failed errno = %d, error = %s", dev_name, errno, strerror(errno));
return kErrorResources;
}
return HWScale::Create(&hw_scale_, hw_resource_.has_qseed3);
}
这里也新增了一个构造函数HWDevice,默认的fb_node_index_为-1,我们的副屏传进去的为1。当fb_node_index_为-1时,在init里,会通过GetFBNodeIndex(device_type_)来查询正确的id,默认Primary display为0,副屏为1.因为我们传进来的device_type_都为kDevicePrimary,如果通过GetFBNodeIndex来取id的话,会取不正确。所以我们直接指定第二个屏的id为1。然后在init里,通过下面的代码:
for (size_t i = 0; i < dev_paths.size(); i++) {
dev_paths[i] += to_string(fb_node_index_);
if (Sys::access_(dev_paths[i].c_str(), F_OK) >= 0) {
dev_name = dev_paths[i].c_str();
DLOGI("access(%s) successful", dev_name);
break;
}
}
来获取对应的设备节点,主屏的为/dev/graphics/fb0″, “/dev/fb0″,副屏的为/dev/graphics/fb1”, “/dev/fb1″,然后在init的最后调用device_fd_ = Sys::open_(dev_name, O_RDWR);来打开刚刚配好的这个节点。如果打开成功,这时对应的屏幕就会启动。
到这里为止,所有的改动都已经完成了。如果一切正常的话,副屏会显示和主屏一样的内容,即同显。如果这时调用Presentation类,并且指定display id为1的话,那么会在副屏上显示不一样的内容,即异显。