当前位置:   article > 正文

DRM驱动移植spi显示屏(st7789芯片驱动)

st7789

引言

        本篇博客介绍了使用DRM驱动开发spi屏幕的开发过程。主要包括:

        1、spi框架驱动屏幕

        2、DRM虚拟驱动

        3、DRM开发spi屏幕显示驱动

一、总体介绍

        1. 学习DRM也点时间了,前段时间被其他事情耽误了,最近才有时间写一下DRM驱动。本篇博客主要介绍了spi屏幕的驱动、虚拟DRM驱动、spi屏幕添加到DRM驱动中,下面分别介绍。     

        2. DRM驱动是linux显示子系统,一个比较复杂的驱动子系统,博主也仅仅只是一些了解,博客里面记录的是我自己对他学习的过程。具体的可以参考博主其他的文章或者其他博主的文章。这里放一下博主以前写的介绍DRM驱动的链接。DRM驱动介绍

        之前对他的学习很不充分,从这篇文章开始,将记录DRM驱动移植到spi接口的st7789屏幕上。

        3. st7789是一个显示芯片,博主使用的是spi接口的,分辨率为240×240,七针接口,3.3v供电。分别是:电源:gdn、vcc;spi通信:sck、sda;  复位:res;dc:数据命令切换;blk:背光。这里放一下图片(防止广告嫌疑,只有图片,具体可以某宝搜一下就有了)。

         4. 博主用的环境:

                虚拟机:ubuntu18

                硬件:stm32mp157(某原子的开发板) + spi屏幕

                开发板内核版本:Linux 5.4.30(开发板带的)

二、使用spi框架驱动spi屏幕

                在设备树中,添加spi屏幕的部分,这里给出我自己的设备节点写的代码。

  1. spidev: htqst7789@0 {
  2. compatible = "htq,st7789";
  3. reg = <0>; /* CS #0 */
  4. scl-gpio = <&gpiof 6 GPIO_ACTIVE_LOW>;
  5. sda-gpio = <&gpiof 7 GPIO_ACTIVE_LOW>;
  6. res-gpio = <&gpiof 8 GPIO_ACTIVE_LOW>;
  7. dc-gpio = <&gpiof 9 GPIO_ACTIVE_LOW>;
  8. blk-gpio = <&gpiof 10 GPIO_ACTIVE_LOW>;
  9. spi-max-frequency = <8000000>;
  10. };

        最开始,使用的是模拟的spi协议,所以设备节点就用了这个,后面改成硬件spi时,把scl、sda接到硬件spi引脚上,res、dc、blk均没动。这部分跟之前使用spi框架驱动icm20608类似,spi框架部分,详细可以参考博主以前写的spi框架部分。spi框架驱动icm20608,想深入了解spi框架运行部分的,也可以参考博主的这个博文。spi框架分析

        驱动部分也是类似,将之前写的代码拿过来改动下,不同之处在于spi的驱动方式。st7789使用的是SPI_MODE_3,需要配置好,其他的基本类似。

  1. spi->mode = SPI_MODE_3;
  2. spi_setup(spi);

这里将博主写的spi驱动st7789的底层读写函数放一下,代码只是用于学习用的,很多未曾考虑到,比如互斥之类的。

  1. struct st7789_device{
  2. dev_t dev_id; //设备号
  3. int major; //主设备号
  4. int minor; //次设备号
  5. struct cdev cdev; //字符设备
  6. struct class *class; //
  7. struct device *device; //设备
  8. struct device_node *device_node; //设备节点
  9. void *privative_data; //私有数据
  10. int scl_gpio;
  11. int sda_gpio;
  12. int res_gpio;
  13. int dc_gpio;
  14. int blk_gpio;
  15. };
  16. static struct st7789_device st7789_device;
  17. #define SPI_RST_L() { gpio_set_value(st7789_device.res_gpio, 0);}
  18. #define SPI_RST_H() { gpio_set_value(st7789_device.res_gpio, 1);}
  19. #define SPI_DC_L() { gpio_set_value(st7789_device.dc_gpio, 0);}
  20. #define SPI_DC_H() { gpio_set_value(st7789_device.dc_gpio, 1);}
  21. #define SPI_BLK_H() {;} //背光直接接到3.3v了
  22. void st7789_spi_send_byte(unsigned char byte)
  23. {
  24. int ret = 0;
  25. unsigned char tx_data[1];
  26. struct spi_message m;
  27. struct spi_transfer *t;
  28. struct spi_device *spi = (struct spi_device *)st7789_device.privative_data;
  29. t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
  30. if(t == NULL){
  31. return -1;
  32. }
  33. //先发送寄存器地址,后发送数据
  34. tx_data[0] = byte; //写数据的时候寄存器地址bit8要清零
  35. t->tx_buf = tx_data; //要发送的数据
  36. t->len = 1;
  37. spi_message_init(&m); //初始化spi消息
  38. spi_message_add_tail(t,&m); //将要发送的数据添加到message消息队列
  39. ret = spi_sync(spi,&m); //发送数据
  40. kzfree(t);
  41. }
  42. void st7789_send_cmd(unsigned char cmd)
  43. {
  44. SPI_DC_L();
  45. st7789_spi_send_byte(cmd);
  46. }
  47. void st7789_send_data(unsigned char data)
  48. {
  49. SPI_DC_H();
  50. st7789_spi_send_byte(data);
  51. }
  52. void st7789_send_color(uint16_t color)
  53. {
  54. SPI_DC_H();
  55. int ret = 0;
  56. unsigned char g_tx_data[2];
  57. struct spi_message m;
  58. struct spi_transfer *t;
  59. struct spi_device *spi = (struct spi_device *)st7789_device.privative_data;
  60. t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
  61. if(t == NULL){
  62. return -1;
  63. }
  64. g_tx_data[0] = color>>8;
  65. g_tx_data[1] = color;
  66. t->tx_buf = g_tx_data; //要发送的数据
  67. t->len = sizeof(g_tx_data);
  68. spi_message_init(&m); //初始化spi消息
  69. spi_message_add_tail(t,&m); //将要发送的数据添加到message消息队列
  70. ret = spi_sync(spi,&m); //发送数据
  71. kzfree(t);
  72. }

        剩下的,对屏幕的寄存器进行初始化什么的就不放了,网上一大堆,也可以用资料里面提供的,博主的这个代码本身就是在单片机的环境下调试好之后,再拿过来改成Linux下的。

        到这里基本上spi屏幕就能正常驱动了,但是,这样写速度十分十分慢。对spi框架熟悉的同学应该知道,使用spi框架发送数据,需要配置spi_transfer,将其放到spi_message中,最终放到spi队列中发送。spi框架会调用spi_pump_messages这个内核线程发送数据,在这个函数里面,调用ctrl->transfer_one_messgae完成最终的发送,并将发送结果返回。这个过程小量数据还可以接受,但spi刷新一帧需要240×240×2B=115200B,对于硬件spi来说,这个数据量并不高。但使用spi框架一次只发生2B,中间框架消耗太多性能,因此需要改动这部分代码,简单的说就是一次多发生一些数据。在这里,博主又添加了GRAM用于存放一帧屏幕的显示数据。改动之后的发送函数如下:

  1. uint16_t* st7789_gram;
  2. st7789_gram = kmalloc(2 * 240 *240, GFP_KERNEL); //probe函数中分配内存
  3. inline void st7789_draw_point(int x, int y,uint16_t color)
  4. {
  5. uint16_t c = 0; //将颜色数据改成spi屏幕的
  6. c = color << 8;
  7. c |= (color>>8 & 0x00ff);
  8. st7789_gram[y * 240 + x] = c;
  9. }
  10. void st7789_full_color(unsigned int color)
  11. {
  12. unsigned int x,y;
  13. for(y = 0;y < 240; y++){
  14. for(x = 0;x < 240 ; x++){
  15. st7789_draw_point(x,y,color);
  16. }
  17. }
  18. }
  19. //发送n行数据
  20. void st7789_send_lines(uint16_t* color, int n)
  21. {
  22. SPI_DC_H();
  23. int ret = 0;
  24. struct spi_message m;
  25. struct spi_transfer *t;
  26. struct spi_device *spi = (struct spi_device *)st7789_device.privative_data;
  27. t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
  28. if(t == NULL){
  29. return -1;
  30. }
  31. t->tx_buf = color; //要发送的数据
  32. t->len = n * 2 * 240;
  33. spi_message_init(&m); //初始化spi消息
  34. spi_message_add_tail(t,&m); //将要发送的数据添加到message消息队列
  35. ret = spi_sync(spi,&m); //发送数据
  36. kzfree(t);
  37. }
  38. void st7789_refresh(void)
  39. {
  40. int n = 120, x, y;
  41. st7789_send_cmd(0x2a); //Column address set
  42. st7789_send_data(0x00); //start column
  43. st7789_send_data(0x00);
  44. st7789_send_data(0x00); //end column
  45. st7789_send_data(0xF0);
  46. st7789_send_cmd(0x2b); //Row address set
  47. st7789_send_data(0x00); //start row
  48. st7789_send_data(0x00);
  49. st7789_send_data(0x00); //end row
  50. st7789_send_data(0xF0);
  51. st7789_send_cmd(0x2C); //Memory write
  52. st7789_send_lines((uint16_t *)(&st7789_gram[0]), 120);
  53. st7789_send_lines((uint16_t *)(&st7789_gram[1 * 240 * 120]), 120);
  54. }

这里实测,一次spi_transfer无法发送完一帧数据量,因此,改成两次发送,速度比之前快多了。放一张spi屏幕驱动效果图。

 到这里,使用Linux下的spi框架驱动st7789显示芯片基本完成。

三、DRM框架

        drm驱动很复杂,kms部分主要有一下部分组成:   

 写DRM驱动,主要也是围绕这几部分来做。

1、最简单的drm驱动

        将之前的spi代码改动下,在spi框架下添加drm框架。在spi probe函数里面,注册drm:

  1. static const struct file_operations htq_st7789_driver_fops = {
  2. .owner = THIS_MODULE,
  3. .open = drm_open,
  4. .release = drm_release,
  5. .unlocked_ioctl = drm_ioctl,
  6. .compat_ioctl = drm_compat_ioctl,
  7. .poll = drm_poll,
  8. .read = drm_read,
  9. .llseek = noop_llseek,
  10. .mmap = drm_gem_cma_mmap,
  11. };
  12. static struct drm_driver htq_st7789_driver = {
  13. .name = "htq_st7789",
  14. .desc = "htq drm st7789 driver by htq",
  15. .date = "20220401",
  16. .major = 1,
  17. .minor = 0,
  18. .fops = &htq_st7789_driver_fops,
  19. };
  20. static int st7789_probe(struct spi_device *spi)
  21. {
  22. int ret = 0;
  23. struct device *dev = &spi->dev;
  24. struct drm_device *ddev;
  25. ddev = drm_dev_alloc(&htq_st7789_driver, dev); //分配一个drm_device结构体
  26. drm_dev_register(ddev, 0); //注册drm
  27. return 0;
  28. }

这样,一个最简单的DRM驱动就完成了(可能还可以再精简,博主未测试),这个驱动什么都不能做,只是演示了drm驱动,将驱动insmod到内核之后,可以使用ls /dev/dri/card0看到有这个节点。

 使用cat /sys/kernel/debug/dri/0/name可以看到,htq_st7789是前面设置的drm驱动名字。

 加载了DRM驱动之后,会在/dev/dri/下面生成对应的card0,用于用户空间应用程序打开设备,控制驱动。驱动加载进入之后,drm会自动生成如下节点(这部分参考别人的):

/dev/dri/card0

/sys/kernel/debug/dri/0

/sys/class/drm/card0

2、添加objects

        最简单的DRM驱动什么也做不了,需要添加plane、crtc、encoder、plane等objects才能完成对应的功能。各个objects什么意思,这里就不详细说明了,具体的请看博客上面,有博主的介绍这部分的博客链接。先放代码。

  1. struct st7789_device {
  2. struct drm_device drm;
  3. struct drm_plane primary;
  4. struct drm_crtc crtc;
  5. struct drm_encoder encoder;
  6. struct drm_connector connector;
  7. struct hrtimer vblank_hrtimer;
  8. };
  9. int st7789_dumb_create(struct drm_file *file, struct drm_device *dev,
  10. struct drm_mode_create_dumb *args)
  11. {
  12. unsigned int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
  13. args->pitch = roundup(min_pitch, 128); //128 Byte对齐,优化传输
  14. args->height = roundup(args->height, 4);
  15. //调用CMA API中的函数创建显存
  16. return drm_gem_cma_dumb_create_internal(file, dev, args);
  17. }
  18. static struct drm_driver htq_st7789_driver = {
  19. .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
  20. .name = "htq_st7789",
  21. .desc = "htq drm st7789 driver by htq",
  22. .date = "20220401",
  23. .major = 1,
  24. .minor = 0,
  25. .fops = &htq_st7789_driver_fops,
  26. .dumb_create = st7789_dumb_create,
  27. .gem_vm_ops = &drm_gem_cma_vm_ops,
  28. .gem_free_object_unlocked = drm_gem_cma_free_object,
  29. };
  30. //初始化plane、crtc、encoder、connector
  31. static int st7789_modeset_init(struct st7789_device *st7789_device)
  32. {
  33. struct drm_device *dev = (struct drm_device *)st7789_device->drm;
  34. int ret = 0;
  35. drm_mode_config_init(dev);
  36. dev->mode_config.funcs = &st7789_mode_funcs; //modeset回调函数
  37. dev->mode_config.min_width = 0; //显示区域的最大、最小范围
  38. dev->mode_config.min_height = 0;
  39. dev->mode_config.max_width = 240;
  40. dev->mode_config.max_height = 240;
  41. dev->mode_config.preferred_depth = 16; //颜色深度,16
  42. dev->mode_config.helper_private = &st7789_mode_config_helpers; //helper回调函数
  43. //初始化plane
  44. ret = drm_universal_plane_init(dev, &st7789_device->primary, 0, &st7789_plane_funcs,
  45. st7789_formats, ARRAY_SIZE(st7789_formats),
  46. NULL, DRM_PLANE_TYPE_PRIMARY, NULL); //主图层
  47. //初始化crtc
  48. printk("drm_crtc_init_with_planes\n");
  49. ret = drm_crtc_init_with_planes(dev, &st7789_device->crtc, &st7789_device->primary, NULL, &st7789_crtc_funcs, NULL);
  50. //初始化encoder
  51. ret = drm_encoder_init(dev, &st7789_device->ncoder, &st7789_encoder_funcs, DRM_MODE_ENCODER_VIRTUAL, NULL); //虚拟的encoder
  52. //初始化connector
  53. ret = drm_connector_init(dev, &st7789_device->connector, &st7789_connector_funcs, DRM_MODE_CONNECTOR_SPI);
  54. ret = drm_connector_attach_encoder(&st7789_device->connector, &st7789_device->encoder);
  55. drm_mode_config_reset(dev);
  56. return 0; //vkms_output_init(vkmsdev, 0);
  57. };

        这部分太多了,简单的说下就是,对plane、crtc、encoder、connector进行初始化,添加对应的回调函数,回调函数里面写的才是真正的驱动底层显示器的函数(这里就是spi屏幕部分)。实际上,这里初始化的只是标准的objects,还有一些xxx_helper_func函数未曾添加进去。这里将驱动加载进去之后会看到这样的字符:

 

3、添加xxx_helper_func和atomic_xxx相关代码

        上述代码只是将标准的objects添加到代码中,实际上,DRM框架还需要具体的Soc、屏幕相关的代码,这部分代码DRM框架中留下回调函数,使用xxx_helper_func相关函数注册到DRM框架中。除此之外,还需要atomic_xxx部分,这里博主都写在这里了。

这里放一个完整的,比较多,而且由于博主是写在多个文件里面的,可能比较乱(忍一下吧0^0)。

  1. static const struct drm_plane_funcs st7789_plane_funcs = {
  2. .update_plane = drm_atomic_helper_update_plane,
  3. .disable_plane = drm_atomic_helper_disable_plane,
  4. .destroy = drm_plane_cleanup,
  5. .reset = drm_atomic_helper_plane_reset,
  6. .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
  7. .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
  8. };
  9. static const struct drm_plane_helper_funcs st7789_plane_helper_funcs = {
  10. .atomic_update = st7789_plane_atomic_update,
  11. };
  12. static const struct drm_crtc_funcs st7789_crtc_funcs = {
  13. .set_config = drm_crtc_helper_set_config,
  14. .page_flip = st7789_crtc_page_flip,
  15. .destroy = drm_crtc_cleanup,
  16. .reset = drm_atomic_helper_crtc_reset,
  17. .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
  18. .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
  19. };
  20. static const struct drm_crtc_helper_funcs st7789_crtc_helper_funcs = {
  21. .atomic_enable = st7789_crtc_atomic_enable,
  22. .atomic_disable = st7789_crtc_atomic_disable,
  23. .atomic_flush = st7789_crtc_atomic_flush,
  24. };
  25. static const struct drm_connector_funcs st7789_connector_funcs = {
  26. .fill_modes = drm_helper_probe_single_connector_modes,
  27. .destroy = drm_connector_cleanup,
  28. .reset = drm_atomic_helper_connector_reset,
  29. .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
  30. .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
  31. };
  32. static const struct drm_connector_helper_funcs st7789_connector_helper_funcs = {
  33. .get_modes = st7789_connector_get_modes,
  34. };
  35. static const struct drm_encoder_funcs st7789_encoder_funcs = {
  36. .destroy = drm_encoder_cleanup,
  37. };
  38. //modeset初始化
  39. static int st7789_modeset_init(struct st7789_device *st7789_device)
  40. {
  41. struct drm_device *dev = (struct drm_device *)st7789_device->drm;
  42. int ret = 0;
  43. drm_mode_config_init(dev);
  44. dev->mode_config.funcs = &st7789_mode_funcs; //modeset回调函数
  45. dev->mode_config.min_width = 0; //显示区域的最大、最小范围
  46. dev->mode_config.min_height = 0;
  47. dev->mode_config.max_width = 240;
  48. dev->mode_config.max_height = 240;
  49. dev->mode_config.preferred_depth = 16; //颜色深度,16
  50. dev->mode_config.helper_private = &st7789_mode_config_helpers; //helper回调函数
  51. //初始化plane
  52. ret = drm_universal_plane_init(dev, &st7789_device->primary, 0, &st7789_plane_funcs,
  53. st7789_formats, ARRAY_SIZE(st7789_formats),
  54. NULL, DRM_PLANE_TYPE_PRIMARY, NULL); //主图层
  55. drm_plane_helper_add(&st7789_device->primary, &st7789_plane_helper_funcs);
  56. //初始化crtc
  57. ret = drm_crtc_init_with_planes(dev, &st7789_device->crtc, &st7789_device->primary, NULL, &st7789_crtc_funcs, NULL);
  58. drm_crtc_helper_add(&st7789_device->crtc, &st7789_crtc_helper_funcs);
  59. //初始化encoder
  60. ret = drm_encoder_init(dev, &st7789_device->ncoder, &st7789_encoder_funcs, DRM_MODE_ENCODER_VIRTUAL, NULL); //虚拟的encoder
  61. //初始化connector
  62. ret = drm_connector_init(dev, &st7789_device->connector, &st7789_connector_funcs, DRM_MODE_CONNECTOR_SPI);
  63. drm_connector_helper_add(&st7789_device->connector, &st7789_connector_helper_funcs);
  64. ret = drm_connector_attach_encoder(&st7789_device->connector, &st7789_device->encoder);
  65. drm_mode_config_reset(dev);
  66. return 0;
  67. };

上面代码中st7789_xxx部分,是需要我们手动实现的,跟具体的屏幕有关系,我这里就不放代码里,相关函数都是空的。无非就是写好对应的回调函数,将函数放到对应的结构体里面,将结构体注册到对应的objects里面,说着感觉很简单。将驱动调试好,insmod到内核之后,会出现这样的字段:

 出现了一些小问题,没有vblank和crtc什么的,这个正常,因为还没有完善这个驱动,下面将其完善。

4、完善的DRM虚拟驱动

         整个DRM虚拟驱动实际上参考了vkms写的,博主根据自己的理解和需要,重写了这个部分。

注:未介绍drm_xxx_funcs、drm_xxx_helper_funcs里面需要自己写的回调函数具体做什么了,大概介绍下,并填写代码。

三、spi屏幕添加到DRM驱动

        到这里,我们已经完成了spi屏幕的驱动和DRM虚拟驱动,剩下的需要将二者结合起来。写了DRM虚拟驱动之后,相信对各个objects做什么、各个drm_xxx_funcs和drm_xxx_helper_funcs做什么有个比较清晰的理解了,剩下的就是将drm_xxx_funcs、drm_xxx_helper_funcs里面的各个回调函数写好,调试好。

        后面发现了drm_mipi_dbi.c是专门为spi等接口屏幕出的drm框架,这一部分代码重写,参考了内核的drm_mipi_dbi.c开发的,mipi_dbi框架支持spi接口的屏幕。我发现我想写的,人家已经写好了,0.0,可以读一下这个代码,1k行多点。

1、plane

  1. static const struct drm_plane_funcs st7789_plane_funcs = {
  2. .update_plane = drm_atomic_helper_update_plane,
  3. .disable_plane = drm_atomic_helper_disable_plane,
  4. .destroy = drm_plane_cleanup,
  5. .reset = drm_atomic_helper_plane_reset,
  6. .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
  7. .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
  8. };

这里的st7789_plane_funcs 结构体基本上用的都是drm提供的api写的,vkms对后面三个成员重写了,博主参考了其他的驱动,可以直接用drm_atomic_helper_plane_xxx写。

2、crtc

  1. struct drm_crtc_helper_funcs st7789_crtc_helper_funcs = {
  2. .mode_valid = st7789_crtc_mode_valid, //检查模式是否支持
  3. .mode_fixup = st7789_crtc_mode_fixup, //验证模式
  4. .mode_set_nofb = st7789_crtc_mode_set_nofb,
  5. .atomic_enable = st7789_crtc_atomic_enable,
  6. .atomic_disable = st7789_crtc_atomic_disable,
  7. .atomic_flush = st7789_crtc_atomic_flush,
  8. };
  9. struct drm_crtc_funcs st7789_crtc_funcs = {
  10. .set_config = drm_crtc_helper_set_config,
  11. .destroy = drm_crtc_cleanup,
  12. .page_flip = drm_atomic_helper_page_flip,
  13. .reset = drm_atomic_helper_crtc_reset,
  14. .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
  15. .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
  16. // .enable_vblank = st7789_enable_vblank,
  17. // .disable_vblank = st7789_disable_vblank,
  18. };

enable_vblank、disable_vblank使能/关闭消影,这个暂时没有用到。

3、encoder

  1. struct drm_encoder_funcs st7789_encoder_funcs = {
  2. .destroy = drm_encoder_cleanup,
  3. };

4、connector

  1. struct drm_connector_helper_funcs st7789_connector_helper_funcs = {
  2. .get_modes = st7789_connector_get_modes,
  3. };
  4. struct drm_connector_funcs st7789_connector_funcs = {
  5. .fill_modes = drm_helper_probe_single_connector_modes,
  6. .destroy = drm_connector_cleanup,
  7. .reset = drm_atomic_helper_connector_reset,
  8. .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
  9. .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
  10. };

st7789_connector_get_modes这个应该是比较重要的了,用于获取屏幕的参数,是drm_display_mode结构体,看其他博客的说明,这个东西不能仅仅理解为一些屏幕参数,需要理解为屏幕的时序,想来应该是和屏幕通信、显示时用到了。回调函数里面使用drm_mode_probed_add(connector, mode);将drm_display_mode添加到connector中。

四、总结

        DRM开发还算比较好理解:将KMS中几个obj初始化,包括plane、crtc、encoder、connector等,之后将对应的回调函数写入注册到系统中,就像上面提到的,包括drm_xxx_funcs、drm_xxx_helper_funcs。这次开发未涉及到内存方面,都是用CMA相关函数,博主对这部分还不太了解,下一次再更新相关的。

参考驱动:

        vkms.c 、drm_mipi_dbi.c、ili9341.c,vkms时Linux虚拟驱动,后面两个跟spi屏幕有关系,ili9341时spi接口的屏幕。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/205762
推荐阅读
相关标签
  

闽ICP备14008679号