Linux驱动学习--DRM框架介绍及基于DRM框架的HDMI开发

目录

一、引言

二、DRM框架介绍

三、DRM框架的使用

四、源码分析

一、引言

Android4开始,hdmi等视频输出框架开始由framebuffer想DRM迁移,今天我们就来简单分析下DRM框架

二、DRM框架介绍

DRM是一个内核级的设备驱动,具体的说是显卡驱动的一种架构

源码位置

因为Linux kernel内部接口和数据结构可能随时发生变化,所以DRI模块要针对特定的内核版本进行编译。kernel 2.6.26之后的版本,DRM(DRI kernel模块)源码存放在kernel/drivers/gpu/drm中;在这之前的版本,源码在kernel/drivers/char/drm目录中。

几个基本要素

画布( FrameBuffer )

对计算机来说,FrameBuffer 就是一块驱动和应用层都能访问的内存,当然画图之前要有一定的格式化,比方说我可以规定什么样的色彩模式(RGB24 , I420 , YUUV 等等), 分辨率是多大,还有啥参数,那就要到绘图现场去看了

绘图现场(CRTC)

简写翻译过来是阴级摄像管上下文,在DRM 里 CRTC 就表示显示输出的上下文了,首先 CRTC 内指一个 FrameBuffer 地址, 外连一个Encoder。 它们俩之间如何沟通? 这就是显示模式(ModeSet)要做的事情,ModeSet 包括了像前面提到的色彩模式 , 还有说显示的时序(timings , ModeLines 等都代表了这个意西)等, 通常时序可以按以下来表达

PCLK HFP HBP HSW X_RES VFP VBP VSW Y_RES

像素时钟 水平前回扫 水平后回扫 水平同步头 水平有效长度 垂直前回扫 垂直后回扫 垂直同步头 垂直有效长度

一个CRTC 可以连接多个 Encoder ,实现复制屏幕功能。

输出转换器(Encoder )

我们的显卡可以连接各种不同的设备,显然输出需要不同的信号转换器,将内存的像素转换成显示器需要的信号(DVID , VGA , YPbPr , CVBS 等等……)

连接器 (Connector )

不是指物理线,回到DRM 这是一个抽象的数据结构 ,代表连接的显示设备,从这里我们可以得到设备的EDID , DPMS 连接状态等.

显示面(Planner)

如果你又要看文字学习,又要看电影打游戏,还有厉害的可以一边聊天一边看电影。这里对立出来两个概念,像文字交互这种小范围更新的Graphics 模式,和全幅更新速度奇快的 Video 模式,这两种模式将显卡的使用拉上了两个极端。

于是 Planner 的概念就发挥了很好的作用,它给视频刷新提供了一条绿色通道,偶不和图形搞在一起了,偶是一个新的图层(或overlay),可以叠加在Graphic之上或之下,偶还可以缩放…

文档上说 Planner 也在 FrameBuffer 上,这个没关系,这里我们看出来 CRTC 里要显示的东东应该是一种组合(blending)了。

三、基于DRM框架的HDMI开发总结

设备节点及常用命令

DRM 的设备节点为 “/dev/dri/cardX”, X为0-15的数值.
默认使用的是/dev/dri/card0

节点控制hdmi和lvds的显示开关

相关命令

HDMI:
echo on > /sys/class/drm/card0-HDMI-A-1/status //打开hdmiout显示                     
echo off > /sys/class/drm/card0-HDMI-A-1/status //关闭hdmiout显示                       
cat /sys/class/drm/card0-HDMI-A-1/status //打开是connected,关闭是disconnected

LVDS:
ls -dl /sys/class/drm/*/st*  && cat /sys/class/drm/card0-LVDS-1/st* 

echo on > /sys/class/drm/card0-LVDS-1/st*  //开
echo off > /sys/class/drm/card0-LVDS-1/st*  //关

cat /proc/device-tree/display-subsystem/route/route-lvds/status

  •  

设备树中的对应节点

1、打开显示设备执行相关hdmi的probe函数,注册显示设备驱动,如打开 HDMI 需要添加

  •  

2、开机 LOGO
如果uboot logo未开启,那kernel阶段也无法显示开机logo,只能等到android启动后才能看到显示;在dts里面将对应的route使能即可打开uboot logo支持

&route_hdmi {
status = “okay”; //okay打开,disabled关闭
connect = <&vopb_out_hdmi>; //vopb_out_hdmi对应hdmi关联vopb
};
  •  

3、绑定VOP


&hdmi_in_vopl {
status = “disabled”;
};

&hdmi_in_vopb {
status = “disabled”;
};
  •  

调试命令

查看当前可用显示分辨率列表

console:/ # cat /sys/class/drm/card0-HDMI-A-1/modes
1920x1080p60
1920x1080p60
1920x1080i60
1920x1080i60
1920x1080p50
1920x1080i50
1680x1050p60
1280x1024p60
1440x900p60
1280x720p60
1280x720p60
1280x720p50
1024x768p60
800x600p60
720x576p50
720x576i50
720x480p60
720x480i60
  •  

查看当前分辨率

console:/ # cat /sys/class/drm/card0-HDMI-A-1/mode
1920x1080p60
  •  

查看vop状态(rk3288/3399平台有2vop,vopb支持4k,vopl支持2k)

console:/ # cat /d/dri/0/summary
VOP [ff370000.vop]: ACTIVE
    Connector: HDMI-A
        overlay_mode[1] bus_format[2025] output_mode[f] color_space[3]
    Display mode: 1920x1080p60
        clk[148500] real_clk[148500] type[48] flag[5]
        H: 1920 2008 2052 2200
        V: 1080 1084 1089 1125
    win0-0: DISABLED
    win1-0: DISABLED
    win2-0: DISABLED
    post: sdr2hdr[0] hdr2sdr[0]
    pre : sdr2hdr[0]
    post CSC: r2y[0] y2r[0] CSC mode[1]
console:/ # 

  •  

查看HDMI当前输出状态

console:/ # cat /d/dw-hdmi/status
PHY: enabled                    Mode: HDMI
Pixel Clk: 148500000Hz          TMDS Clk: 148500000Hz
Color Format: YUV444            Color Depth: 8 bit
Colorimetry: ITU.BT709          EOTF: Off
console:/ # 
  •  

HDMI Output Status 表示当前 PHY 状态,只有当 PHY 使能的时候才会有后续打印。
Pixel Clk 表示当前输出的像素时钟。
TMDS Clk 表示当前输出 HDMI 符号率。
Color Format 表示输出的颜色格式,取值 RGB、YUV444、YUV422、YUV420。
Color Depth 表示输出的颜色深度,取值 8bit、10bit、12bit、16bit。
Colorimery 表示输出的颜色标准,取值 ITU.BT601、ITU.BIT709、ITU.BT2020
EOTF 表示输出的 HDR 电光转换曲线方式,这里不支持。

查看HDMI的插拔状态:

console:/ # cat sys/class/switch/hdmi/state
1
console:/ #
  •  

四、源码分析

我们用HDMI为例子来分析
首先会通过i2c获取屏的edid信息。
EDID的全称是Extended Display Identification Data(扩展显示标识数据),共有128字节。其中包含有关显示器及其性能的参数,包括供应商信息、最大图像大小、颜色设置、厂商预设置、
频率范围的限制以及显示器名和序列号的字符串等等。形象地说,EDID就是显示器的身份证、户口本、技能证书等证件的集合,目的就是告诉别人我是谁,我从哪来,我能干什么

dts匹配

下面为dts中hdmi的相应节点

        hdmi: hdmi@ff3c0000 {
                compatible = "rockchip,rk3328-dw-hdmi";
                reg = <0x0 0xff3c0000 0x0 0x20000>;
                reg-io-width = <4>; 
                interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>,
                             <GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
		......
  •  

对应到驱动代码

Z:\home\clzj\bxcao\gvc3215\kernel\drivers\gpu\drm\rockchip\dw_hdmi-rockchip.c

static struct platform_driver dw_hdmi_rockchip_pltfm_driver = {
	.probe  = dw_hdmi_rockchip_probe,
	.remove = dw_hdmi_rockchip_remove,
	.shutdown = dw_hdmi_rockchip_shutdown,
	.driver = {
		.name = "dwhdmi-rockchip",
		.of_match_table = dw_hdmi_rockchip_dt_ids,
		.pm = &dw_hdmi_pm_ops,
	},
};

static int dw_hdmi_rockchip_probe(struct platform_device *pdev)
{
	pm_runtime_enable(&pdev->dev);
	pm_runtime_get_sync(&pdev->dev);

	return component_add(&pdev->dev, &dw_hdmi_rockchip_ops);
}

static const struct component_ops dw_hdmi_rockchip_ops = {
	.bind	= dw_hdmi_rockchip_bind,
	.unbind	= dw_hdmi_rockchip_unbind,
};

  •  

dw_hdmi_rockchip_bind

之后上层会调用dw_hdmi_rockchip_bind 绑定设备
大致做了以下动作

rockchip_hdmi_parse_dt(hdmi); //解析对应设备节点
plat_data->get_output_bus_format =dw_hdmi_rockchip_get_output_bus_format; ......//填充dw_hdmi_plat_data 结构体
inno_dw_hdmi_init; //设置对应寄存器,初始化基本功能引脚
dw_hdmi_bind()
  •  

dw_hdmi_bind

Z:\home\clzj\bxcao\gvc3215\kernel\drivers\gpu\drm\bridge\synopsys\dw-hdmi.c
hdmi->plat_data = plat_data; ......//填充dw_hdmi结构体变量
hdmi->ddc = dw_hdmi_i2c_adapter(hdmi); //设置hdmi的i2c适配器
clk_prepare_enable();//为一系列时钟初始化
devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq,dw_hdmi_irq, IRQF_SHARED,dev_name(dev), hdmi); //注册HDMI的硬件中断,其中有热拔插等功能
dw_hdmi_register()//注册hdmi设备
  •  

dw_hdmi_register

	drm_connector_helper_add(&hdmi->connector,
				 &dw_hdmi_connector_helper_funcs);
				 
static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = {
	.get_modes = dw_hdmi_connector_get_modes,
	.mode_valid = dw_hdmi_connector_mode_valid,
	.best_encoder = dw_hdmi_connector_best_encoder,
	.atomic_begin = dw_hdmi_connector_atomic_begin,
	.atomic_flush = dw_hdmi_connector_atomic_flush,
};
以上为根据EDID数据找到最适合的配置
  •  
edid = drm_get_edid(connector, hdmi->ddc);//获取edid
			 hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid); //检测显示器是否是HDMI,是就返回true,否则return false;
				->drm_find_cea_extension //
					->drm_find_edid_extension //在edid中搜索CEA扩展块
						->if (cea_db_is_hdmi_vsdb(&edid_ext[i]))
								return true; //因为HDMI标识符在特定于供应商的块中,所以从CEA扩展的所有数据块中搜索它。
			 
             hdmi->sink_has_audio = drm_detect_monitor_audio(edid); //检测显示器audio音频功能,如果显示器支持音频,就返回true,否则return false;
             drm_mode_connector_update_edid_property(connector, edid); //更新连接器的edid属性
             cec_notifier_set_phys_addr_from_edid(hdmi->cec_notifier, edid); 
             ret = drm_add_edid_modes(connector, edid); //从连接的显示器中读取到的edid数据添加分辨率[mode](如果mode可用),
  •  

HAL层代码

HAL层代码目录: /hardware/rockchip/hwcomposer

上一篇:linux内核amdgpu源码解析


下一篇:详解音视频中的DRM数字版权技术