S3C2440之LCD驱动

目录

一、LCD驱动程序之层次分析

二、LCD硬件操作

三、手动编写LCD驱动程序

3.1 分配一个fb_info

3.2 设置fb_info

3.3 硬件相关的操作 

3.3.1 配置GPIO用于LCD

3.3.2 根据LCD手册设置LCD控制器

3.3.3 分配显存(framebuffer)并把地址告诉LCD控制器

3.4 注册

3.5 代码

四、测试

五、修改内核自带的LCD驱动


一、LCD驱动程序之层次分析

  • 内核有自带的LCD驱动程序fbmem.c,具有分层的思想,对于字符驱动程序来说需要主设备号、设置file_operations结构体(open read write)、register_chrdev()注册 、入口函数和出口函数,这些fbmem.c都具备
  • 假设应用程序app:open("/dev/fb0", ...) 主设备号29,次设备号0

在file_operations的open函数中,会定义一个info结构体指针,从registered_fb数组以次设备号为下标得到,并判断info->fbops->fb_open有无open函数,有则调用

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;
}

在 file_operations的read函数中,大概内容先得到设备节点的次设备号,定义一个结构体info从registered_fb数组以次设备号为下标得到,判断info->fbops->fb_read有无read函数,有则调用,不然从screen_base地址来实现

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++);    //从源读数据放到目的framebuffer里
		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;
}
  •  对于info从数组registered_fb得到,而registered_fb在register_framebuffer中被设置
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);
	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;

	event.info = fb_info;
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	return 0;
}
  • 则写一个LCD驱动程序

1. 分配一个fb_info结构体: framebuffer_alloc
2. 设置
3. 注册: register_framebuffer
4. 硬件相关的操作

二、LCD硬件操作

假设有电子枪在LCD上:一遍移动,一边发出颜色(红绿蓝)

1.如何移动
    每来1个CLK,移动1个像素
2.颜色如何确定:由RGB确定颜色
    R,G,B三组线(不是三条线)
3.电子枪如何得知应该跳到下一行:接受到HSYNC脉冲时
    HSYNC:水平同步信号
4.电子枪如何得知应该跳到原点:接受到VSYNC脉冲时
    VSYNC:垂直同步信号
5.RGB线上的数据从何来?
    在显存FrameBufer里面存储着每个像素点的数据,然后传输给RGB三组线

硬件操作:1.配置引脚用于LCD 2.根据LCD手册设置LCD控制器 3.分配显存,并把地址告诉LCD控制器

三、手动编写LCD驱动程序

3.1 分配一个fb_info

在framebuffer_alloc中参数一是分配额外的空间,这里不需要

s3c_lcd = framebuffer_alloc(0, NULL);

3.2 设置fb_info

1.设置固定的参数fix

struct fb_fix_screeninfo {
    char id[16];            /* identification string eg "TT Builtin" */                        结构体ID用strcpy
    unsigned long smem_start;    /* Start of frame buffer mem */                   分配显存(frambbuffer),在硬件操作设置
                    /* (physical address) */
    __u32 smem_len;            /* Length of frame buffer mem */                      LCD的长度,这里为480*272*16/8,像素的格式是RGB565,16bpp,2个字节
    __u32 type;            /* see FB_TYPE_*        */                                           默认
    __u32 type_aux;            /* Interleave for interleaved Planes */                 附加的type  默认
    __u32 visual;            /* see FB_VISUAL_*        */                                     TFTLCD是真彩色,设置为FB_VISUAL_TRUECOLOR
    __u16 xpanstep;            /* zero if no hardware panning  */                       默认
    __u16 ypanstep;            /* zero if no hardware panning  */                       默认
    __u16 ywrapstep;        /* zero if no hardware ywrap    */                           默认
    __u32 line_length;        /* length of a line in bytes    */  //                          1行的长度大小单位是byte,因此为480*2
    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 */                  默认
};

strcpy(s3c_lcd->fix.id, "mylcd");
s3c_lcd->fix.smem_len = 480*272*16/8;
s3c_lcd->fix.type     = FB_TYPE_PACKED_PIXELS;
s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR; /* TFT */
s3c_lcd->fix.line_length = 480*2;

 2.设置可变的参数

struct fb_var_screeninfo {
    __u32 xres;            /* visible resolution        */                                 对于买的lcd分辨率是定死的
    __u32 yres;
    __u32 xres_virtual;        /* virtual resolution        */                          对于买的lcd可以设置虚拟分辨率在这设置为跟硬件一样
    __u32 yres_virtual;
    __u32 xoffset;            /* offset from virtual to visible */                    差值  0
    __u32 yoffset;            /* resolution            */

    __u32 bits_per_pixel;        /* guess what            */                          每个像素用多少位,对于2440不支持18位,只支持16位
    __u32 grayscale;        /* != 0 Graylevels instead of colors */          灰度值,默认

    struct fb_bitfield red;        /* bitfield in fb mem if true color, */          RGB:565对于R的offset为从最低位(右起)偏移11位,占5bit
    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    */                        lcd物理尺寸
    __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 */
};

s3c_lcd->var.xres           = 480;
s3c_lcd->var.yres           = 272;
s3c_lcd->var.xres_virtual   = 480;
s3c_lcd->var.yres_virtual   = 272;
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.activate       = FB_ACTIVATE_NOW;

3.设置操作函数,对显存的操作

static struct fb_ops s3c_lcdfb_ops = {
	.owner		= THIS_MODULE,
	.fb_setcolreg	= s3c_lcdfb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};

s3c_lcd->fbops = &s3c_lcdfb_ops;

4.其他设置


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 int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
			     unsigned int green, unsigned int blue,
			     unsigned int 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 u32 pseudo_palette[16];

	s3c_lcd->pseudo_palette = pseudo_palette;//经过测试需要设置的调色板
	s3c_lcd->screen_base = ;/* 显存的虚拟地址 */
	s3c_lcd->screen_size = 480*272*16/8;/* 显存的大小  */

3.3 硬件相关的操作 

3.3.1 配置GPIO用于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;

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] */
	
*gpbcon &= ~(3);  /* GPB0设置为输出引脚 */
*gpbcon |= 1;
*gpbdat &= ~1;     /* 输出低电平 */
*gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */

3.3.2 根据LCD手册设置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];  //这里bluelut和dithmode地址相差36个字节,因此需要保留36/4=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;

lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
  • 配置LCDCON寄存器 

系统时钟在uboot设置好了,dmesg查看内核输出信息,HCLK为100M 

查看4.3寸LCD手册VCLK为100ns 

bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2],可得CLKVAL=4,LCD为TFTLCD,bit[6:5]: 0b11, 像素点为16bpp,bit[4:1]: 0b1100,为了统一使能,先不使能LCD,bit[0] :0

	lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);

S3C2440的LCD时序图:

4.3寸LCD时序图:

垂直方向:

垂直方向同步信号,单板与LCD的极性不同,后面需要设置,VSPW+1=tvp=10,VSPW=9,VBPD+1=tvb=2,VBPD=1,LINEVAL+1=272,LINEVAL=271,VFPD+1=tvf=2,VFPD=1

水平方向:

水平方向同步信号,单板与LCD的极性不同,后面需要设置,HSPW+1=thp=41,HSPW=40,HBPD+1=thb=2,HBPD=1,HOZVAL+1=480,HOZVAL=479,HFPD+1=thf=2,HFPD=1

	lcd_regs->lcdcon2  = (1<<24) | (271<<14) | (1<<6) | (9<<0);
	lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1<<0);
	lcd_regs->lcdcon4 = 40;

 

bit[11]: 1=565 format,bit[9] : 1 = HSYNC信号要反转,即低电平有效,bit[8] : 1 = VSYNC信号要反转,即低电平有效,bit[3] : 0 = PWREN输出0,PWREN电源输出,后面LCD使能统一设置

	lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);

3.3.3 分配显存(framebuffer)并把地址告诉LCD控制器

  • LCDSADDR1:A[30:22]:framebuffer的高两位去掉,A[24:1]:将framebuffer右移一位

  • LCDSADDR2:单边扫描,去framebuffer的A[21:1],即需要21位即可

  • LCDSADDR3:一行的长度,单位为半字

	s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
	
	lcd_regs->lcdsaddr1  = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
	lcd_regs->lcdsaddr2  = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
	lcd_regs->lcdsaddr3  = (480*16/16);  /* 一行的长度(单位: 2字节) */	

启动LCD

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

3.4 注册

	register_framebuffer(s3c_lcd);

3.5 代码

#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>

static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
			     unsigned int green, unsigned int blue,
			     unsigned int transp, struct fb_info *info);


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 s3c_lcdfb_ops = {
	.owner		= THIS_MODULE,
	.fb_setcolreg	= s3c_lcdfb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};


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];


/* from pxafb.c */
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 int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
			     unsigned int green, unsigned int blue,
			     unsigned int 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 lcd_init(void)
{
	/* 1. 分配一个fb_info */
	s3c_lcd = framebuffer_alloc(0, NULL);

	/* 2. 设置 */
	/* 2.1 设置固定的参数 */
	strcpy(s3c_lcd->fix.id, "mylcd");
	s3c_lcd->fix.smem_len = 480*272*16/8;
	s3c_lcd->fix.type     = FB_TYPE_PACKED_PIXELS;
	s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR; /* TFT */
	s3c_lcd->fix.line_length = 480*2;
	
	/* 2.2 设置可变的参数 */
	s3c_lcd->var.xres           = 480;
	s3c_lcd->var.yres           = 272;
	s3c_lcd->var.xres_virtual   = 480;
	s3c_lcd->var.yres_virtual   = 272;
	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.activate       = FB_ACTIVATE_NOW;
	
	
	/* 2.3 设置操作函数 */
	s3c_lcd->fbops              = &s3c_lcdfb_ops;
	
	/* 2.4 其他的设置 */
	s3c_lcd->pseudo_palette = pseudo_palette;
	//s3c_lcd->screen_base  = ;  /* 显存的虚拟地址 */ 
	s3c_lcd->screen_size   = 480*272*16/8;

	/* 3. 硬件相关的操作 */
	/* 3.1 配置GPIO用于LCD */
	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] */
	
	*gpbcon &= ~(3);  /* GPB0设置为输出引脚 */
	*gpbcon |= 1;
	*gpbdat &= ~1;     /* 输出低电平 */

	*gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */
	
	/* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
	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.
	 */
	lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);

#if 1
	/* 垂直方向的时间参数
	 * bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据
	 * bit[23:14]: 多少行, 272, 所以LINEVAL=272-1=271
	 * bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC             
	 * bit[5:0]  : VSPW, VSYNC信号的脉冲宽度 
	 */
	lcd_regs->lcdcon2  = (1<<24) | (271<<14) | (1<<6) | (9<<0);


	/* 水平方向的时间参数
	 * bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据	 
	 * bit[18:8]: 多少列, 480,所以HOZVAL=480-1=479
	 * bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
	 */
	lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1<<0);

	/* 水平方向的同步信号
	 */	
	lcd_regs->lcdcon4 = 40;

	/* 信号的极性 
	 * 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 
	 */
	lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
	
	/* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
	s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
	
	lcd_regs->lcdsaddr1  = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
	lcd_regs->lcdsaddr2  = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
	lcd_regs->lcdsaddr3  = (480*16/16);  /* 一行的长度(单位: 2字节) */	
	
	//s3c_lcd->fix.smem_start = xxx;  /* 显存的物理地址 */
	/* 启动LCD */
	lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
	lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
	*gpbdat |= 1;     /* 输出高电平, 使能背光 */		

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

static void lcd_exit(void)
{
	unregister_framebuffer(s3c_lcd);
	lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
	*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");

四、测试

  • 在虚拟器内将内核源码中去掉原来的LCD驱动程序,执行make menuconfig,在Device Drivers的Graphics support中将S3C2410 LCD framebuffer support设置为模块

  • 编译内核make uImage,把内核目录arch/arm/boot/uImage提取出来cp arch/arm/boot/uImage /work/nfs_root/uImage_nolcd再make modules
  • 在2440启动uboot通过网络文件系统把内核下载到30000000的位置,并从30000000启动内核

nfs 30000000 192.168.0.13:/work/nfs_root/uImage_nolcd

bootm 30000000

  • 将LCD需要的函数在内核源码提取出来在drivers/video/目录下cfb*.ko,先装载这三个模块,本身不是驱动程序,只是提供某些函数,再加载lcd驱动,将hello输出在屏幕上,可以看到hello,把数据直接扔在/dev/fb0上面则花屏

insmod cfbcopyarea.ko 
insmod cfbfillrect.ko 
insmod cfbimgblt.ko 
insmod lcd.ko
echo hello > /dev/tty1 
cat lcd.ko > /dev/fb0  

  • 修改 /etc/inittab加入tty1::askfirst:-/bin/sh,使sh终端在lcd显示,用新重启内核并加载button.c并重新加载驱动,buttons.c参考输入子系统分析与测试,按下按键输出ls可以看到输出内容

 

五、修改内核自带的LCD驱动

  • 在内核的s3c2410fb.c中,lcdcon1在regs被赋值,在s3c2410fb_probe函数中mregs = &mach_info->regs,mach_info = pdev->dev.platform_data;寄存器的值来自于dev.platform_data,s3c2410fb_probe被platform_driver定义,说明利用了bus_drv_dev模型,根据name找到相应的device,bus_drv_dev模型参考Linux驱动之分层分离模型
writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
...



static int __init s3c2410fb_probe(struct platform_device *pdev)
{
	struct s3c2410fb_info *info;
	struct fb_info	   *fbinfo;
	struct s3c2410fb_hw *mregs;
	int ret;
	int irq;
	int i;
	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;
	}

	mregs = &mach_info->regs;
...

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,
	},
};
  •  在devs.c中找到对应的name, mach_info在s3c24xx_fb_set_platdata函数被赋值,而s3c24xx_fb_set_platdata又被mach-smdk2440.c被调用
struct platform_device s3c_device_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
	}
};

void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
{
	struct s3c2410fb_mach_info *npd;

	npd = kmalloc(sizeof(*npd), GFP_KERNEL);
	if (npd) {
		memcpy(npd, pd, sizeof(*npd));
		s3c_device_lcd.dev.platform_data = npd;
	} else {
		printk(KERN_ERR "no memory for LCD platform data\n");
	}
}
  • 在mach-smdk2440.c中设置smdk2440_lcd_cfg结构体
static void __init smdk2440_machine_init(void)
{
	s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);

	platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
	smdk_machine_init();
}
  • 根据寄存器修改相关值
static struct s3c2410fb_mach_info smdk2440_lcd_cfg __initdata = {
	.regs	= {

		.lcdcon1	= S3C2410_LCDCON1_TFT16BPP |
				  S3C2410_LCDCON1_TFT |
				  S3C2410_LCDCON1_CLKVAL(0x04),

		.lcdcon2	= S3C2410_LCDCON2_VBPD(1) |
				  S3C2410_LCDCON2_LINEVAL(271) |
				  S3C2410_LCDCON2_VFPD(1) |
				  S3C2410_LCDCON2_VSPW(9),

		.lcdcon3	= S3C2410_LCDCON3_HBPD(1) |
				  S3C2410_LCDCON3_HOZVAL(479) |
				  S3C2410_LCDCON3_HFPD(1),

		.lcdcon4	= S3C2410_LCDCON4_MVAL(0) |
				  S3C2410_LCDCON4_HSPW(40),

		.lcdcon5	= S3C2410_LCDCON5_FRM565 |
				  S3C2410_LCDCON5_INVVLINE |
				  S3C2410_LCDCON5_INVVFRAME |
				  S3C2410_LCDCON5_PWREN |
				  S3C2410_LCDCON5_HWSWP,
	},

	/* currently setup by downloader */
	.gpccon		= 0xaaaa56aa,
	.gpccon_mask	= 0xffffffff,
	.gpcup		= 0xffffffff,
	.gpcup_mask	= 0xffffffff,
	.gpdcon		= 0xaaaaaaaa,
	.gpdcon_mask	= 0xffffffff,
	.gpdup		= 0xffffffff,
	.gpdup_mask	= 0xffffffff,

	.fixed_syncs = 1,
	.type		= S3C2410_LCDCON1_TFT16BPP,

	.width		= 480,
	.height		= 272,

	.xres		= {
		.min	= 480,
		.max	= 480,
		.defval	= 480,
	},

	.yres		= {
		.min	= 272,
		.max	= 272,
		.defval = 272,
	},

	.bpp		= {
		.min	= 16,
		.max	= 16,
		.defval = 16,
	},
};
  • 修改后代替虚拟机内核源码mach-smdk2440.c,make menuconfig修改回原样,直接编译进内核,不设置为模块
  • 编译完新内核加载到S3C2440出现乱码的情况,解决方法参考:内核启动过程,修改mach-smdk2440.c中将s3c24xx_init_clocks(16934400)改为s3c24xx_init_clocks(12000000),因为板子上的晶振是12Mhz,而mdk2440_map_io()里,初始化的时钟是基于16934400hz的晶振

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值