最近改了个反人类的需求,就是:
当HDMI输出分辨率设置为固定的2160P后,再热拔插连接到1080P的TV,分辨率会变为1080P的分辨率,客户认为这是bug,需要改为固定。
为什么说这是反人类的需求呢?
比如说,你想固定一个4k,那你接到另一台2K的显示上,那要怎么显示呢?肯定是无显示。
所以RK平台的hdmi显示机制是这样的:
比如你设置个HDMI的分辨率,比如1920x1080p60hz,如果你接另外一台电视有这个分辨率,就会切到这个分辨率去,
如果没有支持这个分辨率,就会切到其他相近的分辨率。
当前尝试了几个办法:
一个就是直接不读EDID了,直接默认支持一堆预设好的分辨率,这些分辨率对应的vic值从drivers/gpu/drm/drm_edid.c中的edid_cea_modes结构体中获得【对应具体的整形数据】,然后随意去设置,这样的话,可以达到固定分辨率的效果,bug就是有可能会设置到有些屏不支持的分辨率,导致无显示;而且自适应分辨率的时候也会导致无法匹配到一个是当前屏最优的分辨率,只会按照def_modes中的第一个分辨率vic值来设置分辨率,此解法不妥。
修改内容如下(修改文件:drivers/gpu/drm/bridge/synopsys/dw-hdmi.c):
//主要的改动就是在def_mode 数组添加默认支持的分辨率值;edid = NULL;强制把 edid 赋为 NULL,不管有没有读到 edid 都强制按 def_modes 来显示;注释掉clock > 340MHz和clock < 340MHz的分辨率过滤
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2533,7 +2533,9 @@ static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
connector);
struct edid *edid;
struct drm_display_mode *mode;
- const u8 def_modes[6] = {4, 16, 31, 19, 17, 2};
+// const u8 def_modes[6] = {4, 16, 31, 19, 17, 2};
+ const u8 def_modes[26] = {16, 4, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 62, 61,
+ 60, 34, 33, 32, 31, 21, 20, 19, 17, 6, 5, 2};
struct drm_display_info *info = &connector->display_info;
struct hdr_static_metadata *metedata =
&connector->display_info.hdmi.hdr_panel_metadata;
@@ -2543,6 +2545,7 @@ static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
return 0;
edid = drm_get_edid(connector, hdmi->ddc);
+ edid = NULL;
if (edid) {
dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n",
edid->width_cm, edid->height_cm);
@@ -2575,6 +2578,7 @@ static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
info->color_formats = 0;
dev_info(hdmi->dev, "failed to get edid\n");
+ dev_info(hdmi->dev, "No get edid, use def_modes resolution\n");
}
return ret;
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index ac8a260..532d09b 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -488,11 +488,12 @@ dw_hdmi_rockchip_mode_valid(struct drm_connector *connector,
* If sink max TMDS clock < 340MHz, we should check the mode pixel
* clock > 340MHz is YCbCr420 or not.
*/
+#if 0
if (mode->clock > 340000 &&
connector->display_info.max_tmds_clock < 340000 &&
!drm_mode_is_420(&connector->display_info, mode))
return MODE_BAD;
-
+#endif
if (!encoder) {
还需要注意一下分辨率的白名单过滤文件(在这个文件里面的分辨率上层才能识别),不然上层有可能会被过滤掉一些分辨率:
对应文件:device/rockchip/common/resolution_white.xml
通过 resolution_white.xml 定义合法分辨率,只有包含在该 xml 中的分辨率才可以设置。若不
存在该 xml 文件,不执行过滤。
还有个方法就是把def_modes这套的接口用法搬到读edid流程用法那里去,但是我觉得这个方法更加不妥。
当前采用了另外一个拙劣的方法(为什么说拙劣呢,因为还没做到零BUG):那就是在设置HDMI分辨率的时候,如果设置为自适应模式(Auto)的时候,就去设置status为detect,如果设置HDMI为固定分辨率的时候(如1080p)就设置HDMI的状态为on,即让它一直为输出HDMI信号的状态,并且分辨率固定【后来验证这种方式只能实现热拔插的时候固定分辨率,最终还是采用了def_mode的方法】。
手动测试验证:
先去Setting设置分辨率为4K的分辨率,然后串口或者adb工具敲入以下指令:
echo on > sys/class/drm/card0-HDMI-A-1/status //相当于一直使能HDMI输出
然后Setting设置为Auto,即分辨率自适应模式(会根据读到的EDID, 获取到的modes,自动匹配一个最优的mode),然后串口或者adb工具敲入以下指令:
echo off > sys/class/drm/card0-HDMI-A-1/status
echo detect > sys/class/drm/card0-HDMI-A-1/status
Android上层代码修改如下(主要是Setting apk部分的修改,即把上面的测试动作添加为代码而已,其实主要就是在设置分辨率(对应setDpMode[设置DP分辨率]和setHdmiMode[设置HDMI分辨率]接口这里,)之后,加上上面的动作):
diff --git a/src/com/android/settings/display/DrmDisplaySetting.java b/src/com/android/settings/display/DrmDisplaySetting.java
index aa8e00f..ad8b1be 100755
--- a/src/com/android/settings/display/DrmDisplaySetting.java
+++ b/src/com/android/settings/display/DrmDisplaySetting.java
@@ -17,6 +17,7 @@ import java.util.Arrays;
import java.util.List;
import com.android.settings.util.ReflectUtils;
+import java.io.FileWriter;
* Drm Display Setting.
@@ -48,6 +49,18 @@ public class DrmDisplaySetting {
Log.d(TAG, SUB_TAG + " - " + text);
}
+ private static int writeSysfs(String path,String value) {
+ try {
+ FileWriter command = new FileWriter(path);
+ command.write(value);
+ command.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return -1;
+ }
+ return 0;
+ }
+
public static List<DisplayInfo> getDisplayInfoList() {
List<DisplayInfo> displayInfos = new ArrayList<DisplayInfo>();
Object rkDisplayOutputManager = null;
@@ -308,6 +321,15 @@ public class DrmDisplaySetting {
ReflectUtils.invokeMethod(rkDisplayOutputManager, "setMode", new Class[]{int.class, int.class, String.class}, new Object[]{DISPLAY_TYPE_HDMI, currMainType, mode});
}
logd(" setHdmiMode 3");
+
+ if ("Auto".equals(mode)) {
+ logd(" czd-Hdmi Auto setHdmiMode mode = " + mode);
+ writeSysfs("sys/class/drm/card0-HDMI-A-1/status", "off");
+ writeSysfs("sys/class/drm/card0-HDMI-A-1/status", "detect");
+ } else {
+ logd(" czd-Hdmi Fix setHdmiMode mode = " + mode);
+ writeSysfs("sys/class/drm/card0-HDMI-A-1/status", "on");
+ }
}
@@ -431,6 +453,16 @@ public class DrmDisplaySetting {
int currMainType = (Integer) ReflectUtils.invokeMethod(rkDisplayOutputManager, "getCurrentInterface", new Class[]{int.class}, new Object[]{DISPLAY_TYPE_DP});
ReflectUtils.invokeMethod(rkDisplayOutputManager, "setMode", new Class[]{int.class, int.class, String.class}, new Object[]{DISPLAY_TYPE_DP, currMainType, reso});
}
+
+ if ("Auto".equals(reso)) {
+ logd(" czd-Dp Auto setHdmiMode mode = " + reso);
+ writeSysfs("sys/class/drm/card0-HDMI-A-1/status", "off");
+ writeSysfs("sys/class/drm/card0-HDMI-A-1/status", "detect");
+ } else {
+ logd(" czd-Dp Fix setHdmiMode mode = " + reso);
+ writeSysfs("sys/class/drm/card0-HDMI-A-1/status", "on");
+ }
+
}
/**
可以单独编译apk,如果验证修改了没效果,注意在Android.mk加上以下这句:
--- a/Android.mk
+++ b/Android.mk
@@ -35,6 +35,7 @@ LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
LOCAL_PACKAGE_NAME := Settings
LOCAL_CERTIFICATE := platform
+LOCAL_DEX_PREOPT := false
LOCAL_PRIVILEGED_MODULE := true
然后模块编译,得到更新后的apk:system/priv-app/Settings/Settings.apk:
rk3399_7.1_ind/packages/apps/Settings$ mm -j16
[100% 6/6] Install: out/target/product/rk3399_all/system/priv-app/Settings/Settings.apk
make: Leaving directory `/work/czd/rk3399_7.1_ind'
#### make completed successfully (07:09 (mm:ss)) ####
然后push进机器验证:
替换apk可验证。
adb root
adb remount
adb push Settings.apk system/priv-app/Settings/Settings.apk
adb shell chmod 644 system/priv-app/Settings/Settings.apk
adb shell sync
adb reboot
kernel 驱动也要做修改: 在热拔插hdmi的时候,不能去刷新edid的数据,modes等,不然会重新读取edid的数据,自动切换分辨率:
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -359,6 +359,12 @@ static bool check_hdmi_irq(struct dw_hdmi *hdmi, int intr_stat,
if (!(intr_stat & HDMI_IH_PHY_STAT0_HPD))
return false;
+ /* add by czd for ne4k fix hdmi resolution
+ * ignore hdmi plug in or out, default plug in
+ */
+ if (hdmi->force == DRM_FORCE_ON)
+ return true;
+
if (phy_int_pol & HDMI_PHY_HPD) {
dev_dbg(hdmi->dev, "dw hdmi plug in\n");
验证情况(操作设置分辨率,然后看log打印):
rk3399_all:/ # logcat | grep czd
01-01 00:45:35.054 757 757 D DrmDisplaySetting: DrmDisplaySetting - czd-Dp Fix setHdmiMode mode = 1920x1080p60.00-0
01-01 00:56:10.987 757 757 D DrmDisplaySetting: DrmDisplaySetting - czd-Dp Auto setHdmiMode mode = Auto
如果您有更优的方法,请不吝赐教!