接到了一个在RK3588s平台上点亮一款HDMI2eDP的黑白屏,本以为是小菜一碟,看规格发现大意了。分辨率是2560*4320,看着这种不常见的分辨率就头大,不由得想起来之前在RK3568平台上尝试点的一块屏。
在RK3568平台上,要点的一款分辨率为2560*3600*23fps的eDP屏幕。点之前查看了RK3568的eDP规格:
虽然分辨率超过了规格,但数据量小于规格,当时天真的以为只要数据量小于规格最大值,就能支持,没想到RK3568的VP在冷冷的看着我:小伙子,你太天真了!按照常规方式,该检查的都检查了,屏幕就是不亮!
static const struct vop2_data rk3568_vop = {
.version = VOP_VERSION(0x40, 0x15),
.nr_vps = 3,
.nr_mixers = 5,
.nr_gammas = 1,
.max_input = { 4096, 2304 },
.max_output = { 4096, 2304 },
.ctrl = &rk3568_vop_ctrl,
.grf_ctrl = &rk3568_grf_ctrl,
.axi_intr = rk3568_vop_axi_intr,
.nr_axi_intr = ARRAY_SIZE(rk3568_vop_axi_intr),
.vp = rk3568_vop_video_ports,
.wb = &rk3568_vop_wb_data,
.layer = rk3568_vop_layers,
.nr_layers = ARRAY_SIZE(rk3568_vop_layers),
.win = rk3568_vop_win_data,
.win_size = ARRAY_SIZE(rk3568_vop_win_data),
.dump_regs = rk3568_dump_regs,
.dump_regs_size = ARRAY_SIZE(rk3568_dump_regs),
};
瞧瞧,最大只有2304,能亮才怪呢! 有了上次的教训,这次老老实实的查了平台资料和源码,一切都符合,看起来事情要顺利起来了!
由于平台端看到的是HDMI接口,基本上不需要做修改,如果在平台的支持范围内,插入HDMI cable就应该亮起来。显然我没有那么幸运,屏并没有如愿亮起来,检查debug fs,发现并没有对应屏规格的mode被创建。没有办法,只能通过log检查整个HDMI插入检测流程了:
HPD检测正常,插入事件顺利获取!
EDID读取正常,Header和Check sum检查顺利Pass!
EDID解析正……,等等,为什么Hactive、Vactive只有12bit,那最大只能到4096啊,我的4320可怎么整!查看EDID标准,果不其然,EDID DTB(Detailed Timing Descriptions)对此有描述:
图中红色方框选中的为Hactive的定义,只有12个bit,可见现在主流的EDID对detail timing的支持最多也只能做到4096*4096。相信新版的EDID能解决这个限制,毕竟,4K虽然是主流,但8K的电视和显示器也慢慢多了起来。
现在面临两个选择:
一是升级EDID,升级DRM EDID相关驱动,升级HDMI2EDP屏使用的EDID内容和版本。要想做到这两点,首先要拿到高版本的EDID规格。很遗憾,网上找了半天也没有找到!然后动手去升级DRM EDID驱动,先假设花了很大的功夫,能过做到。最后还要升级屏中的EDID,显然,这条有点困难,属于不可控行为。看起来这个选择困难大,不确定性多,只能作罢!
二是只把EDID做ID,看到这款屏的EDID,就创建与之对应的mode。说干就干,查看drm_edid.c,很容易就找到了切入点,函数drm_mode_detailed, 看它的注释就觉得很亲切:“create a new mode from an EDID detailed timing section”,简直是量身定做:
/**
* drm_mode_detailed - create a new mode from an EDID detailed timing section
* @dev: DRM device (needed to create new mode)
* @edid: EDID block
* @timing: EDID detailed timing info
* @quirks: quirks to apply
*
* An EDID detailed timing block contains enough info for us to create and
* return a new struct drm_display_mode.
*/
static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev,
struct edid *edid,
struct detailed_timing *timing,
u32 quirks)
判断是否为这款屏也找到了切入点,EDID第8~11个字节做了定义(VID/PID):
废话不多说,直接上简单粗暴的修改:
//20230422, create mode for mfg id: 0x32/0x8d
if( edid->mfg_id[0] == 0x32 && edid->mfg_id[1] == 0x8d)
{
//hardcode timing for 10.1 AUO panel
hactive = 2560;
vactive = 4320;
timing->pixel_clock = 27320;
hsync_pulse_width = 32;
hsync_offset = 48;
hblank = 160;
vsync_pulse_width = 10;
vsync_offset = 3;
vblank = 47;
}
改好代码后,就是肌肉记忆了:编译,刷机,开机,插cable。虽然改的简单粗暴,但效果杠杠的,它亮起来了!
它亮起来了!很高兴,虽然看上去有点怪怪的,但先不管了,交差喽! 发送改后的Image,然后该干嘛干嘛去了。
然后事实有一次教育了我:不能过早开香槟,不能在终场哨吹响前跳舞。还有,人的感觉往往很犀利,感觉不对劲它就是有问题。有显示的屏,字怎么偏黄,隔着屏幕怎么能看到草台桌子的纹理呢!果然,问题马上就报过来了:漏光!说字不够白,背景不够黑!
棘手的问题,往往是看起来对但有那么一丢丢不对,这个显示是有了,画面也没有花,屏幕也没有跑偏,问题出在哪里呢?
Timing估计不会有这方面的影响,但理论归理论,还是要实际验证一下。在屏幕规格范围呢,调了几组,显示画面没有变化!不要问我你一个HDMI的屏,如何调整timing?改EDID,不存在的,转板厂不支持!只能使用hardcode大法!Hblank和Vblank组合试了一遍,没有丝毫影响,只能放弃timing了!
一通猛试无效后,开始思考问题了!漏光,背景不够黑?黑色在RGB中,应该是全0,如果黑色不够黑,那么RGB中,一定是有非零的存在,让液晶排列不如预期,从而有光漏出来?看起来分析的有道理,为什么RGB中会有非零的存在呢?R会不会不是R呢?想到这里,不由得想起来了彩色图像的记录格式。
彩色图像的记录格式,常见的有RGB、YUV等。RGB诉求于人眼对色彩的感应,YUV则着重于视觉对于亮度的敏感程度,Y代表的是亮度,UV代表的是色度(因此黑白电影可省略UV,相近于RGB)……。什么,YUV在黑边电影近似于RGB?当前遇到的问题,显示的内容正常,仅仅是黑色不够黑,也是黑白屏,会不会是把YUV格式用于RGB屏了呢?求教于屏厂,这款屏确实是RGB黑白屏(吐槽一下,屏的规格书里没有这方面的描述)。
知道了屏的规格后,再看驱动侧是如何选择格式的。因为是HDMI接口,应该从EDID解析开始,EDID里,应该有指示这款屏支持的颜色格式的设定。文件drm_edid.c中可以找到答案。struct drm_display_info 中有对颜色格式进行定义:
/**
* @color_formats: HDMI Color formats, selects between RGB and YCrCb
* modes. Used DRM_COLOR_FORMAT\_ defines, which are _not_ the same ones
* as used to describe the pixel format in framebuffers, and also don't
* match the formats in @bus_formats which are shared with v4l.
*/
u32 color_formats;
这个color_formats是如何获取的呢?在函数drm_add_display_info进行设定
u32 drm_add_display_info(struct drm_connector *connector, const struct edid *edid)
{
struct drm_display_info *info = &connector->display_info;
u32 quirks = edid_get_quirks(edid);
drm_reset_display_info(connector);
info->width_mm = edid->width_cm * 10;
info->height_mm = edid->height_cm * 10;
info->non_desktop = !!(quirks & EDID_QUIRK_NON_DESKTOP);
DRM_DEBUG_KMS("non_desktop set to %d\n", info->non_desktop);
if (edid->revision < 3)
return quirks;
if (!(edid->input & DRM_EDID_INPUT_DIGITAL))
return quirks;
info->color_formats |= DRM_COLOR_FORMAT_RGB444;
drm_parse_cea_ext(connector, edid);
看到眼熟的了吗?先给一个初始值DRM_COLOR_FORMAT_RGB444,然后再去解析EDID扩展block。很不幸,这款屏搭配的HDMI转板提供的EDID是有扩展block的。所以要看一下函数drm_parse_cea_ext:
static void drm_parse_cea_ext(struct drm_connector *connector,
const struct edid *edid)
{
struct drm_display_info *info = &connector->display_info;
const u8 *edid_ext;
int i, start, end;
edid_ext = drm_find_cea_extension(edid);
if (!edid_ext)
return;
info->cea_rev = edid_ext[1];
/* The existence of a CEA block should imply RGB support */
info->color_formats = DRM_COLOR_FORMAT_RGB444;
if (edid_ext[3] & EDID_CEA_YCRCB444)
info->color_formats |= DRM_COLOR_FORMAT_YCRCB444;
if (edid_ext[3] & EDID_CEA_YCRCB422)
info->color_formats |= DRM_COLOR_FORMAT_YCRCB422;
从函数的实现来看,关键信息在extension block的第4个Byte,这个Byte,对应的数据是0xF2。
#define EDID_CEA_YCRCB444 (1 << 5)
#define EDID_CEA_YCRCB422 (1 << 4)
对应到0xF2, YUV444和YUV422都有打开,真想说句F**K!不支持还用EDID申明支持。没有办法,EDID无法修改,只能祭出最后的法宝,直接上hardcode!YUV再好,咱不用了,直接注释掉!编译->刷机->验证,丝滑的验证过程预示着美好的结果:
终于,黑的是黑的了!