<简介>

LCD驱动里有个很重要的概念叫帧缓冲(framebuffer),它是Linux系统为显示设备提供的一个接口,应用程序在图形模式允许对显示缓冲区进行读写操作。用户根本不用关心物理显示缓冲区的具体位置及存放方式,因为这些都由缓冲区设备驱动完成了。

启动开发板后执行ls  /dev/fb*  命令可以看到,帧缓冲设备的主设备号为29,对应/dev/fbn设备文件,一般为/dev/fb0

在弄清楚LCD驱动架构之前,我们先弄清楚几个重要的结构体,为了减短篇幅,有一些不是很重要的成员会用省略,具体的源代码请大家参考Linux源代码,这里我使用的源代码是天嵌公司提供的移植好的Linux-2.6.30.4。

<struct fb_info{}>

a:内核中结构fb_info

fb_info结构体(在include/linux/fb.h文件里定义)

struct  fb_info {
int node; /*  序号索引值,/dev/fb0,/dev/fb1  其中0,1 就是从这里获得的*/
int flags;
struct mutex lock;/* 一般在 open/release/ioctl 函数里会用到的锁 */
struct fb_var_screeninfo var;/* 可变参数,很重要 */
struct fb_fix_screeninfo fix;       /* 固定参数,很重要 */
struct fb_monspecs monspecs;/* Current Monitor specs */
struct work_struct queue;       /* Framebuffer event queue */
struct fb_pixmap pixmap;       /* Image hardware mapper */
struct fb_pixmap sprite;       /* Cursor hardware mapper */
struct fb_cmap cmap;       /* Current cmap */
struct list_head modelist;              /* mode list */
struct fb_videomode *mode;/* current mode */

。。。。。。

struct fb_ops *fbops;
struct device *device;/* This is the parent */
struct device *dev;/* This is this fb device */

。。。。。。
char __iomem 
#define FBINFO_STATE_SUSPENDED1
u32 state; /* Hardware state i.e suspend */
void *fbcon_par;                /* fbcon use-only private area */
/* From here on everything is device dependent */
void *par;   /* 这个用来存放私有数据 */
};

<struct fb_ops{}>

a:内核数据结构

fb_ops结构体(在include/linux/fb.h文件里定义)

考虑到fb_ops结构体里面的函数指针成员太多,这里仅简单列举几个比较常见的,具体的请参考源代码。

struct fb_ops{
struct module *owner;  /* 模块计数 */
int (*fb_open)(struct fb_info *info, int user);           /* 打开函数,第一个参数为fb_info */
int (*fb_release)(struct fb_info *info, int user);
。。。。。。
/* fb_check_var函数用来检查可变参数,并调整修改可变参数  */
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
。。。。。。
/*fb_setcolreg函数就是用来设置fb_info里面的pseudo_palette调色板成员 */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
   unsigned blue, unsigned transp, struct fb_info *info);
。。。。。。

/* 下面三个是通用的函数 */
/* Draws a rectangle */
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
/* Copy data from area to another */
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
/* Draws a image to the display */
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
。。。。。。
/* 有了fb_ioctl应用层可以通过ioctl系统调用来设置屏幕参数等 */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg);
。。。。。。
/* 有了fb_mmap应用层可以通过mmap系统调用来读写帧缓冲区内存 */
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
。。。
};

<struct fb_var_screeninfo{}>

a:内核中数据结构

fb_var_screeninfo 结构体(在include/linux/fb.h文件里定义)

fb_var_screeninfo 被fb_info结构体所包含,这个结构体主要用来设置LCD屏幕的参数,如分辨率、像素比特数等,LCD驱动程序里面硬件相关的设置很多都涉及这个结构体。

struct fb_var_screeninfo {
__u32 xres;/* visible resolution,分辨率,即一行有多少个点 */
__u32 yres;
__u32 xres_virtual;/* virtual resolution*/
__u32 yres_virtual;
__u32 xoffset;/* offset from virtual to visible */
__u32 yoffset;/* resolution*/

__u32 bits_per_pixel;/* guess what,定义每个点用多少个自己表示 */
__u32 grayscale;/* != 0 Graylevels instead of colors */

struct fb_bitfield red;/* bitfield in fb mem if true color, */
struct fb_bitfield green;/* else only length is significant */
struct fb_bitfield blue;
struct fb_bitfield transp;/* transparency*/

__u32 nonstd; /* != 0 Non standard pixel format */

__u32 activate;/* see FB_ACTIVATE_**/

__u32 height;/* height of picture in mm    */
__u32 width;/* width of picture in mm     */

__u32 accel_flags;/* (OBSOLETE) see fb_info.flags */

/* Timing: All values in pixclocks, except pixclock (of course) */
__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_**/
__u32 vmode; /* see FB_VMODE_**/
__u32 rotate; /* angle we rotate counter clockwise */
__u32 reserved[5];/* Reserved for future compatibility */
};

<struct fb_fix_screeninfo{}>

a:内核数据结构

fb_fix_screeninfo 结构体(在include/linux/fb.h文件里定义)

struct fb_fix_screeninfo {
char id[16];/* 驱动名字就保存在这里*/
unsigned long smem_start;       /* fb缓冲区的基地址,这是一个物理地址 */
__u32 smem_len;       /* fb缓冲区的长度 */
__u32 type;              /* FB_TYPE_类型*/
__u32 type_aux;/* Interleave for interleaved Planes */
__u32 visual;/* FB_VISUAL_类型*/ 
__u16 xpanstep;/* 一般设置为0  */
__u16 ypanstep;/* 一般设置为0  */
__u16 ywrapstep;/* 一般设置为0  */
__u32 line_length;/* 屏幕一行有多个字节    */
unsigned long mmio_start;/* Start of Memory Mapped I/O   */
/* (physical address) */
__u32 mmio_len;/* Length of Memory Mapped I/O  */
__u32 accel; /* Indicate to driver which*/
/*  specific chip/card we have*/
__u16 reserved[3];/* Reserved for future compatibility */
};

<LCD驱动实例一>

我们的mini2440使用的是X35的LCD屏,根据X35的LCD说明文档,需要在BSP中X35LCD屏的一些参数。

在mach-mini2440.c中添加X35LCD的参数

#if defined(CONFIG_FB_S3C2410_X240320)   //定义X35LCD参数

#define LCD_WIDTH 240   //屏宽

#define LCD_HEIGHT 320  //屏高

#define LCD_PIXCLOCK 170000  //时钟

#define LCD_RIGHT_MARGIN 25    //左边界

#define LCD_LEFT_MARGIN 0    //右边界

#define LCD_HSYNC_LEN 4      //行同步

#define LCD_UPPER_MARGIN 0   //上边界

#define LCD_LOWER_MARGIN 4   //下边界

#define LCD_VSYNC_LEN 9     //帧同步

#define LCD_CON5 (S3C2410_LCDCON5_FRM565 | S3C2410_LCDCON5_INVVDEN | S3C2410_LCDCON5_INVVFRAME | S3C2410_LCDCON5_INVVLINE | S3C2410_LCDCON5_INVVCLK | S3C2410_LCDCON5_HWSWP )

#elif  //定义其他LCD屏参数

#endif

好了,我们现在发现要想上面定义的X35LCD的参数正在起作用,必须使得CONFIG_FB_S3C2410_X240320=y;我们需要在/driver/video/Kconfig中定义

config FB_S3C2410_X240320

boolean "3.5 inch 240X320 LCD(ACX502BMU)"

depends on FB_S3C2410

help

3.5 inch 240X320 LCD(ACX502BMU)

然后我们通过make menuconfig选中"3.5 inch 240X320 LCD(ACX502BMU)"这一选项。

根据我们的X35LCD屏的说明文档,我们已经定义了一些边界参数和同步参数,因为我们的LCD驱动是基于platform总线的,所以需要在这个BSP中添加LCD的平台设备。

struct platform_device s3c_device_lcd = {   //添加LCD平台设备

.name              = "s3c2410-lcd",       //设备名

.id            = -1,

.num_resources      = ARRAY_SIZE(s3c_lcd_resource),

.resource         = s3c_lcd_resource,        //资源

.dev              = {

.dma_mask           = &s3c_device_lcd_dmamask,

.coherent_dma_mask   = 0xffffffffUL

}

};

资源的定义如下

static struct resource s3c_lcd_resource[] = {

[0] = {                       //内存空间资源

.start = S3C24XX_PA_LCD,

.end   = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,

.flags = IORESOURCE_MEM,

},

[1] = {                    //中断资源

.start = IRQ_LCD,

.end   = IRQ_LCD,

.flags = IORESOURCE_IRQ,

}

};

然后我们把s3c_device_lcd放到mini2440_devices[]结构体中,接着调用platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices))将LCD平台设备注册到内核。

对于我们的LCD,需要给这个平台设备添加平台设备数据,通过调用

s3c24xx_fb_set_platdata(&mini2440_fb_info);

static struct s3c2410fb_mach_info mini2440_fb_info __initdata = {

.displays = &mini2440_lcd_cfg,   //定义s3c2410fb_display数据

.num_displays       = 1,

.default_display = 0,

.gpccon =       0xaa955699,  //GPC端口设置

.gpccon_mask =  0xffc003cc,

.gpcup =        0x0000ffff,

.gpcup_mask =   0xffffffff,

.gpdcon =       0xaa95aaa1,     //GPD端口设置

.gpdcon_mask =  0xffc0fff0,

.gpdup =        0x0000faff,

.gpdup_mask =   0xffffffff,

.lpcsel            = 0xf82,

};

继续看

static struct s3c2410fb_display mini2440_lcd_cfg __initdata = {

#if !defined (LCD_CON5)

.lcdcon5 = S3C2410_LCDCON5_FRM565 |

S3C2410_LCDCON5_INVVLINE |

S3C2410_LCDCON5_INVVFRAME |

S3C2410_LCDCON5_PWREN |

S3C2410_LCDCON5_HWSWP,

#else

.lcdcon5 = LCD_CON5,

#endif

.type              = S3C2410_LCDCON1_TFT,   //屏的类型

.width            = LCD_WIDTH,        //屏宽

.height           = LCD_HEIGHT,      //屏高

.pixclock       = LCD_PIXCLOCK,   //时钟

.xres              = LCD_WIDTH,        //水平分辨率

.yres              = LCD_HEIGHT,       //垂直分辨率

.bpp              = 16,                 //每个像素的比特数

.left_margin    = LCD_LEFT_MARGIN + 1,       //左边界

.right_margin  = LCD_RIGHT_MARGIN + 1,  //右边界

.hsync_len     = LCD_HSYNC_LEN + 1,      //行同步

.upper_margin      = LCD_UPPER_MARGIN + 1,   //上边界

.lower_margin       = LCD_LOWER_MARGIN + 1,  //下边界

.vsync_len     = LCD_VSYNC_LEN + 1,          //帧同步

};

好了,这样我们就完成了LCD驱动的移植工作,接着我们通过make menuconfig选择相应的文件层、设备层和X35LCD屏这个三个选项,最后编译生成内核。

三.LCD文件层和驱动层设计思路

LCD驱动可以分为文件层和设备层,文件层又叫FrameBuffer设备驱动,对应的文件是fbmem.c,主要实现为用户提供file_operations接口,同时为设备层提供一些函数接口,这个帧缓冲设备驱动内核已经帮我们编写好,我们不需要编写。在设备层我们专门Mini2440的LCD编写的驱动在s3c2410fb.c中,该驱动叫LCD驱动,主要是填充一个fbinfo结构,然后用register_framebuffer注册到内核,对于fbinfo结构,最主要的是填充它的fs_ops成员。对于驱动工程师,第一件事就是学会根据LCD说明文档,移植LCD。第二件事就是会写设备层LCD驱动。

3.1 LCD驱动中几个重要的数据结构

在分析内核LCD驱动代码之前,我们先要熟悉几个结构体。

struct fb_info {

int node;

int flags;

struct mutex lock;

struct mutex mm_lock;

struct fb_var_screeninfo var;           //当前缓冲区的可变参数

struct fb_fix_screeninfo fix;       //当前缓冲区的固定参数

struct fb_monspecs monspecs;

struct work_struct queue;

struct fb_pixmap pixmap;

struct fb_pixmap sprite;

struct fb_cmap cmap;                    //当前的调试板

struct list_head modelist;

struct fb_videomode *mode;

#ifdef CONFIG_FB_BACKLIGHT       //背光

struct backlight_device *bl_dev;

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;

struct device *dev;

int class_flag;

#ifdef CONFIG_FB_TILEBLITTING

struct fb_tile_ops *tileops;

#endif

char __iomem *screen_base;     //虚拟基地址

unsigned long screen_size;    //虚拟内存大小

void *pseudo_palette;

#define FBINFO_STATE_RUNNING    0

#define FBINFO_STATE_SUSPENDED      1

u32 state;

void *fbcon_par;

void *par;            //私有数据

resource_size_t aperture_base;

resource_size_t aperture_size;

};

为了清晰起见,对于fb_info结构体,我只注释了重点几个成员,每个帧设备都有一个fb_info,该结构体包含了驱动实现的底层函数和记录设备状态的数据。fb_info结构体主要包含fb_var_screeninfo、fb_fix_screeninfo、fb_cmap和fb_ops,

struct fb_var_screeninfo {

__u32 xres;                 //水平分辨率

__u32 yres;        //垂直分辨率

__u32 xres_virtual;

__u32 yres_virtual;

__u32 xoffset;

__u32 yoffset;

__u32 bits_per_pixel;         //每个像素所占的比特数

__u32 grayscale;

struct fb_bitfield red;

struct fb_bitfield green;

struct fb_bitfield blue;

struct fb_bitfield transp;

__u32 nonstd;

__u32 activate;

__u32 height;               //屏高

__u32 width;               //屏宽

__u32 accel_flags;

__u32 pixclock;                  //像素时钟

__u32 left_margin;              //左边界

__u32 right_margin;            //右边界

__u32 upper_margin;          //上边界

__u32 lower_margin;   //下边界

__u32 hsync_len;         //水平同步长度

__u32 vsync_len;         //垂直同步长度

__u32 sync;

__u32 vmode;

__u32 rotate;

__u32 reserved[5];

};

上面的fb_var_screeninfo结构体存放了用户可以修改的显示控制器参数,如分辨率,BPP等参数。

struct fb_fix_screeninfo {

char id[16];

unsigned long smem_start;   //fb缓冲区开始的位置

__u32 smem_len;               //fb缓冲区长度

__u32 type;

__u32 type_aux;

__u32 visual;                 //屏幕色彩模式

__u16 xpanstep;

__u16 ypanstep;

__u16 ywrapstep;

__u32 line_length;

unsigned long mmio_start;     //内存映射开始位置

__u32 mmio_len;              //内存映射长度

__u32 accel;

__u16 reserved[3];

};

上面这个fb_fix_screeninfo主要记录了用户不能修改的固定显示控制器参数,如缓冲区物理地址、缓冲区长度、显示色彩模式、内核映射的开始位置等,这些结构体程序都需要驱动程序初始化时设置。

struct fb_cmap {

__u32 start;          //颜色板的第一个元素入口位置

__u32 len;            //元素长度

__u16 *red;         //红

__u16 *green;   //绿

__u16 *blue;    //蓝

__u16 *transp;     //透明分量值

};

对于上面的fb_cmap,它主要记录了一个颜色板信息,用户空间可以使用ioctl函数的FBIOGETCMAP和FBIOPUTCMAP读取和设置颜色表的值。

struct fb_ops {

struct module *owner;

int (*fb_open)(struct fb_info *info, int user);

int (*fb_release)(struct fb_info *info, int user);

ssize_t (*fb_read)(struct fb_info *info, char __user *buf,

size_t count, loff_t *ppos);

ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,

size_t count, loff_t *ppos);

int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);

int (*fb_set_par)(struct fb_info *info);

int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,

unsigned blue, unsigned transp, struct fb_info *info);

int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);

int (*fb_blank)(int blank, struct fb_info *info);

int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);

void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);

void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);

void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);

int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);

void (*fb_rotate)(struct fb_info *info, int angle);

int (*fb_sync)(struct fb_info *info);

int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,

unsigned long arg);

int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,

unsigned long arg);

int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);

void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,

struct fb_var_screeninfo *var);

void (*fb_destroy)(struct fb_info *info);

};

其中fb_ops就是用来实现对帧缓冲设备的操作。

3.2  LCD驱动层

好了,我们先看看驱动层代码s3c2410fb.c

static struct platform_driver s3c2410fb_driver = {

.probe           = s3c2410fb_probe,  //探测

.remove         = s3c2410fb_remove, //移除

.suspend = s3c2410fb_suspend,   //挂起

.resume         = s3c2410fb_resume,  //恢复

.driver           = {

.name     = "s3c2410-lcd",   //驱动名

.owner    = THIS_MODULE,

},

};

我们看看探测函数s3c2410fb_probe

static int __init s3c2410fb_probe(struct platform_device *pdev)

{

return s3c24xxfb_probe(pdev, DRV_S3C2410);

}

继续看

static int __init s3c24xxfb_probe(struct platform_device *pdev,

enum s3c_drv_type drv_type)

{

struct s3c2410fb_info *info;  //该驱动的全局变量结构体

struct s3c2410fb_display *display; //LCD屏的配置信息

struct fb_info *fbinfo;    //帧缓冲驱动中对应的fb_info结构体

struct s3c2410fb_mach_info *mach_info;  //内核平台设备数据

struct resource *res;             //LCD资源

int ret;

int irq;

int i;

int size;

u32 lcdcon1;

mach_info = pdev->dev.platform_data;        //获得平台设备数据

if (mach_info == NULL) {

dev_err(&pdev->dev,

"no platform data for lcd, cannot attach\n");

return -EINVAL;

}

if (mach_info->default_display >= mach_info->num_displays) {

dev_err(&pdev->dev, "default is %d but only %d displays\n",

mach_info->default_display, mach_info->num_displays);

return -EINVAL;

}

//获得LCD配置信息结构体

display = mach_info->displays + mach_info->default_display;

irq = platform_get_irq(pdev, 0);       //获得中断号

if (irq < 0) {

dev_err(&pdev->dev, "no irq for device\n");

return -ENOENT;

}

//给帧缓冲fb_info分配空间,并将struct s3c2410fb_info作为其私有数据

fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);

if (!fbinfo)

return -ENOMEM;

platform_set_drvdata(pdev, fbinfo);   //把fb_info作为平台设备的私有数据

info = fbinfo->par; //获得fb_info的私有数据

info->dev = &pdev->dev;

info->drv_type = drv_type;

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获取资源

if (res == NULL) {

dev_err(&pdev->dev, "failed to get memory registers\n");

ret = -ENXIO;

goto dealloc_fb;

}

size = (res->end - res->start) + 1;

info->mem = request_mem_region(res->start, size, pdev->name);  //申请内存

if (info->mem == NULL) {

dev_err(&pdev->dev, "failed to get memory region\n");

ret = -ENOENT;

goto dealloc_fb;

}

info->io = ioremap(res->start, size);  //物理地址转换为虚拟地址

if (info->io == NULL) {

dev_err(&pdev->dev, "ioremap() of registers failed\n");

ret = -ENXIO;

goto release_mem;

}

info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);  //基地址

dprintk("devinit\n");

strcpy(fbinfo->fix.id, driver_name);  //驱动名

lcdcon1 = readl(info->io + S3C2410_LCDCON1);

writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);  //禁止输出使能

fbinfo->fix.type         = FB_TYPE_PACKED_PIXELS;

fbinfo->fix.type_aux         = 0;  //LCD屏固定参数设置

fbinfo->fix.xpanstep         = 0;

fbinfo->fix.ypanstep         = 0;

fbinfo->fix.ywrapstep       = 0;

fbinfo->fix.accel        = FB_ACCEL_NONE;

fbinfo->var.nonstd           = 0;     //LCD屏可变参数设置

fbinfo->var.activate          = FB_ACTIVATE_NOW;

fbinfo->var.accel_flags     = 0;

fbinfo->var.vmode           = FB_VMODE_NONINTERLACED;

fbinfo->fbops                  = &s3c2410fb_ops;  //操作函数集合

fbinfo->flags             = FBINFO_FLAG_DEFAULT;

fbinfo->pseudo_palette      = &info->pseudo_pal;

for (i = 0; i < 256; i++)

info->palette_buffer[i] = PALETTE_BUFF_CLEAR;//初始化调试板为空

ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);

if (ret) {

dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);

ret = -EBUSY;

goto release_regs;

}

info->clk = clk_get(NULL, "lcd");  //获取时钟

if (!info->clk || IS_ERR(info->clk)) {

printk(KERN_ERR "failed to get lcd clock source\n");

ret = -ENOENT;

goto release_irq;

}

clk_enable(info->clk);   //使能时钟

dprintk("got and enabled clock\n");

msleep(1);

info->clk_rate = clk_get_rate(info->clk) //设置时钟;

for (i = 0; i < mach_info->num_displays; i++) { //获取最大需要的显存大小

unsigned long smem_len = mach_info->displays[i].xres;

smem_len *= mach_info->displays[i].yres;

smem_len *= mach_info->displays[i].bpp;

smem_len >>= 3;

if (fbinfo->fix.smem_len < smem_len)

fbinfo->fix.smem_len = smem_len;

}

//申请fb_info的显示缓冲区空间,并将其地址写入fbinfo中

ret = s3c2410fb_map_video_memory(fbinfo);

if (ret) {

printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);

ret = -ENOMEM;

goto release_clock;

}

dprintk("got video memory\n");

fbinfo->var.xres = display->xres;  //水平分辨率

fbinfo->var.yres = display->yres;  //垂直分辨率

fbinfo->var.bits_per_pixel = display->bpp;  //每个像素的比特数

s3c2410fb_init_registers(fbinfo);    //初始化GPIO寄存器

//检查fb_info->var与fbinfo支持的哪一种分辨率、色彩模式匹配

s3c2410fb_check_var(&fbinfo->var, fbinfo);

ret = s3c2410fb_cpufreq_register(info);

if (ret < 0) {

dev_err(&pdev->dev, "Failed to register cpufreq\n");

goto free_video_memory;

}

ret = register_framebuffer(fbinfo);//注册帧缓冲设备fb_info到系统中

if (ret < 0) {

printk(KERN_ERR "Failed to register framebuffer device: %d\n",

ret);

goto free_cpufreq;

}

ret = device_create_file(&pdev->dev, &dev_attr_debug);

if (ret) {

printk(KERN_ERR "failed to add debug attribute\n");

}

printk(KERN_INFO "fb%d: %s frame buffer device\n",

fbinfo->node, fbinfo->fix.id);

return 0;

free_cpufreq:

s3c2410fb_cpufreq_deregister(info);

free_video_memory:

s3c2410fb_unmap_video_memory(fbinfo);

release_clock:

clk_disable(info->clk);

clk_put(info->clk);

release_irq:

free_irq(irq, info);

release_regs:

iounmap(info->io);

release_mem:

release_resource(info->mem);

kfree(info->mem);

dealloc_fb:

platform_set_drvdata(pdev, NULL);

framebuffer_release(fbinfo);

return ret;

}

上面这个探测函数中包含了几个重要的函数,如申请帧缓冲设备的显存区空间的函数s3c2410fb_map_video_memory(fbinfo);初始化GPIO寄存器的函数s3c2410fb_init_registers(fbinfo);检查fb_info->var与fbinfo支持的哪一种分辨率、色彩模式匹配,并据此填充var中其他参数的函数s3c2410fb_check_var(&fbinfo->var, fbinfo),下面我们依次对这三个函数进行分析。

首先看s3c2410fb_map_video_memory(fbinfo),即申请帧缓冲设备的显存区空间的函数

static int __init s3c2410fb_map_video_memory(struct fb_info *info)

{

struct s3c2410fb_info *fbi = info->par;  //获得fb_info的私有数据

dma_addr_t map_dma;  //保存DMA缓冲区总线地址

unsigned map_size = PAGE_ALIGN(info->fix.smem_len);

dprintk("map_video_memory(fbi=%p) map_size %u\n", fbi, map_size);

//将分配的一个写合并DMA缓存区设置为LCD屏幕的虚拟地址

info->screen_base = dma_alloc_writecombine(fbi->dev, map_size,

&map_dma, GFP_KERNEL);

if (info->screen_base) {

dprintk("map_video_memory: clear %p:%08x\n",

info->screen_base, map_size);

memset(info->screen_base, 0x00, map_size);  //设置DMA缓存内容为空

//将DMA缓冲区总线地址设为fb_info不可变参数中缓存的开始位置

info->fix.smem_start = map_dma;

dprintk("map_video_memory: dma=%08lx cpu=%p size=%08x\n",

info->fix.smem_start, info->screen_base, map_size);

}

return info->screen_base ? 0 : -ENOMEM;

}

接着我们看看初始化GPIO寄存器的函数s3c2410fb_init_registers(fbinfo)

static int s3c2410fb_init_registers(struct fb_info *info)

{

struct s3c2410fb_info *fbi = info->par;  //获得fb_info的私有数据

struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;

unsigned long flags;

void __iomem *regs = fbi->io;

void __iomem *tpal;

void __iomem *lpcsel;

if (is_s3c2412(fbi)) {

tpal = regs + S3C2412_TPAL;

lpcsel = regs + S3C2412_TCONSEL;

} else {

tpal = regs + S3C2410_TPAL;

lpcsel = regs + S3C2410_LPCSEL;

}

local_irq_save(flags);

//把GPIO端口C和D配置成LCD模式

modify_gpio(S3C2410_GPCUP,  mach_info->gpcup,  mach_info->gpcup_mask);

modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask);

modify_gpio(S3C2410_GPDUP,  mach_info->gpdup,  mach_info->gpdup_mask);

modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask);

local_irq_restore(flags);

dprintk("LPCSEL    = 0x%08lx\n", mach_info->lpcsel);

writel(mach_info->lpcsel, lpcsel);

dprintk("replacing TPAL %08x\n", readl(tpal));

writel(0x00, tpal);

return 0;

}

最后看看检查fb_info->var与fbinfo支持的哪一种分辨率、色彩模式匹配,并据此填充var中其他参数的函数s3c2410fb_check_var(&fbinfo->var, fbinfo)

static int s3c2410fb_check_var(struct fb_var_screeninfo *var,

struct fb_info *info)

{

struct s3c2410fb_info *fbi = info->par;

struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;

struct s3c2410fb_display *display = NULL;

struct s3c2410fb_display *default_display = mach_info->displays +

mach_info->default_display;

int type = default_display->type; //获取LCD类型,TFT

unsigned i;

dprintk("check_var(var=%p, info=%p)\n", var, info);

//验证x/y解析度

if (var->yres == default_display->yres &&

var->xres == default_display->xres &&

var->bits_per_pixel == default_display->bpp)

display = default_display;

else

for (i = 0; i < mach_info->num_displays; i++)

if (type == mach_info->displays[i].type &&

var->yres == mach_info->displays[i].yres &&

var->xres == mach_info->displays[i].xres &&

var->bits_per_pixel == mach_info->displays[i].bpp) {

display = mach_info->displays + i;

break;

}

if (!display) {

dprintk("wrong resolution or depth %dx%d at %d bpp\n",

var->xres, var->yres, var->bits_per_pixel);

return -EINVAL;

}

var->xres_virtual = display->xres; //配置屏的虚拟解析像素

var->yres_virtual = display->yres;

var->height = display->height; //配置屏的高度宽度

var->width = display->width;

var->pixclock = display->pixclock;   //配置屏的时钟

var->left_margin = display->left_margin; //配置屏的行帧同步、水平垂直同步

var->right_margin = display->right_margin;

var->upper_margin = display->upper_margin;

var->lower_margin = display->lower_margin;

var->vsync_len = display->vsync_len;

var->hsync_len = display->hsync_len;

fbi->regs.lcdcon5 = display->lcdcon5;  ///配置LCD寄存器

fbi->regs.lcdcon1 = display->type;

var->transp.offset = 0;  //配置透明度

var->transp.length = 0;

//根据BBP来设置可变参数RGB的颜色位域

switch (var->bits_per_pixel) {

case 1:

case 2:

case 4:

var->red.offset     = 0;

var->red.length     = var->bits_per_pixel;

var->green    = var->red;

var->blue      = var->red;

break;

case 8:

if (display->type != S3C2410_LCDCON1_TFT) {

var->red.length            = 3;

var->red.offset            = 5;

var->green.length  = 3;

var->green.offset  = 2;

var->blue.length    = 2;

var->blue.offset    = 0;

} else {

var->red.offset            = 0;

var->red.length            = 8;

var->green           = var->red;

var->blue             = var->red;

}

break;

case 12:

var->red.length            = 4;

var->red.offset            = 8;

var->green.length  = 4;

var->green.offset  = 4;

var->blue.length    = 4;

var->blue.offset    = 0;

break;

default:

case 16:

if (display->lcdcon5 & S3C2410_LCDCON5_FRM565) {

var->red.offset            = 11;  //偏移

var->green.offset  = 5;

var->blue.offset    = 0;

var->red.length            = 5;   //长度

var->green.length  = 6;

var->blue.length    = 5;

} else {

var->red.offset            = 11;

var->green.offset  = 6;

var->blue.offset    = 1;

var->red.length            = 5;

var->green.length  = 5;

var->blue.length    = 5;

}

break;

case 32:

var->red.length            = 8;

var->red.offset            = 16;

var->green.length  = 8;

var->green.offset  = 8;

var->blue.length    = 8;

var->blue.offset    = 0;

break;

}

return 0;

}

好了,我们已经分析完LCD驱动中probe探测函数了,该函数主要是分配fb_info结构体空间,然后填充fb_info,初始化GPIO控制器,检查并设置fb_info中可变参数,申请帧缓冲设备的显示缓冲区空间,最后调用register_framebuffer函数注册到内核。

下面我们把重点放在fb_info结构体的fb_ops成员上

static struct fb_ops s3c2410fb_ops = {

.owner           = THIS_MODULE,

.fb_check_var      = s3c2410fb_check_var,  //检查参数

.fb_set_par    = s3c2410fb_set_par,    //激活fb_info参数配置

.fb_blank       = s3c2410fb_blank,    //显示空白

.fb_setcolreg  = s3c2410fb_setcolreg,  //设置颜色表

.fb_fillrect      = cfb_fillrect,        //可选

.fb_copyarea = cfb_copyarea,        //可选

.fb_imageblit  = cfb_imageblit,    //可选

};

fp_ops是使得帧缓冲设备工作所需函数的集合,它们最终与LCD控制器打交道。

s3c2410fb_check_va用于调整可变参数,并修改为硬件所支持的值;s3c2410fb_set_par则根据屏幕参数设置具体读写LCD控制器的寄存器,使得LCD控制器进入相应的工作状态。对于fb_ops中的.fb_fillrect、fb_copyarea和fb_imageblit,我们通常使用通用的cfb_fillrect、cfb_copyarea和cfb_imageblit函数即可。s3c2410fb_setcolreg是用来实现伪颜色表和颜色表的填充。

对于fb_ops中的成员中s3c2410fb_check_va这个函数在上面probe探测函数中已经讲过了,剩下的任务就是分析下激活fb_info参数配置函数s3c2410fb_set_par和显示空白函数s3c2410fb_blank。

首先看看显示空白函数s3c2410fb_blank

static int s3c2410fb_blank(int blank_mode, struct fb_info *info)

{

struct s3c2410fb_info *fbi = info->par;   //获得fb_info私有数据

void __iomem *tpal_reg = fbi->io;  //获得内存指针

dprintk("blank(mode=%d, info=%p)\n", blank_mode, info);

tpal_reg += is_s3c2412(fbi) ? S3C2412_TPAL : S3C2410_TPAL;

//根据显示空白的模式设置LCD开启或停止

if (blank_mode == FB_BLANK_POWERDOWN) {

s3c2410fb_lcd_enable(fbi, 0);  //停止LCD

} else {

s3c2410fb_lcd_enable(fbi, 1);  //开启LCD

}

//根据显示空白的模式控制临时调色板是否有效

if (blank_mode == FB_BLANK_UNBLANK)

writel(0x0, tpal_reg);         //调色板寄存器无效

else {

dprintk("setting TPAL to output 0x000000\n");

writel(S3C2410_TPAL_EN, tpal_reg); //调色板寄存器有效

}

return 0;

}

跟踪s3c2410fb_blank中的s3c2410fb_lcd_enable函数

static void s3c2410fb_lcd_enable(struct s3c2410fb_info *fbi, int enable)

{

unsigned long flags;

local_irq_save(flags);

if (enable)

fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID; //开启LCD

else

fbi->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID; //关闭LCD

writel(fbi->regs.lcdcon1, fbi->io + S3C2410_LCDCON1);

local_irq_restore(flags);

}

接着看看这个根据fbinfo->var激活fb_info中的参数配置函数s3c2410fb_set_par

static int s3c2410fb_set_par(struct fb_info *info)

{

struct fb_var_screeninfo *var = &info->var;

switch (var->bits_per_pixel) {//根据色位模式设置色彩模式

case 32:

case 16:

case 12:

info->fix.visual = FB_VISUAL_TRUECOLOR;

break;

case 1:

info->fix.visual = FB_VISUAL_MONO01;

break;

default:

info->fix.visual = FB_VISUAL_PSEUDOCOLOR;

break;

}

//设置fb_info中固定参数中一行的字节数

info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8;

s3c2410fb_activate_var(info); //激活fb_info参数配置

return 0;

}

我们看看s3c2410fb_set_par中激活fb_info参数配置函数s3c2410fb_activate_var

static void s3c2410fb_activate_var(struct fb_info *info)

{

struct s3c2410fb_info *fbi = info->par;

void __iomem *regs = fbi->io;

int type = fbi->regs.lcdcon1 & S3C2410_LCDCON1_TFT;

struct fb_var_screeninfo *var = &info->var;

int clkdiv;

//计算LCD控制器1中的CLKVAL值

clkdiv = DIV_ROUND_UP(s3c2410fb_calc_pixclk(fbi, var->pixclock), 2);

dprintk("%s: var->xres  = %d\n", __func__, var->xres);

dprintk("%s: var->yres  = %d\n", __func__, var->yres);

dprintk("%s: var->bpp   = %d\n", __func__, var->bits_per_pixel);

if (type == S3C2410_LCDCON1_TFT) { //配置TFT屏LCD控制寄存器

s3c2410fb_calculate_tft_lcd_regs(info, &fbi->regs);

--clkdiv;

if (clkdiv < 0)

clkdiv = 0;

} else {   //配置STN屏LCD控制寄存器

s3c2410fb_calculate_stn_lcd_regs(info, &fbi->regs);

if (clkdiv < 2)

clkdiv = 2;

}

//设置LCD控制器1中的CLKVAL值

fbi->regs.lcdcon1 |=  S3C2410_LCDCON1_CLKVAL(clkdiv);

dprintk("new register set:\n");

dprintk("lcdcon[1] = 0x%08lx\n", fbi->regs.lcdcon1);

dprintk("lcdcon[2] = 0x%08lx\n", fbi->regs.lcdcon2);

dprintk("lcdcon[3] = 0x%08lx\n", fbi->regs.lcdcon3);

dprintk("lcdcon[4] = 0x%08lx\n", fbi->regs.lcdcon4);

dprintk("lcdcon[5] = 0x%08lx\n", fbi->regs.lcdcon5);

//设置LCD控制器1-5的参数

writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID,

regs + S3C2410_LCDCON1);

writel(fbi->regs.lcdcon2, regs + S3C2410_LCDCON2);

writel(fbi->regs.lcdcon3, regs + S3C2410_LCDCON3);

writel(fbi->regs.lcdcon4, regs + S3C2410_LCDCON4);

writel(fbi->regs.lcdcon5, regs + S3C2410_LCDCON5);

s3c2410fb_set_lcdaddr(info); //设置帧缓冲起始地址寄存器1-3

fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID,

writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);

}

下面我们主要关注s3c2410fb_calculate_tft_lcd_regs和s3c2410fb_set_lcdaddr函数

static void s3c2410fb_calculate_tft_lcd_regs(const struct fb_info *info,

struct s3c2410fb_hw *regs)

{

const struct s3c2410fb_info *fbi = info->par;

const struct fb_var_screeninfo *var = &info->var;

switch (var->bits_per_pixel) {//根据色模式设置LCD控制器1和5

case 1:

regs->lcdcon1 |= S3C2410_LCDCON1_TFT1BPP;

break;

case 2:

regs->lcdcon1 |= S3C2410_LCDCON1_TFT2BPP;

break;

case 4:

regs->lcdcon1 |= S3C2410_LCDCON1_TFT4BPP;

break;

case 8:

regs->lcdcon1 |= S3C2410_LCDCON1_TFT8BPP;

regs->lcdcon5 |= S3C2410_LCDCON5_BSWP |

S3C2410_LCDCON5_FRM565;

regs->lcdcon5 &= ~S3C2410_LCDCON5_HWSWP;

break;

case 16:

regs->lcdcon1 |= S3C2410_LCDCON1_TFT16BPP;

regs->lcdcon5 &= ~S3C2410_LCDCON5_BSWP;

regs->lcdcon5 |= S3C2410_LCDCON5_HWSWP;

break;

case 32:

regs->lcdcon1 |= S3C2410_LCDCON1_TFT24BPP;

regs->lcdcon5 &= ~(S3C2410_LCDCON5_BSWP |

S3C2410_LCDCON5_HWSWP |

S3C2410_LCDCON5_BPP24BL);

break;

default:

dev_err(fbi->dev, "invalid bpp %d\n",

var->bits_per_pixel);

}

dprintk("setting vert: up=%d, low=%d, sync=%d\n",

var->upper_margin, var->lower_margin, var->vsync_len);

dprintk("setting horz: lft=%d, rt=%d, sync=%d\n",

var->left_margin, var->right_margin, var->hsync_len);

//设置LCD控制器2、3、4

regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres - 1) |

S3C2410_LCDCON2_VBPD(var->upper_margin - 1) |

S3C2410_LCDCON2_VFPD(var->lower_margin - 1) |

S3C2410_LCDCON2_VSPW(var->vsync_len - 1);

regs->lcdcon3 = S3C2410_LCDCON3_HBPD(var->right_margin - 1) |

S3C2410_LCDCON3_HFPD(var->left_margin - 1) |

S3C2410_LCDCON3_HOZVAL(var->xres - 1);

regs->lcdcon4 = S3C2410_LCDCON4_HSPW(var->hsync_len - 1);

}

static void s3c2410fb_set_lcdaddr(struct fb_info *info)

{

unsigned long saddr1, saddr2, saddr3;

struct s3c2410fb_info *fbi = info->par;

void __iomem *regs = fbi->io;

saddr1  = info->fix.smem_start >> 1;

saddr2  = info->fix.smem_start;

saddr2 += info->fix.line_length * info->var.yres;

saddr2 >>= 1;

saddr3 = S3C2410_OFFSIZE(0) |

S3C2410_PAGEWIDTH((info->fix.line_length / 2) & 0x3ff);

dprintk("LCDSADDR1 = 0x%08lx\n", saddr1);

dprintk("LCDSADDR2 = 0x%08lx\n", saddr2);

dprintk("LCDSADDR3 = 0x%08lx\n", saddr3);

//初始化LCD控制器的地址指针

writel(saddr1, regs + S3C2410_LCDSADDR1);

writel(saddr2, regs + S3C2410_LCDSADDR2);

writel(saddr3, regs + S3C2410_LCDSADDR3);

}

归纳下我们分析的这个激活fb_info参数配置函数s3c2410fb_activate_var,该函数主要是计算clkval的值,计算LCD控制器1-5的值,然后设置LCD控制器1-5,并设置帧缓冲寄存器。

<LCD驱动实例二>

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/fb.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/mm.h>

#include <linux/slab.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/div64.h>
#include <mach/regs-lcd.h>
#include <mach/regs-gpio.h>
#include <mach/fb.h>
#include <linux/pm.h>

/*FrameBuffer设备名称*/
static char driver_name[] = "my2440_lcd";

/*定义一个结构体用来维护驱动程序中各函数中用到的变量
  先别看结构体要定义这些成员,到各函数使用的地方就明白了*/
struct my2440fb_var
{
    int lcd_irq_no;           /*保存LCD中断号*/
    struct clk *lcd_clock;    /*保存从平台时钟队列中获取的LCD时钟*/
    struct resource *lcd_mem; /*LCD的IO空间*/
    void __iomem *lcd_base;   /*LCD的IO空间映射到虚拟地址*/
    struct device *dev;

struct s3c2410fb_hw regs; /*表示5个LCD配置寄存器,s3c2410fb_hw定义在mach-s3c2410/include/mach/fb.h中*/

/*定义一个数组来充当调色板。
    据数据手册描述,TFT屏色位模式为8BPP时,调色板(颜色表)的长度为256,调色板起始地址为0x4D000400*/
    u32    palette_buffer[256];

u32 pseudo_pal[16];   
    unsigned int palette_ready; /*标识调色板是否准备好了*/
};

/*用做清空调色板(颜色表)*/
#define PALETTE_BUFF_CLEAR (0x80000000)

/*LCD平台驱动结构体,平台驱动结构体定义在platform_device.h中,该结构体成员接口函数在第②步中实现*/
static struct platform_driver lcd_fb_driver = 
{
    .probe     = lcd_fb_probe,               /*FrameBuffer设备探测*/
    .remove    = __devexit_p(lcd_fb_remove), /*FrameBuffer设备移除*/
    .suspend   = lcd_fb_suspend,             /*FrameBuffer设备挂起*/
    .resume    = lcd_fb_resume,              /*FrameBuffer设备恢复*/
    .driver    = 
    {
        /*注意这里的名称一定要和系统中定义平台设备的地方一致,这样才能把平台设备与该平台设备的驱动关联起来*/
        .name = "s3c2410-lcd",
        .owner = THIS_MODULE,
    },
};

static int __init lcd_init(void)
{
    /*在Linux中,帧缓冲设备被看做是平台设备,所以这里注册平台设备*/
    return platform_driver_register(&lcd_fb_driver);
}

static void __exit lcd_exit(void)
{
    /*注销平台设备*/
    platform_driver_unregister(&lcd_fb_driver);
}

module_init(lcd_init);
module_exit(lcd_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Huang Gang");
MODULE_DESCRIPTION("My2440 LCD FrameBuffer Driver");

②、LCD平台设备各接口函数的实现:

/*LCD FrameBuffer设备探测的实现,注意这里使用一个__devinit宏,到lcd_fb_remove接口函数实现的地方讲解*/
static int __devinit lcd_fb_probe(struct platform_device *pdev)
{
    int i;
    int ret;
    struct resource *res;  /*用来保存从LCD平台设备中获取的LCD资源*/
    struct fb_info  *fbinfo; /*FrameBuffer驱动所对应的fb_info结构体*/
    struct s3c2410fb_mach_info *mach_info; /*保存从内核中获取的平台设备数据*/
    struct my2440fb_var *fbvar; /*上面定义的驱动程序全局变量结构体*/
    struct s3c2410fb_display *display; /*LCD屏的配置信息结构体,该结构体定义在mach-s3c2410/include/mach/fb.h中*/

/*获取LCD硬件相关信息数据,在前面讲过内核使用s3c24xx_fb_set_platdata函数将LCD的硬件相关信息保存到
     了LCD平台数据中,所以这里我们就从平台数据中取出来在驱动中使用*/
    mach_info = pdev->dev.platform_data;
    if(mach_info == NULL)
    {
        /*判断获取数据是否成功*/
        dev_err(&pdev->dev, "no platform data for lcd/n");
        return -EINVAL;
    }

/*获得在内核中定义的FrameBuffer平台设备的LCD配置信息结构体数据*/
    display = mach_info->displays + mach_info->default_display;

    /*给fb_info分配空间,大小为my2440fb_var结构的内存,framebuffer_alloc定义在fb.h中在fbsysfs.c中实现*/
    fbinfo = framebuffer_alloc(sizeof(struct my2440fb_var), &pdev->dev);
    if(!fbinfo)
    {
        dev_err(&pdev->dev, "framebuffer alloc of registers failed/n");
        ret = -ENOMEM;
        goto err_noirq;
    }
    platform_set_drvdata(pdev, fbinfo);/*重新将LCD平台设备数据设置为fbinfo,好在后面的一些函数中来使用*/

/*这里的用途其实就是将fb_info的成员par(注意是一个void类型的指针)指向这里的私有变量结构体fbvar,
     目的是到其他接口函数中再取出fb_info的成员par,从而能继续使用这里的私有变量*/
    fbvar = fbinfo->par;
    fbvar->dev = &pdev->dev;

/*在系统定义的LCD平台设备资源中获取LCD中断号,platform_get_irq定义在platform_device.h中*/
    fbvar->lcd_irq_no = platform_get_irq(pdev, 0);
    if(fbvar->lcd_irq_no < 0)
    {
        /*判断获取中断号是否成功*/
        dev_err(&pdev->dev, "no lcd irq for platform/n");
        return -ENOENT;
    }

/*获取LCD平台设备所使用的IO端口资源,注意这个IORESOURCE_MEM标志和LCD平台设备定义中的一致*/
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if(res == NULL)
    {
        /*判断获取资源是否成功*/
        dev_err(&pdev->dev, "failed to get memory region resource/n");
        return -ENOENT;
    }

/*申请LCD IO端口所占用的IO空间(注意理解IO空间和内存空间的区别),request_mem_region定义在ioport.h中*/
    fbvar->lcd_mem = request_mem_region(res->start, res->end - res->start + 1,pdev->name);
    if(fbvar->lcd_mem == NULL)
    {
        /*判断申请IO空间是否成功*/
        dev_err(&pdev->dev, "failed to reserve memory region/n");
        return -ENOENT;
    }

/*将LCD的IO端口占用的这段IO空间映射到内存的虚拟地址,ioremap定义在io.h中
     注意:IO空间要映射后才能使用,以后对虚拟地址的操作就是对IO空间的操作*/
    fbvar->lcd_base = ioremap(res->start, res->end - res->start + 1);
    if(fbvar->lcd_base == NULL)
    {
        /*判断映射虚拟地址是否成功*/
        dev_err(&pdev->dev, "ioremap() of registers failed/n");
        ret = -EINVAL;
        goto err_nomem;
    }

/*从平台时钟队列中获取LCD的时钟,这里为什么要取得这个时钟,从LCD屏的时序图上看,各种控制信号的延迟
     都跟LCD的时钟有关。系统的一些时钟定义在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/
    fbvar->lcd_clock = clk_get(NULL, "lcd");
    if(!fbvar->lcd_clock)
    {
        /*判断获取时钟是否成功*/
        dev_err(&pdev->dev, "failed to find lcd clock source/n");
        ret = -ENOENT;
        goto err_nomap;
    }
    /*时钟获取后要使能后才可以使用,clk_enable定义在arch/arm/plat-s3c/clock.c中*/
    clk_enable(fbvar->lcd_clock);

/*申请LCD中断服务,上面获取的中断号lcd_fb_irq,使用快速中断方式:IRQF_DISABLED
     中断服务程序为:lcd_fb_irq,将LCD平台设备pdev做参数传递过去了*/
    ret = request_irq(fbvar->lcd_irq_no, lcd_fb_irq, IRQF_DISABLED, pdev->name,fbvar);
    if(ret)
    {
        /*判断申请中断服务是否成功*/
        dev_err(&pdev->dev, "IRQ%d error %d/n", fbvar->lcd_irq_no, ret);
        ret = -EBUSY;
        goto err_noclk;
    }

    /*好了,以上是对要使用的资源进行了获取和设置。下面就开始初始化填充fb_info结构体*/

/*首先初始化fb_info中代表LCD固定参数的结构体fb_fix_screeninfo*/
    /*像素值与显示内存的映射关系有5种,定义在fb.h中。现在采用FB_TYPE_PACKED_PIXELS方式,在该方式下,
    像素值与内存直接对应,比如在显示内存某单元写入一个"1"时,该单元对应的像素值也将是"1",这使得应用层
    把显示内存映射到用户空间变得非常方便。Linux中当LCD为TFT屏时,显示驱动管理显示内存就是基于这种方式*/
    strcpy(fbinfo->fix.id, driver_name);/*字符串形式的标识符*/
    fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
    fbinfo->fix.type_aux = 0;/*以下这些根据fb_fix_screeninfo定义中的描述,当没有硬件是都设为0*/
    fbinfo->fix.xpanstep = 0;
    fbinfo->fix.ypanstep = 0;
    fbinfo->fix.ywrapstep= 0;
    fbinfo->fix.accel = FB_ACCEL_NONE;

    fbinfo

    fbinfo

    

for (i = 0; i < mach_info->num_displays; i++) /*fb缓存的长度*/
    {
        /*计算FrameBuffer缓存的最大大小,这里右移3位(即除以8)是因为色位模式BPP是以位为单位*/
        unsigned long smem_len = (mach_info->displays[i].xres * mach_info->displays[i].yres * mach_info->displays[i].bpp) >> 3;

if(fbinfo->fix.smem_len < smem_len)
        {
            fbinfo->fix.smem_len = smem_len;
        }
    }

/*初始化LCD控制器之前要延迟一段时间*/
    msleep(1);

/*初始化完fb_info后,开始对LCD各寄存器进行初始化,其定义在后面讲到*/
    my2440fb_init_registers(fbinfo);

/*初始化完寄存器后,开始检查fb_info中的可变参数,其定义在后面讲到*/
    my2440fb_check_var(fbinfo);
    
    /*申请帧缓冲设备fb_info的显示缓冲区空间,其定义在后面讲到*/
    ret = my2440fb_map_video_memory(fbinfo);
    if (ret) 
    {
        dev_err(&pdev->dev, "failed to allocate video RAM: %d/n", ret);
        ret = -ENOMEM;
        goto err_nofb;
    }

/*最后,注册这个帧缓冲设备fb_info到系统中, register_framebuffer定义在fb.h中在fbmem.c中实现*/
    ret = register_framebuffer(fbinfo);
    if (ret < 0) 
    {
        dev_err(&pdev->dev, "failed to register framebuffer device: %d/n",ret);
        goto err_video_nomem;
    }

/*对设备文件系统的支持(对设备文件系统的理解请参阅:嵌入式Linux之我行——设备文件系统剖析与使用)
     创建frambuffer设备文件,device_create_file定义在linux/device.h中*/
    ret = device_create_file(&pdev->dev, &dev_attr_debug);
    if (ret) 
    {
        dev_err(&pdev->dev, "failed to add debug attribute/n");
    }

return 0;

/*以下是上面错误处理的跳转点*/
err_nomem:
    release_resource(fbvar->lcd_mem);
    kfree(fbvar->lcd_mem);

err_nomap:
    iounmap(fbvar->lcd_base);

err_noclk:
    clk_disable(fbvar->lcd_clock);
    clk_put(fbvar->lcd_clock);

err_noirq:
    free_irq(fbvar->lcd_irq_no, fbvar);

err_nofb:
    platform_set_drvdata(pdev, NULL);
    framebuffer_release(fbinfo);

err_video_nomem:
    my2440fb_unmap_video_memory(fbinfo);

return ret;
}

/*LCD中断服务程序*/
static irqreturn_t lcd_fb_irq(int irq, void *dev_id)
{
    struct my2440fb_var    *fbvar = dev_id;
    void __iomem *lcd_irq_base;
    unsigned long lcdirq;

/*LCD中断挂起寄存器基地址*/
    lcd_irq_base = fbvar->lcd_base + S3C2410_LCDINTBASE;

/*读取LCD中断挂起寄存器的值*/
    lcdirq = readl(lcd_irq_base + S3C24XX_LCDINTPND);

/*判断是否为中断挂起状态*/
    if(lcdirq & S3C2410_LCDINT_FRSYNC)
    {
        /*填充调色板*/
        if (fbvar->palette_ready)
        {
            my2440fb_write_palette(fbvar);
        }

/*设置帧已插入中断请求*/
        writel(S3C2410_LCDINT_FRSYNC, lcd_irq_base + S3C24XX_LCDINTPND);
        writel(S3C2410_LCDINT_FRSYNC, lcd_irq_base + S3C24XX_LCDSRCPND);
    }

return IRQ_HANDLED;
}

/*填充调色板*/
static void my2440fb_write_palette(struct my2440fb_var *fbvar)
{
    unsigned int i;
    void __iomem *regs = fbvar->lcd_base;

fbvar->palette_ready = 0;

for (i = 0; i < 256; i++) 
    {
        unsigned long ent = fbvar->palette_buffer[i];

if (ent == PALETTE_BUFF_CLEAR)
        {
            continue;
        }

writel(ent, regs + S3C2410_TFTPAL(i));

if (readw(regs + S3C2410_TFTPAL(i)) == ent)
        {
            fbvar->palette_buffer[i] = PALETTE_BUFF_CLEAR;
        }
        else
        {
            fbvar->palette_ready = 1;
        }
    }
}

/*LCD各寄存器进行初始化*/
static int my2440fb_init_registers(struct fb_info *fbinfo)
{
    unsigned long flags;
    void __iomem *tpal;
    void __iomem *lpcsel;

/*从lcd_fb_probe探测函数设置的私有变量结构体中再获得LCD相关信息的数据*/
    struct my2440fb_var    *fbvar = fbinfo->par;
    struct s3c2410fb_mach_info *mach_info = fbvar->dev->platform_data;

/*获得临时调色板寄存器基地址,S3C2410_TPAL宏定义在mach-s3c2410/include/mach/regs-lcd.h中。
    注意对于lpcsel这是一个针对三星TFT屏的一个专用寄存器,如果用的不是三星的TFT屏应该不用管它。*/
    tpal = fbvar->lcd_base + S3C2410_TPAL;
    lpcsel = fbvar->lcd_base + S3C2410_LPCSEL;

/*在修改下面寄存器值之前先屏蔽中断,将中断状态保存到flags中*/
    local_irq_save(flags);

/*这里就是在上一篇章中讲到的把IO端口C和D配置成LCD模式*/
    modify_gpio(S3C2410_GPCUP, mach_info->gpcup, mach_info->gpcup_mask);
    modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask);
    modify_gpio(S3C2410_GPDUP, mach_info->gpdup, mach_info->gpdup_mask);
    modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask);

/*恢复被屏蔽的中断*/
    local_irq_restore(flags);

writel(0x00, tpal);/*临时调色板寄存器使能禁止*/
    writel(mach_info->lpcsel, lpcsel);/*在上一篇中讲到过,它是三星TFT屏的一个寄存器,这里可以不管*/

return 0;
}

/*该函数实现修改GPIO端口的值,注意第三个参数mask的作用是将要设置的寄存器值先清零*/
static inline void modify_gpio(void __iomem *reg, unsigned long set, unsigned longmask)
{
    unsigned long tmp;

tmp = readl(reg) & ~mask;
    writel(tmp | set, reg);
}

/*检查fb_info中的可变参数*/
static int my2440fb_check_var(struct fb_info *fbinfo)
{
    unsigned i;

/*从lcd_fb_probe探测函数设置的平台数据中再获得LCD相关信息的数据*/
    struct fb_var_screeninfo *var = &fbinfo->var;/*fb_info中的可变参数*/
    struct my2440fb_var    *fbvar = fbinfo->par;/*在lcd_fb_probe探测函数中设置的私有结构体数据*/
    struct s3c2410fb_mach_info *mach_info = fbvar->dev->platform_data;/*LCD的配置结构体数据,这个配置结构体的赋值在上一篇章的"3. 帧缓冲设备作为平台设备"中*/

struct s3c2410fb_display *display = NULL;
    struct s3c2410fb_display *default_display = mach_info->displays +mach_info->default_display;
    int type = default_display->type;/*LCD的类型,看上一篇章的"3. 帧缓冲设备作为平台设备"中的type赋值是TFT类型*/

/*验证X/Y解析度*/
    if (var->yres == default_display->yres && 
        var->xres == default_display->xres && 
        var->bits_per_pixel == default_display->bpp)
    {
        display = default_display;
    }
    else
    {
        for (i = 0; i < mach_info->num_displays; i++)
        {
            if (type == mach_info->displays[i].type &&
             var->yres == mach_info->displays[i].yres &&
             var->xres == mach_info->displays[i].xres &&
             var->bits_per_pixel == mach_info->displays[i].bpp) 
            {
                display = mach_info->displays + i;
                break;
            }
        }
    }

if (!display) 
    {
        return -EINVAL;
    }

/*配置LCD配置寄存器1中的5-6位(配置成TFT类型)和配置LCD配置寄存器5*/
    fbvar->regs.lcdcon1 = display->type;
    fbvar->regs.lcdcon5 = display->lcdcon5;

/* 设置屏幕的虚拟解析像素和高度宽度 */
    var->xres_virtual = display->xres;
    var->yres_virtual = display->yres;
    var->height = display->height;
    var->width = display->width;

/* 设置时钟像素,行、帧切换值,水平同步、垂直同步长度值 */
    var->pixclock = display->pixclock;
    var->left_margin = display->left_margin;
    var->right_margin = display->right_margin;
    var->upper_margin = display->upper_margin;
    var->lower_margin = display->lower_margin;
    var->vsync_len = display->vsync_len;
    var->hsync_len = display->hsync_len;

/*设置透明度*/
    var->transp.offset = 0;
    var->transp.length = 0;

/*根据色位模式(BPP)来设置可变参数中R、G、B的颜色位域。对于这些参数值的设置请参考CPU数据
    手册中"显示缓冲区与显示点对应关系图",例如在上一篇章中我就画出了8BPP和16BPP时的对应关系图*/
    switch (var->bits_per_pixel) 
    {
        case 1:
        case 2:
        case 4:
            var->red.offset  = 0;
            var->red.length  = var->bits_per_pixel;
            var->green       = var->red;
            var->blue        = var->red;
            break;
        case 8:/* 8 bpp 332 */
            if (display->type != S3C2410_LCDCON1_TFT) 
            {
                var->red.length     = 3;
                var->red.offset     = 5;
                var->green.length   = 3;
                var->green.offset   = 2;
                var->blue.length    = 2;
                var->blue.offset    = 0;
            }else{
                var->red.offset     = 0;
                var->red.length     = 8;
                var->green          = var->red;
                var->blue           = var->red;
            }
            break;
        case 12:/* 12 bpp 444 */
            var->red.length         = 4;
            var->red.offset         = 8;
            var->green.length       = 4;
            var->green.offset       = 4;
            var->blue.length        = 4;
            var->blue.offset        = 0;
            break;
        case 16:/* 16 bpp */
            if (display->lcdcon5 & S3C2410_LCDCON5_FRM565) 
            {
                /* 565 format */
                var->red.offset      = 11;
                var->green.offset    = 5;
                var->blue.offset     = 0;
                var->red.length      = 5;
                var->green.length    = 6;
                var->blue.length     = 5;
            } else {
                /* 5551 format */
                var->red.offset      = 11;
                var->green.offset    = 6;
                var->blue.offset     = 1;
                var->red.length      = 5;
                var->green.length    = 5;
                var->blue.length     = 5;
            }
            break;
        case 32:/* 24 bpp 888 and 8 dummy */
            var->red.length        = 8;
            var->red.offset        = 16;
            var->green.length      = 8;
            var->green.offset      = 8;
            var->blue.length       = 8;
            var->blue.offset       = 0;
            break;
    }

return 0;
}

/*申请帧缓冲设备fb_info的显示缓冲区空间*/
static int __init my2440fb_map_video_memory(struct fb_info *fbinfo)
{
    dma_addr_t map_dma;/*用于保存DMA缓冲区总线地址*/
    struct my2440fb_var    *fbvar = fbinfo->par;/*获得在lcd_fb_probe探测函数中设置的私有结构体数据*/
    unsigned map_size = PAGE_ALIGN(fbinfo->fix.smem_len);/*获得FrameBuffer缓存的大小, PAGE_ALIGN定义在mm.h中*/

/*将分配的一个写合并DMA缓存区设置为LCD屏幕的虚拟地址(对于DMA请参考DMA相关知识)
    dma_alloc_writecombine定义在arch/arm/mm/dma-mapping.c中*/
    fbinfo->screen_base = dma_alloc_writecombine(fbvar->dev, map_size, &map_dma,GFP_KERNEL);

if (fbinfo->screen_base) 
    {
        /*设置这片DMA缓存区的内容为空*/
        memset(fbinfo->screen_base, 0x00, map_size);

/*将DMA缓冲区总线地址设成fb_info不可变参数中framebuffer缓存的开始位置*/
        fbinfo->fix.smem_start = map_dma;
    }

return fbinfo->screen_base ? 0 : -ENOMEM;
}

/*释放帧缓冲设备fb_info的显示缓冲区空间*/
static inline void my2440fb_unmap_video_memory(struct fb_info *fbinfo)
{
    struct my2440fb_var    *fbvar = fbinfo->par;
    unsigned map_size = PAGE_ALIGN(fbinfo->fix.smem_len);

/*跟申请DMA的地方想对应*/
    dma_free_writecombine(fbvar->dev, map_size, fbinfo->screen_base, fbinfo->fix.smem_start);
}

/*LCD FrameBuffer设备移除的实现,注意这里使用一个__devexit宏,和lcd_fb_probe接口函数相对应。
  在Linux内核中,使用了大量不同的宏来标记具有不同作用的函数和数据结构,这些宏在include/linux/init.h 
  头文件中定义,编译器通过这些宏可以把代码优化放到合适的内存位置,以减少内存占用和提高内核效率。
  __devinit、__devexit就是这些宏之一,在probe()和remove()函数中应该使用__devinit和__devexit宏。
  又当remove()函数使用了__devexit宏时,则在驱动结构体中一定要使用__devexit_p宏来引用remove(),
  所以在第①步中就用__devexit_p来引用lcd_fb_remove接口函数。*/
static int __devexit lcd_fb_remove(struct platform_device *pdev)
{
    struct fb_info *fbinfo = platform_get_drvdata(pdev);
    struct my2440fb_var    *fbvar = fbinfo->par;

/*从系统中注销帧缓冲设备*/
    unregister_framebuffer(fbinfo);

/*停止LCD控制器的工作*/
    my2440fb_lcd_enable(fbvar, 0);

/*延迟一段时间,因为停止LCD控制器需要一点时间 */
    msleep(1);

/*释放帧缓冲设备fb_info的显示缓冲区空间*/
    my2440fb_unmap_video_memory(fbinfo);

/*将LCD平台数据清空和释放fb_info空间资源*/
    platform_set_drvdata(pdev, NULL);
    framebuffer_release(fbinfo);

/*释放中断资源*/
    free_irq(fbvar->lcd_irq_no, fbvar);

/*释放时钟资源*/
    if (fbvar->lcd_clock) 
    {
        clk_disable(fbvar->lcd_clock);
        clk_put(fbvar->lcd_clock);
        fbvar->lcd_clock = NULL;
    }

/*释放LCD IO空间映射的虚拟内存空间*/
    iounmap(fbvar->lcd_base);

/*释放申请的LCD IO端口所占用的IO空间*/
    release_resource(fbvar->lcd_mem);
    kfree(fbvar->lcd_mem);

return 0;
}

/*停止LCD控制器的工作*/
static void my2440fb_lcd_enable(struct my2440fb_var *fbvar, int enable)
{
    unsigned long flags;

/*在修改下面寄存器值之前先屏蔽中断,将中断状态保存到flags中*/
    local_irq_save(flags);

if (enable)
    {
        fbvar->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID;
    }
    else
    {
        fbvar->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
    }

writel(fbvar->regs.lcdcon1, fbvar->lcd_base + S3C2410_LCDCON1);

/*恢复被屏蔽的中断*/
    local_irq_restore(flags);
}

/*对LCD FrameBuffer平台设备驱动电源管理的支持,CONFIG_PM这个宏定义在内核中*/
#ifdef CONFIG_PM
/*当配置内核时选上电源管理,则平台设备的驱动就支持挂起和恢复功能*/
static int lcd_fb_suspend(struct platform_device *pdev, pm_message_t state)
{
    /*挂起LCD设备,注意这里挂起LCD时并没有保存LCD控制器的各种状态,所以在恢复后LCD不会继续显示挂起前的内容
     若要继续显示挂起前的内容,则要在这里保存LCD控制器的各种状态,这里就不讲这个了,以后讲到电源管理再讲*/
    struct fb_info *fbinfo = platform_get_drvdata(pdev);
    struct my2440fb_var    *fbvar = fbinfo->par;

/*停止LCD控制器的工作*/
    my2440fb_lcd_enable(fbvar, 0);

msleep(1);

/*停止时钟*/
    clk_disable(fbvar->lcd_clock);

return 0;
}

static int lcd_fb_resume(struct platform_device *pdev)
{
    /*恢复挂起的LCD设备*/
    struct fb_info *fbinfo = platform_get_drvdata(pdev);
    struct my2440fb_var    *fbvar = fbinfo->par;

/*开启时钟*/
    clk_enable(fbvar->lcd_clock);

/*初始化LCD控制器之前要延迟一段时间*/
    msleep(1);

/*恢复时重新初始化LCD各寄存器*/
    my2440fb_init_registers(fbinfo);

/*重新激活fb_info中所有的参数配置,该函数定义在第③步中再讲*/
    my2440fb_activate_var(fbinfo);

/*正与挂起时讲到的那样,因为没保存挂起时LCD控制器的各种状态,
    所以恢复后就让LCD显示空白,该函数定义也在第③步中再讲*/
    my2440fb_blank(FB_BLANK_UNBLANK, fbinfo);

return 0;
}
#else
/*如果配置内核时没选上电源管理,则平台设备的驱动就不支持挂起和恢复功能,这两个函数也就无需实现了*/
#define lcd_fb_suspend    NULL
#define lcd_fb_resume    NULL
#endif

-

fbinfo->pseudo_palette      = &fbvar->pseudo_pal;

③、帧缓冲设备驱动对底层硬件操作的函数接口实现(即:my2440fb_ops的实现):

/*Framebuffer底层硬件操作各接口函数*/
static struct fb_ops my2440fb_ops = 
{
    .owner          = THIS_MODULE,
    .fb_check_var   = my2440fb_check_var,/*第②步中已实现*/
    .fb_set_par     = my2440fb_set_par,/*设置fb_info中的参数,主要是LCD的显示模式*/
    .fb_blank       = my2440fb_blank,/*显示空白(即:LCD开关控制)*/
    .fb_setcolreg   = my2440fb_setcolreg,/*设置颜色表*/
    /*以下三个函数是可选的,主要是提供fb_console的支持,在内核中已经实现,这里直接调用即可*/
    .fb_fillrect    = cfb_fillrect,/*定义在drivers/video/cfbfillrect.c中*/
    .fb_copyarea    = cfb_copyarea,/*定义在drivers/video/cfbcopyarea.c中*/
    .fb_imageblit   = cfb_imageblit,/*定义在drivers/video/cfbimgblt.c中*/
};

/*设置fb_info中的参数,这里根据用户设置的可变参数var调整固定参数fix*/
static int my2440fb_set_par(struct fb_info *fbinfo)
{
    /*获得fb_info中的可变参数*/
    struct fb_var_screeninfo *var = &fbinfo->var;

/*判断可变参数中的色位模式,根据色位模式来设置色彩模式*/
    switch (var->bits_per_pixel) 
    {
        case 32:
        case 16:
        case 12:/*12BPP时,设置为真彩色(分成红、绿、蓝三基色)*/
            fbinfo->fix.visual = FB_VISUAL_TRUECOLOR;
            break;
        case 1:/*1BPP时,设置为黑白色(分黑、白两种色,FB_VISUAL_MONO01代表黑,FB_VISUAL_MONO10代表白)*/
            fbinfo->fix.visual = FB_VISUAL_MONO01;
            break;
        default:/*默认设置为伪彩色,采用索引颜色显示*/
            fbinfo->fix.visual = FB_VISUAL_PSEUDOCOLOR;
            break;
    }

/*设置fb_info中固定参数中一行的字节数,公式:1行字节数=(1行像素个数*每像素位数BPP)/8 */
    fbinfo->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8;

/*修改以上参数后,重新激活fb_info中的参数配置(即:使修改后的参数在硬件上生效)*/
    my2440fb_activate_var(fbinfo);

return 0;
}

/*重新激活fb_info中的参数配置*/
static void my2440fb_activate_var(struct fb_info *fbinfo)
{
    /*获得结构体变量*/
    struct my2440fb_var *fbvar = fbinfo->par;
    void __iomem *regs = fbvar->lcd_base;

/*获得fb_info可变参数*/
    struct fb_var_screeninfo *var = &fbinfo->var;

/*计算LCD控制寄存器1中的CLKVAL值, 根据数据手册中该寄存器的描述,计算公式如下:
    * STN屏:VCLK = HCLK / (CLKVAL * 2), CLKVAL要求>= 2
    * TFT屏:VCLK = HCLK / [(CLKVAL + 1) * 2], CLKVAL要求>= 0*/
    int clkdiv = my2440fb_calc_pixclk(fbvar, var->pixclock) / 2;

/*获得屏幕的类型*/
    int type = fbvar->regs.lcdcon1 & S3C2410_LCDCON1_TFT;

if (type == S3C2410_LCDCON1_TFT) 
    {
        /*根据数据手册按照TFT屏的要求配置LCD控制寄存器1-5*/
        my2440fb_config_tft_lcd_regs(fbinfo, &fbvar->regs);

--clkdiv;

if (clkdiv < 0)
        {
            clkdiv = 0;
        }
    } 
    else 
    {
        /*根据数据手册按照STN屏的要求配置LCD控制寄存器1-5*/
        my2440fb_config_stn_lcd_regs(fbinfo, &fbvar->regs);

if (clkdiv < 2)
        {
            clkdiv = 2;
        }
    }

/*设置计算的LCD控制寄存器1中的CLKVAL值*/
    fbvar->regs.lcdcon1 |= S3C2410_LCDCON1_CLKVAL(clkdiv);

/*将各参数值写入LCD控制寄存器1-5中*/
    writel(fbvar->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID, regs +S3C2410_LCDCON1);
    writel(fbvar->regs.lcdcon2, regs + S3C2410_LCDCON2);
    writel(fbvar->regs.lcdcon3, regs + S3C2410_LCDCON3);
    writel(fbvar->regs.lcdcon4, regs + S3C2410_LCDCON4);
    writel(fbvar->regs.lcdcon5, regs + S3C2410_LCDCON5);

/*配置帧缓冲起始地址寄存器1-3*/
    my2440fb_set_lcdaddr(fbinfo);

fbvar->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID,
    writel(fbvar->regs.lcdcon1, regs + S3C2410_LCDCON1);
}

/*计算LCD控制寄存器1中的CLKVAL值*/
static unsigned int my2440fb_calc_pixclk(struct my2440fb_var *fbvar, unsignedlong pixclk)
{
    /*获得LCD的时钟*/
    unsigned long clk = clk_get_rate(fbvar->lcd_clock);

/* 像素时钟单位是皮秒,而时钟的单位是赫兹,所以计算公式为:
     * Hz -> picoseconds is / 10^-12
     */
    unsigned long long div = (unsigned long long)clk * pixclk;

div >>= 12;            /* div / 2^12 */
    do_div(div, 625 * 625UL * 625); /* div / 5^12, do_div宏定义在asm/div64.h中*/

return div;
}

/*根据数据手册按照TFT屏的要求配置LCD控制寄存器1-5*/
static void my2440fb_config_tft_lcd_regs(const struct fb_info *fbinfo, structs3c2410fb_hw *regs)
{
    const struct my2440fb_var *fbvar = fbinfo->par;
    const struct fb_var_screeninfo *var = &fbinfo->var;

/*根据色位模式设置LCD控制寄存器1和5,参考数据手册*/
    switch (var->bits_per_pixel) 
    {
        case 1:/*1BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT1BPP;
            break;
        case 2:/*2BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT2BPP;
            break;
        case 4:/*4BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT4BPP;
            break;
        case 8:/*8BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT8BPP;
            regs->lcdcon5 |= S3C2410_LCDCON5_BSWP |S3C2410_LCDCON5_FRM565;
            regs->lcdcon5 &= ~S3C2410_LCDCON5_HWSWP;
            break;
        case 16:/*16BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT16BPP;
            regs->lcdcon5 &= ~S3C2410_LCDCON5_BSWP;
            regs->lcdcon5 |= S3C2410_LCDCON5_HWSWP;
            break;
        case 32:/*32BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT24BPP;
            regs->lcdcon5 &= ~(S3C2410_LCDCON5_BSWP |S3C2410_LCDCON5_HWSWP | S3C2410_LCDCON5_BPP24BL);
            break;
        default:/*无效的BPP*/
            dev_err(fbvar->dev, "invalid bpp %d/n", var->bits_per_pixel);
    }

/*设置LCD配置寄存器2、3、4*/
    regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres - 1) |
            S3C2410_LCDCON2_VBPD(var->upper_margin - 1) |
            S3C2410_LCDCON2_VFPD(var->lower_margin - 1) |
            S3C2410_LCDCON2_VSPW(var->vsync_len - 1);

regs->lcdcon3 = S3C2410_LCDCON3_HBPD(var->right_margin - 1) |
            S3C2410_LCDCON3_HFPD(var->left_margin - 1) |
            S3C2410_LCDCON3_HOZVAL(var->xres - 1);

regs->lcdcon4 = S3C2410_LCDCON4_HSPW(var->hsync_len - 1);
}

/*根据数据手册按照STN屏的要求配置LCD控制寄存器1-5*/
static void my2440fb_config_stn_lcd_regs(const struct fb_info *fbinfo, structs3c2410fb_hw *regs)
{
    const struct my2440fb_var    *fbvar = fbinfo->par;
    const struct fb_var_screeninfo *var = &fbinfo->var;

int type = regs->lcdcon1 & ~S3C2410_LCDCON1_TFT;
    int hs = var->xres >> 2;
    unsigned wdly = (var->left_margin >> 4) - 1;
    unsigned wlh = (var->hsync_len >> 4) - 1;

if (type != S3C2410_LCDCON1_STN4)
    {
        hs >>= 1;
    }

/*根据色位模式设置LCD控制寄存器1,参考数据手册*/
    switch (var->bits_per_pixel) 
    {
        case 1:/*1BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN1BPP;
            break;
        case 2:/*2BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN2GREY;
            break;
        case 4:/*4BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN4GREY;
            break;
        case 8:/*8BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN8BPP;
            hs *= 3;
            break;
        case 12:/*12BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN12BPP;
            hs *= 3;
            break;
        default:/*无效的BPP*/
            dev_err(fbvar->dev, "invalid bpp %d/n", var->bits_per_pixel);
    }
    
    /*设置LCD配置寄存器2、3、4, 参考数据手册*/
    if (wdly > 3) wdly = 3;
    if (wlh > 3) wlh = 3;
    regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres - 1);

regs->lcdcon3 = S3C2410_LCDCON3_WDLY(wdly) |
            S3C2410_LCDCON3_LINEBLANK(var->right_margin / 8) |
            S3C2410_LCDCON3_HOZVAL(hs - 1);

regs->lcdcon4 = S3C2410_LCDCON4_WLH(wlh);
}

/*配置帧缓冲起始地址寄存器1-3,参考数据手册*/
static void my2440fb_set_lcdaddr(struct fb_info *fbinfo)
{
    unsigned long saddr1, saddr2, saddr3;
    struct my2440fb_var *fbvar = fbinfo->par;
    void __iomem *regs = fbvar->lcd_base;

saddr1 = fbinfo->fix.smem_start >> 1;
    saddr2 = fbinfo->fix.smem_start;
    saddr2 += fbinfo->fix.line_length * fbinfo->var.yres;
    saddr2 >>= 1;
    saddr3 = S3C2410_OFFSIZE(0) | S3C2410_PAGEWIDTH((fbinfo->fix.line_length/ 2) & 0x3ff);

writel(saddr1, regs + S3C2410_LCDSADDR1);
    writel(saddr2, regs + S3C2410_LCDSADDR2);
    writel(saddr3, regs + S3C2410_LCDSADDR3);
}

/*显示空白,blank mode有5种模式,定义在fb.h中,是一个枚举*/
static int my2440fb_blank(int blank_mode, struct fb_info *fbinfo)
{
    struct my2440fb_var *fbvar = fbinfo->par;
    void __iomem *regs = fbvar->lcd_base;

/*根据显示空白的模式来设置LCD是开启还是停止*/
    if (blank_mode == FB_BLANK_POWERDOWN) 
    {
        my2440fb_lcd_enable(fbvar, 0);/*在第②步中定义*/
    } 
    else 
    {
        my2440fb_lcd_enable(fbvar, 1);/*在第②步中定义*/
    }

/*根据显示空白的模式来控制临时调色板寄存器*/
    if (blank_mode == FB_BLANK_UNBLANK)
    {
        /*临时调色板寄存器无效*/
        writel(0x0, regs + S3C2410_TPAL);
    }
    else 
    {
        /*临时调色板寄存器有效*/
        writel(S3C2410_TPAL_EN, regs + S3C2410_TPAL);
    }

return 0;
}

/*设置颜色表*/
static int my2440fb_setcolreg(unsigned regno,unsigned red,unsignedgreen,unsigned blue,unsigned transp,struct fb_info *fbinfo)
{
    unsigned int val;
    struct my2440fb_var *fbvar = fbinfo->par;
    void __iomem *regs = fbvar->lcd_base;

switch (fbinfo->fix.visual) 
    {
        case FB_VISUAL_TRUECOLOR:
            /*真彩色*/
            if (regno < 16) 
            {
                u32 *pal = fbinfo->pseudo_palette;

val = chan_to_field(red, &fbinfo->var.red);
                val |= chan_to_field(green, &fbinfo->var.green);
                val |= chan_to_field(blue, &fbinfo->var.blue);

pal[regno] = val;
            }
            break;
        case FB_VISUAL_PSEUDOCOLOR:
            /*伪彩色*/
            if (regno < 256) 
            {
                val = (red >> 0) & 0xf800;
                val |= (green >> 5) & 0x07e0;
                val |= (blue >> 11) & 0x001f;

writel(val, regs + S3C2410_TFTPAL(regno));

/*修改调色板*/
                schedule_palette_update(fbvar, regno, val);
            }
            break;
        default:
            return 1;
    }

return 0;
}

static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
    chan &= 0xffff;
    chan >>= 16 - bf->length;
    return chan << bf->offset;
}

/*修改调色板*/
static void schedule_palette_update(struct my2440fb_var    *fbvar, unsigned intregno, unsigned int val)
{
    unsigned long flags;
    unsigned long irqen;

/*LCD中断挂起寄存器基地址*/
    void __iomem *lcd_irq_base = fbvar->lcd_base + S3C2410_LCDINTBASE;

/*在修改中断寄存器值之前先屏蔽中断,将中断状态保存到flags中*/
    local_irq_save(flags);

fbvar->palette_buffer[regno] = val;

/*判断调色板是否准备就像*/
    if (!fbvar->palette_ready) 
    {
        fbvar->palette_ready = 1;

/*使能中断屏蔽寄存器*/
        irqen = readl(lcd_irq_base + S3C24XX_LCDINTMSK);
        irqen &= ~S3C2410_LCDINT_FRSYNC;
        writel(irqen, lcd_irq_base + S3C24XX_LCDINTMSK);
    }

/*恢复被屏蔽的中断*/
    local_irq_restore(flags);
}

 
 
五、从整体上再描述一下FrameBuffer设备驱动实例代码的结构:
 
1、在第①部分代码中主要做的事情有:
   a.将LCD设备注册到系统平台设备中;
   b.定义LCD平台设备结构体lcd_fb_driver。
 
2、在第②部分代码中主要做的事情有:
   a.获取和设置LCD平台设备的各种资源;
   b.分配fb_info结构体空间;
   c.初始化fb_info结构体中的各参数;
   d.初始化LCD控制器;
   e.检查fb_info中可变参数;
   f.申请帧缓冲设备的显示缓冲区空间;
   g.注册fb_info。
 
3、在第部分代码中主要做的事情有:
   a.实现对fb_info相关参数进行检查的硬件接口函数;
   b.实现对LCD显示模式进行设定的硬件接口函数;
   c.实现对LCD显示开关(空白)的硬件接口函数等。
 

memcpy

        //这里就是将内核中定义的s3c2410fb_mach_info结构体数据保存到LCD平台数据中,所以在写驱动的时候就可以直接在平台数据中获取s3c2410fb_mach_info结构体的数据(即LCD各种参数信息)进行操作
        s3c_device_lcd.dev.platform_data = npd;
    } else {
        printk(KERN_ERR "no memory for LCD platform data/n");
    }
}

//S3C2440初始化函数
static void __init smdk2440_machine_init(void)
{

    //调用该函数将上面定义的LCD硬件信息保存到平台数据中
    s3c24xx_fb_set_platdata(&smdk2440_fb_info);
    
    s3c_i2c0_set_platdata(NULL);

platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
    smdk_machine_init();
}

/* LCD Controller */

//LCD控制器的资源信息
static struct resource s3c_lcd_resource[] = {
    [0] = {
        .start = S3C24XX_PA_LCD, //控制器IO端口开始地址
        .end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,//控制器IO端口结束地址
        .flags = IORESOURCE_MEM,//标识为LCD控制器IO端口,在驱动中引用这个就表示引用IO端口
    },
    [1] = {
        .start = IRQ_LCD,//LCD中断
        .end = IRQ_LCD,
        .flags = IORESOURCE_IRQ,//标识为LCD中断
    }
};

static u64 s3c_device_lcd_dmamask = 0xffffffffUL;

struct platform_device s3c_device_lcd = {
    .name         = "s3c2410-lcd",//作为平台设备的LCD设备名
    .id         = -1,
    .num_resources = ARRAY_SIZE(s3c_lcd_resource),//资源数量
    .resource     = s3c_lcd_resource,//引用上面定义的资源
    .dev = {
        .dma_mask = &s3c_device_lcd_dmamask,
        .coherent_dma_mask = 0xffffffffUL
    }
};

EXPORT_SYMBOL(s3c_device_lcd);//导出定义的LCD平台设备,好在mach-smdk2440.c的smdk2440_devices[]中添加到平台设备列表中

struct fb_info {
    int node;
    int flags;
    struct fb_var_screeninfo var;/*LCD可变参数结构体*/
    struct fb_fix_screeninfo fix;/*LCD固定参数结构体*/
    struct fb_monspecs monspecs; /*LCD显示器标准*/
    struct work_struct queue;    /*帧缓冲事件队列*/
    struct fb_pixmap pixmap;     /*图像硬件mapper*/
    struct fb_pixmap sprite;     /*光标硬件mapper*/
    struct fb_cmap cmap;         /*当前的颜色表*/
    struct fb_videomode *mode;   /*当前的显示模式*/

#ifdef CONFIG_FB_BACKLIGHT
    struct backlight_device *bl_dev;/*对应的背光设备*/
    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;
    struct device *dev;   /*fb设备*/
    int class_flag;    
#ifdef CONFIG_FB_TILEBLITTING
    struct fb_tile_ops *tileops; /*图块Blitting*/
#endif
    char __iomem *screen_base;   /*虚拟基地址*/
    unsigned long screen_size;   /*LCD IO映射的虚拟内存大小*/ 
    void *pseudo_palette;        /*伪16色颜色表*/ 
#define FBINFO_STATE_RUNNING    0
#define FBINFO_STATE_SUSPENDED  1
    u32 state;  /*LCD的挂起或恢复状态*/
    void *fbcon_par;
    void *par;    
};

其中,比较重要的成员有struct fb_var_screeninfo var、struct fb_fix_screeninfo fix和structfb_ops *fbops,他们也都是结构体。下面我们一个一个的来看。

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

而fb_fix_screeninfo结构体又主要记录用户不可以修改的控制器的参数,比如屏幕缓冲区的物理地址和长度等,该结构体的定义如下:

struct fb_fix_screeninfo {
    char id[16];                /*字符串形式的标示符 */
    unsigned long smem_start;   /*fb缓存的开始位置 */
    __u32 smem_len;             /*fb缓存的长度 */
    __u32 type;                 /*看FB_TYPE_* */
    __u32 type_aux;             /*分界*/
    __u32 visual;               /*看FB_VISUAL_* */ 
    __u16 xpanstep;             /*如果没有硬件panning就赋值为0 */
    __u16 ypanstep;             /*如果没有硬件panning就赋值为0 */
    __u16 ywrapstep;            /*如果没有硬件ywrap就赋值为0 */
    __u32 line_length;          /*一行的字节数 */
    unsigned long mmio_start;   /*内存映射IO的开始位置*/
    __u32 mmio_len;             /*内存映射IO的长度*/
    __u32 accel;
    __u16 reserved[3];          /*保留*/
};

struct fb_var_screeninfo {
    __u32 xres;                /*可见屏幕一行有多少个像素点*/
    __u32 yres;                /*可见屏幕一列有多少个像素点*/
    __u32 xres_virtual;        /*虚拟屏幕一行有多少个像素点*/        
    __u32 yres_virtual;        /*虚拟屏幕一列有多少个像素点*/
    __u32 xoffset;             /*虚拟到可见屏幕之间的行偏移*/
    __u32 yoffset;             /*虚拟到可见屏幕之间的列偏移*/
    __u32 bits_per_pixel;      /*每个像素的位数即BPP*/
    __u32 grayscale;           /*非0时,指的是灰度*/

struct fb_bitfield red;    /*fb缓存的R位域*/
    struct fb_bitfield green;  /*fb缓存的G位域*/
    struct fb_bitfield blue;   /*fb缓存的B位域*/
    struct fb_bitfield transp; /*透明度*/

__u32 nonstd;              /* != 0 非标准像素格式*/
    __u32 activate;                
    __u32 height;              /*高度*/
    __u32 width;               /*宽度*/
    __u32 accel_flags;

/*定时:除了pixclock本身外,其他的都以像素时钟为单位*/
    __u32 pixclock;            /*像素时钟(皮秒)*/
    __u32 left_margin;         /*行切换,从同步到绘图之间的延迟*/
    __u32 right_margin;        /*行切换,从绘图到同步之间的延迟*/
    __u32 upper_margin;        /*帧切换,从同步到绘图之间的延迟*/
    __u32 lower_margin;        /*帧切换,从绘图到同步之间的延迟*/
    __u32 hsync_len;           /*水平同步的长度*/
    __u32 vsync_len;           /*垂直同步的长度*/
    __u32 sync;
    __u32 vmode;
    __u32 rotate;
    __u32 reserved[5];         /*保留*/
};

 

<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">

 
 
 
 

linux 驱动之LCD驱动(有framebuffer)的更多相关文章

  1. Linux驱动之LCD驱动编写

    在Linux驱动之内核自带的S3C2440的LCD驱动分析这篇博客中已经分析了编写LCD驱动的步骤,接下来就按照这个步骤来字尝试字节编写LCD驱动.用的LCD屏幕为tft屏,每个像素点为16bit.对 ...

  2. 【驱动】LCD驱动(FrameBuffer)分析

    背景知识 在多媒体的推动下,彩色LCD越来越多地应用到嵌入式系统中,PDA和手机等大多都采用LCD作为显示器材,因此LCD的应用很有实际意义! LCD工作的硬件需求:要使一块LCD正常的显示文字或图像 ...

  3. Linux驱动:LCD驱动框架分析

    一直想花时间来整理一下Linux内核LCD驱动,却一直都忙着做其他事情去了,这些天特意抽出时间来整理之前落下的笔记,故事就这样开始了.LCD驱动也是字符设备驱动的一种,框架上相对于字符设备驱动稍微复杂 ...

  4. Linux驱动:LCD驱动测试

    (1) 进入内核源码目录中,make menuconfig -> Device Drivers -> Graphics support -> [M]Support for frame ...

  5. 【Linux高级驱动】LCD驱动框架分析

    1.framebuffer接口层(fbmem.c) 功能:给用户提供接口 fbmem_init  ),"fb",&fb_fops)  /*2.创建一个设备类*/ fb_cl ...

  6. S3C2440 LCD驱动(FrameBuffer)实例开发<二>(转)

    开发板自带的LCD驱动是基于platform总线写的,所以如果要使其它的LCD能够在自己的开发板上跑起来,那么就先了解platform驱动的架构,下面简单记录下自己看platform驱动时体会,简单的 ...

  7. 裸机LCD驱动配置

    横屏4.3寸LCD为480*272(行:480个像素点        列:272个行) 1.1  LCD原理图 : Pin1:Von  电源正(这里由硬件自动控制) Pin2:VM/VDEN 数据使能 ...

  8. TQ2440平台上LCD驱动的移植

    参考: http://liu1227787871.blog.163.com/blog/static/205363197201242393031250/ http://blog.csdn.net/cum ...

  9. linux驱动之LCD(无framebuffer)

    <简介> a:什么是液晶 物质一般有三态,固态,气态,和液态.这只是一种比较大致的划分,但是有些物质介于液体和固体之间——液晶.一般固体的分子或原子都由固定的排列方式,但是液晶介于固体和液 ...

随机推荐

  1. vue element-ui表格里时间戳转换成时间显示

    工作中遇到后台给的表格数据里时间是一个13位的时间戳,需要转换成时间显示在表格里, 可以用element-ui表格自带的:formatter函数,来格式化表格内容: // 时间戳转换成时间 // 使用 ...

  2. 纯CSS实现表单验证

    ladies and 乡亲们,表单验证你在做吗?客户端or服务器端,javascript or jquery,动手写 or 使用插件,今天我们来探索下使用纯css实现表单验证,借以学习css sele ...

  3. 分布式锁--Redis小试牛刀

    参考文章: Redis分布式锁的正确实现方式 分布式锁看这篇就够了 在这两篇文章的指引下亲测 Redis分布式锁 引言 分布式系统一定会存在CAP权衡问题,所以才会出现分布式锁 什么是CAP理论? 为 ...

  4. OpenGL ES 2.0 Shader 调试新思路(二): 做一个可用的原型

    OpenGL ES 2.0 Shader 调试新思路(二): 做一个可用的原型 目录 背景介绍 请参考前文OpenGL ES 2.0 Shader 调试新思路(一): 改变提问方式 优化 ledCha ...

  5. Nginx 服务器性能Bug和性能优化方案(真实经历)

    一.遇到的问题 1.问题:本应该是3个ffmpeg ,但是怎么会有5个ffmpeg出现? 2.Lua脚本问题,一直写入日志,导致有大量的日志,这里的错误日志是直接写进nginx的error.log 日 ...

  6. 使用 SP_OAXXX 创建文件夹,注意区别于 xp_cmdshell --mkdir xxx

    sp_configure 'show advanced options',1 go reconfigure with override go sp_configure 'Ole Automation ...

  7. Does Deep Learning Come from the Devil?

    Does Deep Learning Come from the Devil? Deep learning has revolutionized computer vision and natural ...

  8. AngularJS入门基础——过滤器

    在HTML中的模板绑定符号{{ }}内通过 | 符号来调用过滤器 {{ name | uppercase }}   以HTML的形式使用过滤器时,如果需要传递参数给过滤器,只要在过滤器名字后面加冒号即 ...

  9. 洛谷 P5089: CodeForces #500 (Div. 1) B / 1012B : Chemical table

    题目传送门:洛谷P5089. 题意简述: 一张 \(n \times m\) 的表格,有一些格子有标记,另外一些格子没有标记. 如果 \((r_1,c_1),(r_1,c_2),(r_2,c_1)\) ...

  10. TCP/UDP区别&&心跳包机制【转】

    转自:https://www.jianshu.com/p/6d93a3c21c34 UDP:用户数据报协议:主要用在实时性要求比较高的以及对质量相对较弱的地方.但是面对现在高质量的线路不会容易丢包,除 ...