RGB屏幕

【简介】

LuckFox Pico Ultra LuckFox Pico Ultra W 现在支持 RGB 屏幕,采用并行的 RGB LCD 接口。图像数据以 RGB666 格式传输,每个像素占用 6 位。目前的只适配分辨率有 720x720 和 480x480。为了帮助大家的理解工作原理,本节会从应用层到驱动层进行介绍,由于屏幕种类繁多,其它分辨率屏幕适配还需要自行研究。


刚到手可以使用开发板自带的程序测试屏幕,默认已经将测试程序打包到开发板上(/oem/usr/bin),如果想了解可执行程序的源码,在

/SDK目录//media/luckfox/examples  和  ./SDK目录/project/app/lvgl


# luckfox_fb_test
The mem is :921600
The line_length is :1920
The xres is :480
The yres is :480
bits_per_pixel is :32

# luckfox_lvgl_test
fbdev_init


MIPI DPI 接口】

RGB 接口也被称为 DPI(Display Pixel Interface) 接口,采用普通的同步、时钟、信号线来传输特定数据,采用 IIC 等控制线完成命令控制,DPI接口信号线:

DPIVSYNC (Vertical Sync): 垂直同步信号,用于指示一帧图像的开始。
DPIHSYNC (Horizontal Sync): 水平同步信号,用于指示一行像素的开始。
DPIDE (Data Enable): 数据有效信号,用于指示当前传输的数据是有效的像素数据。
DPICK (Clock): 像素时钟信号,用于同步数据传输。
数据线: 多条并行数据线(如DPI_DATA0, DPI_DATA1, ...),用于传输像素数据。


【Rockchip 平台显示子系统(DSS)】

显示子系统是Rockchip平台显示输出相关软硬件系统的统称,linux内核采用component框架来构建显示子系统,一个显示子系统由显示处理器(vop,video output processor)、接口控制器(mipi,lvds,hdmi、edp、dp、rgb、BT1120、BT656、I8080(MCU 显示接口)等)、液晶背光,电源等多个独立的功能模块构成。将在内存中的图像数据,转化为电信号送到显示设备称为显示控制器,比如早期的LCDC;后面进行了拓展,可以处理一些简单的图像,比如缩放、旋转、合成等,如瑞芯微的vop和博通的VideoCore都称为显示处理器。RV1106 采用的VOP1.0 ,整个显示系统的硬件框架如下图所示:

【FBDEV 介绍】

Linux 内核中常用的两类图形显示设备驱动框架分别是 DRM(Direct Rendering Manager)和 FBDEV(Framebuffer Device)。在 Framebuffer 驱动框架下,用户可以通过 /dev/fbX 接口直接操作显示设备的显存,进行标准文件操作(如 read, write, ioctl)。用户空间程序可以通过这些设备节点和 ioctl 调用来控制帧缓冲设备。Framebuffer 提供基本的 2D 图形操作,如点、线、矩形的绘制,支持多种像素格式和分辨率。缺点是它不支持现代图形硬件的高级功能,如硬件加速和 3D 渲染。

【DRM 介绍】

DRM 全称是 Direct Rendering Manager,进行显示输出管理、buffer 分配、帧缓冲。对应的 userspace 库为 libdrm,libdrm 库提供 了一系列友好的控制封装,使用户可以方便的进行显示的控制和 buffer 申请。DRM 的设备节点为 "/dev/dri/cardX", X 为 0-15 的数 值,默认使用的是 /dev/dri/card0。 Rockchip 平台从 Linux 4.4 内核开始,显示驱动全部切到 DRM 显示框架。

】DRM 与 framebuffer 的区别

下图是 FBDEV 和 DRM 连接到设备端最终显示过程对比:

【DRM 基本概念】

为了方便管理显示通路上的各种硬件模块,DRM 定义了以下几个概念:

【DRM 框架介绍】

DRM 驱动和 libdrm 的交互过程:

】Libdrm

在 DRM 框架中,libdrm(Direct Rendering Manager library)是一个用户空间库。,提供了DRM驱动的用户空间接口;对底层接口进行封装,向上层应用程序提供通用的API接口,本质上是对各种ioctl接口进行封装。

】KMS

KMS(Kernel Mode Setting)使内核直接管理显示模式和分辨率,提高了系统的稳定性和性能。KMS 包含 CRTC(Cathode Ray Tube Controller),用于控制显示扫描;Plane,用于管理图形数据层;Encoder,将图形数据转换为适当的输出信号;以及 Connector,连接显示设备,如显示器或电视。

】GEM

GEM(Graphics Execution Manager)负责显存的分配和管理,提供高效的显存操作机制。它与 DRM 的其他组件,如 Plane、CRTC 和 Panel 协同工作,以确保图形数据在显存中的高效传输和渲染,支持硬件加速和现代图形硬件的高级功能。

】drm_panel

drm_panel 是 DRM(Direct Rendering Manager)框架中的一个重要概念,尽管它不像 drm_crtcdrm_connectordrm_plane 等直接被认为是对象,但它的存在降低了LCD驱动与encoder驱动之间的耦合度。drm_panel它是一堆回调函数的集合,这些回调函数允许显示驱动程序和其他部分(如 encoder 驱动程序)与显示面板进行交互。



【驱动模块路径】

linux内核将DRM驱动相关的代码都放在drivers/gpu/drm目录下,驱动部分内容作为了解即可。

【设备树设置】

在RV1106 上,包含一个 VOP 和一个 DPI(RGB的并行接口)。整个流程可以参考上面的 《Rockchip 平台显示子系统(DSS)》部分。

】Panel 设备节点

RGB panel 驱动可以参考drivers/gpu/drm/panel/panel-simple.c中的实现, panel节点定义在arch/arm/boot/dts/rv1106-luckfox-pico-ultra-ipc.dtsi:

panel: panel {
                compatible = "simple-panel";
                backlight = <&backlight>;
               reset-gpios = <&gpio0 RK_PA1 GPIO_ACTIVE_LOW>;
               reset-delay-ms = <200>;
                status = "okay";

                bus-format = <MEDIA_BUS_FMT_RGB666_1X18>;
                width-mm = <85>;
                height-mm = <85>;

                display-timings {
                        native-mode = <&timing0>;
                        timing0: timing0 {
                                clock-frequency = <16500000>;
                                hactive = <480>;
                                vactive = <480>;
                                hback-porch = <43>;
                                hfront-porch = <8>;
                                vback-porch = <15>;
                                vfront-porch = <12>;
                                hsync-len = <4>;
                                vsync-len = <10>; //value range <2~22>
                                hsync-active = <0>;
                                vsync-active = <0>;
                                de-active = <0>;
                                pixelclk-active = <1>;
                        };
                };

                port {
                        panel_in_rgb: endpoint {
                                remote-endpoint = <&rgb_out_panel>;
                        };
                };
        };

】display_subsystem 设备节点

DRM显⽰功能的SoC的核⼼设备树⾥⾯,都会有display_subsystem节点:所有的子设备信息都通过设备树描述关联起来,这样系统开机后,就能统一的管理各个设备。display_subsystem设备节点定义在arch/arm/boot/dts/rv1106.dtsi:

display_subsystem: display-subsystem {
                compatible = "rockchip,display-subsystem";
                ports = <&vop_out>;

】VOP 节点

vop 设备节点描述了 vop 硬件资源,控制vop驱动的加载 rockchip_drm_vop.c。以设备节点vop_out为例,vop_out设备节点定义在arch/arm/boot/dts/rv1106.dtsi

vop: vop@ff990000 {
                compatible = "rockchip,rv1106-vop";
                reg = <0xff990000 0x200>;
                reg-names = "regs";
                rockchip,grf = <&grf>;
                interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
                clocks = <&cru ACLK_VOP>, <&cru DCLK_VOP>, <&cru HCLK_VOP>;
                clock-names = "aclk_vop", "dclk_vop", "hclk_vop";
                status = "disabled";

                vop_out: port {
                        #address-cells = <1>;
                        #size-cells = <0>;

                        vop_out_rgb: endpoint@0 {
                                reg = <0>;
                                remote-endpoint = <&rgb_in_vop>;
                        };  
                };
        };
子节点port下的endpoint描述的是vop和显示接口的连接关系,vop_out节点下有vop_out_rgb,每个endpoint通过remote-endpoint属性和对应的显示接口组成一个连接通路。
启用 VOP1.0 节点:

】内核设置

cp ./arch/arm/configs/luckfox_rv1106_linux_defconfig .config
make ARCH=arm menuconfig

 Device Drivers --->
  Graphics support --->
     <*> Direct Rendering Manager (XFree86 4.1.0 and higher DRI support)  ---> 
     <*>  DRM Support for Rockchip (DRM_ROCKCHIP [=y])
     [ ]   Support 3D cubic LUT
     [ ]   Rockchip DRM debug
     [ ]   Rockchip DRM direct show
     [*]   Rockchip VOP driver
     [ ]   Rockchip VOP2 driver
     [ ]   Rockchip specific extensions for Analogix DP driver
     [ ]   Rockchip cdn DP
     [ ]   Rockchip TVE support
     [ ]   Rockchip specific extensions for Synopsys DW HDMI
     [ ]   Rockchip specific extensions for Synopsys DW MIPI DSI
     [ ]   Rockchip specific extensions for Synopsys DW DPTX
     [ ]   Rockchip specific extensions for Innosilicon HDMI
     [ ]   Rockchip LVDS support
     [*]   Rockchip RGB support
     [ ]   Rockchip specific extensions for RK3066 HDMI
     [ ]   Rockchip Virtual connector driver for HDMI/DP/DSI
     [ ]   Rockchip virtual VOP drm driver< >   Synopsis Designware HDCP2 interface   

上述步骤就是将内核开启Rockchip VOP driver和Rockchip RGB support,也就是在内核配置文件中加入CONFIG_ROCKCHIP_VOP和CONFIG_ROCKCHIP_RGB,我们可以搜索相关宏定义:


root@718974014fe6:/home/sysdrv/source/kernel/drivers/gpu/drm/rockchip# grep -rn "CONFIG_ROCKCHIP_RGB" ./
./Makefile:27:rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o
./rockchip_drm_drv.c:2050:      ADD_ROCKCHIP_SUB_DRIVER(rockchip_rgb_driver, CONFIG_ROCKCHIP_RGB);
./rockchip_rgb.h:8:#ifdef CONFIG_ROCKCHIP_RGB

由此可以看出,CONFIG_ROCKCHIP_RGB决定了是否将rockchip_rgb.c编译到内核中,同理,CONFIG_ROCKCHIP_RGB也是有相同的作用。

【DRM 驱动入口】

平台驱动(Platform Driver) 和设备的匹配,这里是直接比较驱动和设备的字段,看看是否相同。通过 compatible 属性,内核会将 simple-panel 驱动与设备树中的 panel 节点进行匹配。


static struct platform_driver panel_simple_platform_driver = {
        .driver = {
                .name = "panel-simple",
                .of_match_table = platform_of_match,
        },
        .probe = panel_simple_platform_probe,
        .remove = panel_simple_platform_remove,
        .shutdown = panel_simple_platform_shutdown,
};

驱动的 probe 函数会检查 display-timings 节点,并解析其中的时序参数。根据解析得到的时序参数,驱动会配置显示控制器,以确保面板能够正确显示。

static int panel_simple_of_get_desc_data(struct device *dev,
                                         struct panel_desc *desc)
{
        struct device_node *np = dev->of_node;
        u32 bus_flags;
        const void *data;
        int len;
        int err;

        if (of_child_node_is_present(np, "display-timings")) {
                struct drm_display_mode *mode;

                mode = devm_kzalloc(dev, sizeof(*mode), GFP_KERNEL);
                if (!mode)
                        return -ENOMEM;

                if (!of_get_drm_display_mode(np, mode, &bus_flags,
                                             OF_USE_NATIVE_MODE)) {
                        desc->modes = mode;
                        desc->num_modes = 1;
                        desc->bus_flags = bus_flags;
                }

设备树中定义的面板节点会被 panel-simple 驱动探测到,panel_simple_platform_probe 函数会读取设备树中的面板描述信息,并通过 panel_simple_probe 函数进行面板初始化和注册。

static int panel_simple_platform_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        const struct of_device_id *id;
        const struct panel_desc *desc;
        struct panel_desc *d;
        int err;

        id = of_match_node(platform_of_match, pdev->dev.of_node);
        if (!id)
                return -ENODEV;

        if (!id->data) {
                d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL);
                if (!d)
                        return -ENOMEM;

                err = panel_simple_of_get_desc_data(dev, d);
                if (err) {
                        dev_err(dev, "failed to get desc data: %d\n", err);
                        return err;
                }
        }

        desc = id->data ? id->data : d;

        return panel_simple_probe(&pdev->dev, desc);
}

注册过程中,drm_panel_init drm_panel_add 函数会将面板初始化并注册到 DRM 框架中,使其成为 DRM 子系统的一部分,供其他 DRM 组件使用。

void drm_panel_init(struct drm_panel *panel, struct device *dev,
                    const struct drm_panel_funcs *funcs, int connector_type)
{
        INIT_LIST_HEAD(&panel->list);
        panel->dev = dev;
        panel->funcs = funcs;
        panel->connector_type = connector_type;
}
EXPORT_SYMBOL(drm_panel_init);

/**
 * drm_panel_add - add a panel to the global registry
 * @panel: panel to add
 *
 * Add a panel to the global registry so that it can be looked up by display
 * drivers.
 */
void drm_panel_add(struct drm_panel *panel)
{
        mutex_lock(&panel_lock);
        list_add_tail(&panel->list, &panel_list);
        mutex_unlock(&panel_lock);
}
EXPORT_SYMBOL(drm_panel_add);

DRM 驱动模块入口函数为 rockchip_drm_init,位于 drivers/gpu/drm/rockchip/rockchip_drm_drv.c

#define ADD_ROCKCHIP_SUB_DRIVER(drv, cond) { \
        if (IS_ENABLED(cond) && \
            !WARN_ON(num_rockchip_sub_drivers >= MAX_ROCKCHIP_SUB_DRIVERS)) \
                rockchip_sub_drivers[num_rockchip_sub_drivers++] = &drv; \
}

static int __init rockchip_drm_init(void)
{
        int ret;

        num_rockchip_sub_drivers = 0;
#if IS_ENABLED(CONFIG_DRM_ROCKCHIP_VVOP)
        ADD_ROCKCHIP_SUB_DRIVER(vvop_platform_driver, CONFIG_DRM_ROCKCHIP_VVOP);
#else
        ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_ROCKCHIP_VOP);
        ...
        ADD_ROCKCHIP_SUB_DRIVER(rockchip_rgb_driver, CONFIG_ROCKCHIP_RGB);
        ...

#endif
        ret = platform_register_drivers(rockchip_sub_drivers,
                                        num_rockchip_sub_drivers);
        if (ret)
                return ret;

        ret = platform_driver_register(&rockchip_drm_platform_driver);
        if (ret)
                goto err_unreg_drivers;

        rockchip_gem_get_ddr_info();

        return 0;

err_unreg_drivers:
        platform_unregister_drivers(rockchip_sub_drivers,
                                    num_rockchip_sub_drivers);
        return ret;
}

在前面的内核配置中我们已经打开 CONFIG_ROCKCHIP_VOP CONFIG_ROCKCHIP_RGB,所以此步将 vop_platform_driver和rockchip_rgb_driver 驱动添加到数组rockchip_sub_drivers 
通过 platform_register_drivers 注册多个驱动和注册 rockchip_drm_platform_driver


平台驱动(Platform Driver) 和设备的匹配,这里是直接比较驱动和设备的字段,看看是否相同。rockchip-drm 平台驱动 rockchip-drm 是一个针对 Rockchip 芯片的 DRM 驱动,它主要用于管理和控制显示子系统。

static struct platform_driver rockchip_drm_platform_driver = {
        .probe = rockchip_drm_platform_probe,
        .remove = rockchip_drm_platform_remove,
        .shutdown = rockchip_drm_platform_shutdown,
        .driver = {
                .name = "rockchip-drm",       //此字段为device和driver匹配的最后一种方式
                .of_match_table = rockchip_drm_dt_ids,
                .pm = &rockchip_drm_pm_ops,
        },
};


of_match_table 用于设备树匹配,匹配设备树中 compatible = "rockchip,display-subsystem" 的设备节点:

static const struct of_device_id rockchip_drm_dt_ids[] = {
        { .compatible = "rockchip,display-subsystem", },
        { /* sentinel */ },
};

在第二步内核中有 platform 设备和 platform 驱动匹配,会调用到 platform_driver 里的成员 .probe,在这里就是 rockchip_drm_platform_probe 函数:

static int rockchip_drm_platform_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        struct component_match *match = NULL;
        int ret;

        ret = rockchip_drm_platform_of_probe(dev);
#if !IS_ENABLED(CONFIG_DRM_ROCKCHIP_VVOP)
        if (ret)
                return ret;
#endif

        match = rockchip_drm_match_add(dev);
        if (IS_ERR(match))
                return PTR_ERR(match);

        ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64));
        if (ret)
                goto err;

        ret = component_master_add_with_match(dev, &rockchip_drm_ops, match);
        if (ret < 0)
                goto err;

        return 0;
err:
        rockchip_drm_match_remove(dev);

        return ret;
}

ret = rockchip_drm_platform_of_probe(dev);: 校验display-subsystem设备节点的属性ports是否有效
match = rockchip_drm_match_add(dev);:构建一个带release函数的component_match
ret = component_master_add_with_match(dev, &rockchip_drm_ops, match);:向系统注册一个aggregate_device,触发执行rockchip_drm_ops.bind,即rockchip_drm_bind,该函数用于绑定设备并初始化DRM驱动


总结:

    • 设备树匹配:设备树中定义的面板节点通过设备树匹配机制与 panel-simple 驱动关联。内核启动时,rockchip-drm 平台驱动也会匹配设备树节点。驱动查找并解析设备树中的 ports 和 iommus 节点,以获取显示管道和内存管理单元的信息。
    • 面板驱动初始化:panel_simple_platform_probe 函数读取设备树中的面板描述信息,并通过 panel_simple_probe 函数进行面板初始化和注册。在此过程中,调用 drm_panel_init 和 drm_panel_add 函数将面板初始化并注册到 DRM 框架中,使其成为 DRM 子系统的一部分,供其他 DRM 组件使用。
    • 注册组件:rockchip-drm 使用 component_master_add_with_match 函数将其注册到组件框架中。通过设置 rockchip_drm_ops,定义了 bind 和 unbind 操作,这些操作将在组件初始化和注销时调用。
    • 添加子组件:各个子组件(例如 VOP、HDMI、MIPI DSI 等)使用 component_add 函数添加到组件框架中。每个子组件都有对应的 component_ops 结构体,定义其自身的 bind 和 unbind 操作。
    • 绑定组件:当 master 组件(即 rockchip-drm)匹配到所有子组件后,会调用其 bind 回调函数。在 bind 回调函数中,执行各个子组件的初始化和配置。
    • 注册到 DRM 核心:rockchip-drm 驱动在内核模块加载时通过 drm_dev_register 函数注册 DRM 设备。在此过程中,drm_dev_register 会调用 drm_minor_register 注册字符设备。drm_minor_register 函数负责创建字符设备并添加到 /dev/dri 目录下,最终创建设备节点,如 /dev/dri/card0。

    想了解更多具体内容,需自己进入源码目录(drivers/gpu/drm)阅读理解。