RK3568平台(显示篇)DRM vop驱动程序分析

发布于:2024-08-02 ⋅ 阅读:(90) ⋅ 点赞:(0)

一.设备树配置

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>;
				};
		};
};

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;

二.驱动分析

const struct component_ops vop_component_ops = {
        .bind = vop_bind,
        .unbind = vop_unbind,
};

vop的驱动程序主要是从vop_bind开始,其定义在:drivers/gpu/drm/rockchip/rockchip_drm_vop.c

vop_bind函数涉及到对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>

0xff902000LUT相关寄存器基地址;

接着调用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相关的初始化工作;

vop_bind函数里面比较重要的几个函数:

vo_win_init用于初始化vop win,那么什么是vop win,指定就是vop图层。

vop_create_crtc用于初始化crtc对象,crtcFramebuffer中读取待显示的图像。

vop_initial用户vop初始化。

vo_win_init:初始化vop win。

/*
 * 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];
        }
}

vop_create_crtc:用于初始化crtc对象。

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,
};

(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)初始化工作队列;

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设置配置寄存器值;


网站公告

今日签到

点亮在社区的每一天
去签到

热门文章