一.设备树配置
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>
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
相关的初始化工作;
vop_bind
函数里面比较重要的几个函数:
vo_win_init
用于初始化vop win
,那么什么是vop win
,指定就是vop
图层。
vop_create_crtc
用于初始化crtc
对象,crtc
从Framebuffer
中读取待显示的图像。
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
,对类型为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,
};
(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)
初始化工作队列;
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
设置配置寄存器值;