tq2440LCD驱动学习记录1

最近看了lcd驱动,,,不是很懂,,但是也记点留着以后看,,不然忘的快。。

   lcd驱动主要是注册一个 register_framebuffer,,程序员主要任务是根据lcd手册,,,填充一个fb_info结构体,,然后系统自带其他的操作函数等的部分。。。

struct fb_info {
	int node;
	int flags;
	struct mutex lock;		/* Lock for open/release/ioctl funcs */
	struct fb_var_screeninfo var;	/* Current var *///可变参数
	struct fb_fix_screeninfo fix;	/* Current 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 */

#ifdef CONFIG_FB_BACKLIGHT
	/* assigned backlight device */
	/* set before framebuffer registration, 
	   remove after unregister */
	struct backlight_device *bl_dev;

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

	struct fb_ops *fbops;
	struct device *device;		/* This is the parent */
	struct device *dev;		/* This is this fb device */
	int class_flag;                    /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
	struct fb_tile_ops *tileops;    /* Tile Blitting */
#endif
	char __iomem *screen_base;	/* Virtual address */
	unsigned long screen_size;	/* Amount of ioremapped VRAM or 0 */ 
	void *pseudo_palette;		/* Fake palette of 16 colors */ 
#define FBINFO_STATE_RUNNING	0
#define FBINFO_STATE_SUSPENDED	1
	u32 state;			/* Hardware state i.e suspend */
	void *fbcon_par;                /* fbcon use-only private area */
	/* From here on everything is device dependent */
	void *par;	
};
程序员做的主要就是把这个结构体填充了,,,然后执行函数register_framebuffer(fbinfo);

register_framebuffer(fbinfo);

这个函数的功能是

registered_fb[i] = fb_info;

把程序员注册的fb_info结构体数据赋给registered_fb[i]数组,,后面这个数组会在注册帧缓冲设备时用到

在Fbmem.c中注册了帧缓冲设备,,

module_init(fbmem_init);

static int __init    
fbmem_init(void)
{
 proc_create("fb", 0, NULL, &fb_proc_fops);

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

在fbmem.c函数中实现了帧缓冲设备的真正注册,在上面的fbmem_init()函数中注册了字符设备fb

fb设备操作函数集

static const struct file_operations fb_fops = {
 .owner = THIS_MODULE,
 .read =  fb_read,
 .write = fb_write,
 .unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
 .compat_ioctl = fb_compat_ioctl,
#endif
 .mmap =  fb_mmap,
 .open =  fb_open,
 .release = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
 .get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
 .fsync = fb_deferred_io_fsync,
#endif
};

open函数,,打开设备时执行,主要完成了info = registered_fb[fbidx];这句程序得到了程序员填写的LCD信息,比如屏的大小,时序,时钟等

static int
fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
 int fbidx = iminor(inode);//得到次设备号
 struct fb_info *info;
 int res = 0;

 if (fbidx >= FB_MAX)
  return -ENODEV;
 info = registered_fb[fbidx];
 if (!info)
  request_module("fb%d", fbidx);
 info = registered_fb[fbidx];
 if (!info)
  return -ENODEV;
 mutex_lock(&info->lock);
 if (!try_module_get(info->fbops->owner)) {
  res = -ENODEV;
  goto out;
 }
 file->private_data = info;
 if (info->fbops->fb_open) {
  res = info->fbops->fb_open(info,1);
  if (res)
   module_put(info->fbops->owner);
 }
#ifdef CONFIG_FB_DEFERRED_IO
 if (info->fbdefio)
  fb_deferred_io_open(info, inode, file);
#endif
out:
 mutex_unlock(&info->lock);
 return res;
}


写函数,,显示的时候要把显示的数据写到帧缓冲内存中

static ssize_t
fb_write(struct file *file, const 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];//得到lcd信息
 u32 *buffer, *src; //定义两个指针,用来缓存数据
 u32 __iomem *dst;//目的数据缓存区
 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_write)
  return info->fbops->fb_write(info, buf, count, ppos);
 
 total_size = info->screen_size;

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

 if (p > total_size)
  return -EFBIG;

 if (count > total_size) {
  err = -EFBIG;
  count = total_size;
 }

 if (count + p > total_size) {
  if (!err)
   err = -ENOSPC;

  count = total_size - p;
 }

 buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
    GFP_KERNEL); ///在内核中分配一个buffer的内存
 if (!buffer)
  return -ENOMEM;

 dst = (u32 __iomem *) (info->screen_base + p);目的地址=LCD的显存(虚拟内存)+偏移

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

 while (count) {
  c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
  src = buffer;源地址

  if (copy_from_user(src, buf, c)) {//从用户空间拷贝数据到内核空间
   err = -EFAULT;
   break;
  }

  for (i = c >> 2; i--; )
   fb_writel(*src++, dst++);写数据到显存

  if (c & 3) {
   u8 *src8 = (u8 *) src;
   u8 __iomem *dst8 = (u8 __iomem *) dst;

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

   dst = (u32 __iomem *) dst8;
  }

  *ppos += c;
  buf += c;
  cnt += c;
  count -= c;
 }

 kfree(buffer);

 return (cnt) ? cnt : err;

}


以上是我刚开始学的lcd大体框架,,,程序员写好lcd参数,,设置到lcd寄存器,设置帧缓冲格式,,然后把lcd参数信息传到内核中,,,内核中已经有了关于lcd操作的其他的上层的函数,驱动程序员只管底层的参数设置和寄存器初始化就可以


上面的内容主要从韦东山2期视频中学来的,,下面是我看着视频做的LCD驱动,,开发板是tq2440,内核是2.6.30

#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/vmalloc.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
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 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;
/*用红绿蓝值组成颜色,写到调色板中*/
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;
return 0;
}
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 *gpgcon;//lcd_pwr--gpg4
static volatile unsigned long *gpgdat;
static volatile unsigned long *gpgup;

static volatile unsigned long *gpccon;
static volatile unsigned long *gpcup;
static volatile unsigned long *gpcdat;

static volatile unsigned long *gpdcon;
static volatile unsigned long *gpddat;
static volatile unsigned long *gpdup;
static volatile struct lcd_regs* lcd_regs;
static u32 pseudo_palette[16];


static int __init lcd_init(void)
{
/*1.分配一个fb_info结构体
*2.设置
3.硬件相关的设置
4.注册
*/
s3c_lcd=framebuffer_alloc(0, NULL);
/*2设置*/
/*1设置固定的参数
*2设置可变的参数
3设置操作函数
4其他的设置
*/
//s3c_lcd->fix.smem_start=xxxx
strcpy(s3c_lcd->fix.id,"my_lcd");
s3c_lcd->fix.smem_len=480*272*16/8;
s3c_lcd->fix.type= FB_TYPE_PACKED_PIXELS;
s3c_lcd->fix.visual=FB_VISUAL_TRUECOLOR;
s3c_lcd->fix.line_length=480*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;

/*操作函数*/
s3c_lcd->fbops = &s3c_lcdfb_ops;

/*其他的设置*/
s3c_lcd->pseudo_palette= pseudo_palette ;
//s3c_lcd->screen_base=; /*显存的虚拟地址*/
s3c_lcd->screen_size=480*272*2;
/*硬件相关的设置
*1设置IO口
2根据lcd手册控制lcd控制器,比如vclk频率等
3分配framebuffer,并告诉lcd控制器地址
*/
gpgcon=ioremap(0x56000060,8);
gpgdat=gpgcon+1;//设置是否上拉的那个寄存器暂时不设置
gpgup=gpgdat+1;
gpccon=ioremap(0x56000020,8);
gpcdat=gpccon+1;
gpdcon=ioremap(0x56000030,8);
gpddat=gpdcon+1;
*gpccon=0xaaaa02a9;
*gpdcon=0xaaaaaaaa;//io口设置为10代表特殊功能
*gpgup=*gpgup&(~(1<<4))|(1<<4); // Pull-up disable
*gpgcon=*gpgcon&(~(3<<8))|(3<<8); //GPG4=LCD_PWREN
*gpgdat = *gpgdat | (1<<4) ;

/* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
/* 
* TQ2440 4.3英寸LCD手册为WXCAT43-TG6#001_V1.0.pdf第22、23页
* 
* LCD手册和2440手册"Figure 15-6. TFT LCD Timing Example"一对比就知道参数含义了
*/

/* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P22 (Dclk=9MHz~15MHz)
* 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);
/* 垂直方向的时间参数
* bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据
* LCD手册 tvb=2
* VBPD=1
* bit[23:14]: 多少行, 272, 所以LINEVAL=272-1=271
* bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC
* LCD手册tvf=2, 所以VFPD=2-1=1
* bit[5:0] : VSPW, VSYNC信号的脉冲宽度, LCD手册tvp=10, 所以VSPW=10-1=9
*/
lcd_regs->lcdcon2 = (1<<24) | (271<<14) | (1<<6) | (9<<0);

/* 水平方向的时间参数
* bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据
* LCD手册 thb=2
* HBPD=1
* bit[18:8]: 多少列, 480, 所以HOZVAL=480-1=479
* bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
* LCD手册thf=2, 所以HFPD=2-1=1
*/
lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1<<0);
/* 水平方向的同步信号
* bit[7:0] : HSPW, HSYNC信号的脉冲宽度, LCD手册Thp=41, 所以HSPW=41-1=40
*/ 
lcd_regs->lcdcon4 = 40;
/* 信号的极性 
* bit[11]: 1=565 format, 对于24bpp这个不用设
* 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
*
* BSWP = 0, HWSWP = 0, BPP24BL = 0 : 当bpp=24时,2440会给每一个象素分配32位即4字节,哪一个字节是不使用的? 看2440手册P412
* bit[12]: 0, LSB valid, 即最高字节不使用
* bit[1] : 0 = BSWP 这两位规定了显示的数据的格式
* bit[0] : 0 = HWSWP
*/
lcd_regs->lcdcon5 = (1<<11) |(0<<10) | (1<<9) | (1<<8) | (0<<12) | (0<<1) | (1<<0);
/*3分配framebuffer,并告诉lcd控制器地址*/
printk("rrrrrrrr!!!!!!");
s3c_lcd->screen_base=dma_alloc_writecombine(NULL,s3c_lcd->fix.smem_len,&s3c_lcd->fix.smem_start,GFP_KERNEL);
if(s3c_lcd->screen_base==0)
{
printk("error!!!!!!");
return 0;
}
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);/*一行的长度,单位为半字*/
/*启动lcd*/
lcd_regs->lcdcon1 |= (1<<0); /*使能lcd控制器*/
lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身: LCD_PWREN */

/* 4. 注册 */
register_framebuffer(s3c_lcd);
return 0;
}
static int __exit lcd_exit(void)
{
unregister_framebuffer(s3c_lcd);
lcd_regs->lcdcon5 &= ~(1<<3); /*关背光 */
dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
iounmap(gpdcon);
iounmap(gpccon);
iounmap(gpgcon);
framebuffer_release(s3c_lcd);

return 0;
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值