Linux 图像子系统涉及 GUI、3D application、DRM/KMS、hardware 等:
在 Linux display 驱动开发时,通常关注 FBDEV(Framebuffer Device),DRM/KMS 子系统。在 FrameBuffer Device 驱动框架下,我们能够快速开发出可供简单使用的显示驱动。
但是随着芯片显示外设的性能逐渐增强、3D 渲染及 GPU 的引入,FrameBuffer 框架就落伍了,显示覆盖 (菜单层级)、GPU 加速、硬件光标等功能并不能得到很好得支持,并且 FrameBuffer 将底层的显存通过 /dev/fb 暴露给用户空间,容易导致不同的应用程序在操作显存时产生访问冲突,不安全。因此,需要一个现代的图形显示框架来解决这些问题,DRM(Direct Rendering Manager,直接图形管理器) 诞生。
DRM 将现代显示领域中会涉及的一些操作进行分层并使这些模块独立,如果上层应用想操作显存、显示效果、GPU,都必须在一些框架的约束下进行。
我们可以从用户空间、内核空间的两个角度去了解 DRM 框架:
用户空间 (libdrm driver):
内核空间 (DRM driver):
通常用 DRM/KMS 来指代整个 DRM subsystem,但是 KMS 和 DRM driver 只是整个 DRM subsystem 的其中 2 个部分。
DRM 框架在用户空间提供的 Libdrm,对底层接口进行封装,主要是对各种 IOCTL 接口进行封装,向上层提供通用的 API 接口,用户或应用程序在用户空间调用 libdrm 提供的库函数,即可访问到显示的资源,并对显示资源进行管理和使用。
这样通过 libdrm 对显示资源进行统一访问,libdrm 将命令传递到内核最终由 DRM 驱动接管各应用的请求并处理,可以有效避免访问冲突。
KMS 属于 DRM 框架下的一个大模块,主要负责两个功能:显示参数设置及显示画面控制。这两个基本功能可以说是显示驱动必须基本的能力,在 DRM 框架下,为了将这两部分适配得符合现代显示设备逻辑,又分出了几部分子模块配合框架。
DRM FrameBuffer 是一个软件抽象,硬件无关的基本元素,描述了图层显示内容的信息 (width, height, pixel_format,pitch 等)。
平面,图层的意思。基本的显示控制单位,每个图像拥有一个 Planes,Planes 的属性控制着图像的显示区域、图像翻转、色彩混合方式等,最终图像经过 Planes 并通过 CRTC 组件,得到多个图像的混合显示或单独显示的等功能。
CRTC:Cathode Ray Tube Controller,负责把要显示图像,转化为底层硬件层面上的具体时序要求,还负责着帧切换、电源控制、色彩调整等,可以连接多个 Encoder ,实现复制屏幕功能。
编码器,转换输出器,负责电源管理、显然输出需要不同的信号转换器,将内存的像素转换成显示器需要的信号。
连接器,负责硬件设备的接入,比如 HDMI,VGA 等,可以获取到设备 EDID , DPMS 连接状态等等。
上述的这些组件,最终完成了一个完整的 DRM 显示控制过程,如下图所示:
上面 CRTC、Planes、Encoder、Connector 这些组件是对硬件的抽象,即使没有实际的硬件与之对应,在软件驱动中也需要实现这些,否则 DRM 子系统无法正常运行。
GEM 负责对 DRM 使用的内存 (如显存) 进行管理, 是一个软件抽象。
GEM 框架提供的功能包括:
显示功能的驱动一般由芯片厂商 rockchip 来负责实现,完成一个 DRM-Host,主机驱动代码一般位于 drivers/gpu/drm/xxx/ 目录下,这里 xxx 代指芯片厂商。
在驱动中 rockchip 的显示驱动使用 component 框架,显示驱动为 master,显示驱动下的设备称为 component。
在 display_subsystem 设备节点中的 ports 节点就是关联的 component,实际指向 vopl_out 和 vopb_out 节点,(VOP 是各种输出图像的接口),在该节点下有多个 port,可以同时输出多个视频信号。
rk3399.dtsi
display_subsystem: display-subsystem {
status = "okay";
compatible = "rockchip,display-subsystem";
ports = <&vopl_out>, <&vopb_out>;
clocks = <&cru PLL_VPLL>, <&cru PLL_CPLL>;
clock-names = "hdmi-tmds-pll", "default-vop-pll";
devfreq = <&dmc>;
logo-memory-region = <&drm_logo>;
secure-memory-region = <&secure_memory>;
route {
route_dsi: route-dsi {
status = "disabled";
logo,uboot = "logo.bmp";
logo,kernel = "logo_kernel.bmp";
logo,mode = "center";
charge_logo,mode = "center";
connect = <&vopb_out_dsi>;
};
......
};
};
vopl: vop@ff8f0000 {
compatible = "rockchip,rk3399-vop-lit";
reg = <0x0 0xff8f0000 0x0 0x600>,
<0x0 0xff8f1c00 0x0 0x200>,
<0x0 0xff8f2000 0x0 0x400>;
reg-names = "regs", "cabc_lut", "gamma_lut";
interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH 0>;
clocks = <&cru ACLK_VOP1>, <&cru DCLK_VOP1>, <&cru HCLK_VOP1>, <&cru DCLK_VOP1_DIV>;
clock-names = "aclk_vop", "dclk_vop", "hclk_vop", "dclk_source";
iommus = <&vopl_mmu>;
power-domains = <&power RK3399_PD_VOPL>;
resets = <&cru SRST_A_VOP1>, <&cru SRST_H_VOP1>, <&cru SRST_D_VOP1>;
reset-names = "axi", "ahb", "dclk";
status = "disabled";
vopl_out: port {
#address-cells = <1>;
#size-cells = <0>;
vopl_out_dsi: endpoint@0 {
reg = <0>;
remote-endpoint = <&dsi_in_vopl>;
};
vopl_out_edp: endpoint@1 {
reg = <1>;
remote-endpoint = <&edp_in_vopl>;
};
vopl_out_hdmi: endpoint@2 {
reg = <2>;
remote-endpoint = <&hdmi_in_vopl>;
};
vopl_out_dp: endpoint@3 {
reg = <3>;
remote-endpoint = <&dp_in_vopl>;
};
vopl_out_dsi1: endpoint@4 {
reg = <4>;
remote-endpoint = <&dsi1_in_vopl>;
};
};
};
vopb: vop@ff900000 {
compatible = "rockchip,rk3399-vop-big";
reg = <0x0 0xff900000 0x0 0x600>,
<0x0 0xff901c00 0x0 0x200>,
<0x0 0xff902000 0x0 0x1000>;
reg-names = "regs", "cabc_lut", "gamma_lut";
interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH 0>;
clocks = <&cru ACLK_VOP0>, <&cru DCLK_VOP0>, <&cru HCLK_VOP0>, <&cru DCLK_VOP0_DIV>;
clock-names = "aclk_vop", "dclk_vop", "hclk_vop", "dclk_source";
resets = <&cru SRST_A_VOP0>, <&cru SRST_H_VOP0>, <&cru SRST_D_VOP0>;
reset-names = "axi", "ahb", "dclk";
power-domains = <&power RK3399_PD_VOPB>;
iommus = <&vopb_mmu>;
status = "disabled";
vopb_out: port {
#address-cells = <1>;
#size-cells = <0>;
vopb_out_edp: endpoint@0 {
reg = <0>;
remote-endpoint = <&edp_in_vopb>;
};
vopb_out_dsi: endpoint@1 {
reg = <1>;
remote-endpoint = <&dsi_in_vopb>;
};
vopb_out_hdmi: endpoint@2 {
reg = <2>;
remote-endpoint = <&hdmi_in_vopb>;
};
vopb_out_dp: endpoint@3 {
reg = <3>;
remote-endpoint = <&dp_in_vopb>;
};
vopb_out_dsi1: endpoint@4 {
reg = <4>;
remote-endpoint = <&dsi1_in_vopb>;
};
};
};
平台驱动 rockchip-drm 匹配到设备树,会到设备树 dts 查找 ports 节点和 iommus 节点,使用 component_master_add_with_match 函数注册自己到 component 框架中,设置了 rockchip_drm_ops,其 component 可以通过 component_add 函数增加,master 匹配上所有 component 后,会调用 master 的 bind 回调函数,最后通过 drm_dev_register() 函数注册到 DRM core
平台驱动源码如下 drivers/gpu/drm/rockchip/rockchip_drm_drv.c
static const struct component_master_ops rockchip_drm_ops = {
.bind = rockchip_drm_bind,
.unbind = rockchip_drm_unbind,
};
static int rockchip_drm_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct component_match *match = NULL;
struct device_node *np = dev->of_node;
struct device_node *port;
int i;
DRM_INFO("Rockchip DRM driver version: %s\n", DRIVER_VERSION);
if (!np)
return -ENODEV;
for (i = 0;; i++) {
struct device_node *iommu;
port = of_parse_phandle(np, "ports", i);
if (!port)
break;
if (!of_device_is_available(port->parent)) {
of_node_put(port);
continue;
}
iommu = of_parse_phandle(port->parent, "iommus", 0);
if (!iommu || !of_device_is_available(iommu->parent)) {
dev_dbg(dev, "no iommu attached for %s, using non-iommu buffers\n",
port->parent->full_name);
is_support_iommu = false;
}
component_match_add(dev, &match, compare_of, port->parent);
of_node_put(port);
}
......
for (i = 0;; i++) {
port = of_parse_phandle(np, "ports", i);
if (!port)
break;
if (!of_device_is_available(port->parent)) {
of_node_put(port);
continue;
}
rockchip_add_endpoints(dev, &match, port);
of_node_put(port);
}
port = of_parse_phandle(np, "backlight", 0);
if (port && of_device_is_available(port)) {
component_match_add(dev, &match, compare_of, port);
of_node_put(port);
}
return component_master_add_with_match(dev, &rockchip_drm_ops, match);
}
static const struct of_device_id rockchip_drm_dt_ids[] = {
{ .compatible = "rockchip,display-subsystem", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rockchip_drm_dt_ids);
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",
.of_match_table = rockchip_drm_dt_ids,
.pm = &rockchip_drm_pm_ops,
},
};
博主手里的是 MIPI DSI 屏幕,设备树配置如下:
dsi@ff960000 {
compatible = "rockchip,rk3399-mipi-dsi";
reg = <0x0 0xff960000 0x0 0x8000>;
interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH 0>;
clocks = <0x8 0xa2 0x8 0x170 0x8 0xa3>;
clock-names = "ref", "pclk", "phy_cfg";
power-domains = <&power RK3399_PD_VIO>;
resets = <&cru SRST_P_MIPI_DSI0>;
reset-names = "apb";
rockchip,grf = <&grf>;
status = "okay";
#address-cells = <0x1>;
#size-cells = <0x0>;
phandle = <0x137>;
ports {
port {
#address-cells = <1>;
#size-cells = <0>;
endpoint@0 {
reg = <0x0>;
remote-endpoint = <&vopb_out_dsi>;
status = "okay";
phandle = <0xab>;
};
endpoint@1 {
reg = <0x1>;
remote-endpoint = <&vopl_out_dsi>;
status = "disabled";
phandle = <0xa3>;
};
};
};
panel@0 {
status = "okay";
compatible = "simple-panel-dsi";
reg = <0x0>;
backlight = <&backlight>;
dsi,flags = <0xa03>;
dsi,format = <MIPI_DSI_FMT_RGB888>;
dsi,lanes = <4>;
dsi,channel = <0>;
enable-delay-ms = <35>;
prepare-delay-ms = <6>;
unprepare-delay-ms = <0>;
disable-delay-ms = <20>;
size,width = <120>;
size,height = <170>;
panel-init-sequence = [29 ...... 29];
panel-exit-sequence = <0x5050128 0x5780110>;
phandle = <0x138>;
power_ctr {
rockchip,debug = <0>;
power_enable = <1>;
phandle = <0x139>;
lcd-rst {
gpios = <&gpio4 RK_PD6 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&lcd_panel_reset>;
rockchip,delay = <6>;
phandle = <0x13a>;
};
};
display-timings {
native-mode = <&timing0>;
phandle = <0x13b>;
timing0 {
clock-frequency = <0x3938700>;
hactive = <0x320>;
vactive = <0x500>;
hsync-len = <0x14>;
hback-porch = <0x14>;
hfront-porch = <0x14>;
vsync-len = <0x4>;
vback-porch = <0x4>;
vfront-porch = <0xa>;
hsync-active = <0x0>;
vsync-active = <0x0>;
de-active = <0x0>;
pixelclk-active = <0x0>;
phandle = <0xba>;
};
};
};
};
对应的 DSI 驱动是 drivers/gpu/drm/rockchip/dw-mipi-dsi.c
对应的 panel 驱动是 drivers/gpu/drm/panel/panel-simple.c
static const struct of_device_id dw_mipi_dsi_dt_ids[] = {
{ .compatible = "rockchip,rk3399-mipi-dsi", .data = &rk3399_socdata, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dw_mipi_dsi_dt_ids);
static struct platform_driver dw_mipi_dsi_driver = {
.probe = dw_mipi_dsi_probe,
.remove = dw_mipi_dsi_remove,
.driver = {
.of_match_table = dw_mipi_dsi_dt_ids,
.pm = &dw_mipi_dsi_pm_ops,
.name = DRIVER_NAME,
},
};
module_platform_driver(dw_mipi_dsi_driver);
static int dw_mipi_dsi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct dw_mipi_dsi *dsi;
struct device_node *np = dev->of_node;
struct resource *res;
void __iomem *regs;
int ret;
int dsi_id;
dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
if (!dsi)
return -ENOMEM;
dsi_id = of_alias_get_id(np, "dsi");
if (dsi_id < 0)
dsi_id = 0;
dsi->id = dsi_id;
dsi->dev = dev;
dsi->pdata = of_device_get_match_data(dev);
platform_set_drvdata(pdev, dsi);
ret = dw_mipi_dsi_parse_dt(dsi);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(dev, res);
dsi->irq = platform_get_irq(pdev, 0);
dsi->pclk = devm_clk_get(dev, "pclk");
dsi->regmap = devm_regmap_init_mmio(dev, regs,&dw_mipi_dsi_regmap_config);
if (dsi->pdata->soc_type == RK3126) {
dsi->h2p_clk = devm_clk_get(dev, "h2p");
}
}
dsi->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
dsi->rst = devm_reset_control_get(dev, "apb");
ret = mipi_dphy_attach(dsi);
ret = devm_request_irq(dev, dsi->irq, dw_mipi_dsi_irq_handler,IRQF_SHARED, dev_name(dev), dsi);
dsi->dsi_host.ops = &dw_mipi_dsi_host_ops;
dsi->dsi_host.dev = dev;
ret = mipi_dsi_host_register(&dsi->dsi_host);
ret = component_add(dev, &dw_mipi_dsi_ops);
if (ret)
mipi_dsi_host_unregister(&dsi->dsi_host);
return ret;
}
这里会注册 mipi dsi 中断,中断处理函数是 dw_mipi_dsi_irq_handler
4、debug
1、连接 ADB,安装测试工具 apt install libdrm-tests,执行 modetest,会打印 Encoders、Connectors、CRTCs、Planes 的详细信息。
2、DRM driver 会在 /dev/dri 下创建 3 个设备节点:card0、controlD64、renderD128,libdrm 可以打开 card0 在用户空间操作。
3、DRM 的应用编程有两种接口:legacy 接口和 atomic 接口,目前一般用 atomic 接口。
虽然 DRM 功能符合现代显示设备的需求,但是仍有众多的老设备及软件需要 Framebuffer 的支持。所以在 DRM 框架下,有部分代码用于实现在 DRM 框架下,去模拟 FB 设备。
在 rockchip 提供的显示驱动代码中,也有模拟 FB 设备的相关代码,参见 drivers/gpu/drm/rockchip/rockchip_drm_fb.c 文件,最终效果就是设备目录下,出现熟悉的身影 /dev/fb0
本文分享自 嵌入式Linux系统开发 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!