请选择 进入手机版 | 继续访问电脑版
查看: 1065|回复: 1

[分享] 【经验分享】i.MX6ULL开发:驱动开发11——LCD驱动实践

[复制链接]
  • TA的每日心情
    开心
    2020-12-18 12:56
  • 签到天数: 55 天

    [LV.5]常住居民I

    79

    主题

    229

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    1702
    最后登录
    2024-5-28
    发表于 2022-8-26 13:46:38 | 显示全部楼层 |阅读模式
    之前在Linux系统移植时提到过LCD驱动,本篇来看下Linux设备树如何配置LCD驱动。
    1 知识点
    首先需要了解一个新的概念:Framebuffer
    1.1 Framebuffer
    Framebuffer直译即帧缓冲,简称 fb,它是Linux将系统中所有跟显示有关的硬件以及软件集合起来,将底层的LCD虚拟抽象出一 个/dev/fbX设备,应用程序可以通过操作/dev/fbX来实现对屏幕的显示控制。
    NXP官方Linux内核已默认开启了LCD驱动,在dev/目录下可以看到fb0这样一个设备
    图片 25.png
    Framebuffer在内核中的表现就是fb_info结构体:
    图片 26.png
    完整的结构体定义如下:
    1. struct fb_info {
    2. atomic_t count;
    3. int node;
    4. int flags;
    5. struct mutex lock;  /* Lock for open/release/ioctl funcs */
    6. struct mutex mm_lock;  /* Lock for fb_mmap and smem_* fields */
    7. struct fb_var_screeninfo var; /* 当前的可变参数 */
    8. struct fb_fix_screeninfo fix; /* 当前的固定参数 */
    9. struct fb_monspecs monspecs; /* Current Monitor specs */
    10. struct work_struct queue; /* Framebuffer event queue */
    11. struct fb_pixmap pixmap; /* Image hardware mapper */
    12. struct fb_pixmap sprite; /* Cursor hardware mapper */
    13. struct fb_cmap cmap;  /* Current cmap */
    14. struct list_head modelist;      /* mode list */
    15. struct fb_videomode *mode; /* current mode */

    16. #ifdef CONFIG_FB_BACKLIGHT
    17. /* assigned backlight device */
    18. /* set before framebuffer registration,
    19.    remove after unregister */
    20. struct backlight_device *bl_dev;

    21. /* Backlight level curve */
    22. struct mutex bl_curve_mutex;
    23. u8 bl_curve[FB_BACKLIGHT_LEVELS];
    24. #endif
    25. #ifdef CONFIG_FB_DEFERRED_IO
    26. struct delayed_work deferred_work;
    27. struct fb_deferred_io *fbdefio;
    28. #endif

    29. struct fb_ops *fbops;       /* 帧缓冲操作函数集 */
    30. struct device *device;  /* This is the parent */
    31. struct device *dev;      /* This is this fb device */
    32. int class_flag;             /* private sysfs flags */
    33. #ifdef CONFIG_FB_TILEBLITTING
    34. struct fb_tile_ops *tileops;    /* Tile Blitting */
    35. #endif
    36. char __iomem *screen_base;     /* 虚拟内存基地址(屏幕显存) */
    37. unsigned long screen_size;     /* 虚拟内存大小(屏幕显存大小) */
    38. void *pseudo_palette;      /* 伪16位调色板 */
    39. #define FBINFO_STATE_RUNNING 0
    40. #define FBINFO_STATE_SUSPENDED 1
    41. u32 state;               /* Hardware state i.e suspend */
    42. void *fbcon_par;                /* fbcon use-only private area */
    43. /* From here on everything is device dependent */
    44. void *par;
    45. /* we need the PCI or similar aperture base/size not
    46.    smem_start/size as smem_start may just be an object
    47.    allocated inside the aperture so may not actually overlap */
    48. struct apertures_struct {
    49. unsigned int count;
    50. struct aperture {
    51.   resource_size_t base;
    52.   resource_size_t size;
    53. } ranges[0];
    54. } *apertures;

    55. bool skip_vt_switch; /* no VT switch on suspend/resume required */
    56. };
    复制代码
    注意结构体中的fb_fops这一项,/dev/fb0 是个字符设备,fb_fops就是它的文件操作结构体,它的file_operations操作集在drivers/video/fbdev/core/fbmem.c 文件中:
    图片 27.png
    1. static const struct file_operations fb_fops = {
    2. .owner = THIS_MODULE,
    3. .read =  fb_read,
    4. .write = fb_write,
    5. .unlocked_ioctl = fb_ioctl,
    6. #ifdef CONFIG_COMPAT
    7. .compat_ioctl = fb_compat_ioctl,
    8. #endif
    9. .mmap =  fb_mmap,
    10. .open =  fb_open,
    11. .release = fb_release,
    12. #ifdef HAVE_ARCH_FB_UNMAPPED_AREA
    13. .get_unmapped_area = get_fb_unmapped_area,
    14. #endif
    15. #ifdef CONFIG_FB_DEFERRED_IO
    16. .fsync = fb_deferred_io_fsync,
    17. #endif
    18. .llseek = default_llseek,
    19. };
    复制代码
    可以看到有熟悉的open、release等函数接口。
    因此,LCD驱动的重点就是初始化fb_info里面的各个成员。
    fb_info结构体的成员变量很多,需要重点关注的是这几个:
    • var:当前的可变参数
    • fix:当前的固定参数
    • fbops:帧缓冲操作函数集
    • screen_base:虚拟内存基地址(屏幕显存)
    • screen_size:虚拟内存大小(屏幕显存大小)
    • pseudo_palette:伪16位调色板

    初始化完成fb_info后,通过register_framebuffer函数向内核注册刚刚初始化的fb_info。
    1.2 LCD驱动文件mxsfb介绍
    LCD的驱动文件为mxsfb.c,这是一种platform驱动框架,驱动和设备匹配之后,mxsfb_probe函数就会执行。
    LCD的初始化通过mxsfb_probe函数来实现,该函数的主要功能有:
    • 申请fb_info
    • 初始化fb_info结构体中的各个成员变量
    • 初始化eLCDIF控制器
    • 使用register_framebuffer函数向Linux内核注册初始化好的fb_info

    该函数位于:/drivers/video/fbdev/mxsfb.c中
    图片 28.png
    该函数的实现如下:
    1. static int mxsfb_probe(struct platform_device *pdev)
    2. {
    3. const struct of_device_id *of_id =
    4.   of_match_device(mxsfb_dt_ids, &pdev->dev);
    5. struct resource *res;
    6. struct mxsfb_info *host; //<-----NXP的fb_info
    7. struct fb_info *fb_info; //<-----Linux的fb_info
    8. struct pinctrl *pinctrl;
    9. int irq = platform_get_irq(pdev, 0);
    10. int gpio, ret;

    11. if (of_id)
    12. pdev->id_entry = of_id->data;

    13. gpio = of_get_named_gpio(pdev->dev.of_node, "enable-gpio", 0);
    14. if (gpio == -EPROBE_DEFER)
    15. return -EPROBE_DEFER;
    16. //省略...

    17. fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);//<--------申请fb_info
    18. if (!fb_info) {
    19. dev_err(&pdev->dev, "Failed to allocate fbdev\n");
    20. devm_kfree(&pdev->dev, host);
    21. return -ENOMEM;
    22. }
    23.    host->fb_info = fb_info; //<---将mxsfb_info与fb_info联系起来
    24.    fb_info->par = host;
    25. //省略...

    26. ret = mxsfb_init_fbinfo(host);
    27. if (ret != 0)
    28. goto fb_pm_runtime_disable;

    29. mxsfb_dispdrv_init(pdev, fb_info);
    30. //省略...

    31. ret = register_framebuffer(fb_info); //<------------注册
    32. if (ret != 0) {
    33. dev_err(&pdev->dev, "Failed to register framebuffer\n");
    34. goto fb_destroy;
    35. }

    36. console_lock();
    37. ret = fb_blank(fb_info, FB_BLANK_UNBLANK);
    38. console_unlock();
    39. if (ret < 0) {
    40. dev_err(&pdev->dev, "Failed to unblank framebuffer\n");
    41. goto fb_unregister;
    42. }

    43. dev_info(&pdev->dev, "initialized\n");

    44. }
    复制代码
    其中,register_framebuffer函数的原型如下:
    图片 29.png
    函数参数和返回值含义:
    • fb_info:需上报的fb_info
    • 返回值:0-成功,负值-失败

    1.3 LCD 驱动程序编写
    6ULL的eLCDIF接口驱动程序 NXP 已经编 写好了,因此 LCD 驱动部分我们不需要去修改。我们需要做的就是按照所使用的 LCD 来修改设备树。
    1.3.1 查看设备树
    1.3 先来看一下NXP官方编写的Linux下的 LCD 驱动。打开 imx6ull.dtsi,然后找到 lcdif节点内容:
    图片 30.png
    1. lcdif: lcdif@021c8000 {
    2.    compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
    3.    reg = <0x021c8000 0x4000>;
    4.    interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
    5.    clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
    6.    <&clks IMX6UL_CLK_LCDIF_APB>,
    7.    <&clks IMX6UL_CLK_DUMMY>;
    8.    clock-names = "pix", "axi", "disp_axi";
    9.    status = "disabled";
    10. };
    复制代码
    其中021c8000 这个地址,可以从参考手册中找到对应的介绍:
    图片 31.png
    1.3.2 屏幕IO配置
    打开 imx6ull-myboard.dts 文件,在 iomuxc 节点中找到如下内容:
    图片 32.png
    具体为:
    1. pinctrl_lcdif_dat: lcdifdatgrp {
    2.   fsl,pins = <
    3.    MX6UL_PAD_LCD_DATA00__LCDIF_DATA00  0x79
    4.    MX6UL_PAD_LCD_DATA01__LCDIF_DATA01  0x79
    5.    MX6UL_PAD_LCD_DATA02__LCDIF_DATA02  0x79
    6.    MX6UL_PAD_LCD_DATA03__LCDIF_DATA03  0x79
    7.    MX6UL_PAD_LCD_DATA04__LCDIF_DATA04  0x79
    8.    MX6UL_PAD_LCD_DATA05__LCDIF_DATA05  0x79
    9.    MX6UL_PAD_LCD_DATA06__LCDIF_DATA06  0x79
    10.    MX6UL_PAD_LCD_DATA07__LCDIF_DATA07  0x79
    11.    MX6UL_PAD_LCD_DATA08__LCDIF_DATA08  0x79
    12.    MX6UL_PAD_LCD_DATA09__LCDIF_DATA09  0x79
    13.    MX6UL_PAD_LCD_DATA10__LCDIF_DATA10  0x79
    14.    MX6UL_PAD_LCD_DATA11__LCDIF_DATA11  0x79
    15.    MX6UL_PAD_LCD_DATA12__LCDIF_DATA12  0x79
    16.    MX6UL_PAD_LCD_DATA13__LCDIF_DATA13  0x79
    17.    MX6UL_PAD_LCD_DATA14__LCDIF_DATA14  0x79
    18.    MX6UL_PAD_LCD_DATA15__LCDIF_DATA15  0x79
    19.    MX6UL_PAD_LCD_DATA16__LCDIF_DATA16  0x79
    20.    MX6UL_PAD_LCD_DATA17__LCDIF_DATA17  0x79
    21.    MX6UL_PAD_LCD_DATA18__LCDIF_DATA18  0x79
    22.    MX6UL_PAD_LCD_DATA19__LCDIF_DATA19  0x79
    23.    MX6UL_PAD_LCD_DATA20__LCDIF_DATA20  0x79
    24.    MX6UL_PAD_LCD_DATA21__LCDIF_DATA21  0x79
    25.    MX6UL_PAD_LCD_DATA22__LCDIF_DATA22  0x79
    26.    MX6UL_PAD_LCD_DATA23__LCDIF_DATA23  0x79
    27.   >;
    28. };

    29. pinctrl_lcdif_ctrl: lcdifctrlgrp {
    30.   fsl,pins = <
    31.    MX6UL_PAD_LCD_CLK__LCDIF_CLK     0x79
    32.    MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE  0x79
    33.    MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC    0x79
    34.    MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC    0x79
    35.   >;
    36. };

    37. pinctrl_pwm1: pwm1grp {
    38.   fsl,pins = <
    39.    MX6UL_PAD_GPIO1_IO08__PWM1_OUT   0x110b0
    40.   >;
    41. };
    复制代码
    这里有3个节点:
    • 子节点pinctrl_lcdif_dat ,为 RGB LCD 的 24根数据线配置项
    • 子节点 pinctrl_lcdif_ctrl ,为RGB LCD 的 4根控制线配置项,包括 CLK、ENABLE、VSYNC 和 HSYNC
    • 子节点 pinctrl_pwm1 ,为RGB LCD 的背光亮度配置项

    1.3.3 屏幕参数配置
    在imx6ull-myboard.dts 文件中找到lcdif 节点,根据自己使用的LCD,修改为对应的参数。
    下面是NXP官方板子的参数:
    图片 33.png
    我用的野火7寸屏(GT911,800x480),其参数为:
    图片 34.png
    修改后的lcdif 节点如下:
    1. &lcdif {
    2. pinctrl-names = "default";             /* 使用到的 IO */
    3. pinctrl-0 = <&pinctrl_lcdif_dat
    4.       &pinctrl_lcdif_ctrl
    5.       &pinctrl_lcdif_reset>;
    6. display = <&display0>;
    7. status = "okay";

    8. display0: display {                  /* LCD 属性信息 */
    9. bits-per-pixel = <16>;           /* 一个像素占用几个bit */
    10. bus-width      = <24>;           /* 总线宽度 */

    11. display-timings {
    12.   native-mode = <&timing0>;    /* 时序信息 */
    13.   timing0: timing0 {
    14.   clock-frequency = <9200000>; /* LCD像素时钟,单位Hz */
    15.   hactive      = <800>;        /* LCD X轴像素个数 */
    16.   vactive      = <480>;        /* LCD Y轴像素个数 */
    17.   hfront-porch = <22>;         /* LCD hfp 参数 */
    18.   hback-porch  = <46>;         /* LCD hbp 参数 */
    19.   hsync-len    = <23>;         /* LCD hspw 参数 */
    20.   vback-porch  = <22>;         /* LCD vbp 参数 */
    21.   vfront-porch = <4>;          /* LCD vfp 参数 */
    22.   vsync-len    = <1>;          /* LCD vspw 参数 */

    23.   hsync-active    = <0>;       /* hsync 数据线极性 */
    24.   vsync-active    = <0>;       /* vsync 数据线极性 */
    25.   de-active       = <1>;       /* de 数据线极性 */
    26.   pixelclk-active = <0>;       /* clk 数据线极性 */
    27.   };
    28. };
    29. };
    30. };
    复制代码
    1.3.4 屏幕背光配置
    通过PWM信号来控制LCD屏幕背光的亮度
    1. pinctrl_pwm1: pwm1grp {
    2.    fsl,pins = <
    3.        MX6UL_PAD_GPIO1_IO08__PWM1_OUT   0x110b0
    4.        >;
    5. };
    复制代码
    LCD 背光要用到PWM1,因此也要设置 PWM1 节点,在imx6ull.dtsi 文件中找到如下内容:
    图片 35.png
    这个节点信息不用修改,使用默认的配置即可。如果要修改的话,也不要修改这里,可以通过imx6ull-myboard.dts文件中进行修改。
    imx6ull-myboard.dts中的pwm1节点:
    1. &pwm1 {
    2. pinctrl-names = "default";
    3. pinctrl-0 = <&pinctrl_pwm1>;
    4. status = "okay";
    5. };
    复制代码
    imx6ull-myboard.dts中的backlight节点:
    1. backlight {
    2.    compatible = "pwm-backlight";
    3.    pwms = <&pwm1 0 5000000>;
    4.    brightness-levels = <0 4 8 16 32 64 128 255>;
    5.    default-brightness-level = <6>;
    6.    status = "okay";
    7. };
    复制代码
    2 实验测试2.1使能Linux logo显示
    uboot启动的时候,LCD左上角上会显示NXP的图标,而Linux内核启动的时候,LCD左上角上会显示一个小企鹅。因此,可以通过小企鹅logo的显示来验证LCD 驱动是否正常。
    默认情况下是已经开启logo显示的,可以再确认一下。
    在Linux内核源码目录,输入以下指令打开内核的图形化配置:
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
    Linux内核配置界面:
    图片 36.png
    然后按下路径找到对应的配置项:
    1. -> Device Drivers
    2.      -> Graphics support
    3.      -> Bootup logo (LOGO [=y])
    复制代码
    最终到达这个界面:
    这三个选项分别对应黑白、16 位、24 位色彩格式的 logo。
    2.2 编译设备树
    修改设备树中的lcdif节点后(主要是修改屏幕的参数),在Linux内核源码目录执行下面的命令,重新编译设备树并拷贝到网络启动位置。
    1. make imx6ull-myboard.dtb
    2. cp arch/arm/boot/dts/imx6ull-myboard.dtb ~/myTest/tftpboot/nxp/
    复制代码
    然后重启开发板,就可以在Linux内核驱动的时候看到屏幕上的企鹅图标了:
    图片 38.png
    2.3 设置LCD作为终端控制台
    之前一直使用串口来显示板子的启动和调试信息,实际上可以设置 LCD 作为终端进行同步显示:
    2.3.1 设置uboot的bootargs
    重启开发板,在倒计时时按回充进入ubout,可以先看下之前的bootargs配置:
    图片 39.png
    只需要在原来的基础上再添加console=tty1即可:
    1. setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.5.104:/home/xxpcb/myTest/nfs/rootfs,proto=tcp,nfsvers=4 rw ip=192.168.5.102:192.168.5.104:192.168.5.1:255.255.255.0::eth1:off'
    2. saveenv
    复制代码
    然后重启开发板,在Linux内核驱动的时候就可以在屏幕上看到输出信息了:
    图片 40.png
    对比一下串口输出的信息,可以看出屏幕输出到Freeing unused kernel memory: 400K (8090e000 - 80972000)这句后就没有了,没有出现按下回车键继续的提示,也没有显示开启自启动的hello word测试程序的打印,这是因为某些设置还未完成。
    图片 41.png
    2.3.2 修改/etc/inittab文件
    该修改用于设置屏幕作为终端进行交互。
    打开根文件系统中的/etc/inittab 文件,加入下面这一行:
    1. tty1::askfirst:-/bin/sh
    复制代码
    图片 42.png

    保存后重启板子,并在板子的USB接口插上键盘,就可以通过键盘和板子交互了:
    图片 43.png
    现在通过板子插入键盘,也可以在屏幕上操作板子了。
    注意,之前设置的开机启动的hello word程序的打印没有出现在屏幕上,是因为printf的输入没有设置的LCD中,我们可以通过将输出指向 /dev/tty1 来实现LCD屏幕的打印,比如测试屏幕输出hello linux:
    1. echo hello linux > /dev/tty1
    复制代码
    图片 44.png
    2.4 其它问题2.4.1 自动熄屏的问题
    当没有操作LCD屏幕一段时间后,屏幕会自动黑屏,这时可以通过接入键盘按下回车键进行唤醒(也可以通过板子的ON/OFF按键进行唤醒,因为该按键也被赋予了回车键的功能)。
    这个时间是在Linux源码的 drivers/tty/vt/vt.c中设置的,默认是10分钟(10*60秒)。
    图片 45.png
    如果想让屏幕一直亮着,可以将改值设为0,并重新编辑Linux内核得到zImage,然后用新的zImage启动开发板。
    如果不想修改zImage,另外一种方式可以创建一个开机启动的应用程序来控制屏幕不熄灭, lcd_always_on.c的内容为:
    1. #include <fcntl.h>
    2. #include <stdio.h>
    3. #include <sys/ioctl.h>

    4. int main(int argc, char *argv[])
    5. {
    6.    int fd;
    7.    fd = open("/dev/tty1", O_RDWR);
    8.    write(fd, "\033[9;0]", 8);
    9.    close(fd);
    10.    return 0;
    11. }
    复制代码
    在ubuntu中编译该程序,然后将可执行程序拷贝到板子的根文件系统中:
    1. arm-linux-gnueabihf-gcc lcd_always_on.c -o lcd_always_on  cp lcd_always_on ~/myTest/nfs/rootfs/usr/bin/
    复制代码
    图片 46.png
    然后,/etc/init.d/rcS中设置该程序开机自启动即可。
    图片 48.png
    保存后,重启开发板,屏幕就不会自动熄屏了。
    2.4.2 屏幕亮度调节
    屏幕的亮度也是可以调节的,设备树中背光节点设置了8 个等级,可以在 0~7范围内进行亮度调节,进入下面的目录,可以查看当前屏幕的亮度:
    1. /sys/devices/platform/backlight/backlight/backlight
    复制代码
    通过下面的指令可以实时修改屏幕的亮度,比如修改亮度为1:
    1. echo 1 > brightness
    复制代码
    总结
    本篇介绍了LCD屏幕驱动相关知识并进行了实验,因为NXP官方的板子和我这个板子的LCD引脚一样,因此主要的修改就是将设备树中的lcdif 节点的屏幕参数进行修改即可。
    通过实验,可以将企鹅logo显示出来,并将板子的输出信息定向到了LCD屏幕显示,通过接入键盘可实现与Linux板子的交互。最后,还测试了屏幕熄屏和亮度调节功能。
    图片 37.png
    图片 47.png
    签到
    回复

    使用道具 举报

  • TA的每日心情
    开心
    1 小时前
  • 签到天数: 503 天

    [LV.9]以坛为家II

    8

    主题

    1398

    帖子

    0

    金牌会员

    Rank: 6Rank: 6

    积分
    3002
    最后登录
    2024-5-29
    发表于 2022-8-29 08:49:51 | 显示全部楼层
    厉害,谢谢分享
    哎...今天够累的,签到来了~
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /4 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2024-5-29 10:28 , Processed in 0.120111 second(s), 20 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

    快速回复 返回顶部 返回列表