LINUX驱动之LCD驱动

1 前言

LCD是Liquid Crystal Display的简称,也就是经常所说的液晶显示器,LCD能够支持彩色图像的显示和视频和播放,是一种非常重要的输出设备,对于LCD的驱动一般就是涉及framebuffer的编程,形象点说,framebuffer驱动负责把我们输入的内容转成送往LCD的信号,那LCD的驱动负责把信号转成LCD上显示的内容,两者是相互合作的关系.

1.1FrameBuffer的概念

FrameBuffer又叫帧缓冲,是LINUX为操作显示设备提供的一个用户接口,用户应用程序可以通过FrameBuffer透明地访问不同类型的显示设备,从这方面来说,FrameBuffe是硬件设备显示缓冲区抽象,屏蔽图像硬件的底层差异,LINUX抽象出FrameBuffer这个帧缓冲区可以供用户应用直接读写,通过更改FrameBuffer中的内容,就可以立刻显示在LCD显示屏上,除此之外,FrameBuffer是一个标准的字符设备,
主设备号为29,对应于/dev/fb%d设备文件,FrameBuffer设备也是一种普通的内存设备,可以直接对其读写,比如执行"cp /dev/fb0 file.png"对屏幕进行抓屏

1.2FrameBuffer与应用程序的交互

在这里插入图片描述
在Linux中,FrameBuffer是一种能够提取图形的硬件设备,是用户进入图像界面的很好接口,它是显存抽象后的一种设备,它允许上层应用程序在直接进行读写操作,这种操作是抽象的,统一的,用户不比关心物理显存的位置,换页机制等具体细节,对于用户程序而言,它和/dev下面的其他设备没有什么区别,用户可以把FrameBuffer看成一块内存,我们想显示图片时只需要映射这块内存,向里面写入数据即可,后续的显示工作由LCD控制完成即可

1.3LCD显示原理

简单地讲,FrameBuffer驱动的功能就是分配一块内存作为显存,然后对LCD控制器的寄存器做一些设置,LCD显示器会不断从显存中获得数据,并将其显示在LCD显示器上,LCD显示器可以显示显存中的一个区域或者整个区域,具体点讲,通过FrameBuffer,应用程序用mmap()把分配在物理空间的显存映射到应用程序虚拟地址空间,应用程序只需要将显示的数据写入到这个内存空间,然后LCD控制器会自动将这个内存空间(显存)中的数据显示在LCD显示屏上,在linux内核中framebuffer驱动的核心在fbmem.c文件中实现,我们下面来分析这个过程

2 Fbmem.c分析:

#define FB_MAJOR		29

static int __init
fbmem_init(void)
{
	create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);

	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
		printk("unable to get major %d for fb devs\n", FB_MAJOR);

	fb_class = class_create(THIS_MODULE, "graphics");
	if (IS_ERR(fb_class)) {
		printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
		fb_class = NULL;
	}
	return 0;
}
#ifdef MODULE
module_init(fbmem_init);

可以看出在入口函数中注册了一个名为"fb"的字符设备和注册一个file_operations结构体"fb_fops"与之关联,注册的主设备号为29,还创建一个类,因为“fbmem.c”是通用的文件,故并不能直接使用这file_operations 结构的.read 等函数,这里的fbmem.c没有在设备类下创建设备,只有真正有硬件设备时才有必要在这个类下去创建设备,至于在哪里创建设备我们后面会提到,我们看到fb_fops的open函数

static int
fb_open(struct inode *inode, struct file *file)
{
	int fbidx = iminor(inode);
	struct fb_info *info;
	int res = 0;

	if (fbidx >= FB_MAX)
		return -ENODEV;
#ifdef CONFIG_KMOD
	if (!(info = registered_fb[fbidx]))
		try_to_load(fbidx);
#endif /* CONFIG_KMOD */
	if (!(info = registered_fb[fbidx]))
		return -ENODEV;
	if (!try_module_get(info->fbops->owner))
		return -ENODEV;
	file->private_data = info;
	if (info->fbops->fb_open) {
		res = info->fbops->fb_open(info,1);
		if (res)
			module_put(info->fbops->owner);
	}
	return res;
}

首先通过iminor(inode)得到这个设备节点的"次设备号"(这里假设为0),然后定义一个类型为struct fb_info*类型的结构体,从这个registered_fb[]数组里得到“以次设备号为0为下标”的一项赋值给这个info,若这个 info=registered_fd[0] 的 “fbops”有“fb_open”函数时就调用这个fb_open打开设备,至于registered_fb[]数组中的项是从哪里来的我们后面会说到,我们先来看看fb_fops的read函数

static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
	unsigned long p = *ppos;
	struct inode *inode = file->f_path.dentry->d_inode;
	int fbidx = iminor(inode);
	struct fb_info *info = registered_fb[fbidx];
	u32 *buffer, *dst;
	u32 __iomem *src;
	int c, i, cnt = 0, err = 0;
	unsigned long total_size;

	if (!info || ! info->screen_base)
		return -ENODEV;

	if (info->state != FBINFO_STATE_RUNNING)
		return -EPERM;

	if (info->fbops->fb_read)
		return info->fbops->fb_read(info, buf, count, ppos);
	
	total_size = info->screen_size//设置屏幕大小

	if (total_size == 0)
		total_size = info->fix.smem_len;

	if (p >= total_size)
		return 0;

	if (count >= total_size)
		count = total_size;

	if (count + p > total_size)
		count = total_size - p;

	buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
			 GFP_KERNEL);//分配一个缓冲区
	if (!buffer)
		return -ENOMEM;

	src = (u32 __iomem *) (info->screen_base + p);//screen_base是指显存的基地址,这里是等于显存的基地址加上某个偏移量
	

	if (info->fbops->fb_sync)
		info->fbops->fb_sync(info);

	while (count) {
		c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;
		dst = buffer;
		for (i = c >> 2; i--; )
			*dst++ = fb_readl(src++);//读源(从显存基地址+P 偏移)那里读到一个数据放到目标“*dst++”里.dst 是  buffer,buffer是 kmalloc()上面分配的空间.   
		if (c & 3) {
			u8 *dst8 = (u8 *) dst;
			u8 __iomem *src8 = (u8 __iomem *) src;

			for (i = c & 3; i--;)
				*dst8++ = fb_readb(src8++);

			src = (u32 __iomem *) src8;
		}

		if (copy_to_user(buf, buffer, c)) {  //拷贝数据到用户空间
			err = -EFAULT;
			break;
		}
		*ppos += c;
		buf += c;
		cnt += c;
		count -= c;
	}

	kfree(buffer);

	return (err) ? err : cnt;
}

可以看出read函数也是以次设备号为下标从 registered_fd 数组中得一项赋给"info"结构,若这个 info 数组项中提供了“fbops”结构的 “fb_read”读函数时:就调用此读函数,若没有则从“src = (u32 __iomem *) (info->screen_base + p);”里读,这个“src”源处读到用 kmalloc()分配的一个目标地址“dst”中(*dst++ = fb_readl(src++);.最后“copy_to_user(buf, buffer, c)”把读到的数据拷贝到用户空间
在这里插入图片描述
上面还遗留一个问题就是registered_fb[]中的数组项从哪里来,我们接着往下看

int
register_framebuffer(struct fb_info *fb_info)
{
	int i;
	struct fb_event event;
	struct fb_videomode mode;

	if (num_registered_fb == FB_MAX)
		return -ENXIO;
	num_registered_fb++;
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])//找到一个空项
			break;
	fb_info->node = i;

	fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), "fb%d", i);//创建我们的设备节点,这里就是我们上文中提到在Fbmem.c只创建了类而未创建设备的原因,该设备在这里被创建了
				   
	if (IS_ERR(fb_info->dev)) {
		/* Not fatal */
		printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
		fb_info->dev = NULL;
	} else
		fb_init_device(fb_info);

	if (fb_info->pixmap.addr == NULL) {
		fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
		if (fb_info->pixmap.addr) {
			fb_info->pixmap.size = FBPIXMAPSIZE;
			fb_info->pixmap.buf_align = 1;
			fb_info->pixmap.scan_align = 1;
			fb_info->pixmap.access_align = 32;
			fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
		}
	}	
	fb_info->pixmap.offset = 0;

	if (!fb_info->pixmap.blit_x)
		fb_info->pixmap.blit_x = ~(u32)0;

	if (!fb_info->pixmap.blit_y)
		fb_info->pixmap.blit_y = ~(u32)0;

	if (!fb_info->modelist.prev || !fb_info->modelist.next)
		INIT_LIST_HEAD(&fb_info->modelist);

	fb_var_to_videomode(&mode, &fb_info->var);
	fb_add_videomode(&mode, &fb_info->modelist);
	registered_fb[i] = fb_info;//这就是我们前文提到的,registered_fb[]中的数组项在这里被创建了

	event.info = fb_info;
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	return 0;
}

可以看出Fbmem.c只是抽象出来的LCD驱动框架程序,并不是支持具体的驱动,它需要依赖底层的某个驱动程序给它注册一个"fb_info"结构体(由"register_framebuffer()来注册"),搜索内核会发现有各种 LCD 驱动程序调用这个“register_framebuffer()” (如 6832fb.c,amifb.c,atmel_lcdfb.c,还有2410的如 s3fb.c, s3c2410fb.c 等).所以想要用"fbmem.c"这一套代码时,要自已定义底层的硬件驱动程序(如上面内核中有 s3c2410fb.c 这个 LCD 底层驱动程序.),总体框架如下图所示
在这里插入图片描述

3重要的数据结构介绍

3.1 struct fb_info

struct fb_info {
	int node;
	int flags;
	struct fb_var_screeninfo var; /* 可变参数 */
    struct fb_fix_screeninfo fix; /* 固定参数 */
    struct fb_monspecs monspecs; /* 显示器标准 */
    struct work_struct queue; /* 帧缓冲事件队列 */
    struct fb_pixmap pixmap; /* 图像硬件mapper */
    struct fb_pixmap sprite; /* 光标硬件mapper */
    struct fb_cmap cmap;/* 目前的颜色表 */
    struct list_head modelist;
    struct fb_videomod *mode; /* 目前的video模式 */
    struct fb_ops *fops; /* fb_ops,帧缓冲操作 */
    char *screen_base;                  /* 虚拟基地址 */
    void *pseudo_palette;      /* 虚拟调色板,用于真彩色没有调色板情况 */
    .......

3.2struct fops


struct fb_ops {
	/* open/release and usage marking */
	struct module *owner;
	/*打开*/
	int (*fb_open)(struct fb_info *info, int user);
	/*释放*/
	int (*fb_release)(struct fb_info *info, int user);

	/* For framebuffers with strange non linear layouts or that do not
	 * work with normal memory mapped access
	 */
	 /*该函数完成的功能就像文件操作函数的read()函数*/
	ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
			   size_t count, loff_t *ppos);
    /*该函数完成的功能就像文件操作函数的write()函数*/
	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);

	/* 会根据info->var变量设置视频模式 */
	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);

	/* 等待blit空闲*/
	int (*fb_sync)(struct fb_info *info);

	/* 处理fb特定的ioctl操作 */
	int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
			unsigned long arg);

	/* 实现fb特定的mmap操作 */
	int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);

	/* 保存当前的硬件状态 */
	void (*fb_save_state)(struct fb_info *info);

	/* 恢复被保存的硬件状态 */
	void (*fb_restore_state)(struct fb_info *info);

	
};

3.3fb_cmap


struct fb_cmap {
	__u32 start;       /* 表示颜色板的第一个元素入口位置	*/
	__u32 len;			/* len表示元素的个数 */
	__u16 *red;			/* 表示红色分量的值*/
	__u16 *green;      /* 表示绿色分量的值*/
	__u16 *blue;       /* 表示蓝色分量的值*/
	__u16 *transp;	  /* 表示透明度分量的值 */
};

在这里插入图片描述

3.4fb_var_screeninfo结构体

fb_var_sceeninfo结构体中存储了用户可以修改的显示控制器参数,例如
屏幕分辨率,每个像素的比特数,透明度等

 /* 可见清晰度 */
  __u32 xres;
  __u32 yres;
  /* 虚拟解析度 */
  __u32 xres_virtual;
  __u32 yres_virtual;
  /* 虚拟到可见之间的偏移 */
  __u32 xoffset;
  __u32 yoffset;
  __u32 bits_per_pixel; /* 每像素位数,BPP */
  __u32 grayscale;       /* 非0时指灰度 */
 
  /* fb缓存的R\G\B位域 */	
  struct fb_bitfield red;
  struct fb_bitfield green;
  struct fb_bitfield blue;
  struct fb_bitfield transp; /* 透明度 */
  __u32 nonstd; /* !=0非标准像素格式 */
  __u32 activate;
  __u32 height; /* 高度 */
  __u32 width; /* 宽度 */
  __u32 accel_flags; /* 看fb_info.flags */
	
  /* 定时:除了pixclock本身外,其他的都以像素时钟为单位 */
  __u32 pixclock; /* 像素时钟(皮秒) */
  __u32 lef_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]; /* 保留 */

3.5fb_fix_screeninfo结构体

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;
  __u16 ywrapstep; 
  __u32 line_length; /* 1行的字节数 */
  unsigned long mmio_start; /* 内存映射I/O的开始位置 */
  __u32 mmio_len; /* 内存映射I/O的长度 */
  __u32 accel;
  __u16 reserved[3];  /* 保留 */

写LCD驱动程序的简单流程
1.分配一个 fb_info 结构体: framebuffer_alloc
2. 设置 fb_info结构体
3. 注册: register_framebuffer
4. 硬件相关的操作
4.1根据LCD手册设置LCD控制器
4.2分配显存
在这里插入图片描述
代码如下

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

#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>

#include <asm/mach/map.h>
#include <asm/arch/regs-lcd.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/fb.h>
int s3c_setcolreg(unsigned regno, unsigned red, unsigned green,unsigned blue, unsigned transp, struct fb_info *info);

static struct fb_info *s3c_lcd;
static volatile unsigned long *gpbcon;
static volatile unsigned long *gpbdat;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile struct lcd_regs* lcd_regs;
static u32 pseudo_palette[16];
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 struct fb_ops lcd_fops =
{  
   .owner        = THIS_MODULE,
   .fb_setcolreg = s3c_setcolreg,
   .fb_fillrect  = cfb_fillrect,
   .fb_copyarea  = cfb_copyarea,
   .fb_imageblit = cfb_imageblit,
};
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length;
	return chan << bf->offset;
}
int s3c_setcolreg(unsigned regno, unsigned red, unsigned green,unsigned blue, unsigned transp, struct fb_info *info)
{
   unsigned int val;
	
	if (regno > 16)
		return 1;

	/* 用red,green,blue三原色构造出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);
	
	//((u32 *)(info->pseudo_palette))[regno] = val;
	pseudo_palette[regno] = val;
	return 0;
   
}

static int __init lcd_init(void)
{   
    s3c_lcd = framebuffer_alloc(0,NULL);
	/*设置lcd的固定参数*/
	strcmp(s3c_lcd->fix.id,"cgs_lcd");
	s3c_lcd->fix.smem_len    = 240*320*16/8;
	s3c_lcd->fix.line_length = 240*2;
	s3c_lcd->fix.visual      = FB_VISUAL_TRUECOLOR;
	s3c_lcd->fix.type        = FB_TYPE_PACKED_PIXELS;
	/*设置lcd的可变参数*/
	s3c_lcd->var.bits_per_pixel = 16;
	s3c_lcd->var.xres           = 240;
	s3c_lcd->var.yres           = 320;
	s3c_lcd->var.xres_virtual   = 240;
	s3c_lcd->var.yres_virtual   = 320;
	/*RGB:565设置RGB参数*/
	s3c_lcd->var.red.length    = 5;
	s3c_lcd->var.red.offset    = 11;
	s3c_lcd->var.green.length  = 6;
	s3c_lcd->var.green.offset  = 5;
	s3c_lcd->var.blue.length   = 5;
	s3c_lcd->var.blue.offset   = 0;
	s3c_lcd->var.activate      = FB_ACTIVATE_NOW;
	/*设置s3c_lcd的fops函数*/
	s3c_lcd->fbops = &lcd_fops;
	/*设置屏幕大小和调色盘*/
	s3c_lcd->screen_size = 240*320*16/8;
	s3c_lcd->pseudo_palette =  pseudo_palette; 
	/*硬件相关的操作,看原理图可以发现用到 GPB,GPC,GPG,GPD组IO口*/	
	gpbcon = ioremap(0x56000010, 8);
	gpbdat = gpbcon+1;
	gpccon = ioremap(0x56000020, 4);
	gpdcon = ioremap(0x56000030, 4);
	gpgcon = ioremap(0x56000060, 4);

	*gpccon = 0xaaaaaaaa;/*设置GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
	*gpdcon = 0xaaaaaaaa;/*设置GPIO管脚用于VD[23:8] */
	/*GPB0设置为输出引脚,用于连接LCD背光*/
    *gpbcon &= ~(3);
    *gpbcon |= 1;
	*gpbdat &= ~1;     /* 输出低电平,先不使能引脚 */
	*gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */
	 /*映射,寄存器较多采用数组映射,配置LCD控制器 */
	lcd_regs = ioremap(0x4D000000,sizeof(struct lcd_regs));
	/* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P14
	 *            10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
	 *            CLKVAL = 4
	 * bit[6:5]: 0b11, TFT LCD
	 * bit[4:1]: 0b1100, 16 bpp for TFT
	 * bit[0]  : 0 = Disable the video output and the LCD control signal.
	 *VBPD:表示在一帧图像开始时,帧同步信号后无效的行数(upper_margin)
     *VFPD:表示在一帧图像结束后,帧同步信号前无效的行数(lower_margin)
     *HBPD:表示从行同步信号有效后,到实际的像素信息之间无效的像素时钟个数(left_margin)
     *HFPD:表示一行的有效数据结束到下一个行同步信号开始之间的像素时钟个数(right_margin)
	 */
	 lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);
	/* 垂直方向的时间参数
	 * bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据
	 *			   LCD手册 T0-T2-T1=4
  	 *			   VBPD=3
  	 * bit[23:14]: 多少行, 320, 所以LINEVAL=320-1=319
  	 * bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC
  	 *			   LCD手册T2-T5=322-320=2, 所以VFPD=2-1=1
  	 * bit[5:0]  : VSPW, VSYNC信号的脉冲宽度, LCD手册T1=1, 所以VSPW=1-1=0
  	 */
	 lcd_regs->lcdcon2  = (3<<24) | (319<<14) | (1<<6) | (0<<0);	
	/* 水平方向的时间参数
	 * bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据
	 *			   LCD手册 T6-T7-T8=17
	 *			   HBPD=16
	 * bit[18:8]: 多少列, 240, 所以HOZVAL=240-1=239
	 * bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
	 *			   LCD手册T8-T11=251-240=11, 所以HFPD=11-1=10
	 */
	lcd_regs->lcdcon3 = (16<<19) | (239<<8) | (10<<0);
	
	/* 水平方向的同步信号
	 * bit[7:0] : HSPW, HSYNC信号的脉冲宽度, LCD手册T7=5, 所以HSPW=5-1=4
	 */ 
	lcd_regs->lcdcon4 = 4;
    /* 信号的极性 
	 * bit[11]: 1=565 format
	 * bit[10]: 0 = The video data is fetched at VCLK falling edge
	 * bit[9] : 1 = HSYNC信号要反转,即低电平有效 
	 * bit[8] : 1 = VSYNC信号要反转,即低电平有效 
	 * bit[6] : 0 = VDEN不用反转
	 * bit[3] : 0 = PWREN输出0
	 * bit[1] : 0 = BSWP
	 * bit[0] : 1 = HWSWP 2440手册P413
	 */
	lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
	/*分配显存*/
	s3c_lcd->screen_base = dma_alloc_writecombine(NULL,s3c_lcd->fix.smem_len,&s3c_lcd->fix.smem_start,GFP_KERNEL);
	/*设置显存的起始地址,设置在lcdsddr1的[29:1]位*/
	lcd_regs->lcdsaddr1  = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
	/*LCD的显存的结束地址,是放在lcdsddr1的[21:1]位,所以右移一位与上20位1*/
	lcd_regs->lcdsaddr2  = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
	/* 一行的长度(单位: 2字节) */
	lcd_regs->lcdsaddr3  = (240*16/16); 
	/* 启动LCD */
	lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
	lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
	*gpbdat |= 1;     /* 输出高电平, 使能背光 */		

	/* 4. 注册 */
	register_framebuffer(s3c_lcd);
	
	return 0;
}

static void __exit lcd_exit(void)
{   
    unregister_framebuffer(s3c_lcd);
	lcd_regs->lcdcon1 &= ~(1<<0);
	*gpbdat &= ~1;     /* 关闭背光 */
	dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
	iounmap(lcd_regs);
	iounmap(gpbcon);
	iounmap(gpccon);
	iounmap(gpdcon);
	iounmap(gpgcon);
	framebuffer_release(s3c_lcd);

}

module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");

我们使用libjpeg测试该驱动程序,首先插入模块会发现/dev目录下多了fb0,测试结果如下,我们将显示1/2图片
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结

在这里插入图片描述


版权声明:本文为qq_41936794原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。