Rockchip RK3399 - DRM vop驱动程序
----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4
开发板
eMMC
:16GB
LPDDR3
:4GB
显示屏 :15.6
英寸HDMI
接口显示屏
u-boot
:2023.04
linux
:6.3
----------------------------------------------------------------------------------------------------------------------------
有关《Rockchip RK3399 - DRM crtc
基础知识》我们已经在前面介绍过了,在linux
内核中Rockchip VOP driver
对应crtc driver
,crtc
负责连接plane
和encoder
。
这里我们介绍一下Rochchip DRM
驱动中与vop
相关的实现,具体实现文件:
drivers/gpu/drm/rockchip/rockchip_drm_vop.c
;drivers/gpu/drm/rockchip/rockchip_vop_reg.c
;drivers/gpu/drm/rockchip/rockchip_drm_vop.h
;drivers/gpu/drm/rockchip/rockchip_vop_reg.h
;
一、设备树配置
1.1 vopb
设备节点
以vopb
设备节点为例:
vopb: vop@ff900000 {
compatible = "rockchip,rk3399-vop-big";
reg = <0x0 0xff900000 0x0 0x2000>, <0x0 0xff902000 0x0 0x1000>;
interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH 0>;
assigned-clocks = <&cru ACLK_VOP0>, <&cru HCLK_VOP0>;
assigned-clock-rates = <400000000>, <100000000>;
clocks = <&cru ACLK_VOP0>, <&cru DCLK_VOP0>, <&cru HCLK_VOP0>;
clock-names = "aclk_vop", "dclk_vop", "hclk_vop";
iommus = <&vopb_mmu>;
power-domains = <&power RK3399_PD_VOPB>;
resets = <&cru SRST_A_VOP0>, <&cru SRST_H_VOP0>, <&cru SRST_D_VOP0>;
reset-names = "axi", "ahb", "dclk";
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_mipi: endpoint@1 {
reg = <1>;
remote-endpoint = <&mipi_in_vopb>;
};
vopb_out_hdmi: endpoint@2 {
reg = <2>;
remote-endpoint = <&hdmi_in_vopb>;
};
vopb_out_mipi1: endpoint@3 {
reg = <3>;
remote-endpoint = <&mipi1_in_vopb>;
};
vopb_out_dp: endpoint@4 {
reg = <4>;
remote-endpoint = <&dp_in_vopb>;
};
};
};
1.2 启用vopb
如果我们希望启用vopb
上,则需要在arch/arm64/boot/dts/rockchip/rk3399-evb.dts
中为以下节点新增属性:
# 使能vopb
&vopb {
status = "okay";
};
&vopb_mmu {
status = "okay";
};
&display_subsystem {
status = "okay";
};
二、Platform
驱动
2.1 模块入口函数
在rockchip_drm_init
函数中调用:
static int __init rockchip_drm_init(void)
{
int ret;
if (drm_firmware_drivers_only())
return -ENODEV;
// 1. 根据配置来决定是否添加xxx_xxx_driver到数组rockchip_sub_drivers
num_rockchip_sub_drivers = 0;
ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_ROCKCHIP_VOP);
......
// 2. 注册多个platform driver
ret = platform_register_drivers(rockchip_sub_drivers,
num_rockchip_sub_drivers);
if (ret)
return ret;
// 3. 注册rockchip_drm_platform_driver
ret = platform_driver_register(&rockchip_drm_platform_driver);
if (ret)
goto err_unreg_drivers;
return 0;
......
}
其中:
ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_ROCKCHIP_VOP);
会将vop_platform_driver
保存到rockchip_sub_drivers
数组中。
并调用platform_register_drivers
遍历rockchip_sub_drivers
数组,多次调用platform_driver_register
注册platform driver
。
2.2 vop_platform_driver
vop_platform_driver
定义在drivers/gpu/drm/rockchip/rockchip_vop_reg.c
;
struct platform_driver vop_platform_driver = {
.probe = vop_probe,
.remove = vop_remove,
.driver = {
.name = "rockchip-vop",
.of_match_table = vop_driver_dt_match,
},
};
2.2.1 of_match_table
其中of_match_table
用于设备树匹配,匹配设备树中compatible = "rockchip,rk3399-vop-big"
或者compatible = "rockchip,rk3399-vop-lit"
的设备节点;
static const struct of_device_id vop_driver_dt_match[] = {
{ .compatible = "rockchip,rk3036-vop",
.data = &rk3036_vop },
{ .compatible = "rockchip,rk3126-vop",
.data = &rk3126_vop },
{ .compatible = "rockchip,px30-vop-big",
.data = &px30_vop_big },
{ .compatible = "rockchip,px30-vop-lit",
.data = &px30_vop_lit },
{ .compatible = "rockchip,rk3066-vop",
.data = &rk3066_vop },
{ .compatible = "rockchip,rk3188-vop",
.data = &rk3188_vop },
{ .compatible = "rockchip,rk3288-vop",
.data = &rk3288_vop },
{ .compatible = "rockchip,rk3368-vop",
.data = &rk3368_vop },
{ .compatible = "rockchip,rk3366-vop",
.data = &rk3366_vop },
{ .compatible = "rockchip,rk3399-vop-big",
.data = &rk3399_vop_big },
{ .compatible = "rockchip,rk3399-vop-lit",
.data = &rk3399_vop_lit },
{ .compatible = "rockchip,rk3228-vop",
.data = &rk3228_vop },
{ .compatible = "rockchip,rk3328-vop",
.data = &rk3328_vop },
{},
};
2.2.2 vop_probe
在plaftorm
总线设备驱动模型中,我们知道当内核中有platform
设备和platform
驱动匹配,会调用到platform_driver
里的成员.probe
,在这里就是vop_probe
函数;
static int vop_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
// 未使用设备树 退出
if (!dev->of_node) {
DRM_DEV_ERROR(dev, "can't find vop devices\n");
return -ENODEV;
}
return component_add(dev, &vop_component_ops);
}
这里代码很简单,就是为设备pdev->dev
向系统注册一个component
,其中组件可执行的初始化操作被设置为了vop_component_ops
,其定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c
;
const struct component_ops vop_component_ops = {
.bind = vop_bind,
.unbind = vop_unbind,
};
我们需要重点关注bind
函数的实现,这个函数内容较多我们单独小节介绍。
三、vop
相关数据结构
3.1 struct vop
struct vop
定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c
,用于表示显示处理器;
// 描述vop win
struct vop_win {
struct drm_plane base; // 对应的struct drm_plane数据结构
const struct vop_win_data *data; // vop win data
const struct vop_win_yuv2yuv_data *yuv2yuv_data;
struct vop *vop; // 指向vop
};
// 描述显示处理器vop
struct vop {
struct drm_crtc crtc;
struct device *dev;
struct drm_device *drm_dev;
bool is_enabled;
struct completion dsp_hold_completion;
unsigned int win_enabled;
/* protected by dev->event_lock */
struct drm_pending_vblank_event *event;
struct drm_flip_work fb_unref_work;
unsigned long pending;
struct completion line_flag_completion;
const struct vop_data *data;
uint32_t *regsbak;
void __iomem *regs;
void __iomem *lut_regs;
/* physical map length of vop register */
uint32_t len;
/* one time only one process allowed to config the register */
spinlock_t reg_lock;
/* lock vop irq reg */
spinlock_t irq_lock;
/* protects crtc enable/disable */
struct mutex vop_lock;
unsigned int irq;
/* vop AHP clk */
struct clk *hclk;
/* vop dclk */
struct clk *dclk;
/* vop share memory frequency */
struct clk *aclk;
/* vop dclk reset */
struct reset_control *dclk_rst;
/* optional internal rgb encoder */
struct rockchip_rgb *rgb;
struct vop_win win[];
};
其中:
crtc
:指向一个struct drm_crtc
;dev
:这是一个指向设备结构体的指针,通常用于表示与vop
关联的设备;data
:存储与vop
相关的数据;drm_dev
:指向一个struct drm_device
,表示当前drm
设备;regs
:vop
相关寄存器起始虚拟地址;len
:vop
相关寄存器所占内存长度,单位字节;regsbak
:指向一块内存,用于备份vop
相关寄存器;irq
:存储vop
中断IRQ
编号;hclk
、dclk
、aclk
:vop
相关的各种时钟;rgb
:这是一个用于管理RGB
编码器的结构体指针;win
:这是一个表示vop win
的结构体数组,比如RK399 vop
支持4个图层,因此该该数组有4个元素;
3.2 struct vop_data
struct vop_data
定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.h
,用于描述与vop
相关的数据,主要是一些寄存器信息;
struct vop_data {
uint32_t version;
const struct vop_intr *intr;
const struct vop_common *common;
const struct vop_misc *misc;
const struct vop_modeset *modeset; // 模式配置相关寄存器信息
const struct vop_output *output; // 输出配置相关寄存器信息
const struct vop_afbc *afbc;
const struct vop_win_yuv2yuv_data *win_yuv2yuv;
const struct vop_win_data *win; // vop win配置相关寄存器信息
unsigned int win_size; // vop win个数
unsigned int lut_size;
#define VOP_FEATURE_OUTPUT_RGB10 BIT(0)
#define VOP_FEATURE_INTERNAL_RGB BIT(1)
u64 feature;
};
以rk3399_vop_big
为例,定义在drivers/gpu/drm/rockchip/rockchip_vop_reg.c
;
static const struct vop_data rk3399_vop_big = {
.version = VOP_VERSION(3, 5),
.feature = VOP_FEATURE_OUTPUT_RGB10,
.intr = &rk3366_vop_intr,
.common = &rk3399_common,
.modeset = &rk3288_modeset, // 模式配置相关寄存器信息
.output = &rk3399_output, // 输出配置相关寄存器信息
.afbc = &rk3399_vop_afbc,
.misc = &rk3368_misc,
.win = rk3399_vop_win_data, // vop win配置相关寄存器信息
.win_size = ARRAY_SIZE(rk3399_vop_win_data), // vop win个数
.win_yuv2yuv = rk3399_vop_big_win_yuv2yuv_data,
.lut_size = 1024,
};
其中rk3366_vop_intr
、rk3288_modeset
,rk3399_output
、rk3399_common
等配置了大量的寄存器信息。
3.2.1 rk3288_modeset
rk3288_modeset
定义如下:
static const struct vop_modeset rk3288_modeset = {
.htotal_pw = VOP_REG(RK3288_DSP_HTOTAL_HS_END, 0x1fff1fff, 0), // horizontal total size配置
.hact_st_end = VOP_REG(RK3288_DSP_HACT_ST_END, 0x1fff1fff, 0),
.vtotal_pw = VOP_REG(RK3288_DSP_VTOTAL_VS_END, 0x1fff1fff, 0), // vertical total size配置
.vact_st_end = VOP_REG(RK3288_DSP_VACT_ST_END, 0x1fff1fff, 0),
.hpost_st_end = VOP_REG(RK3288_POST_DSP_HACT_INFO, 0x1fff1fff, 0),
.vpost_st_end = VOP_REG(RK3288_POST_DSP_VACT_INFO, 0x1fff1fff, 0),
};
VOP_REG
用于定义寄存器信息:
#define _VOP_REG(off, _mask, _shift, _write_mask, _relaxed) \
{ \
.offset = off, \
.mask = _mask, \
.shift = _shift, \
.write_mask = _write_mask, \
.relaxed = _relaxed, \
}
#define VOP_REG(off, _mask, _shift) \
_VOP_REG(off, _mask, _shift, false, true)
3.2.2 rk3399_common
rk3399_common
定义如下:
static const struct vop_common rk3399_common = {
.standby = VOP_REG_SYNC(RK3399_SYS_CTRL, 0x1, 22),
.gate_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 23),
.mmu_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 20),
.dither_down_sel = VOP_REG(RK3399_DSP_CTRL1, 0x1, 4),
.dither_down_mode = VOP_REG(RK3399_DSP_CTRL1, 0x1, 3),
.dither_down_en = VOP_REG(RK3399_DSP_CTRL1, 0x1, 2),
.pre_dither_down = VOP_REG(RK3399_DSP_CTRL1, 0x1, 1),
.dither_up = VOP_REG(RK3399_DSP_CTRL1, 0x1, 6),
.dsp_lut_en = VOP_REG(RK3399_DSP_CTRL1, 0x1, 0),
.update_gamma_lut = VOP_REG(RK3399_DSP_CTRL1, 0x1, 7),
.lut_buffer_index = VOP_REG(RK3399_DBG_POST_REG1, 0x1, 1),
.data_blank = VOP_REG(RK3399_DSP_CTRL0, 0x1, 19),
.dsp_blank = VOP_REG(RK3399_DSP_CTRL0, 0x3, 18),
.out_mode = VOP_REG(RK3399_DSP_CTRL0, 0xf, 0),
.cfg_done = VOP_REG_SYNC(RK3399_REG_CFG_DONE, 0x1, 0),
};
3.2.3 rk3399_vop_wn_data
rk3399_vop_wn_data
定义如下:
static const struct vop_win_phy rk3399_win01_data = {
.scl = &rk3288_win_full_scl,
.data_formats = formats_win_full,
.nformats = ARRAY_SIZE(formats_win_full),
.format_modifiers = format_modifiers_win_full_afbc,
.enable = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 0),
.format = VOP_REG(RK3288_WIN0_CTRL0, 0x7, 1),
.rb_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 12),
.uv_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 15),
.x_mir_en = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 21), // 水平方向上反转(翻转)使能
.y_mir_en = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 22), // 垂直方向上反转(翻转)使能
.act_info = VOP_REG(RK3288_WIN0_ACT_INFO, 0x1fff1fff, 0),
.dsp_info = VOP_REG(RK3288_WIN0_DSP_INFO, 0x0fff0fff, 0),
.dsp_st = VOP_REG(RK3288_WIN0_DSP_ST, 0x1fff1fff, 0),
.yrgb_mst = VOP_REG(RK3288_WIN0_YRGB_MST, 0xffffffff, 0),
.uv_mst = VOP_REG(RK3288_WIN0_CBR_MST, 0xffffffff, 0),
.yrgb_vir = VOP_REG(RK3288_WIN0_VIR, 0x3fff, 0),
.uv_vir = VOP_REG(RK3288_WIN0_VIR, 0x3fff, 16),
.src_alpha_ctl = VOP_REG(RK3288_WIN0_SRC_ALPHA_CTRL, 0xff, 0),
.dst_alpha_ctl = VOP_REG(RK3288_WIN0_DST_ALPHA_CTRL, 0xff, 0),
.channel = VOP_REG(RK3288_WIN0_CTRL2, 0xff, 0),
};
......
/*
* rk3399 vop big windows register layout is same as rk3288, but we
* have a separate rk3399 win data array here so that we can advertise
* AFBC on the primary plane.
*/
static const struct vop_win_data rk3399_vop_win_data[] = {
{ .base = 0x00, .phy = &rk3399_win01_data,
.type = DRM_PLANE_TYPE_PRIMARY }, // primary plane
{ .base = 0x40, .phy = &rk3368_win01_data,
.type = DRM_PLANE_TYPE_OVERLAY }, // overlay plane
{ .base = 0x00, .phy = &rk3368_win23_data,
.type = DRM_PLANE_TYPE_OVERLAY }, // overlay plane
{ .base = 0x50, .phy = &rk3368_win23_data,
.type = DRM_PLANE_TYPE_CURSOR }, // cursor plane
};
RK3399
其vop
包含4个图层,因此这里存放就是RK3399
的4个图层配置相关寄存器的信息。
3.2.4 rk3399_vop_big_win_yuv2yuv_data
rk3399_vop_big_win_yuv2yuv_data
定义如下:
static const struct vop_win_yuv2yuv_data rk3399_vop_big_win_yuv2yuv_data[] = {
{ .base = 0x00, .phy = &rk3399_yuv2yuv_win01_data,
.y2r_en = VOP_REG(RK3399_YUV2YUV_WIN, 0x1, 1) },
{ .base = 0x60, .phy = &rk3399_yuv2yuv_win01_data,
.y2r_en = VOP_REG(RK3399_YUV2YUV_WIN, 0x1, 9) },
{ .base = 0xC0, .phy = &rk3399_yuv2yuv_win23_data },
{ .base = 0x120, .phy = &rk3399_yuv2yuv_win23_data },
};
3.3 rk3399_output
rk3399_output
定义如下:
static const struct vop_output rk3399_output = {
.dp_dclk_pol = VOP_REG(RK3399_DSP_CTRL1, 0x1, 19),
.rgb_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 19),
.hdmi_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 23),
.edp_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 27),
.mipi_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 31),
// 接口引脚极性相关寄存器位
.dp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0x7, 16),
.rgb_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 16),
.hdmi_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 20),
.edp_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 24),
.mipi_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 28),
// 接口使能相关寄存器位
.dp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 11), // dp接口输出使能
.rgb_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 12), // rgb接口输出使能
.hdmi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 13), // hdmi接口输出使能
.edp_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 14), // edp接口输出使能
.mipi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 15), // mipi接口输出使能
.mipi_dual_channel_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 3), // mipi1接口输出使能
};
这里以如下代码为例:
#define RK3288_SYS_CTRL 0x0008
.hdmi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 13),
其中:
RK3288_SYS_CTRL
为offset
,表示寄存器偏移,相对vop
基址,0x1
为mask
:表示掩码;13
为shift
,表示偏移位;
因此,总结下来就是地址为0xff900000+0x0008
=0xff900008
的寄存器的位13用于hdmi
接口的使能。
不幸的是在RK3399 datasheet
没有找到与vop
相关的任何信息,我猜测Rockchip
应该是vop
这部分并不对用户开放。
不过我们可以通过drivers/gpu/drm/rockchip/rockchip_vop_reg.h
中的宏定义猜测出vop
包含了哪些寄存器;
/* rk3399 register definition */
#define RK3399_REG_CFG_DONE 0x0000
#define RK3399_VERSION_INFO 0x0004
#define RK3399_SYS_CTRL 0x0008
#define RK3399_SYS_CTRL1 0x000c
#define RK3399_DSP_CTRL0 0x0010
#define RK3399_DSP_CTRL1 0x0014
#define RK3399_DSP_BG 0x0018
#define RK3399_MCU_CTRL 0x001c
#define RK3399_WB_CTRL0 0x0020
#define RK3399_WB_CTRL1 0x0024
#define RK3399_WB_YRGB_MST 0x0028
#define RK3399_WB_CBR_MST 0x002c
#define RK3399_WIN0_CTRL0 0x0030
#define RK3399_WIN0_CTRL1 0x0034
#define RK3399_WIN0_COLOR_KEY 0x0038
#define RK3399_WIN0_VIR 0x003c
#define RK3399_WIN0_YRGB_MST 0x0040
#define RK3399_WIN0_CBR_MST 0x0044
#define RK3399_WIN0_ACT_INFO 0x0048
#define RK3399_WIN0_DSP_INFO 0x004c
#define RK3399_WIN0_DSP_ST 0x0050
#define RK3399_WIN0_SCL_FACTOR_YRGB 0x0054
#define RK3399_WIN0_SCL_FACTOR_CBR 0x0058
#define RK3399_WIN0_SCL_OFFSET 0x005c
#define RK3399_WIN0_SRC_ALPHA_CTRL 0x0060
#define RK3399_WIN0_DST_ALPHA_CTRL 0x0064
#define RK3399_WIN0_FADING_CTRL 0x0068
#define RK3399_WIN0_CTRL2 0x006c
#define RK3399_WIN1_CTRL0 0x0070
#define RK3399_WIN1_CTRL1 0x0074
#define RK3399_WIN1_COLOR_KEY 0x0078
#define RK3399_WIN1_VIR 0x007c
#define RK3399_WIN1_YRGB_MST 0x0080
#define RK3399_WIN1_CBR_MST 0x0084
#define RK3399_WIN1_ACT_INFO 0x0088
#define RK3399_WIN1_DSP_INFO 0x008c
#define RK3399_WIN1_DSP_ST 0x0090
#define RK3399_WIN1_SCL_FACTOR_YRGB 0x0094
#define RK3399_WIN1_SCL_FACTOR_CBR 0x0098
#define RK3399_WIN1_SCL_OFFSET 0x009c
#define RK3399_WIN1_SRC_ALPHA_CTRL 0x00a0
#define RK3399_WIN1_DST_ALPHA_CTRL 0x00a4
#define RK3399_WIN1_FADING_CTRL 0x00a8
#define RK3399_WIN1_CTRL2 0x00ac
#define RK3399_WIN2_CTRL0 0x00b0
#define RK3399_WIN2_CTRL1 0x00b4
#define RK3399_WIN2_VIR0_1 0x00b8
#define RK3399_WIN2_VIR2_3 0x00bc
#define RK3399_WIN2_MST0 0x00c0
#define RK3399_WIN2_DSP_INFO0 0x00c4
#define RK3399_WIN2_DSP_ST0 0x00c8
#define RK3399_WIN2_COLOR_KEY 0x00cc
#define RK3399_WIN2_MST1 0x00d0
#define RK3399_WIN2_DSP_INFO1 0x00d4
#define RK3399_WIN2_DSP_ST1 0x00d8
#define RK3399_WIN2_SRC_ALPHA_CTRL 0x00dc
#define RK3399_WIN2_MST2 0x00e0
#define RK3399_WIN2_DSP_INFO2 0x00e4
#define RK3399_WIN2_DSP_ST2 0x00e8
#define RK3399_WIN2_DST_ALPHA_CTRL 0x00ec
#define RK3399_WIN2_MST3 0x00f0
#define RK3399_WIN2_DSP_INFO3 0x00f4
#define RK3399_WIN2_DSP_ST3 0x00f8
#define RK3399_WIN2_FADING_CTRL 0x00fc
#define RK3399_WIN3_CTRL0 0x0100
#define RK3399_WIN3_CTRL1 0x0104
#define RK3399_WIN3_VIR0_1 0x0108
#define RK3399_WIN3_VIR2_3 0x010c
#define RK3399_WIN3_MST0 0x0110
#define RK3399_WIN3_DSP_INFO0 0x0114
#define RK3399_WIN3_DSP_ST0 0x0118
#define RK3399_WIN3_COLOR_KEY 0x011c
#define RK3399_WIN3_MST1 0x0120
#define RK3399_WIN3_DSP_INFO1 0x0124
#define RK3399_WIN3_DSP_ST1 0x0128
#define RK3399_WIN3_SRC_ALPHA_CTRL 0x012c
#define RK3399_WIN3_MST2 0x0130
#define RK3399_WIN3_DSP_INFO2 0x0134
#define RK3399_WIN3_DSP_ST2 0x0138
#define RK3399_WIN3_DST_ALPHA_CTRL 0x013c
#define RK3399_WIN3_MST3 0x0140
#define RK3399_WIN3_DSP_INFO3 0x0144
#define RK3399_WIN3_DSP_ST3 0x0148
#define RK3399_WIN3_FADING_CTRL 0x014c
#define RK3399_HWC_CTRL0 0x0150
#define RK3399_HWC_CTRL1 0x0154
#define RK3399_HWC_MST 0x0158
#define RK3399_HWC_DSP_ST 0x015c
#define RK3399_HWC_SRC_ALPHA_CTRL 0x0160
#define RK3399_HWC_DST_ALPHA_CTRL 0x0164
#define RK3399_HWC_FADING_CTRL 0x0168
#define RK3399_HWC_RESERVED1 0x016c
#define RK3399_POST_DSP_HACT_INFO 0x0170
#define RK3399_POST_DSP_VACT_INFO 0x0174
#define RK3399_POST_SCL_FACTOR_YRGB 0x0178
#define RK3399_POST_RESERVED 0x017c
#define RK3399_POST_SCL_CTRL 0x0180
#define RK3399_POST_DSP_VACT_INFO_F1 0x0184
#define RK3399_DSP_HTOTAL_HS_END 0x0188
#define RK3399_DSP_HACT_ST_END 0x018c
#define RK3399_DSP_VTOTAL_VS_END 0x0190
#define RK3399_DSP_VACT_ST_END 0x0194
#define RK3399_DSP_VS_ST_END_F1 0x0198
#define RK3399_DSP_VACT_ST_END_F1 0x019c
#define RK3399_PWM_CTRL 0x01a0
#define RK3399_PWM_PERIOD_HPR 0x01a4
#define RK3399_PWM_DUTY_LPR 0x01a8
#define RK3399_PWM_CNT 0x01ac
#define RK3399_BCSH_COLOR_BAR 0x01b0
#define RK3399_BCSH_BCS 0x01b4
#define RK3399_BCSH_H 0x01b8
#define RK3399_BCSH_CTRL 0x01bc
#define RK3399_CABC_CTRL0 0x01c0
#define RK3399_CABC_CTRL1 0x01c4
#define RK3399_CABC_CTRL2 0x01c8
#define RK3399_CABC_CTRL3 0x01cc
#define RK3399_CABC_GAUSS_LINE0_0 0x01d0
#define RK3399_CABC_GAUSS_LINE0_1 0x01d4
#define RK3399_CABC_GAUSS_LINE1_0 0x01d8
#define RK3399_CABC_GAUSS_LINE1_1 0x01dc
#define RK3399_CABC_GAUSS_LINE2_0 0x01e0
#define RK3399_CABC_GAUSS_LINE2_1 0x01e4
#define RK3399_FRC_LOWER01_0 0x01e8
#define RK3399_FRC_LOWER01_1 0x01ec
#define RK3399_FRC_LOWER10_0 0x01f0
#define RK3399_FRC_LOWER10_1 0x01f4
#define RK3399_FRC_LOWER11_0 0x01f8
#define RK3399_FRC_LOWER11_1 0x01fc
#define RK3399_AFBCD0_CTRL 0x0200
#define RK3399_AFBCD0_HDR_PTR 0x0204
#define RK3399_AFBCD0_PIC_SIZE 0x0208
#define RK3399_AFBCD0_STATUS 0x020c
#define RK3399_AFBCD1_CTRL 0x0220
#define RK3399_AFBCD1_HDR_PTR 0x0224
#define RK3399_AFBCD1_PIC_SIZE 0x0228
#define RK3399_AFBCD1_STATUS 0x022c
#define RK3399_AFBCD2_CTRL 0x0240
#define RK3399_AFBCD2_HDR_PTR 0x0244
#define RK3399_AFBCD2_PIC_SIZE 0x0248
#define RK3399_AFBCD2_STATUS 0x024c
#define RK3399_AFBCD3_CTRL 0x0260
#define RK3399_AFBCD3_HDR_PTR 0x0264
#define RK3399_AFBCD3_PIC_SIZE 0x0268
#define RK3399_AFBCD3_STATUS 0x026c
#define RK3399_INTR_EN0 0x0280
#define RK3399_INTR_CLEAR0 0x0284
#define RK3399_INTR_STATUS0 0x0288
#define RK3399_INTR_RAW_STATUS0 0x028c
#define RK3399_INTR_EN1 0x0290
#define RK3399_INTR_CLEAR1 0x0294
#define RK3399_INTR_STATUS1 0x0298
#define RK3399_INTR_RAW_STATUS1 0x029c
#define RK3399_LINE_FLAG 0x02a0
#define RK3399_VOP_STATUS 0x02a4
#define RK3399_BLANKING_VALUE 0x02a8
#define RK3399_MCU_BYPASS_PORT 0x02ac
#define RK3399_WIN0_DSP_BG 0x02b0
#define RK3399_WIN1_DSP_BG 0x02b4
#define RK3399_WIN2_DSP_BG 0x02b8
#define RK3399_WIN3_DSP_BG 0x02bc
#define RK3399_YUV2YUV_WIN 0x02c0
#define RK3399_YUV2YUV_POST 0x02c4
#define RK3399_AUTO_GATING_EN 0x02cc
#define RK3399_DBG_POST_REG1 0x036c
#define RK3399_WIN0_CSC_COE 0x03a0
#define RK3399_WIN1_CSC_COE 0x03c0
#define RK3399_WIN2_CSC_COE 0x03e0
#define RK3399_WIN3_CSC_COE 0x0400
#define RK3399_HWC_CSC_COE 0x0420
#define RK3399_BCSH_R2Y_CSC_COE 0x0440
#define RK3399_BCSH_Y2R_CSC_COE 0x0460
#define RK3399_POST_YUV2YUV_Y2R_COE 0x0480
#define RK3399_POST_YUV2YUV_3X3_COE 0x04a0
#define RK3399_POST_YUV2YUV_R2Y_COE 0x04c0
#define RK3399_WIN0_YUV2YUV_Y2R 0x04e0
#define RK3399_WIN0_YUV2YUV_3X3 0x0500
#define RK3399_WIN0_YUV2YUV_R2Y 0x0520
#define RK3399_WIN1_YUV2YUV_Y2R 0x0540
#define RK3399_WIN1_YUV2YUV_3X3 0x0560
#define RK3399_WIN1_YUV2YUV_R2Y 0x0580
#define RK3399_WIN2_YUV2YUV_Y2R 0x05a0
#define RK3399_WIN2_YUV2YUV_3X3 0x05c0
#define RK3399_WIN2_YUV2YUV_R2Y 0x05e0
#define RK3399_WIN3_YUV2YUV_Y2R 0x0600
#define RK3399_WIN3_YUV2YUV_3X3 0x0620
#define RK3399_WIN3_YUV2YUV_R2Y 0x0640
#define RK3399_WIN2_LUT_ADDR 0x1000
#define RK3399_WIN3_LUT_ADDR 0x1400
#define RK3399_HWC_LUT_ADDR 0x1800
#define RK3399_CABC_GAMMA_LUT_ADDR 0x1c00
#define RK3399_GAMMA_LUT_ADDR 0x2000
四、vop_bind
vop_bind
函数定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c
,涉及到对vop
设备节点的解析,咱们以vopb
设备节点为例进行分析;
static int vop_bind(struct device *dev, struct device *master, void *data)
{
// 1. 获取platform_device
struct platform_device *pdev = to_platform_device(dev);
const struct vop_data *vop_data;
struct drm_device *drm_dev = data;
struct vop *vop;
struct resource *res;
int ret, irq;
// 2. 得到rk3399_vop_big
vop_data = of_device_get_match_data(dev);
if (!vop_data)
return -ENODEV;
/* Allocate vop struct and its vop_win array,初始化vop win */
vop = devm_kzalloc(dev, struct_size(vop, win, vop_data->win_size),
GFP_KERNEL);
if (!vop)
return -ENOMEM;
vop->dev = dev;
vop->data = vop_data;
vop->drm_dev = drm_dev;
// 设置驱动数据为vop
dev_set_drvdata(dev, vop);
// 3. 初始化vop win
vop_win_init(vop);
// 4. 获取第一个内存资源,即<0x0 0xff900000 0x0 0x2000>; VOP_BIG相关寄存器基地址
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
// 将VOP_BIG相关寄存器起始物理地址映射到虚拟地址,并返回虚拟地址
vop->regs = devm_ioremap_resource(dev, res);
if (IS_ERR(vop->regs))
return PTR_ERR(vop->regs);
vop->len = resource_size(res);
// 5. 获取第二个内存资源,即<0x0 0xff902000 0x0 0x1000>; LUT相关寄存器基地址
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (res) {
if (vop_data->lut_size != 1024 && vop_data->lut_size != 256) {
DRM_DEV_ERROR(dev, "unsupported gamma LUT size %d\n", vop_data->lut_size);
return -EINVAL;
}
// LUT相关寄存器起始物理地址映射到虚拟地址,并返回虚拟地址
vop->lut_regs = devm_ioremap_resource(dev, res);
if (IS_ERR(vop->lut_regs))
return PTR_ERR(vop->lut_regs);
}
vop->regsbak = devm_kzalloc(dev, vop->len, GFP_KERNEL);
if (!vop->regsbak)
return -ENOMEM;
// 6. 获取第1个IRQ编号 interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH 0>;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
DRM_DEV_ERROR(dev, "cannot find irq for vop\n");
return irq;
}
vop->irq = (unsigned int)irq;
// 初始化自旋锁和互斥锁
spin_lock_init(&vop->reg_lock);
spin_lock_init(&vop->irq_lock);
mutex_init(&vop->vop_lock);
// 7. 创建crtc对象,并和plane关联在一起
ret = vop_create_crtc(vop);
if (ret)
return ret;
// 电源管理相关
pm_runtime_enable(&pdev->dev);
// 8. vop初始化
ret = vop_initial(vop);
if (ret < 0) {
DRM_DEV_ERROR(&pdev->dev,
"cannot initial vop dev - err %d\n", ret);
goto err_disable_pm_runtime;
}
// 9. 申请中断,中断处理函数设置为vop_isr
ret = devm_request_irq(dev, vop->irq, vop_isr,
IRQF_SHARED, dev_name(dev), vop);
if (ret)
goto err_disable_pm_runtime;
// 未设置
if (vop->data->feature & VOP_FEATURE_INTERNAL_RGB) {
vop->rgb = rockchip_rgb_init(dev, &vop->crtc, vop->drm_dev);
if (IS_ERR(vop->rgb)) {
ret = PTR_ERR(vop->rgb);
goto err_disable_pm_runtime;
}
}
// 10. dma初始化
rockchip_drm_dma_init_device(drm_dev, dev);
return 0;
err_disable_pm_runtime:
pm_runtime_disable(&pdev->dev);
vop_destroy_crtc(vop);
return ret;
}
这里我们以设备节点vopb
为例,对这段代码进行分析,主要流程如下:
(1) 调用to_platform_device
获取platform device
,设备节点为vopb
;
(2) 调用of_device_get_match_data
获取与特定设备匹配的数据,这里获取到的数据为rk3399_vop_big
;
(3) 调用vop_win_init
初始化vop win
;
(4) 首先调用platform_get_resource(pdev, IORESOURCE_MEM, 0)
获取第一个内存资源,即:
<0x0 0xff900000 0x0 0x2000>
0xff900000
为VOP_BIG
相关寄存器基地址;
接着调用devm_ioremap_resource(dev, res)
将VOP_BIG
相关寄存器起始物理地址映射到虚拟地址,并返回虚拟地址;
(5) 调用platform_get_resource(pdev, IORESOURCE_MEM, 1)
获取第二个内存资源,即:
<0x0 0xff902000 0x0 0x1000>
0xff902000
为LUT
相关寄存器基地址;
接着调用devm_ioremap_resource(dev, res)
将LUT
相关寄存器起始物理地址映射到虚拟地址,并返回虚拟地址;
(6) 调用platform_get_irq(pdev, 0)
获取第1个IRQ
编号:
interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH 0>
(7) 调用vop_create_crtc(vop)
初始化crtc
对象,并和plane
关联在一起;
(8) 调用vop_initial(vop)
进行vop
初始化;
(9) 调用devm_request_irq(dev, vop->irq, vop_isr, IRQF_SHARED, dev_name(dev), vop)
申请中断,中断处理函数设置为vop_isr
;
(10) 调用rockchip_drm_dma_init_device(drm_dev, dev)
进行dma
相关的初始化工作;
4.1 vo_win_init
vo_win_init
用于初始化vop win
,那么什么是vop win
,指定就是vop
图层,以RK3399
其vop
包含4个图层;
/*
* Initialize the vop->win array elements.
*/
static void vop_win_init(struct vop *vop)
{
const struct vop_data *vop_data = vop->data;
unsigned int i;
// 遍历每一个图层
for (i = 0; i < vop_data->win_size; i++) {
// 获取第i个vop win
struct vop_win *vop_win = &vop->win[i];
// 获取第i个vop win配置相关寄存器信息
const struct vop_win_data *win_data = &vop_data->win[i];
// 初始化成员
vop_win->data = win_data;
vop_win->vop = vop;
// rk3399定义了win_yuv2yuv,因此初始化yuv2yuv_data
if (vop_data->win_yuv2yuv)
vop_win->yuv2yuv_data = &vop_data->win_yuv2yuv[i];
}
}
4.2 vop_create_crtc
vop_create_crtc
用于初始化crtc
对象,并和plane
关联在一起,这里我们仍然以设备节点vopb
为例进行分析;
static int vop_create_crtc(struct vop *vop)
{
// 获取vop data
const struct vop_data *vop_data = vop->data;
// 获取device,对应的设备节点为vopb
struct device *dev = vop->dev;
// 获取drm device
struct drm_device *drm_dev = vop->drm_dev;
struct drm_plane *primary = NULL, *cursor = NULL, *plane, *tmp;
// 获取drm crtc
struct drm_crtc *crtc = &vop->crtc;
struct device_node *port;
int ret;
int i;
/*
* Create drm_plane for primary and cursor planes first, since we need
* to pass them to drm_crtc_init_with_planes, which sets the
* "possible_crtcs" to the newly initialized crtc.
* 1. 遍历每一个vop win,每个vop win内部包含一个drm_plane,对类型为primary和cursor plane
* 进行初始化
*/
for (i = 0; i < vop_data->win_size; i++) {
// 获取第i个vop win
struct vop_win *vop_win = &vop->win[i];
// 获取第i个vop win data
const struct vop_win_data *win_data = vop_win->data;
// 只处理primary和cursor plane
if (win_data->type != DRM_PLANE_TYPE_PRIMARY &&
win_data->type != DRM_PLANE_TYPE_CURSOR)
continue;
// 进行plane的初始化,其中funcs被设置为vop_plane_funcs
ret = drm_universal_plane_init(vop->drm_dev, // drm设备
&vop_win->base, // 要初始化的plane对象
0, // 可能的CRTCs的位掩码
&vop_plane_funcs, // plane的控制函数集合
win_data->phy->data_formats, // 支持的格式数组(DRM_FORMAT_*)
win_data->phy->nformats, // formats数组的长度
win_data->phy->format_modifiers,
win_data->type, // plane的类型
NULL);
if (ret) {
DRM_DEV_ERROR(vop->dev, "failed to init plane %d\n",
ret);
goto err_cleanup_planes;
}
// 获取当前plane
plane = &vop_win->base;
// 设置plane的辅助函数helper_private为plane_helper_funcs
drm_plane_helper_add(plane, &plane_helper_funcs);
// 如果vop win data配置了x_mir_en/y_mir_en,则调用drm_plane_create_rotation_property为plane附加rotation property
vop_plane_add_properties(plane, win_data);
// 保存primary plane
if (plane->type == DRM_PLANE_TYPE_PRIMARY)
primary = plane;
// 保存cursor plane
else if (plane->type == DRM_PLANE_TYPE_CURSOR)
cursor = plane;
}
// 2. 使用指定的primary and cursor planes初始化的crtc对象,其中crtc回调函数funcs设置为vop_crtc_funcs
ret = drm_crtc_init_with_planes(drm_dev, crtc, primary, cursor,
&vop_crtc_funcs, NULL);
if (ret)
goto err_cleanup_planes;
// 3. 设置crtc的辅助函数helper_private为vop_crtc_helper_funcs
drm_crtc_helper_add(crtc, &vop_crtc_helper_funcs);
// 进入
if (vop->lut_regs) {
drm_mode_crtc_set_gamma_size(crtc, vop_data->lut_size);
drm_crtc_enable_color_mgmt(crtc, 0, false, vop_data->lut_size);
}
/*
* Create drm_planes for overlay windows with possible_crtcs restricted
* to the newly created crtc.
* 4. 遍历每一个vop win,每个vop win内部包含一个drm_plane,对类型为overlay plane
* 进行初始化
*/
for (i = 0; i < vop_data->win_size; i++) {
// 获取第i个vop win
struct vop_win *vop_win = &vop->win[i];
// 获取第i个vop win data
const struct vop_win_data *win_data = vop_win->data;
unsigned long possible_crtcs = drm_crtc_mask(crtc);
// 只处理overlay plane
if (win_data->type != DRM_PLANE_TYPE_OVERLAY)
continue;
// 进行plane的初始化,其中funcs被设置为vop_plane_funcs
ret = drm_universal_plane_init(vop->drm_dev, &vop_win->base,
possible_crtcs,
&vop_plane_funcs,
win_data->phy->data_formats,
win_data->phy->nformats,
win_data->phy->format_modifiers,
win_data->type, NULL);
if (ret) {
DRM_DEV_ERROR(vop->dev, "failed to init overlay %d\n",
ret);
goto err_cleanup_crtc;
}
// 设置plane的辅助函数helper_private为plane_helper_funcs
drm_plane_helper_add(&vop_win->base, &plane_helper_funcs);
// 如果vop win data配置了x_mir_en/y_mir_en,则调用drm_plane_create_rotation_property为plane附加rotation property
vop_plane_add_properties(&vop_win->base, win_data);
}
// 5. 从vopb节点的子节点列表中查找名为port的子节点,也就是vopb_out节点
port = of_get_child_by_name(dev->of_node, "port");
if (!port) {
DRM_DEV_ERROR(vop->dev, "no port node found in %pOF\n",
dev->of_node);
ret = -ENOENT;
goto err_cleanup_crtc;
}
// 6. 初始化工作队列
drm_flip_work_init(&vop->fb_unref_work, "fb_unref",
vop_fb_unref_worker);
// 初始化完成量,
init_completion(&vop->dsp_hold_completion);
init_completion(&vop->line_flag_completion);
// 设置port节点
crtc->port = port;
// 7. 对crtc进行自刷新相关的辅助函数初始化
ret = drm_self_refresh_helper_init(crtc);
if (ret)
DRM_DEV_DEBUG_KMS(vop->dev,
"Failed to init %s with SR helpers %d, ignoring\n",
crtc->name, ret);
return 0;
err_cleanup_crtc:
drm_crtc_cleanup(crtc);
err_cleanup_planes:
list_for_each_entry_safe(plane, tmp, &drm_dev->mode_config.plane_list,
head)
drm_plane_cleanup(plane);
return ret;
err_cleanup_crtc:
drm_crtc_cleanup(crtc);
err_cleanup_planes:
list_for_each_entry_safe(plane, tmp, &drm_dev->mode_config.plane_list,
head)
drm_plane_cleanup(plane);
return ret;
}
以下是该函数的主要步骤:
(1) 遍历每一个vop win
,每个vop win
内部包含一个drm_plane
,对类型为primary
和cursor plane
进行初始化;
具体是调用drm_universal_plane_init
来初始化drm_plane
,并且添加plane
辅助函数、设置属性等;
其中funcs
被设置为vop_plane_funcs
,定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c
;
static const struct drm_plane_funcs vop_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = vop_plane_destroy,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
.format_mod_supported = rockchip_mod_supported,
};
helper_private
被设为plane_helper_funcs
,定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c
;
static const struct drm_plane_helper_funcs plane_helper_funcs = {
.atomic_check = vop_plane_atomic_check,
.atomic_update = vop_plane_atomic_update,
.atomic_disable = vop_plane_atomic_disable,
.atomic_async_check = vop_plane_atomic_async_check,
.atomic_async_update = vop_plane_atomic_async_update,
};
(2) 调用drm_crtc_init_with_planes
使用指定的primary and cursor planes
初始化的crtc
对象,其中crtc
回调函数funcs
设置为vop_crtc_funcs
,定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c
;
static const struct drm_crtc_funcs vop_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.destroy = vop_crtc_destroy,
.reset = vop_crtc_reset,
.atomic_duplicate_state = vop_crtc_duplicate_state,
.atomic_destroy_state = vop_crtc_destroy_state,
.enable_vblank = vop_crtc_enable_vblank,
.disable_vblank = vop_crtc_disable_vblank,
.set_crc_source = vop_crtc_set_crc_source,
.verify_crc_source = vop_crtc_verify_crc_source,
};
(3) 调用drm_crtc_helper_add
设置crtc
的辅助函数helper_private
为vop_crtc_helper_funcs
,定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c
;
static const struct drm_crtc_helper_funcs vop_crtc_helper_funcs = {
.mode_fixup = vop_crtc_mode_fixup,
.atomic_check = vop_crtc_atomic_check,
.atomic_begin = vop_crtc_atomic_begin,
.atomic_flush = vop_crtc_atomic_flush,
.atomic_enable = vop_crtc_atomic_enable,
.atomic_disable = vop_crtc_atomic_disable,
};
(4) 遍历每一个vop win
,每个vop win
内部包含一个drm_plane
,对类型为overlay plane
进行初始化;
(5) 调用of_get_child_by_name
从vopb
节点的子节点列表中查找名为port
的子节点,也就是vopb_out
节点;
(6) 调用drm_flip_work_init(&vop->fb_unref_work, "fb_unref",vop_fb_unref_worker)
初始化工作队列;
函数drm_flip_work_init
定义在drivers/gpu/drm/drm_flip_work.c
:
static void flip_worker(struct work_struct *w)
{
struct drm_flip_work *work = container_of(w, struct drm_flip_work, worker);
struct list_head tasks;
unsigned long flags;
while (1) {
struct drm_flip_task *task, *tmp;
INIT_LIST_HEAD(&tasks);
spin_lock_irqsave(&work->lock, flags);
list_splice_tail(&work->commited, &tasks);
INIT_LIST_HEAD(&work->commited);
spin_unlock_irqrestore(&work->lock, flags);
if (list_empty(&tasks))
break;
list_for_each_entry_safe(task, tmp, &tasks, node) {
work->func(work, task->data);
kfree(task);
}
}
}
/**
* drm_flip_work_init - initialize flip-work
* @work: the flip-work to initialize
* @name: debug name
* @func: the callback work function
*
* Initializes/allocates resources for the flip-work
*/
void drm_flip_work_init(struct drm_flip_work *work,
const char *name, drm_flip_func_t func)
{
work->name = name;
INIT_LIST_HEAD(&work->queued);
INIT_LIST_HEAD(&work->commited);
spin_lock_init(&work->lock);
work->func = func;
// 设置work->worker工作函数为flip_worker
INIT_WORK(&work->worker, flip_worker);
}
(7) 调用drm_self_refresh_helper_init
对crtc
进行自刷新相关的辅助函数初始化;
drm_self_refresh_helper_init
定义在drivers/gpu/drm/drm_self_refresh_helper.c
;
/**
* drm_self_refresh_helper_init - Initializes self refresh helpers for a crtc
* @crtc: the crtc which supports self refresh supported displays
*
* Returns zero if successful or -errno on failure
*/
int drm_self_refresh_helper_init(struct drm_crtc *crtc)
{
struct drm_self_refresh_data *sr_data = crtc->self_refresh_data;
/* Helper is already initialized */
if (WARN_ON(sr_data))
return -EINVAL;
sr_data = kzalloc(sizeof(*sr_data), GFP_KERNEL);
if (!sr_data)
return -ENOMEM;
INIT_DELAYED_WORK(&sr_data->entry_work,
drm_self_refresh_helper_entry_work);
sr_data->crtc = crtc;
mutex_init(&sr_data->avg_mutex);
ewma_psr_time_init(&sr_data->entry_avg_ms);
ewma_psr_time_init(&sr_data->exit_avg_ms);
/*
* Seed the averages so they're non-zero (and sufficiently large
* for even poorly performing panels). As time goes on, this will be
* averaged out and the values will trend to their true value.
*/
ewma_psr_time_add(&sr_data->entry_avg_ms, SELF_REFRESH_AVG_SEED_MS);
ewma_psr_time_add(&sr_data->exit_avg_ms, SELF_REFRESH_AVG_SEED_MS);
crtc->self_refresh_data = sr_data;
return 0;
}
4.3 vop_initial
vop_initial
用户vop
初始化,定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c
;
static int vop_initial(struct vop *vop)
{
struct reset_control *ahb_rst;
int i, ret;
// 根据时钟名称hclk_vop获取时钟,设备节点属性clock-names、clocks,指定了名字为hclk_vop对应的时钟为<&cru HCLK_VOP0>
vop->hclk = devm_clk_get(vop->dev, "hclk_vop");
if (IS_ERR(vop->hclk)) {
DRM_DEV_ERROR(vop->dev, "failed to get hclk source\n");
return PTR_ERR(vop->hclk);
}
// 根据时钟名称aclk_vop获取时钟,设备节点属性clock-names、clocks,指定了名字为aclk_vop对应的时钟为<&cru ACLK_VOP0>
vop->aclk = devm_clk_get(vop->dev, "aclk_vop");
if (IS_ERR(vop->aclk)) {
DRM_DEV_ERROR(vop->dev, "failed to get aclk source\n");
return PTR_ERR(vop->aclk);
}
// 根据时钟名称dclk_vop获取时钟,设备节点属性clock-names、clocks,指定了名字为dclk_vop对应的时钟为<&cru DCLK_VOP0>
vop->dclk = devm_clk_get(vop->dev, "dclk_vop");
if (IS_ERR(vop->dclk)) {
DRM_DEV_ERROR(vop->dev, "failed to get dclk source\n");
return PTR_ERR(vop->dclk);
}
// 电源相关,,使能设备的runtime pm功能 暂且忽略
ret = pm_runtime_resume_and_get(vop->dev);
if (ret < 0) {
DRM_DEV_ERROR(vop->dev, "failed to get pm runtime: %d\n", ret);
return ret;
}
// dclk时钟准备,使其处于可用状态,但不启用它
ret = clk_prepare(vop->dclk);
if (ret < 0) {
DRM_DEV_ERROR(vop->dev, "failed to prepare dclk\n");
goto err_put_pm_runtime;
}
/* Enable both the hclk and aclk to setup the vop,hclk时钟准备和使能 */
ret = clk_prepare_enable(vop->hclk);
if (ret < 0) {
DRM_DEV_ERROR(vop->dev, "failed to prepare/enable hclk\n");
goto err_unprepare_dclk;
}
// aclk时钟准备和使能
ret = clk_prepare_enable(vop->aclk);
if (ret < 0) {
DRM_DEV_ERROR(vop->dev, "failed to prepare/enable aclk\n");
goto err_disable_hclk;
}
/*
* do hclk_reset, reset all vop registers. 获取ahb相应的reset句柄
*/
ahb_rst = devm_reset_control_get(vop->dev, "ahb");
if (IS_ERR(ahb_rst)) {
DRM_DEV_ERROR(vop->dev, "failed to get ahb reset\n");
ret = PTR_ERR(ahb_rst);
goto err_disable_aclk;
}
// 对传入的reset资源进行复位操作
reset_control_assert(ahb_rst);
// 睡眠,单位为微妙
usleep_range(10, 20);
// 对传入的reset资源进行解复位操作
reset_control_deassert(ahb_rst);
// 设置rk3399_vop_big.intr.clear所描述寄存器相应位的值
VOP_INTR_SET_TYPE(vop, clear, INTR_MASK, 1);
// 设置rk3399_vop_big.intr.enable所描述寄存器相应位的值
VOP_INTR_SET_TYPE(vop, enable, INTR_MASK, 0);
// 备份vop相关寄存器的值
for (i = 0; i < vop->len; i += sizeof(u32))
vop->regsbak[i / 4] = readl_relaxed(vop->regs + i);
// 设置rk3399_vop_big.misc.global_regdone_en所描述寄存器相应位的值
VOP_REG_SET(vop, misc, global_regdone_en, 1);
// 设置rk3399_vop_big.common.dsp_blank所描述寄存器相应位的值
VOP_REG_SET(vop, common, dsp_blank, 0);
// 遍历每一个vop win
for (i = 0; i < vop->data->win_size; i++) {
// 获取第i个vop win
struct vop_win *vop_win = &vop->win[i];
// 获取第i个vop win data
const struct vop_win_data *win = vop_win->data;
int channel = i * 2 + 1;
// 设置rk3399_vop_big.win[i].phy.channel所描述寄存器相应位的值
VOP_WIN_SET(vop, win, channel, (channel + 1) << 4 | channel);
// 禁止当前vop win
vop_win_disable(vop, vop_win);
// 设置rk3399_vop_big.win[i].phy.gate所描述寄存器相应位的值
VOP_WIN_SET(vop, win, gate, 1);
}
// 设置rk3399_vop_big.common.cfg_done所描述寄存器相应位的值,enable reg config
vop_cfg_done(vop);
/*
* do dclk_reset, let all config take affect. 获取dclk相应的reset句柄
*/
vop->dclk_rst = devm_reset_control_get(vop->dev, "dclk");
if (IS_ERR(vop->dclk_rst)) {
DRM_DEV_ERROR(vop->dev, "failed to get dclk reset\n");
ret = PTR_ERR(vop->dclk_rst);
goto err_disable_aclk;
}
// 对传入的reset资源进行复位操作
reset_control_assert(vop->dclk_rst);
// 睡眠,单位为微妙
usleep_range(10, 20);
// 对传入的reset资源进行解复位操作
reset_control_deassert(vop->dclk_rst);
// 禁止时钟hclk
clk_disable(vop->hclk);
// 禁止时钟aclk
clk_disable(vop->aclk);
// 使能标志位
vop->is_enabled = false;
pm_runtime_put_sync(vop->dev);
return 0;
err_disable_aclk:
clk_disable_unprepare(vop->aclk);
err_disable_hclk:
clk_disable_unprepare(vop->hclk);
err_unprepare_dclk:
clk_unprepare(vop->dclk);
err_put_pm_runtime:
pm_runtime_put_sync(vop->dev);
return ret;
}
该函数主要做了两件事件:
vop
相关时钟初始化:aclk_vop
、dclk_vop
、hclk_vop
;vop
相关寄存器配置,底层通过vop_reg_set
设置配置寄存器值;
4.3.1 VOP_REG_SET
VOP_REG_SET
定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c
,目的就是设置指定寄存器指定位的值;
static void vop_reg_set(struct vop *vop,
const struct vop_reg *reg, // 寄存器信息
uint32_t _offset, // 寄存器偏移 传入0
uint32_t _mask, // 掩码 传入0xffffffff
uint32_t v, // 值
const char *reg_name)
{
int offset, mask, shift;
// 参数校验
if (!reg || !reg->mask) {
DRM_DEV_DEBUG(vop->dev, "Warning: not support %s\n", reg_name);
return;
}
// 偏移位
offset = reg->offset + _offset;
// 掩码
mask = reg->mask & _mask;
shift = reg->shift;
if (reg->write_mask) { // false
v = ((v << shift) & 0xffff) | (mask << (shift + 16));
} else {
// 获取寄存器的值
uint32_t cached_val = vop->regsbak[offset >> 2];
// 计算新的值
v = (cached_val & ~(mask << shift)) | ((v & mask) << shift);
// 保存新值
vop->regsbak[offset >> 2] = v;
}
if (reg->relaxed) // true
// 写寄存器
writel_relaxed(v, vop->regs + offset);
else
writel(v, vop->regs + offset);
}
#define VOP_REG_SET(vop, group, name, v) \
vop_reg_set(vop, &vop->data->group->name, 0, ~0, v, #name)
比如VOP_REG_SET(vop, common, dsp_blank, 0);
实际上就是配置RK3399_DSP_CTRL0
寄存器位[19:18]
为0;
rk3399_vop_big.common.dsp_blankk = VOP_REG(RK3399_DSP_CTRL0, 0x3, 18)
4.3.2 VOP_INTR_SET_TYPE
VOP_INTR_SET_TYPE
定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c
,目的就是设置指定寄存器指定位的值;
#define VOP_INTR_SET_MASK(vop, name, mask, v) \
vop_reg_set(vop, &vop->data->intr->name, 0, mask, v, #name)
#define VOP_INTR_SET_TYPE(vop, name, type, v) \
do { \
int i, reg = 0, mask = 0; \
for (i = 0; i < vop->data->intr->nintrs; i++) { \
if (vop->data->intr->intrs[i] & type) { \
reg |= (v) << i; \
mask |= 1 << i; \
} \
} \
VOP_INTR_SET_MASK(vop, name, mask, reg); \
} while (0)
4.3.3 VOP_WIN_SET
VOP_WIN_SET
定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c
,目的就是设置指定寄存器指定位的值;
#define VOP_WIN_SET(vop, win, name, v) \
vop_reg_set(vop, &win->phy->name, win->base, ~0, v, #name)
4.4 vop_isr
vop_isr
为GIC_SPI
中断处理函数:
static irqreturn_t vop_isr(int irq, void *data)
{
struct vop *vop = data;
struct drm_crtc *crtc = &vop->crtc;
uint32_t active_irqs;
int ret = IRQ_NONE;
/*
* The irq is shared with the iommu. If the runtime-pm state of the
* vop-device is disabled the irq has to be targeted at the iommu.
*/
if (!pm_runtime_get_if_in_use(vop->dev))
return IRQ_NONE;
if (vop_core_clks_enable(vop)) {
DRM_DEV_ERROR_RATELIMITED(vop->dev, "couldn't enable clocks\n");
goto out;
}
/*
* interrupt register has interrupt status, enable and clear bits, we
* must hold irq_lock to avoid a race with enable/disable_vblank().
*/
spin_lock(&vop->irq_lock);
active_irqs = VOP_INTR_GET_TYPE(vop, status, INTR_MASK);
/* Clear all active interrupt sources */
if (active_irqs)
VOP_INTR_SET_TYPE(vop, clear, active_irqs, 1);
spin_unlock(&vop->irq_lock);
/* This is expected for vop iommu irqs, since the irq is shared */
if (!active_irqs)
goto out_disable;
if (active_irqs & DSP_HOLD_VALID_INTR) {
// 唤醒等待此特定complete事件的单个线程
complete(&vop->dsp_hold_completion);
active_irqs &= ~DSP_HOLD_VALID_INTR;
ret = IRQ_HANDLED;
}
if (active_irqs & LINE_FLAG_INTR) {
// 唤醒等待此特定complete事件的单个线程
complete(&vop->line_flag_completion);
active_irqs &= ~LINE_FLAG_INTR;
ret = IRQ_HANDLED;
}
if (active_irqs & FS_INTR) {
drm_crtc_handle_vblank(crtc);
vop_handle_vblank(vop);
active_irqs &= ~FS_INTR;
ret = IRQ_HANDLED;
}
/* Unhandled irqs are spurious. */
if (active_irqs)
DRM_DEV_ERROR(vop->dev, "Unknown VOP IRQs: %#02x\n",
active_irqs);
out_disable:
vop_core_clks_disable(vop);
out:
pm_runtime_put(vop->dev);
return ret;
}
4.5 rockchip_drm_dma_init_device
rockchip_drm_dma_init_device
定义在drivers/gpu/drm/rockchip/rockchip_drm_drv.c
;
void rockchip_drm_dma_init_device(struct drm_device *drm_dev,
struct device *dev)
{
struct rockchip_drm_private *private = drm_dev->dev_private;
if (!device_iommu_mapped(dev))
private->iommu_dev = ERR_PTR(-ENODEV);
else if (!private->iommu_dev)
private->iommu_dev = dev;
}
4.6 总结
经过上述的分析,我们大致了解到vop_bind
的主要功能;
-
解析
vopb
设备节点;- 获取
aclk_vop
,dclk_vop
,hclk_vop
时钟,并进行时钟准备和使能功能; - 获取
vop
中断,并申请vop
中断; - 获取
vopb
相关寄存器虚拟地址; - 获取
ahb
、dclk
复位句柄,并通过复位句柄进行设备的复位操作;
- 获取
-
初始化
drm_crtc
,其中:- 回调函数
funcs
设置为vop_crtc_funcs
; - 辅助函数
helper_private
设置为vop_crtc_helper_funcs
;
- 回调函数
-
由于
RK3399
有4个vop win
,每个vop win
内部包含一个drm_plane
,对drm_plane
进行初始化,其中:- 回调函数
funcs
设置为vop_plane_funcs
; - 辅助函数
helper_private
设置为plane_helper_funcs
;
- 回调函数
-
配置
vop
相关的寄存器,比如rk3399_vop_big.intr.clear
、rk3399_vop_big.intr.enable
等;
需要注意的是:vop_bind
中没有进行显示模式的配置,即显示的各种时序参数配置(与显示器息息相关)。
参考文章
[1] DRM (Direct Rendering Manager
)
[2] Wiki
: Direct Rendering Manager
[3] The DRM/KMS subsystem from a newbie’s point of view
[6] DRM
驱动程序开发(VKMS
)
[7] MIPI
自学笔记