OpenHarmony南向之LCD显示屏

概述

LCD(Liquid Crystal Display)驱动,通过对显示器上下电、初始化显示器驱动IC(Integrated Circuit)内部寄存器等操作,使其可以正常工作。

HDF Display驱动模型

LCD器件驱动是显示框架最底层的部分。
向上对接到 Display 公共 HAL 层,辅助 HDI 的实现。通过Display-HDI对图形服务提供各类驱动能力接口;
向下对接显示屏 panel 器件,驱动屏幕正常工作,自上而下打通显示全流程通路。
所以驱动LCD主要在于LCD panel器件驱动。

LCD接口通常可分为MIPI DSI接口、TTL接口和LVDS接口,这里以rk3568平台为例,是常见的mipi接口的显示屏

驱动主要分为2大部分:hcs配置和panel驱动

  • vendor/hihope/rk3568/hdf_config/khdf/device_info/device_info.hcs
  • drivers/hdf_core/framework/model/display/driver/panel/

下面就这2大部分分别来简单分析下

hcs配置及流程

hcs配置

display :: host {
    hostName = "display_host";
    device_hdf_drm_panel :: device {
        device0 :: deviceNode {
            policy = 0;
            priority = 197;
            preload = 0;
            moduleName = "HDF_DRMPANEL";
        }
    }
    device_hdf_disp :: device {
        device0 :: deviceNode {
            policy = 2;
            priority = 196;
            permission = 0660;
            moduleName = "HDF_DISP";
            serviceName = "hdf_disp";
        }
    }

    device_lcd :: device {
        ...
        device3 :: deviceNode {
            policy = 0;
            priority = 100;
            preload = 0;
            moduleName = "LCD_ILI9881_ST_5P5";
        }
    }

    device_pwm_bl :: device {
        device0 :: deviceNode {
            policy = 0;
            priority = 95;
            preload = 0;
            moduleName = "PWM_BL";
            deviceMatchAttr = "pwm_bl_dev";
        }
    }
    device_backlight :: device {
        device0 :: deviceNode {
            policy = 2;
            priority = 90;
            preload = 0;
            permission = 0660;
            moduleName = "HDF_BL";
            serviceName = "hdf_bl";
        }
    }
}

大致流程

从上面的hcs配置可以看出,这里使用的是 DRM Panel,根据 priority值,驱动加载的顺序依次为:

  1. HDF_BL
  2. PWM_BL
  3. LCD
  4. HDF_DISP
  5. HDF_DRMPANEL

这个顺序是跟驱动代码里面的逻辑是相匹配的,LCD驱动在init的时候会注册panel(RegisterPanel),然后在HDF_DISP驱动的init中会通过 GetPanelManager获取注册的panelManager, 并使用 panelManagerDispManagerInit,HDF_DRMPANEL驱动在init的时候会通过 GetDispManager获取在前面初始化的 DispManager,并使用DRM框架的接口来init和add相应drm_panel,且将mipi接口联系起来,整个过程是环环相扣。

panel驱动

Panel驱动中最核心的主要是实现以下接口:

struct PanelData {
    struct HdfDeviceObject *object;
    int32_t (*init)(struct PanelData *panel);       /*panel的软件初始化*/
    int32_t (*on)(struct PanelData *panel);         /*主要控制上电*/
    int32_t (*off)(struct PanelData *panel);        /*主要控制下电*/
    int32_t (*prepare)(struct PanelData *panel);    /*亮屏硬件时序初始化, 通过MIPI DCS发送亮屏初始化序列*/
    int32_t (*unprepare)(struct PanelData *panel);  /*灭屏硬件时序初始化, 通过MIPI DCS发送灭屏代码*/
    struct PanelInfo *info;         /*Panel的一些参数。见下方*/
    enum PowerStatus powerStatus;
    struct PanelEsd *esd;
    struct BacklightDev *blDev;
    void *priv;
};

在驱动初始化接口中实例化后使用 RegisterPanel接口向display模型注册该panel驱动

ili9881_st_5p5MIPI显示屏驱动为例来看看大致的流程:

...
    /*获取panel节点,rk一般都使用的 simple panel*/
    panelNode = of_find_compatible_node(NULL, NULL, "simple-panel-dsi");
    if (panelNode == NULL) {
        HDF_LOGE("%s of_find_compatible_node fail", __func__);
        goto FAIL;
    }
    /*通过panel节点进一步获取mipi dsi的节点*/
    panel_dev->dsiDev = of_find_mipi_dsi_device_by_node(panelNode);
    if (panel_dev->dsiDev == NULL) {
        HDF_LOGE("%s of_find_mipi_dsi_device_by_node fail", __func__);
        goto FAIL;
    }
    /*获取power节点*/
    panel_dev->supply = devm_regulator_get(&panel_dev->dsiDev->dev, "power");
    if (panel_dev->supply == NULL) {
        HDF_LOGE("Get regulator fail");
        goto FAIL;
    }

    /*获取其他gpio控制节点,需根据硬件配置**/
    panel_dev->enable_gpio = devm_gpiod_get_optional(&panel_dev->dsiDev->dev, "enable", GPIOD_ASIS);
    if (IS_ERR(panel_dev->enable_gpio)) {
        HDF_LOGE("get enable_gpio fail");
        goto FAIL;
    }
    ...
    /*初始化PanelData mipi_dsi_device结构体,下面会展开详细说明*/
    PanelResInit(panel_dev);
    ...

    /*注册到Panel Manager*/
    if (RegisterPanel(&panel_dev->panel) != HDF_SUCCESS) {
        HDF_LOGE("RegisterPanel fail");
        goto FAIL;
    }
    ...

PanelResInit函数主要是关键结构体(struct PanelDatastruct mipi_dsi_device )的实例化:

/*mipi dsi 的一些参数*/
panel_dev->dsiDev->lanes = 4;  /* 4: dsi,lanes ,number of active data lanes */
panel_dev->dsiDev->format = MIPI_DSI_FMT_RGB888; // dsi,format pixel format for video mode MIPI_DSI_FMT_RGB888
panel_dev->dsiDev->mode_flags = (MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM \
                                      | MIPI_DSI_MODE_EOT_PACKET);

panel_dev->panel.info = &g_panelInfo; /*Panel的一些参数。见下方*/
panel_dev->panel.init = PanelInit;  /*panel的软件初始化,这里为空函数*/
panel_dev->panel.on = PanelOn;   /*主要控制上电*/
panel_dev->panel.off = PanelOff; /*主要控制下电*/
panel_dev->panel.prepare = PanelPrepare;  /*亮屏硬件时序初始化, 通过MIPI DCS发送亮屏初始化代码*/
panel_dev->panel.unprepare = PanelUnprepare;  /*灭屏硬件时序初始化, 通过MIPI DCS发送灭屏代码*/
panel_dev->panel.priv = panel_dev->dsiDev;

关于 MIPI DCS 可以查看我以前的文章: https://notes.z-dd.online/2020/12/30/MIPI-DSI%E4%B9%8BDCS%E7%9B%B8%E5%85%B3/
这里,在v3.2版本的代码里,最终还是调用的是Linux下mipi dsi相关的接口,并没有使用OH实现的mipi的驱动接口

panel相关的硬件参数定义,这个主要跟显示屏硬件有关,这些参数的具体含义可参看我以前的博文:

static struct PanelInfo g_panelInfo = {
    .width = 720,          /* width */
    .height = 1280,          /* height */
    .hbp = 40,             /* horizontal back porch */
    .hfp = 40,         /* horizontal front porch */
    .hsw = 10,              /* horizontal sync width */
    .vbp = 15,              /* vertical back porch */
    .vfp = 10,              /* vertical front porch */
    .vsw = 36,               /* vertical sync width */
    .clockFreq = 75000000,  /* clock */
    .pWidth = 68,           /* physical width */
    .pHeight = 121,         /* physical height */
    .connectorType = DRM_MODE_CONNECTOR_DPI,   /* DRM_MODE_CONNECTOR_DPI=17 */
    .blk = { BLK_PWM, MIN_LEVEL, MAX_LEVEL, DEFAULT_LEVEL },
};

参考