程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Rockchip RK3399 - DRM vop驱动程序

----------------------------------------------------------------------------------------------------------------------------

开发板 :NanoPC-T4开发板
eMMC16GB
LPDDR34GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot2023.04
linux6.3
----------------------------------------------------------------------------------------------------------------------------

有关《Rockchip RK3399 - DRM crtc基础知识》我们已经在前面介绍过了,在linux内核中Rockchip VOP driver对应crtc drivercrtc负责连接planeencoder

这里我们介绍一下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编号;
  • hclkdclkaclkvop相关的各种时钟;
  • 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_intrrk3288_modesetrk3399_outputrk3399_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
};

RK3399vop包含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_CTRLoffset,表示寄存器偏移,相对vop基址,
  • 0x1mask:表示掩码;
  • 13shift,表示偏移位;

因此,总结下来就是地址为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>

0xff900000VOP_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图层,以RK3399vop包含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,对类型为primarycursor 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_privatevop_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_namevopb节点的子节点列表中查找名为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_initcrtc进行自刷新相关的辅助函数初始化;

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_vopdclk_vophclk_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_isrGIC_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相关寄存器虚拟地址;
    • 获取ahbdclk 复位句柄,并通过复位句柄进行设备的复位操作;
  • 初始化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.clearrk3399_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

[4] DRM驱动概念、组成、框架、源码分析

[5] linux驱动系列学习之DRM(十)

[6] DRM驱动程序开发(VKMS

[7] MIPI自学笔记

[8] Linux reset子系统和驱动实例

posted @ 2024-02-25 23:47  大奥特曼打小怪兽  阅读(164)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步