Linux驱动.之LCD,一文讲解,时序,framebuffer驱动框架不带dts和带dts版本(一)

本文比较长,一文讲解完lcd的知识,非常深入的讲解了lcd,开发lcd驱动。工作很多年后,很多文章是近期写的,以前记录在别的文档,所以比起大学期间,和前几年的理解,现在的理解,就是降维打击,以前很多东西学过,开发过,都不记得了,现在有空来记录一下,有些太过核心的,没有公开,私密。

目录

1、lcd接口,显示原理
2、lcd时序图
3、lcd各个时钟计算公式
4、soc寄存器设置,lcd时序设置
5、编写函数画像素点,显示画图原理
6、frambufer框架
7、新内核,带dts,frambufer框架

一、lcd接口,显示原理

在裸机篇,讲过了,如何控制x210的cpu寄存器,控制soc输出 lcd时序,可以看前面的文章。
链接地址:https://blog.youkuaiyun.com/rjszcb/article/details/141072923

LCD屏,常见的接口有(RGB, LVDS, MIPI, TTL , VGA),关于他们之间的差异,可以百度。
无论什么接口,都是转换成ttl电平,只是传输时,为了信号不衰减,转换成了mipi等等。
无论什么接口,lcd显示器面板,都是下面这种图,3个灯rgb(红绿蓝)组成一个像素点
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

RGB接口
这个rgb888,32位的,有些是16位的rgb565。
在这里插入图片描述

二、LCD时序图
在这里插入图片描述
在这里插入图片描述

VSYNC (Vertical Synchronous):帧同步信号
HSYNC (Horizontal Synchronous):行同步信号
VDEN(Video Display enable):数据有效信号
VCLK(Video Clock):像素时钟,一个时钟周期显示一个像素。(需用户配置)
VD(Video Data):像素数据,一般2个字节或者3个字节表示一个像素的值。
LEND(Line End):行结束信号。
HOZVAL(Horizontal Value):LCD屏幕的列数。(需用户配置)
LINEVAL(Line Value):LCD屏幕的行数。(需用户配置)
VSPW(Vertical Sync Pulse Width):VSYNC信号的宽度。(需用户配置)
VBPD(Vertical Back Porch Delay):VSYNC同步信号下降沿后无效行数。(需用户配置)
VFPD(Vertical Front Porch Delay):VSYNC同步信号上升沿前无效行数。(需用户配置)
HSPW(Horizontal Sync Pulse Width):HSYNC信号的宽度。(需用户配置)
HBPD(Horizontal Back Porch Delay):HSYNC同步信号下降沿后无效像素点个数。(需用户配置)
HFPD(Horizontal Front Porch Delay):HSYNC同步信号上升沿前无效像素点个数。(需用户配置)

三、soc控制lcd显示的示意图
在这里插入图片描述
只有设置好刷新率,时序,应用层往显存地址,写入一组数据,soc控制器一秒30帧自动刷新,就可以显示图像了。
在这里插入图片描述

lcd厂商,会给一份文档,关于时序的配置
在这里插入图片描述

来看下一份dts关于配置时序的设置。可以看下下面一个配置lcd的时序配置
在这里插入图片描述

ccx1k/1000 = fps 即刷新率
clock-frequency-khz = (hactive + hfront + hback + hsync) * (vactive + vfront + vback + vsync) * fps / 1000

30HZ就是 ccx1k,ccx1k/1000 = fps 即刷新率,fps 就是30帧,也就是屏幕的刷新率是30/s,

clock-frequency-khz就是时钟频率,74MHZ,

根据这个公式,就可以计算出各个时序
clock-frequency-khz = (hactive + hfront + hback + hsync) * (vactive + vfront + vback + vsync) * fps / 1000

hactive 就是水平分辨率
vactive 就是垂直分辨率

根据实际情况,分配前尖,后尖等时序,如果屏幕图像往左偏,往右偏,往上偏,往下偏,调整四个参数就行。

要使LCD控制器工作必须配置这些寄存器:LCDCON1,LCDCON2,LCDCON3,LCDCON4,LCDCON5,LCDSADDR1,LCDSADDR2,LCDSADDR3。

LCDCON1寄存器
在这里插入图片描述

CLKVAL=0x4,
PNRMODE=0x3;
BBPMODE=0x0c;
ENVID=0;
LCD手册
Dclk=9MHz~15MHz, 
HCLK=100MHz, 
Dclk=VCLK=HCLK/[(CLKVAL+1)x2]=100/((4+1)*2)=10MHz

所以
LCDCON1=(CLKVAL<<8) | (PNRMODE<<5) | (BBPMODE<<1) | ENVID;

LCDCON2寄存器
在这里插入图片描述

根据LCD手册有
VBPD_480272=(2-1),
LINEVAL_TFT_480272=(272-1),
VFPD_480272=(2-1),
VSPW_480272=(10-1)

LCDCON2 = (VBPD_480272<<24) | (LINEVAL_TFT_480272<<14) | (VFPD_480272<<6) | (VSPW_480272);

LCDCON3寄存器
在这里插入图片描述


根据LCD手册有
HBPD_480272=(2-1),
HOZVAL_TFT_480272=(480-1),
HFPD_480272=(2-1)

所以这个寄存器配置如下
LCDCON3 = (HBPD_480272<<19) | (HOZVAL_TFT_480272<<8) | (HFPD_480272);

LCDCON4寄存器
在这里插入图片描述

LCDCON4 = HSPW_480272;
根据LCD用户手册HSPW_480272=(41-1)

LCDCON5寄存器
在这里插入图片描述
在这里插入图片描述

LCDCON5 = (FORMAT8BPP_565<<11) | (HSYNC_INV<<9) | (VSYNC_INV<<8) | (HWSWP<<1);

根据LCD手册
FORMAT8BPP_565=1,
HSYNC_INV=1,
VSYNC_INV=1,
HWSWP=1
以上主要是根据LCD手册设置了LCD控制器的时序,下面我们要设置LCD的显示缓存地址

LCDSADDR1寄存器
在这里插入图片描述

#define LOWER21BITS(n)  ((n) & 0x1fffff)
#define LCDFRAMEBUFFER 0x30400000  //设置了一款ddr的内存地址

LCDSADDR1 = ((LCDFRAMEBUFFER>>22)<<21) | LOWER21BITS(LCDFRAMEBUFFER>>1);

LCDSADDR2寄存器

在这里插入图片描述

LCDSADDR2 = LOWER21BITS((LCDFRAMEBUFFER+ (LINEVAL_TFT_480272 + 1)*(HOZVAL_TFT_480272+1)*2)>>1);

其中
LINEVAL_TFT_480272=(272-1),
HOZVAL_TFT_480272=(480-1)

LCDSADDR3寄存器
在这里插入图片描述

LCDSADDR3 = (0<<11) | (LCD_XSIZE_TFT_480272 *2 / 2);

四、设置soc寄存器,控制LCD初始化程序
这个设置是mini2440的设置,不是x210的,只是举例,所有soc几乎都是这么设置,但是寄存器可能不一样

初始化用于LCD的引脚 grb,各个控制脚的配置,输出模式

void Lcd_Port_Init(void)
{
    GPCUP   = 0xffffffff;   // 禁止内部上拉
    GPCCON  = 0xaaaaaaaa;   // GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND 
    GPDUP   = 0xffffffff;   // 禁止内部上拉
    GPDCON  = 0xaaaaaaaa;   // GPIO管脚用于VD[23:8]
  	GPBCON &= ~(GPB0_MSK);  // Power enable pin
    GPBCON |= GPB0_out;
    GPBDAT &= ~(1<<0);			// Power off
}

初始化soc LCD控制器

void Tft_Lcd_Init(void)
{
       /* 
* 设置LCD控制器的控制寄存器LCDCON1~5
* 1. LCDCON1:
*    设置VCLK的频率:VCLK(Hz) = HCLK/[(CLKVAL+1)x2]
*    选择LCD类型: TFT LCD   
*    设置显示模式: 16BPP
*    先禁止LCD信号输出
* 2. LCDCON2/3/4:
*    设置控制信号的时间参数
*    设置分辨率,即行数及列数
* 现在,可以根据公式计算出显示器的频率:
* 当HCLK=100MHz时,
* Frame Rate = 1/[{(VSPW+1)+(VBPD+1)+(LIINEVAL+1)+(VFPD+1)}x
*              {(HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1)}x
*              {2x(CLKVAL+1)/(HCLK)}]
*            = 60Hz
* 3. LCDCON5:
*    设置显示模式为16BPP时的数据格式: 5:6:5
*    设置HSYNC、VSYNC脉冲的极性(这需要参考具体LCD的接口信号): 反转
*    半字(2字节)交换使能
*/

        LCDCON1 = (CLKVAL_TFT_480272<<8) | (LCDTYPE_TFT<<5) | \
                  (BPPMODE_16BPP<<1) | (ENVID_DISABLE<<0);
        LCDCON2 = (VBPD_480272<<24) | (LINEVAL_TFT_480272<<14) | \
                  (VFPD_480272<<6) | (VSPW_480272);
        LCDCON3 = (HBPD_480272<<19) | (HOZVAL_TFT_480272<<8) | (HFPD_480272);
        LCDCON4 = HSPW_480272;
        LCDCON5 = (FORMAT8BPP_565<<11) | (HSYNC_INV<<9) | (VSYNC_INV<<8) | \
                  (HWSWP<<1);

        /*
         * 设置LCD控制器的地址寄存器LCDSADDR1~3
         * 帧内存与视口(view point)完全吻合,
         * 图像数据格式如下:
         *         |----PAGEWIDTH----|
         *    y/x  0   1   2       479
         *     0   rgb rgb rgb ... rgb
         *     1   rgb rgb rgb ... rgb
         * 1. LCDSADDR1:
         *    设置LCDBANK、LCDBASEU
         * 2. LCDSADDR2:
         *    设置LCDBASEL: 帧缓冲区的结束地址A[21:1]
         * 3. LCDSADDR3:
         *    OFFSIZE等于0,PAGEWIDTH等于(480*2/2)
         */
        LCDSADDR1 = ((LCDFRAMEBUFFER>>22)<<21) | LOWER21BITS(LCDFRAMEBUFFER>>1);
        LCDSADDR2 = LOWER21BITS((LCDFRAMEBUFFER+ \
                    (LINEVAL_TFT_480272+1)*(HOZVAL_TFT_480272+1)*2)>>1);
        LCDSADDR3 = (0<<11) | (LCD_XSIZE_TFT_480272*2/2);

        /* 禁止临时调色板寄存器 */
        TPAL = 0;

        fb_base_addr = LCDFRAMEBUFFER;
        bpp = 16;
        xsize = 480;
        ysize = 272;
}

void Lcd_PowerEnable(int invpwren, int pwren)
{
    GPGCON = (GPGCON & (~(3<<8))) | (3<<8);   // GPG4用作LCD_PWREN
    GPGUP  = (GPGUP & (~(1<<4))) | (1<<4);    // 禁止内部上拉    
        
    LCDCON5 = (LCDCON5 & (~(1<<5))) | (invpwren<<5);  // 设置LCD_PWREN的极性: 正常/反转
    LCDCON5 = (LCDCON5 & (~(1<<3))) | (pwren<<3);     // 设置是否输出LCD_PWREN
} 


/*
 * 设置LCD控制器是否输出信号
 * 输入参数:
 * onoff: 0 : 关闭 1 : 打开
 */
void Lcd_EnvidOnOff(int onoff)
{
    if (onoff == 1)
    {
        LCDCON1 |= 1;         // ENVID ON
    }
    else
    {
        LCDCON1 &= 0x3fffe;  // ENVID Off
    }
}

    
#include "lcddrv.h"
#include "framebuffer.h"
int main()
{  
    Lcd_Port_Init();                     // 设置LCD引脚
    Tft_Lcd_Init();                      // 初始化LCD控制器
    Lcd_PowerEnable(0, 1);               // 设置LCD_PWREN有效,它用于打开LCD的电源
    Lcd_EnvidOnOff(1);                   // 使能LCD控制器输出信号
    Lcd_Palette8Bit_Init();              // 初始化调色板
    ClearScr(0x0);   
     
    while (1){;} 
    return 0;
}

四、点亮像素点,显示图片
裸机
链接地址:https://blog.youkuaiyun.com/rjszcb/article/details/141072923

应用层,显示图片
原文链接:https://blog.youkuaiyun.com/rjszcb/article/details/142423668

五、老内核,不带dts,framebuffer框架

上面介绍了裸机部分,不带framebuffer框架,设置soc寄存器的,下面介绍将这些设置,加入到frambuffer框架。
从驱动来看,fb是一个典型的字符设备,而且创建了一个类/sys/class/graphics

Framebuffer驱动程序框架,概述:分为上下两层:

fbmem.c:

承上启下

实现、注册file_operations结构体

把APP的调用向下转发到具体的硬件驱动程序

xxx_fb.c:硬件相关的驱动程序

实现、注册fb_info结构体

实现硬件操作

Framebuffer驱动程序框架主要分为两个层次:核心层和硬件相关层。下面是这两个层次的简要概述和关键组件:

1. 核心层(fbmem.c)

核心层主要负责处理与VFS(虚拟文件系统)的交互,以及将应用程序的调用转发到具体的硬件驱动程序。这一层通常包含以下组件:

file_operations结构体:定义了针对Framebuffer的特殊文件操作,例如open(), release(), ioctl(), mmap()等。这些操作处理来自用户空间的请求。

帧缓冲区管理:管理帧缓冲区的内存分配和释放。这可能包括请求内存区域、处理内存映射等。

事件处理:处理与Framebuffer相关的各种事件,如模式变化、显示关闭和打开等。

数据转发:将应用程序的调用(如读写操作)转发到具体的硬件驱动程序。

2. 硬件相关层(xxx_fb.c)

硬件相关层专注于与特定硬件的交互。这一层通常包含以下组件:

fb_info结构体:包含了Framebuffer的所有必要信息,如屏幕大小、颜色深度、帧缓冲区的物理地址等。

硬件操作:实现对硬件的具体操作,包括但不限于:

初始化和配置LCD控制器。
配置屏幕的分辨率、颜色深度等参数。
控制屏幕的开启和关闭。
数据传输:实现数据从CPU内存到Framebuffer的传输,以及可能的DMA(直接内存访问)操作。

中断处理:如果硬件支持中断,注册并实现中断服务例程,以处理例如垂直同步等事件。

注册和注销:在驱动程序加载和卸载时,注册和注销fb_info结构体。

先来看示意图。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

上面这些示意图,别人画的比较好,我就不画了,由图可以看到,framebuffer驱动框架,分为核心层,设备驱动层

帧缓冲设备在Linux中也可以看做是一个完整的子系统,大体由fbmem.c和xxxfb.c组成。向上给应用程序提供完善的设备文件操作接口(即对FrameBuffer设备进行read、write、ioctl等操作),接口在Linux提供的fbmem.c文件中实现;向下提供了硬件操作的接口,只是这些接口Linux并没有提供实现,因为这要根据具体的LCD控制器硬件进行设置,所以这就是我们要做的事情了(即xxxfb.c部分的实现)。

1、LCD驱动编写基础函数

在讲之前,先简单记住几个知识点,重要的数据结构,再来深入分析驱动框架

1、dma_alloc_wc
该函数定义在include/linux/dma-mapping.h:
该函数用于申请一段DMA缓冲区,分配出来的内存会禁止cache缓存(因为DMA传输不需要CPU)。
返回值为:申请到的DMA缓冲区的虚拟地址,若为NULL,表示分配失败

static inline void *dma_alloc_wc(struct device *dev, size_t size,dma_addr_t *dma_addr, gfp_t gfp)

参数如下: 
dev:设备指针;
size:分配的地址大小(字节单位);
dma_addr:申请到的物理起始地址;
gfp:分配出来的内存参数,标志定义在<linux/gfp.h>,常用标志如下:
GFP_ATOMIC 用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠;
GFP_KERNEL 内核内存的正常分配. 可能睡眠;
GFP_USER 用来为用户空间页来分配内存; 它可能睡眠.

2、dma_free_wc
该函数用于释放DMA缓冲区,参数和dma_alloc_wc一样,释放内存,避免内存泄漏。

static inline void dma_free_wc(struct device *dev, size_t size,void *cpu_addr, dma_addr_t dma_addr)

3、 framebuffer_alloc
动态申请一个fb_info结构体

struct fb_info *framebuffer_alloc(size_t size, struct device *dev)

参数如下:

size:额外的内存大小;
dev:设备指针,用于初始化fb_info->device成员;

2、LCD驱动编写步骤

2.1 入口函数

在驱动入口函数中实现:

(1) 动态分配fb_info结构体;
(2) 设置fb_info
(2.1).设置固定的参数fb_info->fix;
(2.2) 设置可变的参数fb_info->var;
(2.3) 设置操作函数fb_info->fbops;
(2.4) 设置fb_info其它的成员;
(3) 设置硬件相关的操作
(3.1) 配置LCD引脚:设置GPIO端口C和GPIO端口D用于LCD;
(3.2) 根据LCD手册设置LCD控制器时序参数;
(3.3) 分配显存(framebuffer),把显存地址告诉LCD控制器和fb_info;
(4) 开启LCD,并注册fb_info
(4.1) 开启LCD
控制LCDCON5允许PWREN信号,开启背光;
控制LCDCON1使能LCD;
(4.2) 注册fb_info;


2.2 出口函数
在驱动出口函数中实现:

(1) 卸载内核中的fb_info;
(2) 控制LCDCON1关闭PWREN信号,关背光,iounmap注销地址;
(3) 释放DMA缓存地址dma_free_wc;
(4) 释放注册的fb_info;

4、LCD驱动编写
下面这种是把裸机的代码,加上frambufer的框架,来写的驱动,不是标准的驱动,所以可以看出,驱动编写,真的不难,就是裸机套上框架,但是呢,提供lcd的芯片商,和soc,驱动是不会这么写,会套上标准的gpio,request申请gpio,来设置寄存器,这个我在gpio驱动讲了。

帧缓冲设备为标准的字符型设备,在Linux中主设备号29,定义在/include/linux/major.h中的FB_MAJOR,次设备号定义帧缓冲的个数,最大允许有32个FrameBuffer,定义在/include/linux/fb.h中的FB_MAX,对应于文件系统下/dev/fb%d设备文件。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
/dev/fb0就是LCD对应的设备文件,/dev/fb0是个字符设备,因此肯定有file_operations操作集,fb的file_operations操作集定义在drivers/video/fbdev/core/fbmem.c文件中

Framebuffer 相关的重要数据结构:

1、fb_info
从Framebuffer设备驱动程序结构看,该驱动主要跟fb_info结构体有关,该结构体记录了Framebuffer设备的全部信息,包括设备的设置参数、状态以及对底层硬件操作的函数指针。在Linux中,每一个Framebuffer设备都必须对应一个fb_info,fb_info在/linux/fb.h中的定义如下:(只列出重要的一些)

struct fb_info {
    atomic_t count;
    int node; ///次设备号
    int flags;
    struct mutex lock;  //用于open、release、ioctrl功能的锁
    struct mutex mm_lock;  /* Lock for fb_mmap and smem_* fields */
    struct fb_var_screeninfo var;  //LCD 可变参数
    struct fb_fix_screeninfo fix;   //LCD 固定参数
    struct fb_monspecs monspecs;   /* Current Monitor specs *///LCD 显示器标准
    struct work_struct queue;   //帧缓冲事件队列
    struct fb_pixmap pixmap;   ///图像硬件 mapper
    struct fb_pixmap sprite;   //光标硬件 mapper
    struct fb_cmap cmap;     //当前颜色表
    struct list_head modelist;   //模式列表
    struct fb_videomode *mode;   //当前的显示模式

#ifdef CONFIG_FB_BACKLIGHT
    /* assigned backlight device */
    /* set before framebuffer registration,
       remove after unregister */
    struct backlight_device *bl_dev;///对应的背光设备

    /* Backlight level curve */
    struct mutex bl_curve_mutex;
    u8 bl_curve[FB_BACKLIGHT_LEVELS];///背光调整
#endif
#ifdef CONFIG_FB_DEFERRED_IO
    struct delayed_work deferred_work;
    struct fb_deferred_io *fbdefio;
#endif

    struct fb_ops *fbops;///---->对底层硬件设备操作的函数指针
    struct device *device;     /* This is the parent *////父设备节点
    struct device *dev;      /* This is this fb device *////当前的帧缓冲设备
    int class_flag;               /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
    struct fb_tile_ops *tileops;  /* Tile Blitting *///图块Blitting ?
#endif
    char __iomem *screen_base;   /* Virtual address *///虚拟地址
    unsigned long screen_size;   /* Amount of ioremapped VRAM or 0 */ ///LCD IO映射的虚拟内存大小
    void *pseudo_palette;       /* Fake palette of 16 colors *///伪16色 颜色表
#define FBINFO_STATE_RUNNING   0
#define FBINFO_STATE_SUSPENDED   1
    u32 state;            /* Hardware state i.e suspend *////LCD 挂起或复位的状态
    void *fbcon_par;                /* fbcon use-only private area */
    /* From here on everything is device dependent */
    void *par;
    /* we need the PCI or similar aperture base/size not
       smem_start/size as smem_start may just be an object
       allocated inside the aperture so may not actually overlap */
    struct apertures_struct {
        unsigned int count;
        struct aperture {
            resource_size_t base;
            resource_size_t size;
        } ranges[0];
    } *apertures;

    bool skip_vt_switch; /* no VT switch on suspend/resume required */
};

其中,fb_var_screeninfo和fb_fix_screeninfo两个结构体跟LCD硬件属性相关,fb_var_screeninfo代表可修改的LCD显示参数,如分辨率和像素比特数;fb_fix_screeninfo代表不可修改的LCD属性参数,如显示内存的物理地址和长度等。另外一个非常重要的成员是fb_ops,其是LCD底层硬件操作接口集。

fb_ops硬件操作接口集包含很多接口,如设置可变参数fb_set_par、设置颜色寄存器fb_setcolreg、清屏接口fb_blank、画位图接口fb_imageblit、内存映射fb_mmap等等。

fb_info结构体在调用register_framebuffer之前完成初始化。一般来说,LCD设备属于平台设备,其初始化是在平台设备驱动的probe接口完成。而LCD设备所涉及的硬件初始化则在平台设备初始化中完成。

2、fb_fix_screeninfo
结构体主要记录用户不可以修改的控制器的参数,该结构体的定义如下:

struct fb_fix_screeninfo {
    char id[16];            //字符串形式的标识符
    unsigned long smem_start;   /fb 缓存的开始位置
                    /* (physical address) */
    __u32 smem_len;            /* Length of frame buffer mem *///fb 缓存的长度
    __u32 type;            /* see FB_TYPE_*        *////看FB_TYPE_* -->
    __u32 type_aux;            /* Interleave for interleaved Planes *///分界
    __u32 visual;             ///看FB_VISUAL_* -->
    __u16 xpanstep;         //如果没有硬件panning就赋值为0
    __u16 ypanstep;            //如果没有硬件panning就赋值为0
    __u16 ywrapstep;        //如果没有硬件ywrap就赋值为0
    __u32 line_length;        //一行的字节数
    unsigned long mmio_start;   //内存映射 IO的开始位置
                    /* (physical address) */
    __u32 mmio_len;            //内存映射 IO的长度
    __u32 accel;            /* Indicate to driver which    */
                    /*  specific chip/card we have    */
    __u16 capabilities;        /* see FB_CAP_*            *///功能 ---FB_CAP_FOURCC--- Device supports FOURCC-based formats
    __u16 reserved[2];       //为以后的兼容性保留
};

其中参数含义如下:

id:唯一标识符;
smem_start			:framebuffer缓冲区物理起始位置(一般是显示控制器DMA起始地址);
smem_len			:framebuffer缓冲区的的长度,单位为字节;
type				:lcd类型,默认FB_TYPE_PACKED_PIXELS即可,可选参数如下;
#define FB_TYPE_PACKED_PIXELS           0       /* Packed Pixels        */
#define FB_TYPE_PLANES                  1       /* Non interleaved planes */
#define FB_TYPE_INTERLEAVED_PLANES      2       /* Interleaved planes   */
#define FB_TYPE_TEXT                    3       /* Text/attributes      */
#define FB_TYPE_VGA_PLANES              4       /* EGA/VGA planes       */
#define FB_TYPE_FOURCC                  5       /* Type identified by a V4L2 FOURCC */


type_aux:附加类型,默认0即可;
visual:颜色设置,常用参数如下:
#define FB_VISUAL_MONO01                0       /* Monochr. 1=Black 0=White  单侧 0白色 1黑色 */
#define FB_VISUAL_MONO10                1       /* Monochr. 1=White 0=Black  单色 0黑色 1白色 */
#define FB_VISUAL_TRUECOLOR             2       /* True color   真彩 */
#define FB_VISUAL_PSEUDOCOLOR           3       /* Pseudo color (like atari) 伪彩 */
#define FB_VISUAL_DIRECTCOLOR           4       /* Direct color 直彩 */
#define FB_VISUAL_STATIC_PSEUDOCOLOR    5       /* Pseudo color readonly 只读伪彩 */
#define FB_VISUAL_FOURCC                6       /* Visual identified by a V4L2 FOURCC */

xpanstep:如果没有硬件panning赋值0;
ypanstep:如果没有硬件panning赋值0;
ywrapstep:若果没有硬件ywarp赋值0;
line_length:一行所占的字节数,:(RGB565)240*320,那么这里就等于240*16/8;
mmio_start:内存映射IO的起始地址,用于应用层直接访问寄存器,可以不需要;
mmio_len:内存映射IO的长度,可以不需要;
accel:指明使用的芯片,用于硬件加速,默认FB_ACCEL_NONE即可,可选参数如下;:
#define FB_ACCEL_NONE           0       /* no hardware accelerator      */
#define FB_ACCEL_ATARIBLITT     1       /* Atari Blitter                */
#define FB_ACCEL_AMIGABLITT     2       /* Amiga Blitter                */
#define FB_ACCEL_S3_TRIO64      3       /* Cybervision64 (S3 Trio64)    */
#define FB_ACCEL_NCR_77C32BLT   4       /* RetinaZ3 (NCR 77C32BLT)      */
......

capabilities:查看FB_CAP_;
reserved:为将来的兼容保留位;

3、fb_var_screeninfo
结构体主要记录用户可以修改的控制器的参数,比如屏幕的分辨率和每个像素的比特数等,该结构体定义如下:

struct fb_var_screeninfo { ///显示屏信息
    __u32 xres;            /* visible resolution*//可视区域,一行有多少个像素点
    __u32 yres;            ///可视区域,一列有多少个像素点
    __u32 xres_virtual;  /* virtual resolution*//虚拟区域,一行有多少个像素点,简单的意思就是内存中定义的区间是比较大的
    __u32 yres_virtual;虚拟区域,一列有多少个像素点
    __u32 xoffset;            //虚拟到可见屏幕之间的行偏移
    __u32 yoffset;            /* resolution *//虚拟到可见屏幕之间的列偏移

    __u32 bits_per_pixel; /* guess what*/ 每个像素的 bit 数,这个参数不需要自己配置,而是通过上层在调用 checkvar 函数传递 bpp 的时候赋值的
    __u32 grayscale;        /* 0 = color, 1 = grayscale,*////等于零就成黑白 (灰度)
                    /* >1 = FOURCC            */
    // 通过 pixel per bpp 来设定 red green 和 blue 的位置; pixel per bpp 可以通过 ioctl 设定
    struct fb_bitfield red;        //fb缓存的R位域
    struct fb_bitfield green;    /* else only length is significant *//fb缓存的G位域
    struct fb_bitfield blue;                                        //fb缓存的B位域
    struct fb_bitfield transp;    /* transparency *//透明度

    __u32 nonstd;            /* != 0 Non standard pixel format *///如果nonstd 不等于0,非标准的像素格式

    __u32 activate;            /* see FB_ACTIVATE_*     */

    __u32 height;         //内存中的图像高度
    __u32 width;        //内存中的图像宽度

    __u32 accel_flags;        /* (OBSOLETE) see fb_info.flags *////加速标志

    /* Timing: All values in pixclocks, except pixclock (of course) */
    ///时序,这些部分就是显示器的显示方法了,和具体的液晶显示屏有关,在驱动中一般放在 具体液晶屏的配置文件,现在有dts,放在dts里了
    __u32 pixclock;            /* pixel clock in ps (pico seconds) *///像素时钟
    __u32 left_margin;        /* time from sync to picture    *////行切换,从同步到绘图之间的延迟
    __u32 right_margin;        /* time from picture to sync    *///行切换,从绘图到同步之间的延迟
    __u32 upper_margin;        /* time from sync to picture    *///帧切换,从同步到绘图之间的延迟
    __u32 lower_margin;                                        帧切换,从绘图到同步之间的延迟
    __u32 hsync_len;        /* length of horizontal sync    */ //水平同步的长度
    __u32 vsync_len;        /* length of vertical sync    */ //垂直同步的长度
    __u32 sync;            /* see FB_SYNC_*      *////---->看 FB_SYNC_*
    __u32 vmode;            /* see FB_VMODE_*      *////---->看 FB_VMODE_*
    __u32 rotate;            /* angle we rotate counter clockwise */
    __u32 colorspace;        /* colorspace for FOURCC-based modes */
    __u32 reserved[4];        /* Reserved for future compatibility */
};

其中参数含义如下:

xres:行分辨率
yres:列分辨率
xres_virtual:行虚拟分辨率,设置和硬件一样即可;
yres_virtual:列虚拟分辨率,设置和硬件一样即可;
xoffset:行偏移,设置为0即可;
yoffset:列偏移,设置为0即可;
bits_per_pixel:每个像素用多少位,对于s3c2440不支持18位,只支持16位;
grayscale:灰度值,默认即可;
red:RGB:565对于R的offset为从最低位(右起)偏移11位,占5bit;
green:RGB:565对于G的offset为从最低位(右起)偏移5位,占6bit;
blue:RGB:565对于B的offset为从最低位(右起)偏移0位,占5bit;
transp:透明度,默认即可;
nonstd:0标准像素格式,默认即可;
activate:默认即可;
height:LCD物理高度,单位mm;
width:LCD物理宽度,单位mm;
accel_flags:默认即可,过时参数;
pixclock:像素时钟,单位皮秒;
left_margin:行切换,从同步到绘图之间的延迟;
right_margin:行切换,从绘图到同步之间的延迟;
upper_margin:帧切换,从同步到绘图之间的延迟;
lower_margin:帧切换,从绘图到同步之间的延迟;
hsync_len:水平同步的长度;
vsync_len:垂直同步的长度;
sync:参考FB_SYNC_*;
vmode:参考FB_BMODE_*,默认即可;
rotate::时针旋转的角度;
colorspace:色彩空间;
reserved:保留值;

4、fb_ops
结构体是对底层硬件操作的函数指针,该结构体中定义了对硬件的操作有:
注: fb_ops结构与file_operations 结构不同,fb_ops是底层操作的抽象,而file_operations是提供给上层系统调用的接口,可以直接调用.

1 struct fb_ops {
 2     /* open/release and usage marking */
 3     struct module *owner;
 4     int (*fb_open)(struct fb_info *info, int user);
 5     int (*fb_release)(struct fb_info *info, int user);
/*对于非线性布局的/常规内存映射无法工作的帧缓冲设备需要*/ 
10     ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
11                size_t count, loff_t *ppos);
12     ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
13                 size_t count, loff_t *ppos);
15     /* checks var and eventually tweaks it to something supported,
16      * DO NOT MODIFY PAR */
17     int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);///检查可变参数并进行设置
19     /* set the video mode according to info->var */
20     int (*fb_set_par)(struct fb_info *info);///根据设置的值进行更新,使之有效
22     /* set color register */
23     int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green, 设置颜色寄存器
24                 unsigned blue, unsigned transp, struct fb_info *info);
26     /* set color registers in batch */
27     int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);29    
30     int (*fb_blank)(int blank, struct fb_info *info);///显示空白
32     /*pan显示*/
33     int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
36     void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);///矩形填充 
38     void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);///复制数据
40     void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);///图形填充
42     /* 绘制光标 */
43     int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
45     /* 旋转显示 */
46     void (*fb_rotate)(struct fb_info *info, int angle);
48     /* 等待blit空闲 */
49     int (*fb_sync)(struct fb_info *info);
51     /* fb 特定的 ioctl */
52     int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
53             unsigned long arg);
55     /* 处理32bit compat ioctl (optional) */
56     int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
57             unsigned long arg);
59     /* fb 特定的 mmap */
60     int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
62     /* get capability given var */
63     void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
64                 struct fb_var_screeninfo *var);
66     /* teardown any resources to do with this framebuffer */
67     void (*fb_destroy)(struct fb_info *info);
69     /* called at KDB enter and leave time to prepare the console */
70     int (*fb_debug_enter)(struct fb_info *info);
71     int (*fb_debug_leave)(struct fb_info *info);
72 };

5、fb_cmap
设备独立的 colormap 信息,可以通过 ioctl 的 FBIOGETCMAP 和 FBIOPUTCMAP 命令设置 colormap;

struct fb_cmap { //颜色映射表
    __u32 start;            /* First entry    *////第一个入口
    __u32 len;            /* Number of entries *///入口的数字
    __u16 *red;            /* Red values    *///红色
    __u16 *green;                        ///绿色
    __u16 *blue;                        ///蓝色
    __u16 *transp;           //透明度,允许为空
};

这些结构相互之间的关系如下所示:非常的清晰明朗。
在这里插入图片描述

LCD驱动编写

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>
 
/*  LCD T35参数设定 */
#define     LCD_WIDTH   240             /* LCD面板的行宽 */
#define     LCD_HEIGHT  320             /* LCD面板的列宽 */
 
#define     VSPW        1                /* 通过计算无效行数垂直同步脉冲宽度决定VSYNC脉冲的高电平宽度 */
#define     VBPD        1                /* 垂直同步周期后的无效行数 */
#define     LINEVAL     (LCD_HEIGHT-1)  /* LCD的垂直宽度-1 */
#define     VFPD        1                /* 垂直同步周期前的的无效行数 */
 
#define     CLKVAL      7              /* VCLK = HCLK / [(CLKVAL  + 1)  × 2] */
 
#define     HSPW        9                /* 通过计算VCLK的数水平同步脉冲宽度决定HSYNC脉冲的高电平宽度 */
#define     HBPD        19               /* 描述水平后沿为HSYNC的下降沿与有效数据的开始之间的VCLK周期数 */
#define     HOZVAL      (LCD_WIDTH-1)    /* LCD的水平宽度-1 */
#define     HFPD        9                /* 水平后沿为有效数据的结束与HSYNC的上升沿之间的VCLK周期数 */
 
/* 定义fb_info */
static struct fb_info *s3c_lcd;
/* 调色板数组,被fb_info->pseudo_palette调用 */
static u32 pseudo_palette[16];
/* 时钟 */
static struct clk *lcd_clk;
 
/* GPIO相关寄存器 */
static volatile unsigned long *gpcup;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdup;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile unsigned long *gpgdat;
static volatile unsigned long *gpgup;
 
/* lcd相关寄存器 */
struct lcd_regs{
    unsigned long   lcdcon1;
    unsigned long    lcdcon2;
    unsigned long    lcdcon3;
    unsigned long    lcdcon4;
    unsigned long    lcdcon5;
    unsigned long    lcdsaddr1;
    unsigned long    lcdsaddr2;
    unsigned long    lcdsaddr3;
    unsigned long    redlut;
    unsigned long    greenlut;
    unsigned long    bluelut;
    unsigned long    reserved[9];
    unsigned long    dithmode;
    unsigned long    tpal;
    unsigned long    lcdintpnd;
    unsigned long    lcdsrcpnd;
    unsigned long    lcdintmsk;
    unsigned long    lpcsel;
};
 
static volatile struct lcd_regs* lcd_regs;

//分配一个fb_info结构 初始化fb_info
s3c_lcd = framebuffer_alloc(0,0);
if (!s3c_lcd)
      return -ENOMEM;

//设置fb_info(设置固定的参数fb_info->fix)

strcpy(s3c_lcd->fix.id, "mylcd");
s3c_lcd->fix.smem_len        = LCD_WIDTH * LCD_HEIGHT * 2;   // framebuffer缓冲区的大小 每个像素两个字节
s3c_lcd->fix.type            = FB_TYPE_PACKED_PIXELS;
s3c_lcd->fix.type_aux        = 0;
s3c_lcd->fix.xpanstep        = 0;
s3c_lcd->fix.ypanstep        = 0;
s3c_lcd->fix.ywrapstep       = 0;
s3c_lcd->fix.accel           = FB_ACCEL_NONE;
s3c_lcd->fix.visual          = FB_VISUAL_TRUECOLOR;      //真彩色
s3c_lcd->fix.line_length     = LCD_WIDTH * 2;             // LCD一行所占字节数

//设置fb_info(设置可变的参数fb_info->var)

s3c_lcd->var.xres            = LCD_WIDTH;         // 行分辨率
s3c_lcd->var.yres            = LCD_HEIGHT;        // 列分辨率
s3c_lcd->var.xres_virtual    = LCD_WIDTH;         // 行虚拟分辨率
s3c_lcd->var.yres_virtual    = LCD_HEIGHT;        // 列虚拟分辨率
s3c_lcd->var.xoffset         = 0;                 //虚拟到可见屏幕之间的行偏移
s3c_lcd->var.yoffset         = 0;                 //虚拟到可见屏幕之间的行偏移
s3c_lcd->var.bits_per_pixel  = 16;                // 每像素使用位数

/* RGB:565 */

s3c_lcd->var.red.offset      = 11;
s3c_lcd->var.red.length      = 5;
s3c_lcd->var.green.offset    = 5;
s3c_lcd->var.green.length    = 6;
s3c_lcd->var.blue.offset     = 0;
s3c_lcd->var.blue.length     = 5;

s3c_lcd->var.nonstd          = 0;
s3c_lcd->var.activate        = FB_ACTIVATE_NOW;   // 使设置的值立即生效
s3c_lcd->var.accel_flags     = 0;
s3c_lcd->var.vmode           = FB_VMODE_NONINTERLACED;

// 设置fb_info(设置操作函数fb_info->fbops)调色板数组,被fb_info->pseudo_palette调用 */

static u32 pseudo_palette[16];
 
/*
 * 将内核单色使用bf表示
 * @param chan:单色 内核中的单色都是16位
 * @param bf:颜色位信息
 */
static inline unsigned int chan_to_field(unsigned int chan,struct fb_bitfield *bf)
{
    chan &= 0xffff;
    chan >>= 16 - bf->length;
    return chan << bf->offset;
}
 
/*
 * 调色板操作函数
 *  @param regno: 调色板数组元素索引
 */
static int s3c2440fb_setcolreg(unsigned regno,unsigned red, unsigned green, unsigned blue,unsigned transp, struct fb_info *info)
{
    unsigned int val;
    //调色板数组不能大于16
    if (regno > 16)
         return 1;
 
    /* 小于16位,进行转换 */
    u32 *pal = info->pseudo_palette;
 
    /* 用red,green,blue三个颜色值构造出16色数据val */
    val  = chan_to_field(red,   &info->var.red);
    val |= chan_to_field(green, &info->var.green);
    val |= chan_to_field(blue,  &info->var.blue);
 
    /* 放到调色板数组中 */
    pseudo_palette[regno] = val;
    return 0;
}

fb_info操作函数fbops

static struct fb_ops s3c2440fb_ops = {
    .owner        = THIS_MODULE,
    .fb_setcolreg    = s3c2440fb_setcolreg,   // 调色板操作函数 设置调色板fb_info-> pseudo_palette
    .fb_fillrect    = cfb_fillrect,          // 填充矩形  函数在drivers/video/fbdev/core/cfbfillrect.c中定义
    .fb_copyarea    = cfb_copyarea,          // 复制数据  函数在drivers/video/fbdev/core/cfbcopyarea.c中定义
    .fb_imageblit    = cfb_imageblit,         // 绘制图形  函数在drivers/video/fbdev/core/cfbimgblt.c中定义
};

设置操作函数

 s3c_lcd->fbops               = &s3c2440fb_ops;

 /* 2.4 其他设置 */
 s3c_lcd->flags               = FBINFO_FLAG_DEFAULT;
 s3c_lcd->pseudo_palette      = pseudo_palette;             // 保存调色板数组
 s3c_lcd->screen_size         = LCD_WIDTH * LCD_HEIGHT * 2;    // framebuffer缓冲区的大小

// 时钟相关,获取lcd时钟
lcd_clk = clk_get(NULL, "lcd");
if (IS_ERR(lcd_clk)) {
    printk("failed to get lcd clock source\n");
    return PTR_ERR(lcd_clk);
}

clk_prepare_enable(lcd_clk);       // 时钟使能
printk("got and enabled clock\n");

硬件相关操作 配置GPIO口用于LCD 接口,用于输出数据,时序,这个要看数据手册,用到哪个gpio,寄存器

gpcup = ioremap(0x56000028,4);
gpccon = ioremap(0x56000020,4);
gpdup = ioremap(0x56000038,4);
gpdcon = ioremap(0x56000030,4);
gpgcon = ioremap(0x56000060,12);
gpgdat = gpgcon + 1;
gpgup = gpgdat + 1;

// GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND
*gpcup = 0xffffffff;
*gpccon = 0xaaaaaaaa;
// GPIO管脚用于VD[23:8]
*gpdup = 0xffffffff;
*gpdcon = 0xaaaaaaaa;
/* Pull - up disable */
*gpgup |= (1 << 4);
// 设置GPG4引脚为LCD_PWREN模式
*gpgcon |= (3 << 8);

设置lcd控制寄存器,在裸机部分讲过了,如何设置的,挪过来就行。所以驱动就是加了框架的裸机

 根据LCD手册设置LCD控制器, 比如VCLK的频率等 
lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
printk("lcd_regs=%px map_size %u\n", lcd_regs, sizeof(struct lcd_regs));

/*  [17:8]  CLKVAL
*  [6:5]   PNRMODE;选择显示模式
*                  00 = 4 位双扫描显示模式   01 = 4 位单扫描显示模式(STN)
*                  10 = 8 位单扫描显示模式   11 = TFT LCD 面板
*  [4:1]  BPPMODE  选择BPP(位每像素)模式    1100 = TFT 的16 BPP
*  [0]    ENVID    LCD 视频输出和逻辑使能/禁止。
*                  0 = 禁止视频输出和LCD 控制信号  1 = 允许视频输出和LCD 控制信号
*/
lcd_regs->lcdcon1   = (CLKVAL<<8)| (3<<5) | (0xC<<1);              /* 16 bpp for TFT */

/*  [31:24]   VBPD:帧同步信号的后肩
*  [23:14]   LINEVAL:LCD面板的垂直尺寸
*  [13:6]    VFPD:帧同步信号的前肩
*  [5:0]     VSPW:同步信号的脉宽
*/
lcd_regs->lcdcon2 = (VBPD<<24)|(LINEVAL<<14)|(VFPD<<6)|(VSPW);

/* [25:19] HBPD: 行同步信号的后肩
* [18:8] HOZVAL: LCD面板的水平尺寸
* [7:0] HFPD: 行同步信号的前肩
*/
lcd_regs->lcdcon3 = (HBPD<<19)|(HOZVAL<<8)|(HFPD);
lcd_regs->lcdcon4 = (HSPW);

/* [11] FRM565: 此位选择16 BPP 输出视频数据的格式   0 = 5:5:5:1 格式   1= 5:6:5 格式
* [10] STN/TFT: 此位控制VCLK 有效沿的极性
* [9]    INVVLINE: STN/TFT:此位表明VLINE/HSYNC 脉冲极性  0 = 正常 1 = 反转
* [8]  INVVFRAME: STN/TFT:此位表明VFRAME/VSYNC 脉冲极性 0 = 正常 1 = 反转
* VLINE/HSYNC 脉冲极性、VFRAME/VSYNC 脉冲极性反转(LCD型号决定)
* [0]    HWSWP: STN/TFT:半字节交换控制位   0 = 交换禁止 1 = 交换使能
*/
lcd_regs->lcdcon5 = ((1<<11) | (1<<10) | (1 << 9) | (1 << 8) | (1 << 0));

/*  关闭PWREN信号输出 */
lcd_regs->lcdcon1 &= ~(1<<0);
/* 禁止PWREN信号 */
lcd_regs->lcdcon5  &=~(1<<3);

/* 第一位设置为1 选择输出分片率类型0:320 * 240  1:240*320 */
lcd_regs->lpcsel = ((0xCE6) & ~7) | 1<<1;

//设置硬件相关的操作分配显存,裸机也讲过

分配显存(framebuffer), 并把地址告诉LCD控制器 */
ret = s3c2440fb_map_video_memory(s3c_lcd);
if (ret) {
	printk("failed to allocate video RAM: %d\n", ret);
	// todo 这里应该进行资源释放,我这里就不释放了
	return -ENOMEM;
}

/* [29:21] LCDBANK:存放帧缓冲起始地址的[30:22]
* [20:0] LCDBASEU: 存放帧缓冲起始地址的[21:1]
*/
lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
/* 存放帧结束地址[21:1] */
lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
/* [21:11] OFFSIZE:表示虚拟屏偏移尺寸  即上一行最后像素点到下一行第一个像素点之间间隔多少个像素点
*  [10:0] PAGEWIDTH:表示行的宽度(半字为单位 16bit)
*/
lcd_regs->lcdsaddr3 = LCD_WIDTH & 0x3ff;

s3c2440fb_map_video_memory这个函数原型可以看下,就是那倒设置的地址,然后申请dma映射地址。这样我们不用操作了,dma直接数据过去了。

static int s3c2440fb_map_video_memory(struct fb_info *fbinfo)
{
        dma_addr_t map_dma;
        unsigned long map_size = PAGE_ALIGN(fbinfo->fix.smem_len);
 
        printk("map_video_memory(info=%px) map_size %u\n", fbinfo, map_size);
 
        // 第一个参数不能为空 否则会抛异常
        fbinfo->screen_base = dma_alloc_wc(&s3c_device_lcd.dev, map_size, &map_dma,GFP_KERNEL);
 
        if (fbinfo->screen_base) {
                /* prevent initial garbage on screen */
                printk("map_video_memory: clear %px:%08x\n",fbinfo->screen_base, map_size);
                memset(fbinfo->screen_base, 0x00, map_size);
                fbinfo->fix.smem_start = map_dma;
                printk("map_video_memory: dma=%08lx cpu=%p size=%08x\n",fbinfo->fix.smem_start, fbinfo->screen_base, map_size);
        }
        else
        {
          printk("map_video_memory fail\n");
        }
 
        return fbinfo->screen_base ? 0 : -ENOMEM;
}

//开启LCD ,控制LCDCON5允许PWREN信号 */

lcd_regs->lcdcon5 |= (1 << 3);
/* 控制LCDCON1 LCD使能 */
lcd_regs->lcdcon1 |= (1<<0);
/* 输出高电平, 使能背光 */
*gpgdat |= 1<<4;

printk("lcdcon[1] = 0x%08lx\n", lcd_regs->lcdcon1);
printk("lcdcon[2] = 0x%08lx\n", lcd_regs->lcdcon2);
printk("lcdcon[3] = 0x%08lx\n", lcd_regs->lcdcon3);
printk("lcdcon[4] = 0x%08lx\n", lcd_regs->lcdcon4);
printk("lcdcon[5] = 0x%08lx\n", lcd_regs->lcdcon5);
printk("lcdsaddr[1]= 0x%08lx\n", lcd_regs->lcdsaddr1);
printk("lcdsaddr[2]= 0x%08lx\n", lcd_regs->lcdsaddr2);
printk("lcdsaddr[3]= 0x%08lx\n", lcd_regs->lcdsaddr3);

//注册驱动到内核

register_framebuffer(s3c_lcd);

完整的驱动注册过程

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>
 
/*  LCD T35参数设定 */
#define     LCD_WIDTH   240             /* LCD面板的行宽 */
#define     LCD_HEIGHT  320             /* LCD面板的列宽 */
 
#define     VSPW        1                /* 通过计算无效行数垂直同步脉冲宽度决定VSYNC脉冲的高电平宽度 */
#define     VBPD        1                /* 垂直同步周期后的无效行数 */
#define     LINEVAL     (LCD_HEIGHT-1)  /* LCD的垂直宽度-1 */
#define     VFPD        1                /* 垂直同步周期前的的无效行数 */
 
#define     CLKVAL      7              /* VCLK = HCLK / [(CLKVAL  + 1)  × 2] */
 
#define     HSPW        9                /* 通过计算VCLK的数水平同步脉冲宽度决定HSYNC脉冲的高电平宽度 */
#define     HBPD        19               /* 描述水平后沿为HSYNC的下降沿与有效数据的开始之间的VCLK周期数 */
#define     HOZVAL      (LCD_WIDTH-1)    /* LCD的水平宽度-1 */
#define     HFPD        9                /* 水平后沿为有效数据的结束与HSYNC的上升沿之间的VCLK周期数 */
 
/* 定义fb_info */
static struct fb_info *s3c_lcd;
/* 调色板数组,被fb_info->pseudo_palette调用 */
static u32 pseudo_palette[16];
/* 时钟 */
static struct clk *lcd_clk;
 
/* GPIO相关寄存器 */
static volatile unsigned long *gpcup;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdup;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile unsigned long *gpgdat;
static volatile unsigned long *gpgup;
 
/* lcd相关寄存器 */
struct lcd_regs{
    unsigned long   lcdcon1;
    unsigned long    lcdcon2;
    unsigned long    lcdcon3;
    unsigned long    lcdcon4;
    unsigned long    lcdcon5;
    unsigned long    lcdsaddr1;
    unsigned long    lcdsaddr2;
    unsigned long    lcdsaddr3;
    unsigned long    redlut;
    unsigned long    greenlut;
    unsigned long    bluelut;
    unsigned long    reserved[9];
    unsigned long    dithmode;
    unsigned long    tpal;
    unsigned long    lcdintpnd;
    unsigned long    lcdsrcpnd;
    unsigned long    lcdintmsk;
    unsigned long    lpcsel;
};
 
static volatile struct lcd_regs* lcd_regs;
 
/*
 * 将内核单色使用bf表示
 * @param chan:单色 内核中的单色都是16位
 * @param bf:颜色位信息
 */
static inline unsigned int chan_to_field(unsigned int chan,struct fb_bitfield *bf)
{
    chan &= 0xffff;
    chan >>= 16 - bf->length;
    return chan << bf->offset;
}
 
/*
 * 调色板操作函数
 *  @param regno: 调色板数组元素索引
 */
static int s3c2440fb_setcolreg(unsigned regno,unsigned red, unsigned green, unsigned blue,
 unsigned transp, struct fb_info *info)
{
    unsigned int val;
    //调色板数组不能大于15
    if (regno >= 16)
         return 1;
 
    /* 用red,green,blue三个颜色值构造出16色数据val */
    val  = chan_to_field(red,   &info->var.red);
    val |= chan_to_field(green, &info->var.green);
    val |= chan_to_field(blue,  &info->var.blue);
 
    /* 放到调色板数组中 */
    pseudo_palette[regno] = val;
    return 0;
}
 
 
#define samsung_device_dma_mask (*((u64[]) { DMA_BIT_MASK(32) }))
 
/*platform设备 */
static struct platform_device s3c_device_lcd = {
        .name           = "s3c2440-lcd",
        .id             = -1,
        .num_resources  = 0,
        .dev            = {
                .dma_mask               = &samsung_device_dma_mask,
                .coherent_dma_mask      = DMA_BIT_MASK(32),
        }
};
 
/*
 * s3c2440fb_map_video_memory():
 *      Allocates the DRAM memory for the frame buffer.  This buffer is
 *      remapped into a non-cached, non-buffered, memory region to
 *      allow palette and pixel writes to occur without flushing the
 *      cache.  Once this area is remapped, all virtual memory
 *      access to the video memory should occur at the new region.
 */
static int s3c2440fb_map_video_memory(struct fb_info *fbinfo)
{
        dma_addr_t map_dma;
        unsigned long map_size = PAGE_ALIGN(fbinfo->fix.smem_len);
 
        printk("map_video_memory(info=%px) map_size %u\n", fbinfo, map_size);
 
        // 第一个参数不能为空 否则会抛异常
        fbinfo->screen_base = dma_alloc_wc(&s3c_device_lcd.dev, map_size, &map_dma,GFP_KERNEL);
 
        if (fbinfo->screen_base) {
                /* prevent initial garbage on screen */
                printk("map_video_memory: clear %px:%08x\n",fbinfo->screen_base, map_size);
                memset(fbinfo->screen_base, 0x00, map_size);
 
                fbinfo->fix.smem_start = map_dma;
 
                printk("map_video_memory: dma=%08lx cpu=%p size=%08x\n",fbinfo->fix.smem_start, fbinfo->screen_base, map_size);
        }
        else
        {
          printk("map_video_memory fail\n");
        }
 
        return fbinfo->screen_base ? 0 : -ENOMEM;
}
 
 
/*
 * fb_info操作函数fbops
 */
static struct fb_ops s3c2440fb_ops = {
    .owner        = THIS_MODULE,
    .fb_setcolreg    = s3c2440fb_setcolreg,   // 调色板操作函数 设置调色板fb_info-> pseudo_palette
    .fb_fillrect    = cfb_fillrect,          // 填充矩形  函数在drivers/video/fbdev/core/cfbfillrect.c中定义
    .fb_copyarea    = cfb_copyarea,          // 复制数据  函数在drivers/video/fbdev/core/cfbcopyarea.c中定义
    .fb_imageblit    = cfb_imageblit,         // 绘制图形  函数在drivers/video/fbdev/core/cfbimgblt.c中定义
};
 
 
/*
 * lcd驱动模块入口
 */
static int lcd_init(void)
{
   int ret;
   printk("lcd device registered\n");
 
   /* 1. 初始化fb_info */
   s3c_lcd = framebuffer_alloc(0,NULL);
       if (!s3c_lcd)
       {
           return -ENOMEM;
       }
 
       printk("s3c_lcd=%px\n", s3c_lcd);
 
   /* 2. 设置fb_info */
   /* 2.1设置固定参数 */
    strcpy(s3c_lcd->fix.id, "mylcd");
    s3c_lcd->fix.smem_len        = LCD_WIDTH * LCD_HEIGHT * 2;   // framebuffer缓冲区的大小 每个像素两个字节
    s3c_lcd->fix.type            = FB_TYPE_PACKED_PIXELS;
    s3c_lcd->fix.type_aux        = 0;
    s3c_lcd->fix.xpanstep        = 0;
    s3c_lcd->fix.ypanstep        = 0;
    s3c_lcd->fix.ywrapstep       = 0;
    s3c_lcd->fix.accel           = FB_ACCEL_NONE;
    s3c_lcd->fix.visual          = FB_VISUAL_TRUECOLOR;      //真彩色
    s3c_lcd->fix.line_length     = LCD_WIDTH * 2;             // LCD一行所占字节数
 
    /* 2.2 设置可变参数 */
     s3c_lcd->var.xres            = LCD_WIDTH;         // 行分辨率
     s3c_lcd->var.yres            = LCD_HEIGHT;        // 列分辨率
     s3c_lcd->var.xres_virtual    = LCD_WIDTH;         // 行虚拟分辨率
     s3c_lcd->var.yres_virtual    = LCD_HEIGHT;        // 列虚拟分辨率
     s3c_lcd->var.xoffset         = 0;                 //虚拟到可见屏幕之间的行偏移
     s3c_lcd->var.yoffset         = 0;                 //虚拟到可见屏幕之间的行偏移
     s3c_lcd->var.bits_per_pixel  = 16;                // 每像素使用位数
 
     /* RGB:565 */
     s3c_lcd->var.red.offset      = 11;
     s3c_lcd->var.red.length      = 5;
     s3c_lcd->var.green.offset    = 5;
     s3c_lcd->var.green.length    = 6;
     s3c_lcd->var.blue.offset     = 0;
     s3c_lcd->var.blue.length     = 5;
 
     s3c_lcd->var.nonstd          = 0;
     s3c_lcd->var.activate        = FB_ACTIVATE_NOW;   // 使设置的值立即生效
     s3c_lcd->var.accel_flags     = 0;
     s3c_lcd->var.vmode           = FB_VMODE_NONINTERLACED;
 
     /* 2.3 设置操作函数 */
     s3c_lcd->fbops               = &s3c2440fb_ops;
 
     /* 2.4 其他设置 */
     s3c_lcd->flags               = FBINFO_FLAG_DEFAULT;
     s3c_lcd->pseudo_palette      = pseudo_palette;             // 保存调色板数组
     s3c_lcd->screen_size         = LCD_WIDTH * LCD_HEIGHT * 2;    // framebuffer缓冲区的大小
 
    // 时钟相关,获取lcd时钟
    lcd_clk = clk_get(NULL, "lcd");
    if (IS_ERR(lcd_clk)) {
        printk("failed to get lcd clock source\n");
        return PTR_ERR(lcd_clk);
    }
 
    clk_prepare_enable(lcd_clk);       // 时钟使能
    printk("got and enabled clock\n");
 
    /* 3.硬件相关操作 */
    /* 3.1 配置GPIO口用于LCD */
    gpcup = ioremap(0x56000028,4);
    gpccon = ioremap(0x56000020,4);
    gpdup = ioremap(0x56000038,4);
    gpdcon = ioremap(0x56000030,4);
    gpgcon = ioremap(0x56000060,12);
    gpgdat = gpgcon + 1;
    gpgup = gpgdat + 1;
 
    // GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND
    *gpcup = 0xffffffff;
    *gpccon = 0xaaaaaaaa;
    // GPIO管脚用于VD[23:8]
    *gpdup = 0xffffffff;
    *gpdcon = 0xaaaaaaaa;
    /* Pull - up disable */
    *gpgup |= (1 << 4);
    // 设置GPG4引脚为LCD_PWREN模式
    *gpgcon |= (3 << 8);
    /* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
    lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
    printk("lcd_regs=%px map_size %u\n", lcd_regs, sizeof(struct lcd_regs));
 
    /*  [17:8]  CLKVAL
     *  [6:5]   PNRMODE;选择显示模式
     *                  00 = 4 位双扫描显示模式   01 = 4 位单扫描显示模式(STN)
     *                  10 = 8 位单扫描显示模式   11 = TFT LCD 面板
     *  [4:1]  BPPMODE  选择BPP(位每像素)模式    1100 = TFT 的16 BPP
     *  [0]    ENVID    LCD 视频输出和逻辑使能/禁止。
     *                  0 = 禁止视频输出和LCD 控制信号  1 = 允许视频输出和LCD 控制信号
     */
    lcd_regs->lcdcon1   = (CLKVAL<<8)| (3<<5) | (0xC<<1);              /* 16 bpp for TFT */
 
    /*  [31:24]   VBPD:帧同步信号的后肩
     *  [23:14]   LINEVAL:LCD面板的垂直尺寸
     *  [13:6]    VFPD:帧同步信号的前肩
     *  [5:0]     VSPW:同步信号的脉宽
     */
    lcd_regs->lcdcon2 = (VBPD<<24)|(LINEVAL<<14)|(VFPD<<6)|(VSPW);
 
    /* [25:19] HBPD: 行同步信号的后肩
     * [18:8] HOZVAL: LCD面板的水平尺寸
     * [7:0] HFPD: 行同步信号的前肩
     */
    lcd_regs->lcdcon3 = (HBPD<<19)|(HOZVAL<<8)|(HFPD);
    lcd_regs->lcdcon4 = (HSPW);
 
    /* [11] FRM565: 此位选择16 BPP 输出视频数据的格式   0 = 5:5:5:1 格式   1= 5:6:5 格式
     * [10] STN/TFT: 此位控制VCLK 有效沿的极性
     * [9]    INVVLINE: STN/TFT:此位表明VLINE/HSYNC 脉冲极性  0 = 正常 1 = 反转
     * [8]  INVVFRAME: STN/TFT:此位表明VFRAME/VSYNC 脉冲极性 0 = 正常 1 = 反转
     * VLINE/HSYNC 脉冲极性、VFRAME/VSYNC 脉冲极性反转(LCD型号决定)
     * [0]    HWSWP: STN/TFT:半字节交换控制位   0 = 交换禁止 1 = 交换使能
     */
    lcd_regs->lcdcon5 = ((1<<11) | (1<<10) | (1 << 9) | (1 << 8) | (1 << 0));
 
     /*  关闭PWREN信号输出 */
     lcd_regs->lcdcon1 &= ~(1<<0);
     /* 禁止PWREN信号 */
     lcd_regs->lcdcon5  &=~(1<<3);
 
     /* 第一位设置为1 选择输出分片率类型0:320 * 240  1:240*320 */
     lcd_regs->lpcsel = ((0xCE6) & ~7) | 1<<1;
 
    /* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
    ret = s3c2440fb_map_video_memory(s3c_lcd);
    if (ret) {
        printk("failed to allocate video RAM: %d\n", ret);
        // todo 这里应该进行资源释放,我这里就不释放了
        return -ENOMEM;
    }
 
    /* [29:21] LCDBANK:存放帧缓冲起始地址的[30:22]
     * [20:0] LCDBASEU: 存放帧缓冲起始地址的[21:1]
     */
    lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
    /* 存放帧结束地址[21:1] */
    lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
    /* [21:11] OFFSIZE:表示虚拟屏偏移尺寸  即上一行最后像素点到下一行第一个像素点之间间隔多少个像素点
    *  [10:0] PAGEWIDTH:表示行的宽度(半字为单位 16bit)
    */
    lcd_regs->lcdsaddr3 = LCD_WIDTH & 0x3ff;
 
    /* 4.1 开启LCD */
    /* 控制LCDCON5允许PWREN信号 */
    lcd_regs->lcdcon5 |= (1 << 3);
    /* 控制LCDCON1 LCD使能 */
    lcd_regs->lcdcon1 |= (1<<0);
    /* 输出高电平, 使能背光 */
    *gpgdat |= 1<<4;
    printk("lcdcon[1] = 0x%08lx\n", lcd_regs->lcdcon1);
    printk("lcdcon[2] = 0x%08lx\n", lcd_regs->lcdcon2);
    printk("lcdcon[3] = 0x%08lx\n", lcd_regs->lcdcon3);
    printk("lcdcon[4] = 0x%08lx\n", lcd_regs->lcdcon4);
    printk("lcdcon[5] = 0x%08lx\n", lcd_regs->lcdcon5);
    printk("lcdsaddr[1]= 0x%08lx\n", lcd_regs->lcdsaddr1);
    printk("lcdsaddr[2]= 0x%08lx\n", lcd_regs->lcdsaddr2);
    printk("lcdsaddr[3]= 0x%08lx\n", lcd_regs->lcdsaddr3);
 
    /* 4.2 注册 */
    register_framebuffer(s3c_lcd);
 
    return 0;
}
 
/*
 * lcd驱动模块出口
 */
static void __exit lcd_exit(void)
{
    printk("lcd device unregistered\n");
 
    unregister_framebuffer(s3c_lcd);
     /* 禁止LCD使能 */
    lcd_regs->lcdcon1 &= ~(1<<0);
    /* 关闭背光 */
    *gpgdat &= ~(1<<4);
    dma_free_wc(&s3c_device_lcd.dev, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
    iounmap(lcd_regs);
    iounmap(gpcup);
    iounmap(gpccon);
    iounmap(gpdup);
    iounmap(gpdcon);
    iounmap(gpgcon);
    framebuffer_release(s3c_lcd);
}
 
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");

如果内核没有配置lcd,需要配置一下。支持frambufer
在这里插入图片描述

在这里插入图片描述

编译后,安装驱动,就可以看到这么一个设备
在这里插入图片描述

上面的驱动就是玩的,有利于理解,当然开发时,小项目,也可以这么玩,如果是复杂的设备,比如汽车,怎么死的都不知道,是不会这么玩的。

六、标准的frambufer驱动,带dts

在Linux中应用程序最终也是通过操作RGB LCD的显存来实现在LCD上显示字符、图片等信息。在裸机中我们可以随意的分配显存,但是在Linux系统中内存的管理很严格,显存是需要申请的,不是你想用就能用的。而且因为虚拟内存的存在,驱动程序设置的显存和应用程序访问的显存要是同一片物理内存。

LCD裸机例程主要分两部分:
①、获取LCD的屏幕参数。
②、根据屏幕参数信息来初始化eLCDIF接口控制器

frambufer驱动框架,其实非常简单,就是字符设备那一套,套上几个比较复杂一点的结构体,描述设备,操控设备,甚至比i2c驱动模型还简单。

不同分辨率的LCD屏幕其eLCDIF控制器驱动代码都是一样的,只需要修改好对应的屏幕参数即可。屏幕参数信息属于屏幕设备信息内容,这些肯定是要放到设备树中的,因此我们就是修改设备树。比如NXP官方的设备树已经添加了LCD设备节点,只是此节点的LCD屏幕信息是打开imx6ull.dtsi,然后找到lcdif节点内容,如下所示:

imx6ull.dtsi文件中lcdif节点内容
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 };

lcdif节点信息是所有使用I.MX6ULL芯片的板子所共有的,并不是完整的lcdif节点信息。像屏幕参数这些需要根据不同的硬件平台去添加,比如向imx6ull-alientek-emmc.dts中的lcdif节点添加其他的属性信息。可以看出lcdif节点的compatible属性值为“fsl,imx6ul-lcdif”和“fsl,imx28-lcdif”,因此在Linux源码中搜索这两个字符串即可找到I.MX6ULL的LCD驱动文件,这个文件为drivers/video/fbdev/mxsfb.c,mxsfb.c就是I.MX6ULL的LCD驱动文件,在此文件中找到如下内容:

示例代码platform下的LCD驱动
1362 static const struct of_device_id mxsfb_dt_ids[] = {
1363    { .compatible = "fsl,imx23-lcdif", 
.			data = &mxsfb_devtype[0], 
		},
1364    { .compatible = "fsl,imx28-lcdif", 
			.data = &mxsfb_devtype[1],
		},
1365    { /* sentinel */ }
1366 };
......
 static struct platform_driver mxsfb_driver = {
    .probe = mxsfb_probe,
    .remove = mxsfb_remove,
    .shutdown = mxsfb_shutdown,
    .id_table = mxsfb_devtype,
    .driver = {
            .name = DRIVER_NAME,
            .of_match_table = mxsfb_dt_ids,
           .pm = &mxsfb_pm_ops,
    },
 };

module_platform_driver(mxsfb_driver);

这是一个标准的platform驱动,当驱动和设备匹配以后mxsfb_probe函数就会执行。在看mxsfb_probe函数之前我们先简单了解一下Linux下Framebuffer驱动的编写流程,Linux内核将所有的Framebuffer抽象为一个叫做fb_info的结构体,fb_info结构体包含了Framebuffer设备的完整属性和操作集合,因此每一个Framebuffer设备都必须有一个fb_info。换言之就是,LCD的驱动就是构建fb_info,并且向系统注册fb_info的过程。fb_info结构体定义在include/linux/fb.h文件里面,这个结构体上面我们说过了。

fb_info结构体的成员变量很多,我们重点关注

var、
fix、
fbops、
screen_base、
screen_size
pseudo_palette

申请fb_info。
②、初始化fb_info结构体中的各个成员变量。
③、初始化eLCDIF控制器。
④、使用register_framebuffer函数向Linux内核注册初始化好的fb_info。

示例代码59.1.2.4 mxsfb_probe函数
1369 static int mxsfb_probe(struct platform_device *pdev)
1370 {
1371    const struct of_device_id *of_id =of_match_device(mxsfb_dt_ids, &pdev->dev);
1373    struct resource *res;
1374    struct mxsfb_info *host;
1375    struct fb_info *fb_info;
1376    struct pinctrl *pinctrl;
1377    int irq = platform_get_irq(pdev, 0);
1378    int gpio, ret;
1379 
......
1394 
1395    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1396    if (!res) {
1397        dev_err(&pdev->dev, "Cannot get memory IO resource\n");
1398        return -ENODEV;
1399    }
1400 
1401    host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL);
1402    if (!host) {
1403        dev_err(&pdev->dev, "Failed to allocate IO resource\n");
1404        return -ENOMEM;
1405    }
1406 
1407    fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
1408    if (!fb_info) {
1409        dev_err(&pdev->dev, "Failed to allocate fbdev\n");
1410        devm_kfree(&pdev->dev, host);
1411        return -ENOMEM;
1412    }
1413    host->fb_info = fb_info;
1414    fb_info->par = host;
1415 
1416    ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0,dev_name(&pdev->dev), host);
1418    if (ret) {
1419        dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", irq, ret);
1421        ret = -ENODEV;
1422        goto fb_release;
1423    }
1424 
1425    host->base = devm_ioremap_resource(&pdev->dev, res);
1426    if (IS_ERR(host->base)) {
1427        dev_err(&pdev->dev, "ioremap failed\n");
1428        ret = PTR_ERR(host->base);
1429        goto fb_release;
1430    }
......
1461 
1462    fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16, GFP_KERNEL);
1464    if (!fb_info->pseudo_palette) {
1465        ret = -ENOMEM;
1466        goto fb_release;
1467    }
1468 
1469    INIT_LIST_HEAD(&fb_info->modelist);
1470 
1471    pm_runtime_enable(&host->pdev->dev);
1472 
1473    ret = mxsfb_init_fbinfo(host);
1474    if (ret != 0)
1475        goto fb_pm_runtime_disable;
1476 
1477    mxsfb_dispdrv_init(pdev, fb_info);
1478 
1479    if (!host->dispdrv) {
1480        pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
1481        if (IS_ERR(pinctrl)) {
1482            ret = PTR_ERR(pinctrl);
1483            goto fb_pm_runtime_disable;
1484        }
1485    }
1486 
1487    if (!host->enabled) {
1488        writel(0, host->base + LCDC_CTRL);
1489        mxsfb_set_par(fb_info);
1490        mxsfb_enable_controller(fb_info);
1491        pm_runtime_get_sync(&host->pdev->dev);
1492    }
1493 
1494    ret = register_framebuffer(fb_info);
1495    if (ret != 0) {
1496        dev_err(&pdev->dev, "Failed to register framebuffer\n");
1497        goto fb_destroy;
1498    }
......
1525    return ret;
1526 }

在prob函数里会去register_framebuffer,注册到内核,这个我们以前讲过的,platform模型。在前面不带dts老内核的方式下,套上了platform模型。

第1374行,host结构体指针变量,表示I.MX6ULL的LCD的主控接口,mxsfb_info结构体是NXP定义的针对I.MX系列SOC的Framebuffer设备结构体。也就是我们前面一直说的设备结构体,此结构体包含了I.MX系列SOC的Framebuffer设备详细信息,比如时钟、eLCDIF控制器寄存器基地址、fb_info等。
第1395行,从设备树中获取eLCDIF接口控制器的寄存器首地址,设备树中lcdif节点已经设置了eLCDIF寄存器首地址为0X021C8000,因此res=0X021C8000。
第1401行,给host申请内存,host为mxsfb_info类型结构体指针。
第1407行,给fb_info申请内存,也就是申请fb_info。
第1413~1414行,设置host的fb_info成员变量为fb_info,设置fb_info的par成员变量为host。通过这一步就将前面申请的host和fb_info联系在了一起。
第1416行,申请中断,中断服务函数为mxsfb_irq_handler。
第1425行,对从设备树中获取到的寄存器首地址(res)进行内存映射,得到虚拟地址,并保存到host的base成员变量。因此通过访问host的base成员即可访问I.MX6ULL的整个eLCDIF寄存器。其实在mxsfb.c中已经定义了eLCDIF各个寄存器相比于基地址的偏移值,如下所示:

示例代码59.1.2.4 eLCDIF各个寄存器偏移值
67 #define LCDC_CTRL            			0x00
68 #define LCDC_CTRL1           			0x10
69 #define LCDC_V4_CTRL2            		0x20
70 #define LCDC_V3_TRANSFER_COUNT     	0x20
71 #define LCDC_V4_TRANSFER_COUNT     	0x30
......
89 #define LCDC_V4_DEBUG0          	 	0x1d0
90 #define LCDC_V3_DEBUG0           		0x1f0

mxsfb_probe函数,第1462行,给fb_info中的pseudo_palette申请内存。
第1473行,调用mxsfb_init_fbinfo函数初始化fb_info,重点是fb_info的var、fix、fbops,screen_base和screen_size。其中fbops是Framebuffer设备的操作集,NXP提供的fbops为mxsfb_ops,内容如下:

示例代码59.1.2.5 mxsfb_ops操作集合
987 static struct fb_ops mxsfb_ops = {
988     .owner = THIS_MODULE,
989     .fb_check_var = mxsfb_check_var,
990     .fb_set_par = mxsfb_set_par,
991     .fb_setcolreg = mxsfb_setcolreg,
992     .fb_ioctl = mxsfb_ioctl,
993     .fb_blank = mxsfb_blank,
994     .fb_pan_display = mxsfb_pan_display,
995     .fb_mmap = mxsfb_mmap,
996     .fb_fillrect = cfb_fillrect,
997     .fb_copyarea = cfb_copyarea,
998     .fb_imageblit = cfb_imageblit,
999 };

关于mxsfb_ops里面的各个操作函数这里就不去详解的介绍了。mxsfb_init_fbinfo函数通过调用mxsfb_init_fbinfo_dt函数从设备树中获取到LCD的各个参数信息。最后,mxsfb_init_fbinfo函数会调用mxsfb_map_videomem函数申请LCD的帧缓冲内存(也就是显存)。
第1489~1490行,设置eLCDIF控制器的相应寄存器。
第1494行,最后调用register_framebuffer函数向Linux内核注册fb_info。
mxsfb.c文件很大,还有一些其他的重要函数,比如mxsfb_remove、mxsfb_shutdown等,这里我们就简单的介绍了一下mxsfb_probe函数,至于其他的函数大家自行查阅。

LCD驱动程序编写
前面已经说了,6ULL的eLCDIF接口驱动程序NXP已经编写好了,因此LCD驱动部分我们不需要去修改。我们需要做的就是按照所使用的LCD来修改设备树。重点要注意三个地方:
①、LCD所使用的IO配置。
②、LCD屏幕节点修改,修改相应的属性值,换成我们所使用的LCD屏幕参数。
③、LCD背光节点信息修改,要根据实际所使用的背光IO来修改相应的设备节点信息。
接下来我们依次来看一下上面这两个节点改如何去修改:

1、LCD屏幕IO配置
首先要检查一下设备树中LCD所使用的IO配置,这个其实NXP都已经给我们写好了,不需要修改,不过我们还是要看一下。打开imx6ull-alientek-emmc.dts文件,在iomuxc节点中找到如下内容:

设备树LCD IO配置
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
30 pinctrl_lcdif_ctrl: lcdifctrlgrp {
31     fsl,pins = <
32         MX6UL_PAD_LCD_CLK__LCDIF_CLK        	0x79
33         MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE  	0x79
34         MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC    	0x79
35         MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC    	0x79
36     >;
37 pinctrl_pwm1: pwm1grp {
38     fsl,pins = <
39         MX6UL_PAD_GPIO1_IO08__PWM1_OUT   	0x110b0
40     >;
41 };

第2~27行,子节点pinctrl_lcdif_dat,为RGB LCD的24根数据线配置项。
第30~36行,子节点pinctrl_lcdif_ctrl,RGB LCD的4根控制线配置项,包括CLK、ENABLE、VSYNC和HSYNC。
第37~40行,子节点pinctrl_pwm1,LCD背光PWM引脚配置项。这个引脚要根据实际情况设置,LCD的背光IO尽量和半导体厂商的官方开发板一致。
注意示例代码59.3.1中默认将LCD的电气属性都设置为0X79,这里将其都改为0X49,也就是将LCD相关IO的驱动能力改为R0/1,也就是降低LCD相关IO的驱动能力。因为前面已经说了,ALPHA开发板上的LCD接口用了三个SGM3157模拟开关,为了防止模拟开关影响到网络,

2、LCD屏幕参数节点信息修改
继续在imx6ull-alientek-emmc.dts文件中找到lcdif节点,节点内容如下所示:

lcdif节点默认信息
1  &lcdif {
2      pinctrl-names = "default";
3      pinctrl-0 = <&pinctrl_lcdif_dat      	/* 使用到的IO		*/
4               &pinctrl_lcdif_ctrl
5               &pinctrl_lcdif_reset>;
6      display = <&display0>;
7      status = "okay";
8  
9      display0: display {                  	/* LCD属性信息       	*/
10         bits-per-pixel = <16>;           	/* 一个像素占用几个bit	*/
11         bus-width = <24>;                	/* 总线宽度            	*/
12 
13         display-timings {
14             native-mode = <&timing0>;    	/* 时序信息           	*/
15             timing0: timing0 {       
16             clock-frequency = <9200000>;	/* LCD像素时钟,单位Hz	*/
17             hactive = <480>;             	/* LCD X轴像素个数     	*/
18             vactive = <272>;             	/* LCD Y轴像素个数     	*/
19             hfront-porch = <8>;          	/* LCD hfp参数        	*/
20             hback-porch = <4>;           	/* LCD hbp参数        	*/
21             hsync-len = <41>;            	/* LCD hspw参数       	*/
22             vback-porch = <2>;           	/* LCD vbp参数      	*/
23             vfront-porch = <4>;          	/* LCD vfp参数        	*/
24             vsync-len = <10>;            	/* LCD vspw参数       	*/
25 
26             hsync-active = <0>;          	/* hsync数据线极性   	*/
27             vsync-active = <0>;          	/* vsync数据线极性   	*/
28             de-active = <1>;             	/* de数据线极性        	*/
29             pixelclk-active = <0>;       	/* clk数据线先极性    	*/
30             };
31         };
32     };
33 };

向imx6ull.dtsi文件中的lcdif节点追加的内容,我们依次来看一下示例代码的这些属性都是写什么含义。
第3行,pinctrl-0属性,LCD所使用的IO信息,这里用到了pinctrl_lcdif_dat、pinctrl_lcdif_ctrl和pinctrl_lcdif_reset这三个IO相关的节点,前两已经讲解了。pinctrl_lcdif_reset是LCD复位IO信息节点,正点原子的I.MX6U-ALPHA开发板的LCD没有用到复位IO,因此pinctrl_lcdif_reset可以删除掉。
第6行,display属性,指定LCD属性信息所在的子节点,这里为display0,下面就是display0子节点内容。
第9~32行,display0子节点,描述LCD的参数信息,第10行的bits-per-pixel属性用于指明一个像素占用的bit数,默认为16bit。将LCD配置为RGB888模式,因此一个像素点占用24bit,bits-per-pixel属性要改为24。第11行的bus-width属性用于设置数据线宽度,因为要配置为RGB888模式,因此bus-width也要设置为24。
第13~30行,这几行非常重要!因为这几行设置了LCD的时序参数信息,NXP官方的EVK开发板使用了一个4.3寸的480*272屏幕,因此这里默认是按照NXP官方的那个屏幕参数设置的。每一个属性的含义后面的注释已经写的很详细了,大家自己去看就行了,这些时序参数就是我们重点要修改的,需要根据自己所使用的屏幕去修改。

这里以正点原子的ATK7016(7寸1024*600)屏幕为例,将imx6ull-alientek-emmc.dts文件中的lcdif节点改为如下内容:

针对ATK7016 LCD修改后的lcdif节点信息
1  &lcdif {
2      pinctrl-names = "default";
3      pinctrl-0 = <&pinctrl_lcdif_dat 		/* 使用到的IO 			*/
4               &pinctrl_lcdif_ctrl>;
5      display = <&display0>;
6      status = "okay";
7  
8      display0: display {                  	/* LCD属性信息      	*/
9          bits-per-pixel = <24>;           	/* 一个像素占用24bit	*/
10         bus-width = <24>;                	/* 总线宽度             	*/
11 
12         display-timings {
13             native-mode = <&timing0>;    	/* 时序信息             	*/
14             timing0: timing0 {       
15             clock-frequency = <51200000>;	/* LCD像素时钟,单位Hz	*/
16             hactive = <1024>;             	/* LCD X轴像素个数  	*/
17             vactive = <600>;           		/* LCD Y轴像素个数    	*/
18             hfront-porch = <160>;         	/* LCD hfp参数     	*/
19             hback-porch = <140>;        	/* LCD hbp参数       	*/
20             hsync-len = <20>;           	/* LCD hspw参数       	*/
21             vback-porch = <20>;          	/* LCD vbp参数       	*/
22             vfront-porch = <12>;         	/* LCD vfp参数       	*/
23             vsync-len = <3>;         		/* LCD vspw参数     	*/
24 
25             hsync-active = <0>;          	/* hsync数据线极性   	*/
26             vsync-active = <0>;          	/* vsync数据线极性   	*/
27             de-active = <1>;             	/* de数据线极性        	*/
28             pixelclk-active = <0>;       	/* clk数据线先极性    	*/
29             };
30         };
31     };
32 };

3、LCD屏幕背光节点信息
LCD接口背光控制IO连接到了I.MX6U的GPIO1_IO08引脚上,GPIO1_IO08复用为PWM1_OUT,通过PWM信号来控制LCD屏幕背光的亮度,看一下如何在设备树中添加背光节点信息。
首先是GPIO1_IO08这个IO的配置,在imx6ull-alientek-emmc.dts中找到如下内容:

 GPIO1_IO08引脚配置
1 pinctrl_pwm1: pwm1grp {
2     fsl,pins = <
3         MX6UL_PAD_GPIO1_IO08__PWM1_OUT   0x110b0
4     >;
5 };

imx6ull.dtsi文件中的pwm1节点
1 pwm1: pwm@02080000 {
2     compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
3     reg = <0x02080000 0x4000>;
4     interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>;
5     clocks = <&clks IMX6UL_CLK_PWM1>,
6          <&clks IMX6UL_CLK_PWM1>;
7     clock-names = "ipg", "per";
8     #pwm-cells = <2>;
9 };

向pwm1节点追加的内容
1 &pwm1 {
2     pinctrl-names = "default";
3     pinctrl-0 = <&pinctrl_pwm1>;
4     status = "okay";
5 };

第3行,设置pwm1所使用的IO为pinctrl_pwm1,也就是示例代码59.3.4所定义的GPIO1_IO08这个IO。
第4行,将status设置为okay。
如果背光用的其他pwm通道,比如pwm2,那么就需要仿照示例代码59.3.6的内容,向pwm2节点追加相应的内容。pwm和相关的IO已经准备好了,但是Linux系统怎么知道PWM1_OUT就是控制LCD背光的呢?因此我们还需要一个节点来将LCD背光和PWM1_OUT连接起来。这个节点就是backlight,backlight节点描述可以参考Documentation/devicetree/indings/video/backlight/pwm-backlight.txt这个文档,此文档详细讲解了backlight节点该如何去创建,这里大概总结一下:
①、节点名称要为“backlight”。
②、节点的compatible属性值要为“pwm-backlight”,因此可以通过在Linux内核中搜索“pwm-backlight”来查找PWM背光控制驱动程序,这个驱动程序文件为drivers/video/backlight/pwm_bl.c,感兴趣的可以去看一下这个驱动程序。
③、pwms属性用于描述背光所使用的PWM以及PWM频率,比如本章我们要使用的pwm1,pwm频率设置为5KHz(NXP官方推荐设置)。
④、brightness-levels属性描述亮度级别,范围为0~255,0表示PWM占空比为0%,也就是亮度最低,255表示100%占空比,也就是亮度最高。至于设置几级亮度,大家可以自行填写此属性。
⑤、default-brightness-level属性为默认亮度级别。
根据上述5点设置backlight节点,这个NXP已经给我们设置好了,大家在imx6ull-alientek-emmc.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 };

第3行,设置背光使用pwm1,PWM频率为200Hz。
第4行,设置背8级背光(0~7),分别为0、4、8、16、32、64、128、255,对应占空比为0%、1.57%、3.13%、6.27%、12.55%、25.1%、50.19%、100%,如果嫌少的话可以自行添加一些其他的背光等级值。
第5行,设置默认背光等级为6,也就是50.19%的亮度。
关于背光的设备树节点信息就讲到这里,整个的LCD设备树节点内容我们就讲完了,按照这些节点内容配置自己的开发板即可。

关于pwm驱动,放在后面介绍。

运行测试

1、编译新的设备树
上一小节我们已经配置好了设备树,所以需要输入如下命令重新编译一下设备树:
make dtbs
等待编译生成新的imx6ull-alientek-emmc.dtb设备树文件,一会要使用新的设备树启动Linux内核。

然后就可以写应用程序测试。应用程序后面一篇文章,有写怎么使用lcd,可以去看下。

2、使能Linux logo显示
Linux内核启动的时候可以选择显示小企鹅logo,只要这个小企鹅logo显示没问题那么我们的LCD驱动基本就工作正常了。这个logo显示是要配置的,不过Linux内核一般都会默认开启logo显示,但是奔着学习的目的,我们还是来看一下如何使能Linux logo显示。打开Linux内核图形化配置界面,按下路径找到对应的配置项:

-> Device Drivers
-> Graphics support
-> Bootup logo (LOGO [=y])
-> Standard black and white Linux logo
-> Standard 16-color Linux logo
-> Standard 224-color Linux logo

在这里插入图片描述

在这里插入图片描述

3、设置LCD作为终端控制台
我们一直使用SecureCRT作为Linux开发板终端,开发板通过串口和SecureCRT进行通信。现在我们已经驱动起来LCD了,所以可以设置LCD作为终端,也就是开发板使用自己的显示设备作为自己的终端,然后接上键盘就可以直接在开发板上敲命令了,将LCD设置为终端控制台的方法如下:

1、设置uboot中的bootargs
重启开发板,进入Linux命令行,重新设置bootargs参数的console内容,命令如下所示:

setenv bootargs ‘console=tty1 console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250:
/home/zuozhongkai/linux/nfs/rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:
off’

注意红色字体部分设置console,这里我们设置了两遍console,第一次设置console=tty1,也就是设置LCD屏幕为控制台,第二遍又设置console=ttymxc0,115200,也就是设置串口也作为控制台。相当于我们打开了两个console,一个是LCD,一个是串口,大家重启开发板就会发现LCD和串口都会显示Linux启动log信息。但是此时我们还不能使用LCD作为终端进行交互,因为我们的设置还未完成。

2、修改/etc/inittab文件
打开开发板根文件系统中的/etc/inittab文件,在里面加入下面这一行:

tty1::askfirst:-/bin/sh

添加完成以后的/etc/inittab文件内容如图59.4.2.1所示:
在这里插入图片描述

修改完成以后保存/etc/inittab并退出,然后重启开发板,重启以后开发板LCD屏幕

4、 LCD背光调节

背光设备树节点设置了8个等级的背光调节,可以设置为0~7,我们可以通过设置背光等级来实现LCD背光亮度的调节,进入如下目录:

/sys/devices/platform/backlight/backlight/backlight

在这里插入图片描述

当前屏幕亮度等级为6,根据前面的分析可以,这个是50%亮度。屏幕最大亮度等级为7。如果我们要修改屏幕亮度,只需要向brightness写入需要设置的屏幕亮度等级即可。比如设置屏幕亮度等级为7,那么可以使用如下命令:

echo 7 > brightness

输入上述命令以后就会发现屏幕亮度增大了,如果设置brightness为0的话就会关闭LCD背光,屏幕就会熄灭。

再来讲PWM背光驱动

给这个背光控制引脚输入高电平就会点亮背光,输入低电平就会关闭背光。假如我们不断的打开和关闭背光,当速度足够快的时候就不会感觉到背光关闭这个过程了。这个正好可以使用PWM来完成,PWM全称是Pulse Width Modulation,也就是脉冲宽度调制,PWM信号如图
在这里插入图片描述

PWM信号有两个关键的术语:频率和占空比,频率就是开关速度,把一次开关算作一个周期,那么频率就是1秒内进行了多少次开关。占空比就是一个周期内高电平时间和低电平时间的比例,一个周期内高电平时间越长占空比就越大,反之占空比就越小。占空比用百分之表示,如果一个周期内全是低电平那么占空比 就是0%,如果一个周期内全是高电平那么占空比就是100%。
我们给LCD的背光引脚输入一个PWM信号,这样就可以通过调整占空比的方式来调整LCD背光亮度了。提高占空比就会提高背光亮度,降低占空比就会降低背光亮度。重点就在于PWM信号的产生和占空比的控制,很幸运的是,I.MX6U提供了PWM外设,因此我们可以配置PWM外设来产生PWM信号。
打开《I.MX6ULL参考手册》的第40章“Chapter 40 Pulse Width Modulation(PWM)”,I.MX6U一共有8路PWM信号,每个PWM包含一个16位的计数器和一个4 x 16的数据FIFO

关于blacklight驱动,在kernel,找到一个驱动源码,参考一下。pwm框架,这里不写。

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/slab.h>

#include <linux/mfd/wm831x/core.h>
#include <linux/mfd/wm831x/pdata.h>
#include <linux/mfd/wm831x/regulator.h>

struct wm831x_backlight_data {
	struct wm831x *wm831x;
	int isink_reg;
	int current_brightness;
};

static int wm831x_backlight_set(struct backlight_device *bl, int brightness)
{
	struct wm831x_backlight_data *data = bl_get_data(bl);
	struct wm831x *wm831x = data->wm831x;
	int power_up = !data->current_brightness && brightness;
	int power_down = data->current_brightness && !brightness;
	int ret;

	if (power_up) {
		/* Enable the ISINK */
		ret = wm831x_set_bits(wm831x, data->isink_reg,
				      WM831X_CS1_ENA, WM831X_CS1_ENA);
		if (ret < 0)
			goto err;

		/* Enable the DC-DC */
		ret = wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE,
				      WM831X_DC4_ENA, WM831X_DC4_ENA);
		if (ret < 0)
			goto err;
	}

	if (power_down) {
		/* DCDC first */
		ret = wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE,
				      WM831X_DC4_ENA, 0);
		if (ret < 0)
			goto err;

		/* ISINK */
		ret = wm831x_set_bits(wm831x, data->isink_reg,
				      WM831X_CS1_DRIVE | WM831X_CS1_ENA, 0);
		if (ret < 0)
			goto err;
	}

	/* Set the new brightness */
	ret = wm831x_set_bits(wm831x, data->isink_reg,
			      WM831X_CS1_ISEL_MASK, brightness);
	if (ret < 0)
		goto err;

	if (power_up) {
		/* Drive current through the ISINK */
		ret = wm831x_set_bits(wm831x, data->isink_reg,
				      WM831X_CS1_DRIVE, WM831X_CS1_DRIVE);
		if (ret < 0)
			return ret;
	}

	data->current_brightness = brightness;

	return 0;

err:
	/* If we were in the middle of a power transition always shut down
	 * for safety.
	 */
	if (power_up || power_down) {
		wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, WM831X_DC4_ENA, 0);
		wm831x_set_bits(wm831x, data->isink_reg, WM831X_CS1_ENA, 0);
	}

	return ret;
}

static int wm831x_backlight_update_status(struct backlight_device *bl)
{
	return wm831x_backlight_set(bl, backlight_get_brightness(bl));
}

static int wm831x_backlight_get_brightness(struct backlight_device *bl)
{
	struct wm831x_backlight_data *data = bl_get_data(bl);

	return data->current_brightness;
}

static const struct backlight_ops wm831x_backlight_ops = {
	.options = BL_CORE_SUSPENDRESUME,
	.update_status	= wm831x_backlight_update_status,
	.get_brightness	= wm831x_backlight_get_brightness,
};

static int wm831x_backlight_probe(struct platform_device *pdev)
{
	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
	struct wm831x_pdata *wm831x_pdata = dev_get_platdata(pdev->dev.parent);
	struct wm831x_backlight_pdata *pdata;
	struct wm831x_backlight_data *data;
	struct backlight_device *bl;
	struct backlight_properties props;
	int ret, i, max_isel, isink_reg, dcdc_cfg;

	/* We need platform data */
	if (wm831x_pdata)
		pdata = wm831x_pdata->backlight;
	else
		pdata = NULL;

	if (!pdata) {
		dev_err(&pdev->dev, "No platform data supplied\n");
		return -EINVAL;
	}

	/* Figure out the maximum current we can use */
	for (i = 0; i < WM831X_ISINK_MAX_ISEL; i++) {
		if (wm831x_isinkv_values[i] > pdata->max_uA)
			break;
	}

	if (i == 0) {
		dev_err(&pdev->dev, "Invalid max_uA: %duA\n", pdata->max_uA);
		return -EINVAL;
	}
	max_isel = i - 1;

	if (pdata->max_uA != wm831x_isinkv_values[max_isel])
		dev_warn(&pdev->dev,
			 "Maximum current is %duA not %duA as requested\n",
			 wm831x_isinkv_values[max_isel], pdata->max_uA);

	switch (pdata->isink) {
	case 1:
		isink_reg = WM831X_CURRENT_SINK_1;
		dcdc_cfg = 0;
		break;
	case 2:
		isink_reg = WM831X_CURRENT_SINK_2;
		dcdc_cfg = WM831X_DC4_FBSRC;
		break;
	default:
		dev_err(&pdev->dev, "Invalid ISINK %d\n", pdata->isink);
		return -EINVAL;
	}

	/* Configure the ISINK to use for feedback */
	ret = wm831x_reg_unlock(wm831x);
	if (ret < 0)
		return ret;

	ret = wm831x_set_bits(wm831x, WM831X_DC4_CONTROL, WM831X_DC4_FBSRC,
			      dcdc_cfg);

	wm831x_reg_lock(wm831x);
	if (ret < 0)
		return ret;

	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
	if (data == NULL)
		return -ENOMEM;

	data->wm831x = wm831x;
	data->current_brightness = 0;
	data->isink_reg = isink_reg;

	memset(&props, 0, sizeof(props));
	props.type = BACKLIGHT_RAW;
	props.max_brightness = max_isel;
	bl = devm_backlight_device_register(&pdev->dev, "wm831x", &pdev->dev,
					data, &wm831x_backlight_ops, &props);
	if (IS_ERR(bl)) {
		dev_err(&pdev->dev, "failed to register backlight\n");
		return PTR_ERR(bl);
	}

	bl->props.brightness = max_isel;

	platform_set_drvdata(pdev, bl);

	/* Disable the DCDC if it was started so we can bootstrap */
	wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, WM831X_DC4_ENA, 0);

	backlight_update_status(bl);

	return 0;
}

static struct platform_driver wm831x_backlight_driver = {
	.driver		= {
		.name	= "wm831x-backlight",
	},
	.probe		= wm831x_backlight_probe,
};

module_platform_driver(wm831x_backlight_driver);

MODULE_DESCRIPTION("Backlight Driver for WM831x PMICs");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:wm831x-backlight");

七、应用层app测试
本来是不想写的,下一篇有介绍,在这里写下吧

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
 
// 宏定义
#define FBDEVICE	"/dev/fb0"
#define WIDTH		1024	
#define HEIGHT		600
 
#define WHITE		0xffffffff			// test ok
#define BLACK		0x00000000
#define RED			0xffff0000
#define GREEN		0xff00ff00			// test ok
#define BLUE		0xff0000ff			
#define GREENP		0x0000ff00			// 一样,说明前2个ff透明位不起作用
 
// 函数声明
void draw_back(unsigned int width, unsigned int height, unsigned int color);
void draw_line(unsigned int color);
 
// 全局变量
unsigned int *pfb = NULL;
 
 
int main(void)
{
	int fd = -1, ret = -1;
	
	
	struct fb_fix_screeninfo finfo = {0};
	struct fb_var_screeninfo vinfo = {0};
	
	// 第1步:打开设备
	fd = open(FBDEVICE, O_RDWR);
	if (fd < 0)
	{
		perror("open");
		return -1;
	}
	printf("open %s success.\n", FBDEVICE);
	
	// 第2步:获取设备的硬件信息
	ret = ioctl(fd, FBIOGET_FSCREENINFO, &finfo);
	if (ret < 0)
	{
		perror("ioctl");
		return -1;
	}
	printf("smem_start = 0x%x, smem_len = %u.\n", finfo.smem_start, finfo.smem_len);
	
	ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
	if (ret < 0)
	{
		perror("ioctl");
		return -1;
	}
	printf("xres = %u, yres = %u.\n", vinfo.xres, vinfo.yres);
	printf("xres_virtual = %u, yres_virtual = %u.\n", vinfo.xres_virtual, vinfo.yres_virtual);
	printf("bpp = %u.\n", vinfo.bits_per_pixel);
 
	
	// 第3步:进行mmap,申请到显存的地址。
	unsigned long len = vinfo.xres_virtual * vinfo.yres_virtual * vinfo.bits_per_pixel / 8;
	printf("len = %ld\n", len);
	pfb = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (NULL == pfb)
	{
		perror("mmap");
		return -1;
	}
	printf("pfb = %p.\n", pfb);
	
	draw_back(WIDTH, HEIGHT, WHITE);
	draw_line(RED);
	
 
	close(fd);
	
	return 0;
}
 
 
//刷背景函数
void draw_back(unsigned int width, unsigned int height, unsigned int color)
{
	unsigned int x, y;
	
	for (y=0; y<height; y++)
	{
		for (x=0; x<width; x++)
		{
			*(pfb + y * WIDTH + x) = color;
		}
	}
}
 
//画线函数
void draw_line(unsigned int color)
{
	unsigned int x, y;
	
	for (x=50; x<600; x++)
	{
		*(pfb + 200 * WIDTH + x) = color;
	}
}
 

八、驱动深入分析

有了上面的驱动代码,就可以继续分析内核实现

内核实现fbmen_init()函数:路径drivers/video/fbmem.c

负责创建graphics类、注册FB的字符设备驱动、register_framebuffer()函数提供接口给具体framebuffer驱动编写着来注册fb设备。

在这里插入图片描述调用了proc文件系统,在proc下就可以看到这些fb了。
在这里插入图片描述

下面这个函数,可以看到,他干了嘛,很熟悉,很input一样,内核帮我们,注册好了,字符设备这一套,提供file_opration这一套。给应用层,注册到class,device。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

再来驱动层注册示意图。就是注册填充一个fb_info
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值