ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

Linux驱动开发十七.LCD屏幕驱动

2022-09-07 00:04:29  阅读:192  来源: 互联网

标签:mxsfb struct LCD fb Linux active 驱动 display


屏幕是一个嵌入式设备中相当重要的外设了。在做裸机驱动开发的时候,闹疫情隔离在家,手里没有触摸屏,所以就没看驱动开发。好在内核已经为我们提供了现成的驱动,我们只需要在设备树里定义好LCD的相关信息,就可以点亮屏幕。至于实际底层是驱动的,我们这里就先不搞了,以后如果有机会再回头看看裸机驱动里的LCD篇。有一点要注意的是:这一章我们只考虑LCD的正常点亮,暂时不考虑屏幕的触摸驱动。

FrameBuffer设备

可以先参考一下教程里是如何实现LCD的裸机驱动的:

  1. 初始化I.MX的eLCDif控制器,重点是LCD的信息
  2. 初始化LCD像素时钟
  3. 设置RGBLCD显存
  4. 直接通过操作显存来实现在LCD上显示字符、图片等信息

我们在Linux中最终也是通过RGBLCD的显存来实现LCD的显示操作都,但是我们在一开始写Linux驱动就讲过,内存都是要申请才能使用的,并且由于MMU的介入我们并不能直接对内存进行操作。所以就有了Framebuffer这个概念。在Linux中,“Framebuffer”或者“fb”都指的是framebuffer设备(与其说是设备,更不如说是一种机制),Linux内核将所有和显示有关的软件和硬件集成到一起,虚拟出来了一个fb设备,在LCD能够正常驱动以后就会有一个/dev/fbn(n从0开始的任意数)设备文件,这个fb有些时候还被称作“帧缓存”。

 用户应用程序直接访问这个设备就可以实现LCD的操作了。具体的使用方法我们这里暂时也用不到,也不再展开讲了,如果以后有兴趣的可以百度一下framebuffer编程,大把的资源可以参考。

framebuffer驱动流程

framebuffer在Linux内核中时通过结构特fb_info来进行描述的(include/linux/fb.h)

 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;    /* Current var */
 8     struct fb_fix_screeninfo fix;    /* Current 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 
17 #ifdef CONFIG_FB_BACKLIGHT
18     /* assigned backlight device */
19     /* set before framebuffer registration, 
20        remove after unregister */
21     struct backlight_device *bl_dev;
22 
23     /* Backlight level curve */
24     struct mutex bl_curve_mutex;    
25     u8 bl_curve[FB_BACKLIGHT_LEVELS];
26 #endif
27 #ifdef CONFIG_FB_DEFERRED_IO
28     struct delayed_work deferred_work;
29     struct fb_deferred_io *fbdefio;
30 #endif
31 
32     struct fb_ops *fbops;
33     struct device *device;        /* This is the parent */
34     struct device *dev;        /* This is this fb device */
35     int class_flag;                    /* private sysfs flags */
36 #ifdef CONFIG_FB_TILEBLITTING
37     struct fb_tile_ops *tileops;    /* Tile Blitting */
38 #endif
39     char __iomem *screen_base;    /* Virtual address */
40     unsigned long screen_size;    /* Amount of ioremapped VRAM or 0 */ 
41     void *pseudo_palette;        /* Fake palette of 16 colors */ 
42 #define FBINFO_STATE_RUNNING    0
43 #define FBINFO_STATE_SUSPENDED    1
44     u32 state;            /* Hardware state i.e suspend */
45     void *fbcon_par;                /* fbcon use-only private area */
46     /* From here on everything is device dependent */
47     void *par;
48     /* we need the PCI or similar aperture base/size not
49        smem_start/size as smem_start may just be an object
50        allocated inside the aperture so may not actually overlap */
51     struct apertures_struct {
52         unsigned int count;
53         struct aperture {
54             resource_size_t base;
55             resource_size_t size;
56         } ranges[0];
57     } *apertures;
58 
59     bool skip_vt_switch; /* no VT switch on suspend/resume required */
60 };

归根到底LCD的驱动就是对这个fb_info里面的成员进行初始化,然后通过register_framebuffer函数进行注册就行了。恩智浦还对I.MX系列的控制器还定义了一个专用的结构体(drivers/video/fbdev/mxsfb.c)

struct mxsfb_info {
    struct fb_info *fb_info;
    struct platform_device *pdev;
    struct clk *clk_pix;
    struct clk *clk_axi;
    struct clk *clk_disp_axi;
    bool clk_pix_enabled;
    bool clk_axi_enabled;
    bool clk_disp_axi_enabled;
    void __iomem *base;    /* registers */
    u32 sync;        /* record display timing polarities */
    unsigned allocated_size;
    int enabled;
    unsigned ld_intf_width;
    unsigned dotclk_delay;
    const struct mxsfb_devdata *devdata;
    struct regulator *reg_lcd;
    bool wait4vsync;
    struct completion vsync_complete;
    struct completion flip_complete;
    int cur_blank;
    int restore_blank;
    char disp_dev[32];
    struct mxc_dispdrv_handle *dispdrv;
    int id;
    struct fb_var_screeninfo var;
};

这个mxsfb_info里除了封装了一个fb_info结构体,还有一些常用的LCD相关属性。如果我们想要分析恩智浦是怎么写的这个LCD屏幕的驱动,就可以分析这个mxsfb.c文件。下面粗略的捋一下流程:

mxsfb.c实质上也是一个platform框架下的驱动,所以也就可以倒着看下(为了能看到行号,这里用截图,不再单独粘贴代码)

最后是注册模块

所以我们要关注的就是mxsfb_driver结构体

这个是不是很眼熟, 就是用来做platform驱动设备直接匹配到结构体。看看那个mxsfb_dt_ids

 

在无设备树的情况下要将LCD设备命名为mxsfb,但是我们是使用设备树的,就要将匹配的名称定义成上面那个列表里的fsl,imx23(8)-lcdif。 

在设备和驱动匹配程勇以后要执行probe对应的函数,这个函数比较大,就一步步大概的功能说一下吧

1.gpio相关初始化

这里第1388行的gpio设置是lcd的电源设置(供电输出),我们使用的LCD电源是直接拉在板子电源上的,这行实际没有实际意义。

  2.资源获取

获取到资源是IORSOURCE_MEM类型,也就是寄存器类型的数据,我们可以搜索一下设备树信息,可以在imx6ull.dtsi里找到和驱动name匹配的参数

也就是收上面资源获取到的就是0x021c8000。在开发手册里能找到这个地址对应的寄存器

 这个地址就是eLCDIF通用寄存器的首地址。然后通过devm_kzalloc申请了一段内存给参数host,内存大小跟mxsfb_info一致,host就是一个前面说的mxsfb_info对象。

接下来通过framebuffer_alloc申请了一个fb_info,然后把这个fb_info指向了mxsfb_info(1413和1414行)

下面是申请中断

 中断处理函数是mxsfb_irq_handler,在中断处理函数中主要是一对寄存器的处理,这里不再细说

从1425行开始就是进行内存映射了,res是获取到的LCDIF寄存器的首地址,host->base就是LCDIF寄存器组首地址的映射地址。后面一对代码就是获取时钟什么的映射地址

 到1462行,就开始对fb_info进行初始化了。

 到1473行,调用了mxsfb_init_fb_info函数对host,也就是mxsfb_inof进行了初始化

到1494行,对初始化后的fb_info对象进行注册

 

这样就完成了驱动的加载。有些函数可以再展开看下,比如哪个mxsfb_init_fbinfo ,里面就有根据分辨率计算各种数据,然后根据计算结果通过mxsfb_map_videomem去申请显存(放给成员变量screen_base)。还有通过of函数从设备树文件里获取到一系列信息。

在mxsfb_init_fbinfo里还定义了一个mxsfb_ops,相当于我们前面写驱动时候那个文件操作集合。

这些函数NXP也已经为我们写好了。这里也不分析。

总之,mxsfb_probe的主要作用就是用来初始化fb_info然后向内核注册,还有就是初始化LCDIF控制器。

点亮屏幕

上面说过了,LCD的驱动也属于platform架构驱动,并且NXP已经为我们完成了驱动的编写,我们要做的就是在设备树里完善我们显示屏的相关信息就可以了。在上面我们截取了imx6ull.dtsi里的lcdif节点的信息,可以里面内容比较少,哪个imx6ull.dtsi是基于imx6ull架构的通用设备信息,我们可以在板级的设备树里找一下有没有相关的节点

&lcdif {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_lcdif_dat
             &pinctrl_lcdif_ctrl
             &pinctrl_lcdif_reset>;
    display = <&display0>;
    status = "okay";

    display0: display {
        bits-per-pixel = <16>;
        bus-width = <24>;

        display-timings {
            native-mode = <&timing0>;
            timing0: timing0 {
            clock-frequency = <9200000>;
            hactive = <480>;
            vactive = <272>;
            hfront-porch = <8>;
            hback-porch = <4>;
            hsync-len = <41>;
            vback-porch = <2>;
            vfront-porch = <4>;
            vsync-len = <10>;

            hsync-active = <0>;
            vsync-active = <0>;
            de-active = <1>;
            pixelclk-active = <0>;
            };
        };
    };

};

注意一下他这个节点信息,是一个480X272分辨率的屏幕,并且和函数xsfb_init_fbinfo_dt要通过of函数获取的东西一样,我们要修改的内容就是这个设备树里的信息

 

 

 上面的表格就是我们使用的7寸的LCD对应的时钟参数,可以参考Documentation/devicetree/bindings/fb/mxsfb.txt里的参考文档

* Freescale MXS LCD Interface (LCDIF)

Required properties:
- compatible: Should be "fsl,<chip>-lcdif".  Supported chips include
  imx23 and imx28.
- reg: Address and length of the register set for lcdif
- interrupts: Should contain lcdif interrupts
- display : phandle to display node (see below for details)

* display node

Required properties:
- bits-per-pixel : <16> for RGB565, <32> for RGB888/666.
- bus-width : number of data lines.  Could be <8>, <16>, <18> or <24>.

Required sub-node:
- display-timings : Refer to binding doc display-timing.txt for details.

Examples:

lcdif@80030000 {
    compatible = "fsl,imx28-lcdif";
    reg = <0x80030000 2000>;
    interrupts = <38 86>;

    display: display {
        bits-per-pixel = <32>;
        bus-width = <24>;

        display-timings {
            native-mode = <&timing0>;
            timing0: timing0 {
                clock-frequency = <33500000>;
                hactive = <800>;
                vactive = <480>;
                hfront-porch = <164>;
                hback-porch = <89>;
                hsync-len = <10>;
                vback-porch = <23>;
                vfront-porch = <10>;
                vsync-len = <10>;
                hsync-active = <0>;
                vsync-active = <0>;
                de-active = <1>;
                pixelclk-active = <0>;
            };
        };
    };
};

我们的屏幕使用的像素为RGB888(RGB各占8位,还有一个Alpha透明度也占8位)所以bits-per-pixel就是32,bus-width跟我们硬件电路有关系,可以看下图

 

 LCD_DATA,也就是数据线一共是24根,所以带宽就是24。后面display-timings里的属性就按LCD供货商给我们提供的参数就行了。

display1: display {
    bits-per-pixel = <32>;
    bus-width = <24>;

    display-timings {
        native-mode = <&timing0>;
        timing0: timing0 {
        clock-frequency = <51200000>;
        hactive = <1024>;
        vactive = <600>;
        hfront-porch = <160>;
        hback-porch = <140>;
        hsync-len = <20>;
        vback-porch = <20>;
        vfront-porch = <12>;
        vsync-len = <3>;

        hsync-active = <0>;
        vsync-active = <0>;
        de-active = <1>;
        pixelclk-active = <0>;
        };
    };
};

时间的说明可以按照文档里给出的提示文档

Required sub-node:
- display-timings : Refer to binding doc display-timing.txt for details.

可以查到Documentation/devicetree/bindings/video/display-timing.txt里面对这个有描述

 1 timing subnode
 2 --------------
 3 
 4 required properties:
 5  - hactive, vactive: display resolution
 6  - hfront-porch, hback-porch, hsync-len: horizontal display timing parameters
 7    in pixels
 8    vfront-porch, vback-porch, vsync-len: vertical display timing parameters in
 9    lines
10  - clock-frequency: display clock in Hz
11 
12 optional properties:
13  - hsync-active: hsync pulse is active low/high/ignored
14  - vsync-active: vsync pulse is active low/high/ignored
15  - de-active: data-enable pulse is active low/high/ignored
16  - pixelclk-active: with
17             - active high = drive pixel data on rising edge/
18                     sample data on falling edge
19             - active low  = drive pixel data on falling edge/
20                     sample data on rising edge
21             - ignored     = ignored
22  - interlaced (bool): boolean to enable interlaced mode
23  - doublescan (bool): boolean to enable doublescan mode
24  - doubleclk (bool): boolean to enable doubleclock mode
25 
26 All the optional properties that are not bool follow the following logic:
27     <1>: high active
28     <0>: low active
29     omitted: not used on hardware

文档里说了,属性为1的时候是高电平有效,0的时候为低电平有效。这个就是最后4行同步信号的属性,这个同步信号可以从屏幕的时序图里找到。如果使用哪家的屏幕一定要来一份详细的资料。

GPIO修改

从设备树里可以看到Pinctrl的配置

pinctrl-0 = <&pinctrl_lcdif_dat
            &pinctrl_lcdif_ctrl
            &pinctrl_lcdif_reset>;

 主要分了数据组、控制组、复位组。可以看下对应的参数设置

pinctrl_lcdif_dat: lcdifdatgrp {
            fsl,pins = <
                MX6UL_PAD_LCD_DATA00__LCDIF_DATA00  0x79
                MX6UL_PAD_LCD_DATA01__LCDIF_DATA01  0x79
                MX6UL_PAD_LCD_DATA02__LCDIF_DATA02  0x79
                MX6UL_PAD_LCD_DATA03__LCDIF_DATA03  0x79
                MX6UL_PAD_LCD_DATA04__LCDIF_DATA04  0x79
                MX6UL_PAD_LCD_DATA05__LCDIF_DATA05  0x79
                MX6UL_PAD_LCD_DATA06__LCDIF_DATA06  0x79
                MX6UL_PAD_LCD_DATA07__LCDIF_DATA07  0x79
                MX6UL_PAD_LCD_DATA08__LCDIF_DATA08  0x79
                MX6UL_PAD_LCD_DATA09__LCDIF_DATA09  0x79
                MX6UL_PAD_LCD_DATA10__LCDIF_DATA10  0x79
                MX6UL_PAD_LCD_DATA11__LCDIF_DATA11  0x79
                MX6UL_PAD_LCD_DATA12__LCDIF_DATA12  0x79
                MX6UL_PAD_LCD_DATA13__LCDIF_DATA13  0x79
                MX6UL_PAD_LCD_DATA14__LCDIF_DATA14  0x79
                MX6UL_PAD_LCD_DATA15__LCDIF_DATA15  0x79
                MX6UL_PAD_LCD_DATA16__LCDIF_DATA16  0x79
                MX6UL_PAD_LCD_DATA17__LCDIF_DATA17  0x79
                MX6UL_PAD_LCD_DATA18__LCDIF_DATA18  0x79
                MX6UL_PAD_LCD_DATA19__LCDIF_DATA19  0x79
                MX6UL_PAD_LCD_DATA20__LCDIF_DATA20  0x79
                MX6UL_PAD_LCD_DATA21__LCDIF_DATA21  0x79
                MX6UL_PAD_LCD_DATA22__LCDIF_DATA22  0x79
                MX6UL_PAD_LCD_DATA23__LCDIF_DATA23  0x79
            >;
        };

pinctrl_lcdif_ctrl: lcdifctrlgrp {
    fsl,pins = <
        MX6UL_PAD_LCD_CLK__LCDIF_CLK        0x79
        MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE  0x79
        MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC    0x79
        MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC    0x79
    >;
};

正点原子这个阿尔法开发板的电路设计会导致BUG,需要将GPIO的电气属性修改一下,将原来的0x79修改成0x49,主要就是修改了对应GPIO输出引脚的输出能力(原来79的时候输出能力强,有可能会影响到网络,这里就把GPIO的驱动能力降下来)。reset我们没有使用,是直接跟电源走的,一上电屏幕点亮,掉电关闭。不用reset功能。

显示测试

现在已经完成了platform设备的定义,把设备树make一下启动系统,下面我们需要用一个能显示出来的东西测试一下驱动。最直接的方法就是显示出来Linux的小企鹅logo。这个logo需要在编译内核的时候配置出来

 按照上面的路径进去以后会看到有三个选项,我们都选中

保存退出。启动开发板,如果屏幕连接正常,就可以在进入内核后显示一个小企鹅的logo在左上角

拍照的时候把自己映进去了,打个码。这样就说明屏幕驱动没问题。要注意这个LOGO不是开机时候显示的NXP那个LOGO,NXP是通过Uboot里的屏幕显示出来的,即便我们不做那个设备树都有可能显示出来。

此外我们还可以在屏幕上通过命令直接打印一个信息出来

/ # echo hello world>/dev/tty1

 tty1就是LCD上的终端。执行完上面的指令就会在屏幕上打印出hello world。

LCD终端

在完成了LCD驱动以后,可以让LCD作为终端来工作,

修改uboot

首先是修改uboot里的环境变量

=> setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.0.100:/home/qi/IMX6UL/rootfs ip=192.168.0.200:192.168.0.100:192.168.0.1:255.255.255.0::eth0:off'
=> saveenv

开机以后进入Uboot,用上面的命令修改bootargs。后面的根文件系统路径以及IP根据实际情况修改。主要就是添加红色加粗的部分。开机以后就可以看到LCD上打印出来跟串口一样的信息。但是这个时候还没法使用,我们还要修改一个文件

运行级别修改

我们要修改/etc/inittab这个文件

添加第4行那条命令(注意tty1后面是两个冒号!)重启开发板以后就会在各个终端(LCD或串口输出)显示一句话:

就是上面图中最后一行提示,按下回车键就可以使用终端。我们可以在开发板上插个键盘,就可以直接当个简单的电脑使用了。

背光调节

我们的LCD是有背光的,背光亮度在设备树中给了8个级别可以调节(如果我没记错是基于PWM来实现的)。背光是一个单独的设备呈现在内核中,也是一个platform框架下的驱动设备,可以看一下设备树中定义的节点信息

backlight {
    compatible = "pwm-backlight";
    pwms = <&pwm1 0 5000000>;
    brightness-levels = <0 4 8 16 32 64 128 255>;
    default-brightness-level = <6>;
    status = "okay";
};

也可以看一下设备文件

 

 

展开以后找到这个文件

 

 

从设备树信息里可以看到,背光的默认值是6,可以向这个文件里写入0-7就会改变背光的亮度。

/sys/devices/platform/backlight/backlight/backlight # echo 7 > brightness

如果写入值为0,屏幕亮度最低,基本上看不到内容,写入7就是最亮了。

屏幕休眠

屏幕在没有操作的时候大概10分钟就熄屏了,我们在讲input子系统的时候把那个按键定义成了回车键,在熄屏以后可以点击按键重新点亮屏幕,如果不想要熄屏可以用下面两种方法搞定!

修改内核

如果我们不想要屏幕熄灭可以修改内核文件(drivers/tty/vt/vt.c)

那个blankinterval就是熄屏的时间,10*60就是10分钟,把它设置为0就关闭了熄屏功能

脚本 

如果不想重新编译内核,可以直接写个脚本控制

 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 }

用arm-linux-gnueabihf-gcc(一定要用交叉编译器编译啊!)编译完生成的执行文件放在rootfs下,在熄屏状态下直接运行一下看看屏有没有恢复点亮,如果没问题可以在/etc/init.d/rcS文件最后追加这个文件的运行指令

 

保存以后重启开发板,屏就不会熄灭了。

标签:mxsfb,struct,LCD,fb,Linux,active,驱动,display
来源: https://www.cnblogs.com/yinsedeyinse/p/16659228.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有