我们在适配众达的硬件的时候,发现Openharmony系统开机无法显示logo,遂排查,这里提供解决过程
logo无法显示的问题,是内核开机logo驱动可能出错,我们需要找出核心的代码,如下
drivers/gpu/drm/rockchip/rockchip_drm_logo.c
根据我们之前梳理的众达显示的文章《Openharmony之众达rk3568内核显示panel代码梳理》,我们梳理代码逻辑如下:
rockchip_drm_bind--->rockchip_drm_show_logo---->of_parse_display_resource--→find_sub_dev_by_bridge
根据上面代码的梳理,我们可以知道find_sub_dev_by_bridge是可能出现错误的地方,根据原始代码逻辑如下:
static struct rockchip_drm_sub_dev * find_sub_dev_by_bridge(struct drm_device *drm_dev, struct device_node *node) { struct device_node *np_encoder, *np_connector = NULL; struct rockchip_drm_sub_dev *sub_dev = NULL; struct device_node *port, *endpoint; np_encoder = of_graph_get_remote_port_parent(node); if (!np_encoder || !of_device_is_available(np_encoder)) goto err_put_encoder; port = of_graph_get_port_by_id(np_encoder, 1); if (!port) { dev_err(drm_dev->dev, "can't found port point!\n"); goto err_put_encoder; } for_each_child_of_node(port, endpoint) { np_connector = of_graph_get_remote_port_parent(endpoint); if (!np_connector) { dev_err(drm_dev->dev, "can't found connector node, please init!\n"); goto err_put_port; } if (!of_device_is_available(np_connector)) { of_node_put(np_connector); np_connector = NULL; continue; } else { break; } } if (!np_connector) { dev_err(drm_dev->dev, "can't found available connector node!\n"); goto err_put_port; } sub_dev = rockchip_drm_get_sub_dev(np_connector); if (!sub_dev) goto err_put_port; of_node_put(np_connector); err_put_port: of_node_put(port); err_put_encoder: of_node_put(np_encoder); return sub_dev; }
根据分析上面代码的关键,我们可以知道,默认情况下,作为hdmi设备,我们将其作为bridge时,我们只寻找他的下一个remote port,然后再通过rockchip_drm_get_sub_dev拿到sub_dev
而现有的代码两点不满足
根据《Openharmony之众达rk3568内核显示panel代码梳理》第一章的概念中解释的,现在众达的显示架构下,我们是hdmi--->rk628-→panel。所以单独一个for_each_child_of_node是找不到最终的设备,代码如下
for_each_child_of_node(port, endpoint) { np_connector = of_graph_get_remote_port_parent(endpoint); if (!np_connector) { dev_err(drm_dev->dev, "can't found connector node, please init!\n"); goto err_put_port; } if (!of_device_is_available(np_connector)) { of_node_put(np_connector); np_connector = NULL; continue; } else { break; } }
根据上述的代码,我们可以知道,它可以从port中编译endpoint,然后根据endpoint找到对应的节点np。在实际情况中,它找到的是rk628_hdmirx,这不符合我们的期望。
从rockchip_drm_get_sub_dev的分析我们知道, 我们需要在最后一个bridge的时候,register一个drm sub dev设备,这样才能通过rockchip_drm_get_sub_dev拿到这个设备。代码如下
sub_dev = rockchip_drm_get_sub_dev(np_connector);
根据2.1的分析,我们拿到的np_connector是rk628_hdmirx,但是很明显,他并不是连接panel的哪个connector,真实的应该是rk628_lvds,所以我们应该分析rk628_lvds.c驱动,代码如下
static int rk628_lvds_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { struct rk628_lvds *lvds = bridge_to_lvds(bridge); struct drm_connector *connector = &lvds->connector; struct drm_device *drm = bridge->dev; int ret; if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) return 0; ret = drm_connector_init(drm, connector, &rk628_lvds_connector_funcs, DRM_MODE_CONNECTOR_LVDS); if (ret) { dev_err(lvds->dev, "Failed to initialize connector with drm\n"); return ret; } drm_connector_helper_add(connector, &rk628_lvds_connector_helper_funcs); drm_connector_attach_encoder(connector, bridge->encoder); return 0; }
可以发现,这里没有注册sub dev,所以需要修改
关于上面的解析,我们需要修改两处代码
对于2.1的问题,我们需要借用递归的思路寻找下一个bridge,所以修改代码如下
again: for_each_child_of_node(port, endpoint) { np_connector = of_graph_get_remote_port_parent(endpoint); if (!np_connector) { dev_err(drm_dev->dev, "can't found connector node, please init!\n"); goto err_put_port; } if(of_device_is_available(np_connector)){ if (strcmp(np_connector->name, "panel") != 0) { port = of_graph_get_port_by_id(np_connector, 1); if(port == NULL){ break; } else { np_encoder = np_connector; goto again; } } else { np_connector = np_encoder; break; } }else{ of_node_put(np_connector); np_connector = NULL; continue; } }
此时我们找到的下一个bridge为rk628_hdmirx的时候,还是会继续往下寻找,知道找到最后一个名为panel的节点
根据3.1的递归,我们可以正确的找到rk628_lvds,所以需要在驱动中添加注册drm sub dev,如下
static int rk628_lvds_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { struct rk628_lvds *lvds = bridge_to_lvds(bridge); struct drm_connector *connector = &lvds->connector; struct drm_device *drm = bridge->dev; int ret; if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) return 0; ret = drm_connector_init(drm, connector, &rk628_lvds_connector_funcs, DRM_MODE_CONNECTOR_LVDS); if (ret) { dev_err(lvds->dev, "Failed to initialize connector with drm\n"); return ret; } drm_connector_helper_add(connector, &rk628_lvds_connector_helper_funcs); drm_connector_attach_encoder(connector, bridge->encoder); lvds->sub_dev.connector = &lvds->connector; lvds->sub_dev.of_node = lvds->dev->of_node; rockchip_drm_register_sub_dev(&lvds->sub_dev); return 0; }
根据分析代码,我们需要添加日志从而确定问题情况,如下是调试日志
printk("kylin: %s %d port=%pOF\n", __func__, __LINE__, port);
根据此,我们可以从启动日志找到如下信息
[ 4.490238] kylin: find_sub_dev_by_bridge 93 np_connector=/i2c@fe5d0000/rk628@50/hdmirx [ 4.490288] kylin: find_sub_dev_by_bridge 106 port=/i2c@fe5d0000/rk628@50/hdmirx/ports/port@1 [ 4.490325] kylin: find_sub_dev_by_bridge 93 np_connector=/i2c@fe5d0000/rk628@50/post-process [ 4.490367] kylin: find_sub_dev_by_bridge 106 port=/i2c@fe5d0000/rk628@50/post-process/ports/port@1 [ 4.490403] kylin: find_sub_dev_by_bridge 93 np_connector=/i2c@fe5d0000/rk628@50/lvds [ 4.490443] kylin: find_sub_dev_by_bridge 106 port=/i2c@fe5d0000/rk628@50/lvds/ports/port@1 [ 4.490474] kylin: find_sub_dev_by_bridge 93 np_connector=/panel
此时我们正确的找到了panel节点,
我们知道我们的屏幕是panel-simple驱动的,所以我们需要留意get_timings回调如下
static const struct drm_panel_funcs panel_simple_funcs = { .disable = panel_simple_disable, .unprepare = panel_simple_unprepare, .prepare = panel_simple_prepare, .enable = panel_simple_enable, .get_modes = panel_simple_get_modes, .get_timings = panel_simple_get_timings, };
这里面当timing的数量为1的时候,默认设置了perfered,如下
static unsigned int panel_simple_get_timings_modes(struct panel_simple *panel, struct drm_connector *connector) { struct drm_display_mode *mode; unsigned int i, num = 0; for (i = 0; i < panel->desc->num_timings; i++) { const struct display_timing *dt = &panel->desc->timings[i]; struct videomode vm; videomode_from_timing(dt, &vm); mode = drm_mode_create(connector->dev); if (!mode) { dev_err(panel->base.dev, "failed to add mode %ux%u\n", dt->hactive.typ, dt->vactive.typ); continue; } drm_display_mode_from_videomode(&vm, mode); mode->type |= DRM_MODE_TYPE_DRIVER; if (panel->desc->num_timings == 1) mode->type |= DRM_MODE_TYPE_PREFERRED; drm_mode_probed_add(connector, mode); num++; } return num; }
此时我们需要在fill bmp数据之前找到我们正确的mode,需要添加如下:
if (!found) { list_for_each_entry(mode, &connector->modes, head) { if (mode->type & DRM_MODE_TYPE_PREFERRED) { found = 1; break; } } }
其关键代码如下:
if (setup_initial_state(drm_dev, state, set)) { drm_framebuffer_put(set->fb); INIT_LIST_HEAD(&set->head); list_add_tail(&set->head, &mode_unset_list); continue; }
至此,开机logo即正常显示。