android 9.0上,实现双mipi屏

  • Post author:
  • Post category:其他


我们知道,在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的话,那么会在副屏上显示不一样的内容,即异显。



版权声明:本文为xuhui_7810原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。