深入解析Loongson LSDC DRM驱动:从原理到实现

深入解析Loongson LSDC DRM驱动:从原理到实现

1. 引言:LSDC驱动与DRM子系统概述

在现代嵌入式系统中,图形显示功能扮演着越来越重要的角色。从简单的用户界面到复杂的多媒体应用,显示驱动的性能和稳定性直接影响着用户体验。Linux内核提供了一套强大的图形显示框架------ DRM (Direct Rendering Manager) ,它为用户空间应用程序提供了直接访问GPU硬件的接口,并负责管理显示模式设置 (KMS, Kernel Mode Setting)、内存管理 (GEM, Graphics Execution Manager) 等核心功能。

本文将深入探讨龙芯 (Loongson) 平台下的 LSDC (Loongson Display Controller) DRM驱动。LSDC驱动是龙芯系列处理器中显示控制器的核心软件组件,它负责初始化、配置和管理显示硬件,以实现图形输出。我们将从LSDC驱动的整体架构入手,详细解析其如何与Linux DRM子系统协同工作,如何通过设备树 (Device Tree) 进行硬件配置,以及驱动的注册、设备查找、HDMI显示流程和相关原理。通过本文,读者将对LSDC DRM驱动的内部机制有一个全面而深入的理解。

2. DRM子系统核心原理

Linux的DRM (Direct Rendering Manager) 子系统是现代图形栈的核心,它提供了一套统一的接口来管理图形硬件。DRM的核心是KMS (Kernel Mode Setting),它允许内核直接控制显示硬件的模式设置,包括分辨率、刷新率、时序等,从而避免了用户空间程序直接访问硬件可能带来的安全和稳定性问题。

2.1 DRM/KMS架构概览

DRM/KMS架构将显示输出抽象为一系列相互关联的组件,这些组件共同协作完成图像的渲染和显示。其主要组件包括:

  • Frame Buffer (帧缓冲区) :存储待显示图像数据的内存区域。
  • Plane (平面) :表示一个可以被扫描到显示器上的图像层。一个显示器可以有多个平面,例如主平面 (Primary Plane) 用于显示桌面,以及光标平面 (Cursor Plane) 用于显示鼠标指针。
  • CRTC (Cathode Ray Tube Controller) :负责从帧缓冲区读取像素数据,并根据时序信息将其发送给编码器。每个CRTC对应一个独立的显示输出通道,可以控制一个或多个显示器。
  • Encoder (编码器) :将CRTC输出的数字信号转换为特定显示接口(如HDMI、DVI、LVDS等)所需的格式。
  • Connector (连接器) :代表一个物理的显示输出端口,例如HDMI接口、VGA接口等。它负责检测显示器的连接状态,并获取显示器的EDID (Extended Display Identification Data) 信息。
  • Bridge (桥接器) :一个可选组件,通常用于连接外部显示控制器或转换器。它位于Encoder和Connector之间,用于处理复杂的信号转换或多路复用。
    这些组件通过管道 (Pipeline) 的方式连接起来,形成一个完整的显示路径: Frame Buffer -> Plane -> CRTC -> Encoder -> (Bridge) -> Connector -> Display 。

2.2 DRM核心组件详解 Plane (平面)

平面是DRM中用于管理图像缓冲区的抽象。每个平面都关联一个帧缓冲区,并可以独立地进行缩放、裁剪和位置调整。LSDC驱动通常会实现至少两个平面:

  • Primary Plane (主平面) :用于显示主图像内容,例如桌面、应用程序窗口等。

  • Cursor Plane (光标平面) :用于显示鼠标光标,通常具有较小的尺寸和独立的更新机制,以确保光标移动的流畅性。 CRTC (阴极射线管控制器)

    CRTC是显示管道的核心,它负责生成显示时序信号,并从帧缓冲区中读取像素数据。一个CRTC可以驱动一个或多个连接器,但同一时间只能输出一种模式。LSDC驱动会为每个独立的显示通道初始化一个CRTC。

    Encoder (编码器)

    编码器负责将CRTC输出的像素数据和时序信号转换为特定显示接口(如HDMI)所需的电信号。例如,一个HDMI编码器会将数字视频信号打包成HDMI协议格式,并通过物理接口发送出去。

    Connector (连接器)

    连接器代表了物理的显示输出端口。它的主要职责包括:

  • 热插拔检测 (Hotplug Detection) :检测显示器是否连接或断开。

  • EDID读取 (EDID Reading) :通过DDC (Display Data Channel) 接口读取显示器的EDID信息,其中包含了显示器支持的分辨率、刷新率等详细参数。

  • 模式验证 (Mode Validation) :根据EDID信息和硬件能力,验证CRTC请求的显示模式是否有效。 Bridge (桥接器)

    桥接器在LSDC驱动中可能扮演重要角色,特别是在需要连接外部显示设备或进行复杂信号处理时。例如,如果LSDC芯片内部的显示控制器需要通过一个外部芯片才能输出HDMI信号,那么这个外部芯片的驱动就会被实现为一个DRM Bridge。Bridge负责在Encoder和Connector之间进行信号的传递和转换。

3. HDMI显示原理简介

HDMI (High-Definition Multimedia Interface) 是一种全数字化的音视频接口技术,用于在各种设备(如显示器、电视、投影仪等)之间传输高质量的未压缩视频数据和压缩/未压缩音频数据。理解HDMI的基本工作原理对于理解LSDC驱动如何实现HDMI输出至关重要。

HDMI的核心传输技术是 TMDS (Transition Minimized Differential Signaling) 。TMDS是一种高速串行数据传输技术,它通过差分信号传输数据,具有抗干扰能力强、传输距离远等优点。

一个标准的HDMI接口通常包含以下几组TMDS差分对:

  • 3对数据通道 (Data Channels) :用于传输视频像素数据、音频数据以及辅助数据。每对数据通道以串行方式传输8位数据,通过10位编码(8b/10b编码)实现直流平衡和最小化转换,从而提高信号完整性。

  • 1对时钟通道 (Clock Channel) :用于传输像素时钟信号,同步数据通道的传输。接收端通过时钟通道恢复数据,确保数据传输的准确性。

    HDMI传输的数据内容主要包括:

  • 视频数据 (Video Data) :经过编码的像素数据,按照一定的时序(如行同步、场同步)进行传输。

  • 音频数据 (Audio Data) :可以是PCM格式的未压缩音频,也可以是Dolby Digital、DTS等压缩格式的音频。

  • 辅助数据 (Auxiliary Data) :包括控制信息、HDCP (High-bandwidth Digital Content Protection) 加密数据、以及EDID (Extended Display Identification Data) 等。EDID信息在显示器连接时由源设备读取,用于了解显示器支持的分辨率、刷新率、音频格式等能力。

    HDMI显示流程简述:

  1. 连接检测与EDID读取 :当HDMI线缆连接时,源设备(如LSDC控制器)会检测到连接,并通过DDC (Display Data Channel) 接口读取显示器的EDID信息。
  2. 模式协商 :源设备根据EDID信息和自身的硬件能力,选择一个最佳的显示模式(分辨率、刷新率)。
  3. 时序生成 :显示控制器(如LSDC内部的CRTC)根据选定的显示模式生成精确的视频时序信号,包括水平同步 (HSYNC)、垂直同步 (VSYNC)、数据使能 (DE) 等。
  4. 数据编码与传输 :视频像素数据、音频数据和控制数据被编码成TMDS信号,并通过HDMI接口的TMDS差分对传输到显示器。
  5. 显示 :显示器接收到TMDS信号后,解码数据,并根据时序信号将图像和声音显示出来。
    在LSDC驱动中,HDMI的实现将涉及到配置LSDC硬件的TMDS发送器、处理EDID读取、生成符合HDMI规范的视频时序,以及将DRM子系统中的视频流转换为HDMI协议数据流。

4. LSDC驱动与设备树的深度融合

在Linux嵌入式系统中,设备树 (Device Tree, DT) 已经成为描述硬件配置的标准方式。它将硬件信息从内核代码中分离出来,使得内核可以更通用,而硬件的特定配置则通过设备树文件 (DTS/DTB) 提供。LSDC DRM驱动充分利用了设备树的优势,通过解析设备树节点来获取其硬件资源和配置信息。

4.1 设备树在LSDC中的作用

对于LSDC驱动而言,设备树扮演着至关重要的角色,它定义了:

  • 硬件识别 :通过 compatible 属性,内核能够识别出特定的LSDC显示控制器硬件,并将其与对应的驱动程序关联起来。
  • 资源分配 :包括内存映射寄存器地址 ( reg 属性) 和中断号 ( interrupts 属性),这些是驱动程序与硬件交互的基础。
  • 显示拓扑 :描述显示控制器有哪些输出端口,以及这些端口可能连接到哪些外部桥接芯片或显示设备。
  • 特定配置 :一些LSDC特有的配置,例如是否支持某些功能、是否存在硬件缺陷等,也可以通过设备树属性进行传递。

4.2 compatible属性与芯片识别

compatible 属性是设备树中最关键的属性之一,它用于将设备节点与驱动程序进行匹配。在LSDC驱动中, lsdc_platform_drv.c 文件定义了一个 lsdc_dt_ids 数组,其中包含了LSDC支持的不同Loongson芯片的 compatible 字符串:

复制代码
// drivers/gpu/drm/lsdc/lsdc_platform_drv.c
static const struct lsdc_platform_desc dc_in_ls2k1000 = {
    .chip = LSDC_CHIP_2K1000,
    // ...
};
// ... similar structs for 2K0500, 2K0300

static const struct of_device_id lsdc_dt_ids[] = {
    { .compatible = "loongson,ls2k1000-dc", .data = &
    dc_in_ls2k1000, },
    { .compatible = "loongson,la2k0500-dc", .data = &
    dc_in_ls2k0500, },
    { .compatible = "loongson,la2k0300-dc", .data = &
    dc_in_ls2k0300, },
    { .compatible = "loongson,display-subsystem", }, /* must be the 
    last */
    {}
};

当内核启动时, platform_bus 会遍历设备树中的所有节点。如果一个节点的 compatible 属性与 lsdc_dt_ids 中的某个条目匹配,那么 lsdc_platform_probe 函数就会被调用,从而启动LSDC驱动的初始化过程。 data 字段则指向一个 lsdc_platform_desc 结构体,该结构体包含了特定芯片的显示能力描述,如最大分辨率、CRTC数量、硬件光标尺寸等。

4.3 资源定义:reg与interrupts

reg 属性定义了设备的内存映射寄存器地址和大小。LSDC驱动通过解析此属性来获取其硬件寄存器的基地址,以便进行读写操作。例如,在设备树中可能会看到类似以下配置:

复制代码
display-controller@xxxx0000 {
    compatible = "loongson,ls2k1000-dc";
    reg = <0x0 0xxxx0000 0x0 0x10000>; // 基地址和大小
    interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>; // 中断号
    // ...
};

interrupts 属性则定义了设备使用的中断号。LSDC驱动在 lsdc_platform_probe 函数中会使用 platform_get_resource 和 platform_get_irq 等函数来获取这些资源,并注册相应的中断处理程序。

4.4 输出端口与桥接:output-ports

现代显示控制器通常支持多个输出端口,并且可能需要通过外部桥接芯片连接到实际的显示器。设备树的 output-ports 节点用于描述这种复杂的显示拓扑结构。

复制代码
display-controller@xxxx0000 {
    // ...
    ports {
        #address-cells = <1>;
        #size-cells = <0>;

        port@0 { // 第一个输出端口
            reg = <0>;
            endpoint {
                remote-endpoint = <&hdmi_con_in>; // 连接到HDMI桥接芯
                片的输入端
            };
        };
    };
};

hdmi-bridge@yyyy0000 {
    compatible = "vendor,hdmi-bridge";
    reg = <0x0 0yyyy0000 0x0 0x1000>;
    // ...
    port@0 {
        reg = <0>;
        hdmi_con_in: endpoint {
            remote-endpoint = <&display_out_0>;
        };
    };
    port@1 {
        reg = <1>;
        hdmi_con_out: endpoint {
            remote-endpoint = <&hdmi_connector_in>; // 连接到HDMI连接
            器
        };
    };
};

hdmi-connector {
    compatible = "hdmi-connector";
    // ...
    port {
        hdmi_connector_in: endpoint {
            remote-endpoint = <&hdmi_con_out>;
        };
    };
};

在LSDC驱动中, lsdc_modeset_init 函数会检查 of_graph_is_present 来判断是否存在这种基于OF Graph的复杂连接。如果存在,它会调用 lsdc_attach_bridges 来遍历并连接这些桥接设备,从而构建完整的显示管道。这种机制使得LSDC驱动能够灵活地支持各种显示输出配置。

4.5 模块参数配置

除了设备树,LSDC驱动还支持通过模块参数进行一些运行时配置。这些参数通常用于调试或在特定场景下调整驱动行为。例如, lsdc_drv.c 中定义了多个模块参数:

复制代码
// drivers/gpu/drm/lsdc/lsdc_drv.c
int lsdc_modeset = -1;
module_param_named(modeset, lsdc_modeset, int, 0644);

int lsdc_shadowfb = 0;
module_param_named(shadowfb, lsdc_shadowfb, int, 0644);

// ... 其他参数,如 lsdc_ddc0, lsdc_dvo0 等

这些参数允许用户在加载LSDC模块时,通过命令行或modprobe配置,例如: modprobe lsdc modeset=0 可以禁用模式设置功能。驱动在 lsdc_mode_config_init 函数中会读取并处理这些模块参数。

5. LSDC驱动注册与设备查找流程

LSDC驱动作为Linux内核中的一个平台设备驱动,其注册和设备查找遵循标准的Linux驱动模型。这个过程确保了驱动程序能够正确地识别并绑定到对应的硬件设备上,并进行初始化。

5.1 平台驱动注册

LSDC驱动通过 platform_driver 机制向内核注册自身。在 lsdc_platform_drv.c 文件中,定义了 lsdc_platform_driver 结构体,其中包含了驱动的名称、设备匹配ID列表以及核心的 probe 和 remove 回调函数。

复制代码
// drivers/gpu/drm/lsdc/lsdc_platform_drv.c
static struct platform_driver lsdc_platform_driver = {
    .probe      = lsdc_platform_probe,
    .remove     = lsdc_platform_remove,
    .driver     = {
        .name   = "lsdc",
        .of_match_table = lsdc_dt_ids, // 关键:设备树匹配表
    },
};

module_platform_driver(lsdc_platform_driver);

module_platform_driver(lsdc_platform_driver) 宏会在模块加载时(或内核启动时静态编译)自动调用 platform_driver_register 函数,将 lsdc_platform_driver 注册到内核的平台总线 (platform bus) 上。

5.2 lsdc_platform_probe函数解析

当内核发现一个平台设备,其设备树 compatible 属性与 lsdc_dt_ids 中的某个条目匹配时, lsdc_platform_probe 函数就会被调用。这是LSDC驱动初始化的主要入口点。

lsdc_platform_probe 函数的主要步骤如下:

  1. 设置DMA掩码 :

    复制代码
    // drivers/gpu/drm/lsdc/lsdc_platform_drv.c:100
    ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
    if (ret) {
        DRM_ERROR("dma_set_mask_and_coherent failed\n");
        return ret;
    }

    这确保了DMA操作能够正确进行,通常设置为32位掩码。

  2. 设备树匹配与芯片描述获取 :

    复制代码
    // drivers/gpu/drm/lsdc/lsdc_platform_drv.c:105
    of_id = of_match_device(lsdc_dt_ids, dev);
    if (of_id)
        desc = of_id->data;
    else
        desc = lsdc_detect_platform_chip(pdev); // Fallback for 
        non-DT cases
    if (!desc) {
        DRM_ERROR("failed to detect platform chip\n");
        return -ENODEV;
    }

    of_match_device 函数根据设备树节点与 lsdc_dt_ids 进行匹配,如果匹配成功,则从 of_id->data 中获取特定芯片的 lsdc_platform_desc 结构体。这包含了芯片的显示能力信息。如果是非设备树场景,则会尝试通过 lsdc_detect_platform_chip 进行检测。

  3. 分配 lsdc_device 结构体 :

    复制代码
    // drivers/gpu/drm/lsdc/lsdc_platform_drv.c:120
    ldev = kzalloc(sizeof(struct lsdc_device), GFP_KERNEL);
    if (!ldev)
        return -ENOMEM;
    ldev->dev = dev;
    ldev->desc = desc;

    lsdc_device 是LSDC驱动的核心私有数据结构,它包含了DRM设备、平台设备指针、芯片描述等关键信息。

  4. 获取硬件资源 :

    复制代码
    // drivers/gpu/drm/lsdc/lsdc_platform_drv.c:129
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    ldev->regs = devm_ioremap_resource(dev, res);
    if (IS_ERR(ldev->regs)) {
        ret = PTR_ERR(ldev->regs);
        goto err_free_ldev;
    }
    
    ldev->irq = platform_get_irq(pdev, 0);
    if (ldev->irq < 0) {
        ret = ldev->irq;
        goto err_free_ldev;
    }

    通过 platform_get_resource 获取设备树中定义的内存映射寄存器区域 ( IORESOURCE_MEM ),并使用 devm_ioremap_resource 将其映射到内核虚拟地址空间。同时, platform_get_irq 获取中断号。

  5. DRM设备初始化 :

    复制代码
    // drivers/gpu/drm/lsdc/lsdc_platform_drv.c:150
    ret = drm_dev_init(&ldev->ddev, &lsdc_drm_driver, dev);
    if (ret) {
        DRM_ERROR("Failed to init DRM device: %d\n", ret);
        goto err_free_ldev;
    }

    这是将LSDC设备注册为DRM设备的关键一步。 drm_dev_init 函数会初始化 ldev->ddev ( drm_device 结构体),并将其与 lsdc_drm_driver (定义在 lsdc_drv.c 中)关联起来。

  6. 设置驱动私有数据 :

    复制代码
    // drivers/gpu/drm/lsdc/lsdc_platform_drv.c:155
    platform_set_drvdata(pdev, ldev);

    将 lsdc_device 结构体作为平台设备的私有数据存储起来,方便后续通过 platform_get_drvdata 获取。

  7. DRM模式配置初始化 :

    复制代码
    // drivers/gpu/drm/lsdc/lsdc_platform_drv.c:157
    ret = lsdc_mode_config_init(ldev);
    if (ret) {
        DRM_ERROR("Failed to init mode config: %d\n", ret);
        goto err_drm_dev_unregister;
    }

    lsdc_mode_config_init 是LSDC驱动中进行DRM模式设置相关初始化(如CRTC、Plane、Encoder、Connector的创建和配置)的核心函数,我们将在下一节详细讨论。

  8. DRM设备注册 :

    复制代码
    // drivers/gpu/drm/lsdc/lsdc_platform_drv.c:162
    ret = drm_dev_register(&ldev->ddev, 0);
    if (ret) {
        DRM_ERROR("Failed to register DRM device: %d\n", ret);
        goto err_mode_config_cleanup;
    }

    drm_dev_register 函数将DRM设备注册到内核,使其对用户空间可见,并创建对应的 /dev/dri/cardX 设备节点。

5.3 DRM设备初始化:drm_dev_init与lsdc_mode_config_init

drm_dev_init 是DRM核心提供的通用初始化函数,它会设置 drm_device 结构体的基本字段,并将其与DRM驱动( lsdc_drm_driver )关联。

而 lsdc_mode_config_init (定义在 lsdc_drv.c 中) 则是LSDC驱动特有的模式设置初始化逻辑:

  1. 处理模块参数 :

    复制代码
    // drivers/gpu/drm/lsdc/lsdc_drv.c:500 (大致位置)
    if (lsdc_modeset == 0) {
        DRM_INFO("DRM modesetting on lsdc disabled.\n");
        return 0;
    }

    根据 lsdc_modeset 等模块参数决定是否启用模式设置功能。

  2. 初始化 drm_mode_config :

    复制代码
    // drivers/gpu/drm/lsdc/lsdc_drv.c:510 (大致位置)
    drm_mode_config_init(ddev);
    ddev->mode_config.funcs = &lsdc_mode_funcs;
    ddev->mode_config.min_width = 0;
    ddev->mode_config.min_height = 0;
    ddev->mode_config.max_width = ldev->desc->max_width;
    ddev->mode_config.max_height = ldev->desc->max_height;
    ddev->mode_config.prefer_shadow_fb = lsdc_shadowfb;
    // ... 设置光标尺寸等

    drm_mode_config_init 初始化DRM模式配置结构体,并设置LSDC驱动特有的回调函数集 lsdc_mode_funcs 。同时,根据芯片描述设置最大/最小分辨率、是否使用shadow framebuffer等。

  3. 调用 lsdc_modeset_init :

    复制代码
    // drivers/gpu/drm/lsdc/lsdc_drv.c:530 (大致位置)
    ret = lsdc_modeset_init(ddev, total_crtcs);
    if (ret) {
        DRM_ERROR("Failed to init modeset: %d\n", ret);
        goto err_mode_config_cleanup;
    }

    lsdc_modeset_init 是真正创建和初始化DRM核心组件(CRTC、Plane、Encoder、Connector)的地方,它会根据设备树或硬编码配置来构建显示管道。

  4. 注册中断处理程序 :

    复制代码
    // drivers/gpu/drm/lsdc/lsdc_drv.c:540 (大致位置)
    ret = devm_request_threaded_irq(dev, ldev->irq, lsdc_irq_handler,
                                    lsdc_irq_thread_handler, 
                                    IRQF_SHARED,
                                    dev_name(dev), ldev);
    if (ret) {
        DRM_ERROR("Failed to request irq: %d\n", ret);
        goto err_mode_config_cleanup;
    }

    注册LSDC显示控制器的中断处理函数,用于处理垂直同步 (VSYNC)、错误等事件。

    至此,LSDC驱动的注册和核心DRM设备初始化流程已经完成,硬件资源被正确获取,DRM模式配置框架也已搭建完毕,等待进一步的模式设置操作。

6. HDMI显示数据传输与流程实现

在LSDC驱动中,HDMI显示的数据传输和流程实现是DRM模式设置 (KMS) 的核心。这涉及到CRTC、Encoder、Connector等DRM组件的创建、配置和相互连接,最终将图像数据通过HDMI接口输出到显示器。

6.1 模式设置初始化:lsdc_modeset_init

lsdc_modeset_init 函数(位于 lsdc_drv.c )是LSDC驱动中负责构建显示管道的关键函数。它在 lsdc_mode_config_init 中被调用,主要完成以下任务:

  1. 检查OF Graph :

    复制代码
    // drivers/gpu/drm/lsdc/lsdc_drv.c (大致位置)
    if (of_graph_is_present(ddev->dev->of_node)) {
        ret = lsdc_attach_bridges(ddev);
        if (ret) {
            DRM_ERROR("Failed to attach bridges: %d\n", ret);
            return ret;
        }
    } else {
        ret = lsdc_create_outputs(ddev, num_crtc);
        if (ret) {
            DRM_ERROR("Failed to create outputs: %d\n", ret);
            return ret;
        }
    }

    该函数首先检查设备树中是否存在OF Graph(Open Firmware Graph)来描述复杂的显示拓扑。

    • 如果存在,说明显示控制器可能连接了外部桥接芯片,此时会调用 lsdc_attach_bridges 函数来遍历并连接这些桥接设备。 lsdc_bridge.c 文件包含了桥接设备的实现逻辑。
    • 如果不存在,则假定是简单的显示输出,直接调用 lsdc_create_outputs 函数来创建CRTC、Encoder和Connector。
  2. 初始化像素PLL :

    复制代码
    // drivers/gpu/drm/lsdc/lsdc_drv.c (大致位置)
    ret = lsdc_init_pix_pll(ldev);
    if (ret) {
        DRM_ERROR("Failed to init pixel PLL: %d\n", ret);
        return ret;
    }

    像素PLL (Phase-Locked Loop) 负责生成精确的像素时钟,这是驱动显示器所必需的。 lsdc_pll.c 文件包含了PLL的配置和控制逻辑。

  3. 创建和初始化DRM组件 : lsdc_modeset_init 会为每个显示通道(CRTC)创建和初始化相应的DRM组件:

    • Plane (平面) :

      复制代码
      // drivers/gpu/drm/lsdc/lsdc_plane.c (大致位置)
      lsdc_plane_init(ddev, ldev, i, DRM_PLANE_TYPE_PRIMARY); // 主平
      面
      lsdc_plane_init(ddev, ldev, i, DRM_PLANE_TYPE_CURSOR);  // 光标
      平面
      ```LSDC驱动通常会为每个CRTC创建主平面和光标平面。 lsdc_plane.c 文件定义了平面的初始化和操作函数。
    • CRTC (阴极射线管控制器) :

      复制代码
      // drivers/gpu/drm/lsdc/lsdc_crtc.c (大致位置)
      lsdc_crtc_init(ddev, ldev, i);
      ```为每个显示通道初始化一个CRTC。 lsdc_crtc.c 文件包含了CRTC的初始化、模式设置、原子操作等函数。

6.2 输出设备创建:lsdc_create_outputs

lsdc_create_outputs 函数(位于 lsdc_drv.c )负责创建和连接Encoder和Connector。它会遍历LSDC显示控制器支持的输出端口,并为每个端口创建相应的DRM组件。

复制代码
// drivers/gpu/drm/lsdc/lsdc_drv.c (大致位置)
static int lsdc_create_outputs(struct drm_device *ddev, unsigned 
int num_crtc)
{
    struct lsdc_device *ldev = to_lsdc_device(ddev);
    int i, ret;

    for (i = 0; i < LSDC_MAX_OUTPUTS; i++) { // 遍历所有可能的输出
        if (!ldev->desc->output_available[i])
            continue; // 如果该输出不可用,则跳过

        // ... 处理克隆模式 (cloning)
        // ... 创建 lsdc_connector
        ret = lsdc_connector_init(ddev, ldev, i);
        if (ret) {
            DRM_ERROR("Failed to create connector %d: %d\n", i, 
            ret);
            return ret;
        }

        // ... 创建 lsdc_encoder
        ret = lsdc_encoder_init(ddev, ldev, i);
        if (ret) {
            DRM_ERROR("Failed to create encoder %d: %d\n", i, ret);
            return ret;
        }

        // ... 连接 encoder 和 connector
        drm_connector_attach_encoder(ldev->connectors[i], 
        ldev->encoders[i]);
    }
    return 0;
}

该函数的主要步骤包括:

  1. 遍历输出端口 :根据 lsdc_device 中的芯片描述 ( ldev->desc ),检查哪些输出端口是可用的。
  2. 处理克隆模式 :如果支持克隆模式(即多个显示器显示相同内容),则会进行相应的配置。
  3. 创建 lsdc_connector :调用 lsdc_connector_init 函数(位于 lsdc_connector.c )来初始化一个DRM连接器。这个连接器负责检测显示器连接、读取EDID等。
  4. 创建 lsdc_encoder :调用 lsdc_encoder_init 函数(位于 lsdc_encoder.c )来初始化一个DRM编码器。这个编码器负责将CRTC的数字信号转换为HDMI所需的TMDS信号。
  5. 连接Encoder和Connector :使用 drm_connector_attach_encoder 将创建的Encoder和Connector关联起来,形成显示管道的一部分。

6.3 数据传输路径与组件协作

一旦所有的DRM组件都被创建和连接,HDMI显示的数据传输路径就建立起来了。当用户空间应用程序请求设置显示模式或显示图像时,DRM子系统会协调这些组件协同工作:

  1. 用户空间请求 :应用程序通过DRM API(如 ioctl(DRM_IOCTL_MODE_SETCRTC) )请求设置显示模式或更新帧缓冲区。
  2. DRM核心处理 :DRM核心验证请求的合法性,并调用LSDC驱动中注册的回调函数。
  3. CRTC模式设置 :LSDC驱动的CRTC回调函数(在 lsdc_crtc.c 中实现)会根据请求的模式配置LSDC硬件的CRTC,包括设置分辨率、刷新率、时序参数等。
  4. Plane数据扫描 :CRTC从主平面(或光标平面)关联的帧缓冲区中读取像素数据。
  5. Encoder编码 :CRTC输出的数字视频信号被发送到LSDC的Encoder(在 lsdc_encoder.c 中实现),Encoder将其编码为HDMI TMDS信号。
  6. Bridge处理 (如果存在) :如果显示管道中存在Bridge(在 lsdc_bridge.c 中实现),Encoder的输出会先经过Bridge进行处理或转换。
  7. Connector输出 :最终,TMDS信号通过Connector(物理HDMI端口)输出到外部显示器。
  8. 显示器接收与显示 :显示器接收到HDMI信号后,解码并显示图像。
    整个过程是一个高度协调的流程,LSDC驱动通过对LSDC硬件寄存器的精确控制,结合DRM子系统的抽象和管理,实现了从帧缓冲区到HDMI显示器的完整数据传输和显示功能。

7. 总结与展望

本文对龙芯LSDC DRM驱动进行了全面而深入的剖析,从其在Linux图形栈中的定位,到DRM/KMS子系统的核心原理,再到HDMI显示技术的基础知识,以及LSDC驱动如何将这些理论转化为实际的硬件控制。

我们详细探讨了LSDC驱动与设备树的紧密结合,设备树通过 compatible 属性实现芯片识别,通过 reg 和 interrupts 定义硬件资源,并通过 output-ports 描述复杂的显示拓扑结构。同时,模块参数为驱动提供了灵活的运行时配置选项。

在驱动注册和设备查找流程方面,我们解析了 platform_driver 的注册机制,以及 lsdc_platform_probe 函数作为驱动初始化入口的关键作用。该函数负责获取硬件资源、初始化DRM设备,并最终调用 lsdc_mode_config_init 来启动DRM模式设置的配置。

最后,我们深入分析了HDMI显示数据传输与流程的实现,重点阐述了 lsdc_modeset_init 和 lsdc_create_outputs 函数如何协同工作,创建并连接DRM的Plane、CRTC、Encoder和Connector等核心组件,从而构建完整的显示管道,实现从帧缓冲区到HDMI显示器的图像输出。

通过对LSDC DRM驱动的深入理解,我们可以看到Linux图形子系统的强大和灵活性。它提供了一套标准化的接口和抽象层,使得硬件厂商能够高效地开发和集成其显示控制器驱动。未来,随着显示技术的发展,如更高分辨率、更高刷新率、HDR、VR/AR等,DRM子系统和LSDC这类驱动也将持续演进,以适应新的挑战和需求。

希望本文能为读者提供一个清晰的LSDC DRM驱动学习路径,并对Linux图形驱动开发有更深层次的理解。

相关推荐
用户805533698032 小时前
嵌入式Linux模块学习——`insmod` 底层全流程解剖:从用户命令到内核内存
linux
aningx2 小时前
开机自启modprobe -r kvm_intel
linux
D4c-lovetrain2 小时前
linux实战之多配置部署(ansible、nginx、keepalived、dhcp、dns多元化操作)
linux·运维·服务器
我爱学习好爱好爱2 小时前
Ansible force_handlers delegate委托 playbook语法格式 template模块
linux·运维·ansible
cpp_learners2 小时前
Linux ARM架构 使用 linuxdeployqt 打包QT程序
linux·arm开发·qt
2401_827499992 小时前
python项目实战07-DeepSeek调用测试(本地部署)
linux·运维·服务器
longxibo2 小时前
【Ubuntu datasophon1.2.1 二开之九:验证离线数据入湖】
大数据·linux·运维·ubuntu
似水এ᭄往昔2 小时前
【Linux】--命令行参数和环境变量
linux·运维·服务器
linux修理工2 小时前
在Debian上安装桌面环境并启用远程登录
linux·运维·服务器