drm驱动学习(一)sunxi_drm初始化
平台 | 内核版本 | 文件系统 |
---|---|---|
全志T527 | linux 5.15 | buildroot |
文章目录
- 一、从设备驱动
- 1.1 sunxi_tcon_top_platform_driver
- 1.2 sunxi_de_platform_driver
- 1.3 sunxi_lvds_platform_driver
- 1.4 sunxi_tcon_platform_driver
- 二、 主设备驱动
- 三、初始化代码分析
- 3.1 __devm_drm_dev_alloc
- 3.2 drmm_mode_config_init
- 3.3 sunxi_drm_mode_config_init
- 3.4 sunxi_drm_property_create
- 3.5 get_boot_display_info
- 3.6 component_bind_all
- 3.7 dev_set_drvdata
- 3.8 drm_mode_config_reset
- 3.9 drm_kms_helper_poll_init
- 3.10 setup_bootloader_connecting_state
- 3.11 commit_init_connecting
- 3.12 drm_dev_register
- 3.13 sunxi_drm_procfs_init
现在基于全志的开发板来时讲解drm驱动。DRM 显示驱动加载使用的是Component 方法,Linux 内核中的 Component 框架 是一种用于管理复杂子系统中多个设备模块加载顺序的机制。它通过定义主设备(master)和从设备(component)之间的关系,确保在初始化过程中,所有依赖的组件都能按照正确的顺序加载和初始化。在 DRM 架构中,master 是一个显示子系统,而 component 是各个显示控制器、编码器等。在驱动文件sunxi_drm_drv.c文件的init函数中,先注册一系列的从设备驱动,也就是sunxi_drm_sub_drivers数组中的驱动,再注册主驱动。sunxi_drm_platform_driver。
一、从设备驱动
在驱动文件sunxi_drm_drv.c文件的init函数中调用sunxi_drm_register_drivers函数,在函数中使用for循环使用platform_driver_register函数注册sunxi_drm_sub_drivers数组的每一个成员,从设备驱动:
static struct platform_driver *sunxi_drm_sub_drivers[] = {DRV_PTR(sunxi_tcon_top_platform_driver, CONFIG_AW_DRM_TCON_TOP),DRV_PTR(sunxi_de_platform_driver, CONFIG_AW_DRM_DE),DRV_PTR(sunxi_dsi_combo_phy_platform_driver, CONFIG_AW_DRM_DSI_COMBOPHY),DRV_PTR(sunxi_dsi_platform_driver, CONFIG_AW_DRM_DSI),DRV_PTR(sunxi_lvds_platform_driver, CONFIG_AW_DRM_LVDS),DRV_PTR(sunxi_rgb_platform_driver, CONFIG_AW_DRM_RGB),DRV_PTR(sunxi_hdmi_platform_driver, CONFIG_AW_DRM_HDMI_TX),DRV_PTR(sunxi_drm_edp_platform_driver, CONFIG_AW_DRM_EDP),DRV_PTR(sunxi_tcon_platform_driver, CONFIG_AW_DRM_TCON),/* TODO add tv*/
};
这里我们根据成员的CONFIG配置和实际使用情况,我们用到的驱动有sunxi_tcon_top_platform_driver、sunxi_de_platform_driver、sunxi_lvds_platform_driver、sunxi_tcon_platform_driver这几个。
1.1 sunxi_tcon_top_platform_driver
sunxi_tcon_top_platform_driver驱动在sunxi_device/sunxi_tcon_top.c文件中,他的probe函数只做了一件事,就是component_add:
struct platform_driver sunxi_tcon_top_platform_driver = {.probe = sunxi_tcon_top_probe,.remove = sunxi_tcon_top_remove,.driver = {.name = "tcon-top",.owner = THIS_MODULE,.of_match_table = sunxi_tcon_top_match,},
};
component_add(&pdev->dev, &sunxi_tcon_top_component_ops);
static const struct component_ops sunxi_tcon_top_component_ops = {.bind = sunxi_tcon_top_bind,.unbind = sunxi_tcon_top_unbind,
};
1.2 sunxi_de_platform_driver
sunxi_de_platform_driver驱动在sunxi_device/hardware/lowlevel_de/sunxi_de.c文件中:
struct platform_driver sunxi_de_platform_driver = {.probe = sunxi_de_probe,.remove = sunxi_de_remove,.driver = {.name = "sunxi-display-engine",.owner = THIS_MODULE,.of_match_table = sunxi_de_match,},
};
component_add(&pdev->dev, &sunxi_de_component_ops);
static const struct component_ops sunxi_de_component_ops = {.bind = sunxi_de_bind,.unbind = sunxi_de_unbind,
};
1.3 sunxi_lvds_platform_driver
sunxi_lvds_platform_driver驱动在sunxi_drm_lvds.c文件中
struct platform_driver sunxi_lvds_platform_driver = {.probe = sunxi_drm_lvds_probe,.remove = sunxi_drm_lvds_remove,.driver = {.name = "drm-lvds",.owner = THIS_MODULE,.of_match_table = sunxi_drm_lvds_match,},
};
component_add(&pdev->dev, &sunxi_drm_lvds_component_ops);
static const struct component_ops sunxi_drm_lvds_component_ops = {.bind = sunxi_drm_lvds_bind,.unbind = sunxi_drm_lvds_unbind,
};
1.4 sunxi_tcon_platform_driver
sunxi_tcon_platform_driver驱动在sunxi_device/sunxi_tcon.c文件中:
struct platform_driver sunxi_tcon_platform_driver = {.probe = sunxi_tcon_probe,.remove = sunxi_tcon_remove,.driver = {.name = "tcon",.owner = THIS_MODULE,.of_match_table = sunxi_tcon_match,},
};
component_add(&pdev->dev, &sunxi_tcon_component_ops);
static const struct component_ops sunxi_tcon_component_ops = {.bind = sunxi_tcon_bind,.unbind = sunxi_tcon_unbind,
};
这几个从设备驱动的probe函数,都是从设备树中获取一些信息,初始化硬件,最后调用component_add函数添加,关键是这几个bind函数。我们继续分析bind函数做了什么。
二、 主设备驱动
注册平台设备sunxi_drm_platform_driver:
static struct platform_driver sunxi_drm_platform_driver = {.probe = sunxi_drm_platform_probe,.remove = sunxi_drm_platform_remove,.driver = {.owner = THIS_MODULE,.name = "sunxi-drm",.of_match_table = sunxi_of_match,.pm = &sunxi_drm_pm_ops,},
};static int sunxi_drm_platform_probe(struct platform_device *pdev)
{struct component_match *match;DRM_INFO("%s start\n", __FUNCTION__);match = sunxi_drm_match_add(&pdev->dev);if (IS_ERR(match)) {DRM_INFO("sunxi_drm_match_add fail\n");return PTR_ERR(match);}return component_master_add_with_match(&pdev->dev, &sunxi_drm_ops,match);
}
在probe函数中然后调用函数platform_driver_register注册主驱动sunxi_drm_platform_driver。然后在probe函数中遍历sunxi_drm_sub_drivers数组,对每一个驱动通过platform_find_device_by_driver函数找到对应的device,调用函数device_link_add绑定主设备和从设备,再调用函数component_match_add把各个组件绑定到主设备中。然后调用component_master_add_with_match函数将主设备(master)注册到 Component 框架中,这里会调用master的bind函数。
static int sunxi_drm_bind(struct device *dev)
{private = __devm_drm_dev_alloc(dev, &sunxi_drm_driver,sizeof(*private) + sizeof(struct sunxi_drm_pri),offsetof(struct sunxi_drm_private, base));ret = drmm_mode_config_init(drm);sunxi_drm_mode_config_init(drm);sunxi_drm_property_create(private);get_boot_display_info(drm);ret = component_bind_all(dev, drm);dev_set_drvdata(dev, drm);drm_mode_config_reset(drm);drm_kms_helper_poll_init(drm);ret = setup_bootloader_connecting_state(drm);commit_init_connecting(drm);ret = drm_dev_register(drm, 0);return 0;
}
如上面的裁剪代码所示,在sunxi_drm_ops的bind成员sunxi_drm_bind函数中调用__devm_drm_dev_alloc函数分配并且初始化drm_dev实例,其绑定的驱动是sunxi_drm_driver。之前定义struct drm_driver sunxi_drm_driver ,包含各种func和fops,我们仔细看看:
DEFINE_DRM_GEM_CMA_FOPS(sunxi_drm_driver_fops);
#define DEFINE_DRM_GEM_CMA_FOPS(name) \static const struct file_operations name = {\.owner = THIS_MODULE,\.open = drm_open,\.release = drm_release,\.unlocked_ioctl = drm_ioctl,\.compat_ioctl = drm_compat_ioctl,\.poll = drm_poll,\.read = drm_read,\.llseek = noop_llseek,\.mmap = drm_gem_mmap,\.get_unmapped_area = drm_gem_cma_get_unmapped_area, \}#define DRM_GEM_CMA_DRIVER_OPS_VMAP \DRM_GEM_CMA_DRIVER_OPS_VMAP_WITH_DUMB_CREATE(drm_gem_cma_dumb_create)#define DRM_GEM_CMA_DRIVER_OPS_VMAP_WITH_DUMB_CREATE(dumb_create_func) \.dumb_create = dumb_create_func, \.prime_handle_to_fd = drm_gem_prime_handle_to_fd, \.prime_fd_to_handle = drm_gem_prime_fd_to_handle, \.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table_vmap, \.gem_prime_mmap = drm_gem_prime_mmapstatic struct drm_driver sunxi_drm_driver = {.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,.fops = &sunxi_drm_driver_fops,.ioctls = sunxi_drm_ioctls,.num_ioctls = ARRAY_SIZE(sunxi_drm_ioctls),.name = DRIVER_NAME,.desc = DRIVER_DESC,.date = DRIVER_DATE,.major = DRIVER_MAJOR,.minor = DRIVER_MINOR,.gem_create_object = sunxi_gem_create_object,
#if LINUX_VERSION_CODE <= KERNEL_VERSION(6, 1, 0)DRM_GEM_CMA_DRIVER_OPS_VMAP,
#elseDRM_GEM_DMA_DRIVER_OPS_VMAP,
#endif
};
预处理后变为
static struct drm_driver sunxi_drm_driver = {.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,.fops = &sunxi_drm_driver_fops,.ioctls = sunxi_drm_ioctls,.num_ioctls = ARRAY_SIZE(sunxi_drm_ioctls),.name = DRIVER_NAME,.desc = DRIVER_DESC,.date = DRIVER_DATE,.major = DRIVER_MAJOR,.minor = DRIVER_MINOR,.gem_create_object = sunxi_gem_create_object,.dumb_create = dumb_create_func, .prime_handle_to_fd = drm_gem_prime_handle_to_fd, .prime_fd_to_handle = drm_gem_prime_fd_to_handle, .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table_vmap, .gem_prime_mmap = drm_gem_prime_mmap,
};
static const struct file_operations sunxi_drm_driver_fops = {.owner = THIS_MODULE,.open = drm_open,.release = drm_release,.unlocked_ioctl = drm_ioctl,.compat_ioctl = drm_compat_ioctl,.poll = drm_poll,.read = drm_read,.llseek = noop_llseek,.mmap = drm_gem_mmap,.get_unmapped_area = drm_gem_cma_get_unmapped_area,
}
分配完drm_dev数据结构后,分别调用drmm_mode_config_init、sunxi_drm_mode_config_init、sunxi_drm_property_create、get_boot_display_info、component_bind_all、dev_set_drvdata、drm_mode_config_reset、drm_kms_helper_poll_init、setup_bootloader_connecting_state、commit_init_connecting函数初始化,最后调用drm_dev_register函数注册frm字符设备。
三、初始化代码分析
3.1 __devm_drm_dev_alloc
这个函数用于分配并初始化一个新的 drm_device
实例。它是 DRM 驱动程序初始化过程中非常重要的一步,确保设备资源的生命周期与设备对象绑定,并在适当的时候自动清理资源。
3.2 drmm_mode_config_init
初始化 DRM 设备的模式配置,也就是初始化drm_device的struct drm_mode_config mode_config成员。这个成员是 记录当前drm设备当前模式配置。这里列举一些成员,因为很多成员的很重要,但是数量有太多,只能写一些比较靠前的成员:
struct drm_mode_config {struct idr object_idr; //管理 DRM 模式配置中的对象(如 CRTC、平面、编码器、连接器等)的ID,用于高效查找struct list_head fb_list; //fb链表struct list_head connector_list;//连接器链表struct list_head encoder_list; //编码器链表struct list_head plane_list; //plane链表struct list_head crtc_list; //crtc链表struct list_head property_list; //属性链表struct list_head property_blob_list; // 属性 blob 链表int min_width, min_height; //最小宽度和高度int max_width, max_height; //最大宽度和高度struct drm_mode_config_funcs *funcs;// 模式配置函数表resource_size_t fb_base; //fb的基地址...
}
3.3 sunxi_drm_mode_config_init
继续初始化drm设备的当前配置,这里主要是设置了最大和最小宽度高度,和两个成员:
dev->mode_config.funcs = &sunxi_drm_mode_config_funcs;
dev->mode_config.helper_private = &sunxi_mode_config_helpers;static const struct drm_mode_config_funcs sunxi_drm_mode_config_funcs = {.atomic_check = drm_atomic_helper_check,.atomic_commit = sunxi_drm_atomic_helper_commit,.fb_create = sunxi_drm_gem_fb_create,
};static const struct drm_mode_config_helper_funcs sunxi_mode_config_helpers = {.atomic_commit_tail = sunxi_drm_atomic_helper_commit_tail,
};
这两个func都是很重要的回调方法,在后面会详细分析。
3.4 sunxi_drm_property_create
创建各种属性而已,这些属性存放在下面的数据中:
struct sunxi_drm_private {struct drm_device base;struct drm_property *prop_blend_mode[OVL_REMAIN];struct drm_property *prop_alpha[OVL_REMAIN];struct drm_property *prop_src_x[OVL_REMAIN], *prop_src_y[OVL_REMAIN];struct drm_property *prop_src_w[OVL_REMAIN], *prop_src_h[OVL_REMAIN];struct drm_property *prop_crtc_x[OVL_REMAIN], *prop_crtc_y[OVL_REMAIN];struct drm_property *prop_crtc_w[OVL_REMAIN], *prop_crtc_h[OVL_REMAIN];struct drm_property *prop_fb_id[OVL_REMAIN];struct drm_property *prop_color[OVL_MAX];struct drm_property *prop_layer_id;struct drm_property *prop_frontend_data;struct drm_property *prop_backend_data;struct drm_property *prop_sunxi_ctm;struct drm_property *prop_feature;struct drm_property *prop_eotf;struct drm_property *prop_color_space;struct drm_property *prop_color_format;struct drm_property *prop_color_depth;struct drm_property *prop_color_range;struct drm_property *prop_frame_rate_change;struct drm_property *prop_compressed_image_crop;struct sunxi_drm_pri *priv;
};
还会调用函数drm_mode_create_tv_properties创建TV显示的通用属性。
3.5 get_boot_display_info
获取uboot写入了的各种信息,写入到drm_device的私有数据中。猜测,看的不太懂
3.6 component_bind_all
int component_bind_all(struct device *parent, void *data)
{struct master *master;struct component *c;size_t i;int ret = 0;WARN_ON(!mutex_is_locked(&component_mutex));master = __master_find(parent, NULL);if (!master)return -EINVAL;/* Bind components in match order */for (i = 0; i < master->match->num; i++)if (!master->match->compare[i].duplicate) {c = master->match->compare[i].component;ret = component_bind(c, master, data);if (ret)break;}if (ret != 0) {for (; i > 0; i--)if (!master->match->compare[i - 1].duplicate) {c = master->match->compare[i - 1].component;component_unbind(c, master, data);}}return ret;
}
遍历之前绑定的全部组件,调用他们的bind函数。
3.7 dev_set_drvdata
设置刚刚申请的struct sunxi_drm_private为dev的私有数据。
3.8 drm_mode_config_reset
遍历drm_device下的每一个plane,调用plane->funcs->reset(plane);
遍历drm_device下的每一个crtc,调用crtc->funcs->reset(crtc);
遍历drm_device下的每一个connector,调用connector->funcs->reset(connector);
也就是把全部组件的func的reset方法全部调用一遍。
3.9 drm_kms_helper_poll_init
void drm_kms_helper_poll_init(struct drm_device *dev)
{INIT_DELAYED_WORK(&dev->mode_config.output_poll_work, output_poll_execute);dev->mode_config.poll_enabled = true;drm_kms_helper_poll_enable(dev);
}void drm_kms_helper_poll_enable(struct drm_device *dev)
{bool poll = false;struct drm_connector *connector;struct drm_connector_list_iter conn_iter;unsigned long delay = DRM_OUTPUT_POLL_PERIOD;drm_connector_list_iter_begin(dev, &conn_iter);drm_for_each_connector_iter(connector, &conn_iter) {if (connector->polled & (DRM_CONNECTOR_POLL_CONNECT |DRM_CONNECTOR_POLL_DISCONNECT))poll = true;}drm_connector_list_iter_end(&conn_iter);if (dev->mode_config.delayed_event) {poll = true;delay = HZ;}if (poll)schedule_delayed_work(&dev->mode_config.output_poll_work, delay);
}
drm_kms_helper_poll_init函数的作用是初始化KMS的轮询机制,以检测连接器(connector)的状态变化。如果连接器支持热插拔检测(HPD),但无法生成热插拔中断,则需要通过轮询来检测连接器的状态变化。在这个函数会初始化一个运行output_poll_execute函数的延时任务,在延时任务中探测连接状态是否发生改变,同时判断连接是否处于强制模式,如果处于强制模式,则停止轮询。目前开发板只连接lvds的显示屏,是强制连接模式的,这个函数poll初始化的delay work只会运行一次,因为检测到lvds屏的强制模式。如果是HMDI接口的connnect,这里的poll机制机会用上了。
3.10 setup_bootloader_connecting_state
遍历每一个连接器,给每一个连接器分配struct drm_connector的内存空间。然后调用函数init_connecting初始化每一个connector的struct sunxi_init_connecting数据结构,主要是查看连接器支持哪些模式,如果已经是连接状态,记录当前模式,并且加载到struct sunxi_drm_private->priv->connecting_head链表中。就是判断在bootloader阶段是否已经有连接器已经连接显示了,如果有,读取状态记录到链表中。
3.11 commit_init_connecting
commit_init_connectingsunxi_fbdev_init //根据uboot设置的显示,初始化内核drm数据drm_fb_config //读取bootloader显示的fb info数据sunxi_drm_get_logo_info //读取boot模式中的fb数据和显示宽度高度drm_fb_init //根据读取到的fb数据初始化显示 channel和drm相关结构体drm_modeset_acquire_init//初始化一个模式设置上下文。管理模式操作的锁drm_atomic_state_alloc //分配并且初始化drm_atomic_statelist_for_each_entry { //遍历pri->priv->connecting_head中每一个sunxi_init_connecting数据drm_atomic_get_crtc_state//获取crtc的stastdrm_atomic_set_mode_for_crtc //根据drm_display_mode直接设置crtc模式drm_atomic_get_connector_state //获取连接器的当前状态drm_atomic_set_crtc_for_connector //把connect绑定到crtcdrm_atomic_get_plane_state //获取plane的statusto_display_channel_state //通过drm_plane_state找到display_channel_state数据drm_atomic_set_fb_for_plane //为fb设置plane,其实就是更新fb的引用drm_atomic_set_crtc_for_plane //将plane与crtc绑定}drm_atomic_commit//原子提交配置
commit_init_connecting函数做了很多事情,首先调用函数sunxi_fbdev_init函数根据uboot是都的显示,初始化内核drm数据,平滑显示过度;然后调用函数drm_modeset_acquire_init初始化一个模式设置上下文,用于管理模式操作;再调用函数drm_atomic_state_alloc分配并且初始化drm_atomic_state内存;接着遍历pri->priv->connecting_head中每一个sunxi_init_connecting数据,在循环中获取crtc的模式stast,获取连接器的当前状态,再把connect绑定到crtc,再获取plane的status,通过drm_plane_state找到display_channel_state数据,为fb设置plane,最后将plane与crtc绑定后进入下一个循环或者退出循环。最后调用函数drm_atomic_commit进行原子提交配置。
3.12 drm_dev_register
这个函数是注册drm字符设备的函数,首先调用函数drm_mode_config_validate校验drm模式是否ok;然后调用函数drm_minor_register创建渲染设备节点和主设备节点,由于硬件原因,只有主设备节点创建成功;然后调用函数create_compat_control_link创建sysfs目录的一些控制连接;最后调用drm_modeset_register_all函数注册所有的组件,包括plane、crtc、encoder和connector,但是并没有什么实际的作用的,因为这些组件的late_register成员都是空的。详细代码调用可以查看下面:
drm_dev_registerdrm_mode_config_validate //应该是模式相关的一些校验drm_minor_register(dev, DRM_MINOR_RENDER); //创建渲染设备节点/dev/dri/renderD128,全志没有这个功能drm_minor_register(dev, DRM_MINOR_PRIMARY); //创建主设备节点/dev/dri/card0create_compat_control_link //创建sysfs的链接节点drm_modeset_register_all //注册所有的组件drm_plane_register_all //注册所有的planedrm_for_each_plane(plane, dev) ret = plane->funcs->late_register(plane);drm_crtc_register_all//注册所有的crtcdrm_for_each_crtc(crtc, dev) {drm_debugfs_crtc_add(crtc);crtc->funcs->late_register(crtc);drm_encoder_register_all//注册所有的encoderdrm_for_each_encoder(encoder, dev) {encoder->funcs->late_register(encoder);drm_connector_register_all//注册所有的connectordrm_for_each_connector_iter(connector, &conn_iter) {drm_connector_registerconnector->funcs->late_register
3.13 sunxi_drm_procfs_init
proc_entry = proc_mkdir("sunxi-drm", NULL);sunxi_drm_procfs_initproc_create_single_data("debug", 444, proc_entry, sunxidrm_debug_show, NULL);proc_create_single_data("status", 444, proc_entry,sunxi_drm_procfs_status_show, drm);
从上面的代码可以看到这个函数主要是在proc的sunxi-drm目录下创建debug和status两个节点,分别用于调试和查看drm各组件的状态,仅此而已。