Linux显示(三):DRM子系统(以及LCDC/Panel/Backlight驱动)
关键词:DRM、CRTC、Plane、Framebuffer、Encoder、Connector、Bridge、Panel、Backlight、GEM、TTM、KMS等等。
DRM(Direct Rendering Manager)是Linux内核中负责与显卡交互的管理架构,用户空间很方便的利用DRM提供的API,实现3D渲染、视频解码和GPU计算等工作。
基本概念 | 说明 |
---|---|
DRM(Direct Rendering Manager) |
DRM作为Linux负责显示的子系统,负责和显示控制器/GPU交互,对用户提供显示、3D渲染等操作。 《Linux GPU Driver Developer’s Guide》提供了对Linux kernel DRM描述,包括内存管理TTM/GEM、KMS、各种抽象组件、DRM fbdev、libdrm等等。 |
TTM(Transplation Table Manager) |
参考《DRM Memory Management — TTM》。 |
GEM(Graphics Execution Manager) |
DRM下buffer管理和分配,类似ION、DMA BUFFER。 |
KMS(Kernel Mode Setting) | Kernel Mode Setting (KMS) — The Linux Kernel documentation |
Framebuffer |
Framebuffer是CRTC输出的像素源的抽象内存。 参考《Kernel Mode Setting (KMS) — Framebuffer Abstraction》,包括Framebuffer说明、Framebuffer数据结构、Framebuffer相关API。 《Mode Setting Helper Functions — Framebuffer GEM Helper Reference》使用GEM给Framebuffer分配内存。 《Mode Setting Helper Functions — Framebuffer CMA Helper Functions Reference》使用CMA给Framebuffer分配内存。 |
Plane |
图层,SoC内部VOP模块win图层的抽象。 参考《Kernel Mode Setting (KMS) — Plane Abstraction》,包括Plane说明、数据结构、API。 |
CRTC(Cathode Ray Tube Controller) |
CRTC(阴极射线管控制器)即显示控制器,内部VOP模块或者VOP2中Video Port的抽象 参考《Kernel Mode Setting (KMS) — CRTC Abstraction》,包括CRTC功能简单说明、CRTC相关数据结构、CRTC相关API说明。 |
Encoder |
输出显示数据编码器,指的是RGB、LVDS、DSI、eDP、DP、HDMI、CVBS、VGA等格式编码。 参考《Kernel Mode Setting (KMS) — Encoder Abstraction》,包括Enncoder说明、数据结构、API。 |
Connector |
连接器,指的是encoder和panel之间交互的接口部分。比如RGB、DSI、HDMI等引脚。 参考《Kernel Mode Setting (KMS) — Connector Abstraction》,包括Connector的说明、数据结构、API。 |
Bridge |
桥接设备,一般用于注册Encoder后面另外再转接芯片,如DSI2HDMI转换芯片。 参考《Mode Setting Helper Functions — Bridge》,包括Bridge的说明、数据结构、API。 |
Panel |
泛指屏幕,各种LCD显示设备的抽象。 参考《Mode Setting Helper Functions — Panel Helper Reference》,包括Panel说明、数据结构、API。 《Mode Setting Helper Functions — Panel Self Refresh Helper Reference》支持Panel自刷新功能的API。 |
Backlight | 作为Panel背光设备。 |
Vertical Blanking |
Vertical Blanking是帧同步功能,避免出现泪痕效应。 参考《Kernel Mode Setting (KMS) — Vertical Blanking》,包括Vblank的说明、数据结构、API。 |
fbdev |
DRM下fbdev是基于DRM实现fb设备,用户空间可以通过访问Framebuffer子系统到DRM子系统对显示控制器进行操作。 参考《Mode Setting Helper Functions — fbdev Helper Functions Reference》,包括fbdev说明、数据结构、API。 |
Kernel clients |
运行在Kernel内部的DRM client,比如fbdev。 参考《Kernel clients — Kernel clients》,包括Kernel clients的说明、数据结构、API等。 |
libdrm |
DRM设备通过ioctl导出一系列命令到用户空间。libdrm对DRM设备导出到用空间的接口进行封装,这些接口包括:memory mapping, context management, DMA operations, AGP management, vblank control, fence management, memory management, and output management。 参考《Userland interfaces》。 |
可以将Linux下DRM系统分为3部分:
- DRM相关驱动,可以是LCDC驱动、Panel驱动、GPU驱动。
- DRM Core,负责给底层驱动提供功能,对用户空间导出各种接口。
- libdrm等上层应用,负责GUI框架、3D调用、调用底层显示控制和GPU功能等等。
1 DRM/LCD配置
DRM相关的设置有:DRM使能、显示控制器驱动、背光驱动、Framebuffer驱动等。
Device Drivers
Graphics support
->Direct Rendering Manager (XFree86 4.1.0 and higher DRI support)
->Insert extra checks and debug info into the DRM range managers
->Enable legacy fbdev support for your modesetting driver--支持基于DRM的模拟fbdev设备。
->Overallocation of the fbdev buffer
->DRM Support for STMicroelectronics SoC Series--STM32 LCDC驱动。 ->STMicroelectronics specific extensions for Synopsys MIPI DSI ->Display Panels
->support for simple panels--包含一个regulator和gpio上电,以及一个backlight的DRM panel模板驱动。 ->Display Interface Bridges ---> ->Frame buffer Devices--Framebuffer驱动。
->Support for frame buffer devices
->ARM PrimeCell PL110 support ->Backlight & LCD device support
->Lowlevel Backlight controls--背光驱动。
->Generic PWM based Backlight Driver ->Console display driver support--基于Framebuffer的console。
1.1 DRM子系统初始化
drm模块进行DRM子系统初始化:
- 创建drm类。
- 创建version节点。
- 创建dri debugfs。
- 注册drm字符设备。
- 创建debug、edid_fixup、timestamp_precision_usec、vblankoffdelay等参数。
drm_core_init
->drm_connector_ida_init
->drm_sysfs_init
->class_create--创建drm类。
->class_create_file--创建/sys/class/drm/version节点。
->debugfs_create_dir--创建/sys/kernel/debug/dri目录。
->register_chrdev--注册drm chrdev设备,文件操作函数集为drm_stub_fops。
drm_kms_helper模块初始化:
drm_kms_helper_init
drm_fb_helper_modinit--宏定义满足则等待fbcon模块进行初始化。
drm_dp_aux_dev_init--创建drm_dp_aux_dev类,并注册aux设备。
模块参数包括:dp_aux_i2c_speed_khz、dp_aux_i2c_transfer_size、drm_fbdev_overalloc、fbdev_emulation、poll。
2 DRM文件
drivers/gpu ├── drm │ ├── bridge │ │ ├── panel.c │ │ ├── synopsys │ │ │ ├── dw-mipi-dsi.c--Synopsys DW MIPI DSI host驱动。 │ ├── drm_agpsupport.c │ ├── drm_atomic.c │ ├── drm_atomic_helper.c │ ├── drm_atomic_state_helper.c │ ├── drm_atomic_uapi.c │ ├── drm_auth.c │ ├── drm_blend.c │ ├── drm_bridge.c--bridge添加、删除、附着、去附着等操作。 │ ├── drm_bufs.c │ ├── drm_cache.c │ ├── drm_client.c │ ├── drm_client_modeset.c │ ├── drm_color_mgmt.c │ ├── drm_connector.c │ ├── drm_context.c │ ├── drm_crtc.c--CRTC初始化、注册、模式设置等。 │ ├── drm_crtc_helper.c │ ├── drm_damage_helper.c │ ├── drm_debugfs.c--DRM debugfs初始化,以及创建connector、edid、crtc等调试节点。 │ ├── drm_debugfs_crc.c │ ├── drm_dma.c │ ├── drm_dp_aux_dev.c--DRM DP Aux设备创建和处理函数。 │ ├── drm_dp_cec.c │ ├── drm_dp_dual_mode_helper.c │ ├── drm_dp_helper.c │ ├── drm_dp_mst_topology.c │ ├── drm_drv.c │ ├── drm_dsc.c │ ├── drm_dumb_buffers.c │ ├── drm_edid.c │ ├── drm_edid_load.c │ ├── drm_encoder.c--encoder初始化、去初始化、注册、注销等。 │ ├── drm_encoder_slave.c │ ├── drm_fb_cma_helper.c--DRM Framebuffer使用CMA分配内存。 │ ├── drm_fb_helper.c--DRM Framebuffer创建、操作函数集、配置等。 │ ├── drm_file.c │ ├── drm_flip_work.c │ ├── drm_format_helper.c │ ├── drm_fourcc.c │ ├── drm_framebuffer.c--基于DRM的Framebuffer的初始化、释放、ioctl等操作。 │ ├── drm_gem.c │ ├── drm_gem_cma_helper.c │ ├── drm_gem_framebuffer_helper.c │ ├── drm_gem_shmem_helper.c │ ├── drm_gem_vram_helper.c │ ├── drm_hashtab.c │ ├── drm_hdcp.c │ ├── drm_ioc32.c │ ├── drm_ioctl.c--DRM设备用于空间ioctl命令对应实现。 │ ├── drm_irq.c │ ├── drm_kms_helper_common.c--创建drm_kms_helper模块,处理fdcon模块请求和dp auxdev创建。 │ ├── drm_lease.c │ ├── drm_legacy_misc.c │ ├── drm_lock.c │ ├── drm_memory.c │ ├── drm_mipi_dbi.c │ ├── drm_mipi_dsi.c │ ├── drm_mm.c │ ├── drm_mode_config.c │ ├── drm_mode_object.c │ ├── drm_modes.c │ ├── drm_modeset_helper.c │ ├── drm_modeset_lock.c │ ├── drm_of.c--处理DRM的DTS属性。 │ ├── drm_panel.c--panel的初始化、添加、附着、使能以及对应反向操作等。 │ ├── drm_panel_orientation_quirks.c │ ├── drm_pci.c │ ├── drm_plane.c │ ├── drm_plane_helper.c │ ├── drm_prime.c │ ├── drm_print.c │ ├── drm_probe_helper.c--探测connector的帮助函数。 │ ├── drm_property.c │ ├── drm_rect.c │ ├── drm_scatter.c │ ├── drm_scdc_helper.c │ ├── drm_self_refresh_helper.c │ ├── drm_simple_kms_helper.c │ ├── drm_syncobj.c │ ├── drm_sysfs.c │ ├── drm_trace_points.c │ ├── drm_vblank.c │ ├── drm_vma_manager.c │ ├── drm_vm.c │ ├── drm_vram_helper_common.c │ ├── drm_vram_mm_helper.c │ ├── drm_writeback.c │ ├── Makefile │ ├── stm │ │ ├── drv.c--STM32 LTDC platform_driver驱动。 │ │ ├── dw_mipi_dsi-stm.c--STM32 DSI platform_driver驱动。 │ │ ├── ltdc.c--STM32 LTDC底层驱动主题函数实现。
│ ├── panel
│ │ ├── panel-simple.c--Panel驱动程序。
3 DRM数据结构和API
3.1 DRM数据结构
struct drm_device是一个虚拟的设备,可以将DRM KMS各种组件关联到设备上。struct drm_driver是一类DRM设备的驱动程序。
struct drm_framebuffer表示一个提供给CRTC的抽象内存对象。
struct drm_framebuffer { struct drm_device *dev; struct list_head head; struct drm_mode_object base; char comm[TASK_COMM_LEN]; const struct drm_format_info *format; const struct drm_framebuffer_funcs *funcs; unsigned int pitches[4]; unsigned int offsets[4]; uint64_t modifier; unsigned int width; unsigned int height; int flags; int hot_x; int hot_y; struct list_head filp_head; struct drm_gem_object *obj[4]; };
struct drm_plane表示一个输入到CRTC的图像源,上游是drm_framebuffer,下游是drm_crtc。
struct drm_plane { struct drm_device *dev; struct list_head head; char *name; struct drm_modeset_lock mutex; struct drm_mode_object base; uint32_t possible_crtcs; uint32_t *format_types; unsigned int format_count; bool format_default; uint64_t *modifiers; unsigned int modifier_count; struct drm_crtc *crtc;--plane的下游。 struct drm_framebuffer *fb;--plane的上游。 struct drm_framebuffer *old_fb; const struct drm_plane_funcs *funcs; struct drm_object_properties properties; enum drm_plane_type type; unsigned index; const struct drm_plane_helper_funcs *helper_private; struct drm_plane_state *state; ... };
struct drm_crtc表示整个显示流,从一个或多个drm_plane接收像素数据,然后将其融合输出到一个或多个drm_encoder。
struct drm_crtc {
struct drm_device *dev;
struct device_node *port;
struct list_head head;
char *name;
struct drm_modeset_lock mutex;
struct drm_mode_object base;
struct drm_plane *primary;
struct drm_plane *cursor;
unsigned index;
int cursor_x;
int cursor_y;
bool enabled;
struct drm_display_mode mode;
struct drm_display_mode hwmode;
int x;
int y;
const struct drm_crtc_funcs *funcs;
...
};
struct drm_encoder表示CRTC和Connector之间的连接单元,从drm_crtc获取像素数据,转换成适合附着其上的drm_bridge。
struct drm_encoder { struct drm_device *dev; struct list_head head; struct drm_mode_object base; char *name; int encoder_type; unsigned index; uint32_t possible_crtcs; uint32_t possible_clones; struct drm_crtc *crtc;--Encoder上游CRTC。 struct drm_bridge *bridge;--Encoder下游。 const struct drm_encoder_funcs *funcs; const struct drm_encoder_helper_funcs *helper_private; };
struct drm_connector将Encoder输出的像素数据传递给Panel。
struct drm_connector { struct drm_device *dev; struct device *kdev; struct device_attribute *attr; struct list_head head; struct drm_mode_object base; char *name; ... struct drm_encoder *encoder;--Connector上游。 ... };
struct drm_bridge挂在Encoder后端,其后端可以是Connector或者Bridge。
struct drm_bridge { struct drm_device *dev; struct drm_encoder *encoder;--Bridge上游。 struct drm_bridge *next; #ifdef CONFIG_OF struct device_node *of_node; #endif struct list_head list; const struct drm_bridge_timings *timings; const struct drm_bridge_funcs *funcs; void *driver_private; };
struct drm_panel表示一个显示设备,作为DRM显示链路的末端,其上游是Connector。
struct drm_panel { struct drm_device *drm; struct drm_connector *connector;--Panel上游。 struct device *dev; const struct drm_panel_funcs *funcs; struct list_head list; };
struct drm_client_dev表示DRM client设备。
struct drm_client_dev { struct drm_device *dev; const char *name; struct list_head list; const struct drm_client_funcs *funcs; struct drm_file *file; struct mutex modeset_mutex; struct drm_mode_set *modesets; };
常见的KMS mode-setting pipeline如下:
3.2 DRM API
3.2.1 DRM Device
《DRM Internals》中介绍了drm_device和drm_driver数据接口和API函数,以及drm_driver示例。
drm_dev_alloc()主要分配并初始化drm_device结构体:
drm_dev_alloc
drm_dev_init
drm_minor_alloc
drm_sysfs_minor_alloc--分配/dev/dri/card0设备对应的结构体。
drm_gem_init--分配并初始化DRM设备的GEM。
drm_dev_register()是DRM Core核心:
- 创建子设备的debugfs、sysfs等节点。
- 创建/dev/dri/cardX设备。
- 进行plane/crtc/encoder/connector的late_register,以及创建connector的sysfs和debugfs节点。
drm_dev_register
->drm_minor_register
->drm_debugfs_init
->debugfs_create_dir--创建/sys/kernel/debug/dri/0子目录。
->drm_debugfs_list--创建类似/sys/kernel/debug/dri/0/name、clients、gem_names子节点。
->drm_framebuffer_debugfs_init--创建/sys/kernel/debug/dri/0/framebuffer,输出framebuffer信息。
->drm_client_debugfs_init--创建类似/sys/kernel/debug/dri/0/internal_clients,输出DRM设备的客户列表。
->device_add--创建/dev/dri/card0设备。
->create_compat_control_link
->drm_modeset_register_all
->drm_plane_register_all--遍历plane的late_register()函数。
->drm_crtc_register_all--遍历crtc的late_register()函数。
->drm_encoder_register_all--遍历encoder的late_register()函数。
->drm_connector_register_all
->drm_connector_register--创建connector的sysfs、debugfs节点,并调用late_register()函数。
->drm_sysfs_connector_add
->device_create_with_groups--在设备下创建drm/card0/card0-DPI-1类似目录,sysfs节点为status、enabled、dpms、modes、edid等。
->drm_sysfs_hotplug_event--发送一个HOTPLUG=1的uevent。
->drm_debugfs_connector_add--创建类似/sys/kernel/debug/dri/0/DPI-1目录,节点为force、edid_override。
->drm_mode_object_register
以CMA分配方式为例,用户空间对DRM设备的read/write/ioctl都会落到如下函数集中:
#define DEFINE_DRM_GEM_CMA_FOPS(name) \
static const struct file_operations name = {\
.owner = THIS_MODULE,\
.open = drm_open,\
.release = drm_release,\
.unlocked_ioctl = drm_ioctl,\
.compat_ioctl = drm_compat_ioctl,\
.poll = drm_poll,\
.read = drm_read,\
.llseek = noop_llseek,\
.mmap = drm_gem_cma_mmap,\
DRM_GEM_CMA_UNMAPPED_AREA_FOPS \
}
#define DRM_GEM_CMA_UNMAPPED_AREA_FOPS \
.get_unmapped_area = drm_gem_cma_get_unmapped_area,
特别关注一下drm_ioctl():
drm_ioctl
->[DRM_COMMAND_BASE, DRM_COMMAND_END)是特定驱动的ioctl命令。
->小于DRM_CORE_IOCTL_COUNT是DRM core固定的ioctl命令drm_ioctls。
->drm_ioctl_kernel
->drm_ioctl_permit--对调用者进行权限检查。
->func()--执行ioctl命令对应的函数。
drm_ioctls[]定义了丰富的命令列表:
/* Ioctl table */ static const struct drm_ioctl_desc drm_ioctls[] = { DRM_IOCTL_DEF(DRM_IOCTL_VERSION, drm_version, DRM_RENDER_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_GET_UNIQUE, drm_getunique, 0), DRM_IOCTL_DEF(DRM_IOCTL_GET_MAGIC, drm_getmagic, 0), DRM_IOCTL_DEF(DRM_IOCTL_IRQ_BUSID, drm_irq_by_busid, DRM_MASTER|DRM_ROOT_ONLY), ... DRM_IOCTL_DEF(DRM_IOCTL_CRTC_GET_SEQUENCE, drm_crtc_get_sequence_ioctl, 0), DRM_IOCTL_DEF(DRM_IOCTL_CRTC_QUEUE_SEQUENCE, drm_crtc_queue_sequence_ioctl, 0), DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_LEASE, drm_mode_create_lease_ioctl, DRM_MASTER), DRM_IOCTL_DEF(DRM_IOCTL_MODE_LIST_LESSEES, drm_mode_list_lessees_ioctl, DRM_MASTER), DRM_IOCTL_DEF(DRM_IOCTL_MODE_GET_LEASE, drm_mode_get_lease_ioctl, DRM_MASTER), DRM_IOCTL_DEF(DRM_IOCTL_MODE_REVOKE_LEASE, drm_mode_revoke_lease_ioctl, DRM_MASTER), };
3.2.2 DRM Framebuffer
如果打开CONFIG_DRM_FBDEV_EMULATION,则支持在DRM基础上创建fddev设备,注册到Framebuffer子系统。
DRM中fbdev作为DRM client,创建drm_fb_helper表示fbdev。
struct drm_fb_helper { struct drm_client_dev client;--drm fb对应的DRM client。 struct drm_client_buffer *buffer; struct drm_framebuffer *fb; struct drm_device *dev;--drm fb所属的drm设备。 const struct drm_fb_helper_funcs *funcs; struct fb_info *fbdev;--模拟的fbdev设备信息。 u32 pseudo_palette[17]; struct drm_clip_rect dirty_clip; spinlock_t dirty_lock; struct work_struct dirty_work; struct work_struct resume_work; struct mutex lock; struct list_head kernel_fb_list; bool delayed_hotplug; bool deferred_setup; int preferred_bpp; };
drm_fbdev_generic_setup()在DRM上层创建一个模拟的fbdev作为DRM client。
- 创建drm_fb_helper表示fbdev作为DRM Client。
- 根据drm_client_dev特性选择合适的crtc、connector。
- 为fbdev创建framebuffer内存。
- 注册drm_client_dev。
drm_fbdev_generic_setup
->drm_client_init--初始化名称为fbdev的drm_fb_helper作为DRM client,操作函数集为drm_fbdev_client_funcs。
->drm_fbdev_client_hotplug
->drm_fb_helper_prepare--初始化drm_fb_helper,以及配置处理函数drm_fb_helper_generic_probe()。
->drm_fb_helper_init
->drm_fb_helper_initial_config
->__drm_fb_helper_initial_config_and_unlock
->drm_client_modeset_probe--为drm_client_dev选择合适的crtc、connector。
->drm_client_modeset_probe--根据crtc的最大width/height来对drm_client_device进行modeset。
->drm_client_connectors_enabled--使能connectors。
->drm_client_firmware_config--是否使用固件提供的配置。
->drm_client_target_preferred
->drm_connector_has_preferred_mode--根据crtc的最大width/height从connector中查找优选显示模式。
->drm_client_pick_crtcs--根据connector和width/height选择合适的crtc。
->drm_fb_helper_single_fb_probe
->drm_fb_helper_generic_probe--
->drm_client_framebuffer_create--创建一个drm_client_buffer类型的framebuffer,及其使用的内存。
->drm_fb_helper_alloc_fbi--分配fb_info。
->drm_fb_helper_fill_info--初始化并填充fb_info,对应的操作函数集为drm_fbdev_fb_ops。
->drm_setup_crtcs_fb
->register_framebuffer--根据drm_fb_helper_generic_probe创建的fb_info,注册一个FrameBuffer设备/dev/fb0。操作函数集为drm_fbdev_fb_ops。
drm_client_register--将drm_client_dev注册到DRM子系统,即加入到当前drm_device的clientlist上。
Framebuffer子系统对接DRM fbdev主要通过drm_fbdev_fb_ops进行:
#define DRM_FB_HELPER_DEFAULT_OPS \ .fb_check_var = drm_fb_helper_check_var, \ .fb_set_par = drm_fb_helper_set_par, \ .fb_setcmap = drm_fb_helper_setcmap, \ .fb_blank = drm_fb_helper_blank, \ .fb_pan_display = drm_fb_helper_pan_display, \ .fb_debug_enter = drm_fb_helper_debug_enter, \ .fb_debug_leave = drm_fb_helper_debug_leave, \ .fb_ioctl = drm_fb_helper_ioctl static struct fb_ops drm_fbdev_fb_ops = { .owner = THIS_MODULE, DRM_FB_HELPER_DEFAULT_OPS, .fb_open = drm_fbdev_fb_open, .fb_release = drm_fbdev_fb_release, .fb_destroy = drm_fbdev_fb_destroy, .fb_mmap = drm_fbdev_fb_mmap, .fb_read = drm_fb_helper_sys_read, .fb_write = drm_fb_helper_sys_write, .fb_fillrect = drm_fb_helper_sys_fillrect, .fb_copyarea = drm_fb_helper_sys_copyarea, .fb_imageblit = drm_fb_helper_sys_imageblit, };
上述函数被Framebuffer自此同的fb_fops成员函数调用。
4 LCDC驱动(STM32 LTDC)
4.1 LTDC dts
LTDC(LCD-TFT Display Controller)是STM32 RGB并口显示控制器。其DTS包括寄存器、时钟、中断、pinctrl、复位,以及两个表示Connector的endpoint。
ltdc: display-controller@5a001000 { compatible = "st,stm32-ltdc"; reg = <0x5a001000 0x400>; interrupts = <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>; clocks = <&rcc LTDC_PX>; clock-names = "lcd"; resets = <&rcc LTDC_R>; status = "disabled"; }; <dc { pinctrl-names = "default", "sleep"; pinctrl-0 = <<dc_pins_b>; pinctrl-1 = <<dc_pins_sleep_b>; status = "okay"; port { #address-cells = <1>; #size-cells = <0>; ltdc_ep0_out: endpoint@0 { reg = <0>; remote-endpoint = <&sii9022_in>; }; }; }; <dc { port { #address-cells = <1>; #size-cells = <0>; ltdc_ep1_out: endpoint@1 { reg = <1>; remote-endpoint = <&dsi_panel_in>;--作为connector endpoint对应的panel为dsi_panel_in。 }; }; };
4.2 STM32 LTDC Panel初始化
STM32 LTDC提供RGB并口信号、horizontal/vertical同步信号、时钟、数据使能等信号。下面是其初始化:
- 指定drm_driver,然后创建drm_device并初始化。
- 分配并初始化drm_bridge/drm_connector。
- 分配并初始化drm_encoder。
- 分配并初始化drm_crtc/drm_plane。
- 依次对plane/crtc/encoder/connector进行reset。
- 将drm_device注册到DRM子系统。
- 配置DRM fbdev设备。
stm_drm_platform_probe
drm_dev_alloc--分配一个DRM设备struct drm_device,驱动使用drv_driver。
drv_load
->drm_mode_config_init--初始化drm_device的mode_config,并对各种属性创建默认值或取值范围等。
->ltdc_load
->of_graph_get_endpoint_count--获取当前ltdc的端点数。
->devm_clk_get--获取lcd时钟并使能。
->遍历endpoint子节点。
->drm_of_find_panel_or_bridge--根据当前endpoint查找remote-endpoint,返回连接的panel或者bridge。
->of_drm_find_panel--遍历panel_list,如找到则返回struct drm_panel。
->of_drm_find_bridge--遍历bridge_list,如找到则返回struct drm_bridge。
->drm_panel_bridge_add--分配并初始化一个bridge,连接类型为DRM_MODE_CONNECTOR_DPI。建立panel<->bridge之间的关系。
->drm_bridge_add--将bridge加入到bridge_list中。
->ltdc_encoder_init
->drm_encoder_init--初始化一个encoder,将encoder加入到drm_device的encoder_list上。
->drm_encoder_helper_add--初始化encoder的帮助函数。
->drm_bridge_attach--将bridge附着到初始化好的encoder。建立bridge<->encoder之间的关系。
->devm_reset_control_get_exclusive
->ltdc_get_caps--读取寄存器判断LTDC的特性。
->遍历注册中断。
->platform_get_irq--获取中断号。
->devm_request_threaded_irq--中断处理函数ltdc_irq和线程化处理函数ltdc_irq_thread。60Hz刷新率,则每16ms一次中断。
->ltdc_irq--读取中断状态。
->ltdc_irq_thread--根据中断状态进行处理。
->drm_crtc_handle_vblank--如果是ISR_LIF,则需要进行vblank处理。
->drm_crtc_handle_vblank
->drm_handle_vblank--处理vblank事件。
->drm_update_vblank_count
->drm_handle_vblank_events
->ltdc_crtc_init
->ltdc_plane_create--创建DRM_PLANE_TYPE_PRIMARY plane。
->drm_universal_plane_init--初始化drm_plane,操作函数为drm_plane_funcs。
->drm_plane_helper_add--帮助函数为ltdc_plane_helper_funcs。
->drm_crtc_init_with_planes--初始化CRTC并赋值primary和cursor,操作函数集为ltdc_crtc_funcs。建立plane<->crtc之间的关系。
->drm_crtc_helper_add--CRTC帮助函数集为ltdc_crtc_helper_funcs。
->drm_mode_crtc_set_gamma_size
->drm_crtc_enable_color_mgmt
->使用更多layer作为DRM_PLANE_TYPE_OVERLAY Plane。
->drm_vblank_init--初始化CRTC的vblank队列、定时器等。
->pinctrl_pm_select_sleep_state--根据当前低功耗状态,设置pinctrl。
->drm_mode_config_reset--依次调用所有plane、crtc、encoder、connector的reset函数。
->drm_kms_helper_poll_init--初始化查询connector的帮助函数。
->platform_set_drvdata
drm_dev_register--注册一个DRM设备。
drm_fbdev_generic_setup--如果打开CONFIG_DRM_FBDEV_EMULATION,则创建一个模拟FrameBuffer设备。
drv_driver是用户空间对DRM设备文件操作的入口,
static struct drm_driver drv_driver = { .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, .name = "stm", .desc = "STMicroelectronics SoC DRM", .date = "20170330", .major = 1, .minor = 0, .patchlevel = 0, .fops = &drv_driver_fops,--DRM设备文件操作函数集。 .dumb_create = stm_gem_cma_dumb_create, .prime_handle_to_fd = drm_gem_prime_handle_to_fd, .prime_fd_to_handle = drm_gem_prime_fd_to_handle, .gem_free_object_unlocked = drm_gem_cma_free_object, .gem_vm_ops = &drm_gem_cma_vm_ops, .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, .gem_prime_vmap = drm_gem_cma_prime_vmap, .gem_prime_vunmap = drm_gem_cma_prime_vunmap, .gem_prime_mmap = drm_gem_cma_prime_mmap, .get_scanout_position = ltdc_crtc_scanoutpos, .get_vblank_timestamp = drm_calc_vbltimestamp_from_scanoutpos, };
根据mmap所使用的分配内存方式不同,分为三种:
- DEFINE_DRM_GEM_FOPS:使用普通GEM内存。
- DEFINE_DRM_GEM_CMA_FOPS:使用CMA作为GEM对象内存分配器。
- DEFINE_DRM_GEM_SHMEM_FOPS:使用shmem作为GEM对象内存分配器。
5 Backlight驱动(pwm-backlight)
backlight类初始化:
backlight_class_init
class_create--创建backlight类。
->backlight类设备属性。
->初始化backlight设备列表等。
每一个backlight类设备都会创建如下属性:
static struct attribute *bl_device_attrs[] = { &dev_attr_bl_power.attr, &dev_attr_brightness.attr, &dev_attr_actual_brightness.attr, &dev_attr_max_brightness.attr, &dev_attr_scale.attr, &dev_attr_type.attr, NULL, }; ATTRIBUTE_GROUPS(bl_device);
如下是backlight类设备的属性实例:
/sys/class/backlight/panel-backlight/ |-- actual_brightness--当前实际backlight等级。 |-- bl_power--power模式,0-full on;1-3 power saving;4-full off。 |-- brightness--可设置或读取backlight等级。 |-- max_brightness--backlight最大等级。 |-- scale--显示当前backlight是linear还是non-linear。 |-- type--当前backlight类型:raw、platform、firmware。 `-- uevent
一个使用pwm-backlight的驱动dts实例:
panel_backlight: panel-backlight { compatible = "pwm-backlight"; pwms = <&pwm4 1 5000000>; brightness-levels = <0 4 8 16 32 64 128 255>;--亮度等级列表,对应设置到pwm设备中。 power-supply = <&v3v3>; default-brightness-level = <7>;--默认亮度等级。 status = "okay"; };
STM32 pwm backlight驱动:
pwm_backlight_probe
pwm_backlight_parse_dt--解析子节点属性,获取pwms、power、brightness等属性。
devm_regulator_get--获取panel的power-supply。
devm_pwm_get--获取panel的pwms。
pwm_init_state--获取pwm初始状态。
pwm_apply_state--设置pwm状态。
backlight_device_register--注册一个backlight_device类设备,操作函数集为pwm_backlight_ops。
backlight_update_status
6 Panel驱动(panel simple)
panel_simple表示一个panel设备,包括panele在DRM子系统中表示、背光、复位等信息。
struct panel_simple { struct drm_panel base; bool prepared; bool enabled; bool no_hpd; const struct panel_desc *desc; struct backlight_device *backlight;--对应的背光设备。 struct regulator *supply; ... };
panel simple dts配置如下:
- 通过compatible和panel_simple驱动绑定。
- 指定backlight。
- 将panel和connector endpoint绑定。
panel_rgb: panel-rgb { compatible = "alientek,lcd-rgb"; backlight = <&panel_backlight>;--背光使用的是panel_backlight,一种pwm backlight。 status = "okay"; port { dsi_panel_in: endpoint { remote-endpoint = <<dc_ep1_out>;--connector对应的endpoint。 }; }; };
panel_simple支持platform_driver和mipi_dsi_driver两种驱动。platform_driver的probe做如下工作:
- 电源和GPIO获取。
- backlight获取。
- panel时序配置获取。
- 初始化drm_panel并添加到panel_list中。
panel_simple_init
->panel_simple_platform_driver
->panel_simple_platform_probe
->panel_simple_probe
->从dts中解析panel属性,包括enable、backlight、panel-timing等。
->drm_panel_init
->drm_panel_add--将当前struct drm_panel加入到panel_list中。panel操作函数集为panel_simple_funcs。
panel_simple_funcs是跟随drm_panel注册到DRM子系统的操作函数集,在panel被使能关闭时调用:
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, };
7 DRM相关调试
7.1 DRM相关sysfs节点
关于对DRM子系统跟踪和调试,参考《DRM KMS overview - How to trace and debug the framework》。
drm模块提供参数debug,可以配置输出不同DRM子模块日志:
/sys/module/drm/parameters/debug
或者通过设置bootargs参数drm.debug=0x1ff,则打开全部log。
#define DRM_UT_NONE 0x00 #define DRM_UT_CORE 0x01 #define DRM_UT_DRIVER 0x02 #define DRM_UT_KMS 0x04 #define DRM_UT_PRIME 0x08 #define DRM_UT_ATOMIC 0x10 #define DRM_UT_VBL 0x20 #define DRM_UT_STATE 0x40 #define DRM_UT_LEASE 0x80 #define DRM_UT_DP 0x100
或者修改drb_debug默认值:
unsigned int drm_debug = DRM_UT_CORE|DRM_UT_DRIVER|DRM_UT_KMS|DRM_UT_PRIME|DRM_UT_ATOMIC|DRM_UT_STATE|DRM_UT_LEASE|DRM_UT_DP; EXPORT_SYMBOL(drm_debug);
DRI调试节点:
/sys/kernel/debug/dri/0 |-- DPI-1 | |-- edid_override | `-- force |-- clients |-- crtc-0 |-- framebuffer |-- gem_names |-- internal_clients |-- name `-- state
8 DRM相关udev
DRM相关udev存放于/lib/udev/rules.d/60-drm.rules:
# do not edit this file, it will be overwritten on update ACTION!="remove", SUBSYSTEM=="drm", SUBSYSTEMS=="pci|usb|platform", IMPORT{builtin}="path_id" # by-path ENV{ID_PATH}=="?*", KERNEL=="card*", SYMLINK+="dri/by-path/$env{ID_PATH}-card" ENV{ID_PATH}=="?*", KERNEL=="controlD*", SYMLINK+="dri/by-path/$env{ID_PATH}-control" ENV{ID_PATH}=="?*", KERNEL=="renderD*", SYMLINK+="dri/by-path/$env{ID_PATH}-render"
参考文档:
《DRM KMS overview - stm32mpu (stmicroelectronics.cn)》:关于DRM系统框架图、内核配置、测试工具(modetest/kmscube)、跟踪和调试、用户空间libdrm等介绍。
《Linux图形显示系统之DRM - 沉默的思想 - 博客园 (cnblogs.com)》