目录
一、LCD驱动框架
1.1LCD驱动概述
假设
app: open("/dev/fb0", ...) 主设备号: 29, 次设备号: 0
--------------------------------------------------------------
kernel:
fb_open
int fbidx = iminor(inode);
struct fb_info *info = = registered_fb[0];
app: read()
---------------------------------------------------------------
kernel:
fb_read
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos);
src = (u32 __iomem *) (info->screen_base + p);
dst = buffer;
*dst++ = fb_readl(src++);
copy_to_user(buf, buffer, c)
1.2Fbmem.c分析
"入口函数"——fbmem_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;
}
“fbmem.c”是通用的文件,故并不能直接使用这个 file_operations 结构中的.read 等函数,其中创建类并没有在设备类下创建设备,只有在真正有硬件的时候才在这个类创建设备。“register_framebuffer()”中可以看到创建设备。
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), "fb%d", i);
fb_open:
APP:open():想知道内存上有什么内容,看file_operations fb_fops结构中
“.read = fb_open”
static int
fb_open(struct inode *inode, struct file *file)
{
int fbidx = iminor(inode); //次设备号
struct fb_info *info;
int res = 0;
.........
if (!(info = registered_fb[fbidx])) //从registered_fb数组中赋值给info
return -ENODEV;
......
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1);
if (res)
module_put(info->fbops->owner);
}
return res;
}
fb_read:
APP:read():想知道内存上有什么内容,看file_operations fb_fops结构中
“.read = fb_read”
static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
......
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos);
/*没有read函数*/
total_size = info->screen_size; //屏幕大小
.....
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL); //分配缓冲区
if (!buffer)
return -ENOMEM;
src = (u32 __iomem *) (info->screen_base + p); /*screen_base 是指
显存的基地址。这里是读源 src 等于显存的*/
while (count) {
.....
dst = buffer;
for (i = c >> 2; i--; )
*dst++ = fb_readl(src++);/*读源(从显存基地址+P 偏移)那里读到一
个数据放到目标“*dst++”里。dst 是 buffer,buffer 是 kmalloc()上面分
配的空间。*/
..........
if (copy_to_user(buf, buffer, c)) { //把数据拷贝到用户空间
err = -EFAULT;
break;
}
......
}
kfree(buffer);
return (err) ? err : cnt;
}
从上面知道“.open”和“.read”都依赖一个结构体 fb_info 结构体。这个结构体是从数组“registered_fd[]”经“次设备号”为下标得到一项为 fd_info结构体。
struct
fb_info
*info = registered_fb[fbidx];
问:registered_fb在哪里被设置?
register_framebuffer
分析register_framebuffer:
int
register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode;
............
for (i = 0 ; i < FB_MAX; i++) //FB_MAX:32
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);/*在fb_class类下
创建设备*/
..........
}
1.3分析底层硬件驱动程序
path:/drivers/video/s3c2440.c为例(从入口函数看)
int __devinit s3c2410fb_init(void)
{
//注册一个平台驱动(总线设备驱动模型)
return platform_driver_register(&s3c2410fb_driver);
}
s3c2410fb_probe:
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;//根据pdev获得"mach_info"信息
mregs = &mach_info->regs;
irq = platform_get_irq(pdev, 0);//根据pdev获得"irq "中断信息
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info),
&pdev->dev);
if (!fbinfo) {
return -ENOMEM;
}
/*硬件相关设置*/
info = fbinfo->par;
info->fb = fbinfo;
info->dev = &pdev->dev;
......
/* Stop the video and unset ENVID if set */
info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
lcdcon1 = readl(S3C2410_LCDCON1);
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);
/*硬件相关的操作:*/
s3c2410_gpio_setpin(S3C2410_GPB0, 0); // back light control
info->mach_info = pdev->dev.platform_data;
fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
fbinfo->fix.type_aux = 0;
fbinfo->fix.xpanstep = 0;
fbinfo->fix.ypanstep = 0;
fbinfo->fix.ywrapstep = 0;
fbinfo->fix.accel = FB_ACCEL_NONE;
fbinfo->var.nonstd = 0;
fbinfo->var.activate = FB_ACTIVATE_NOW;
fbinfo->var.height = mach_info->height;
fbinfo->var.width = mach_info->width;
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;
fbinfo->var.xres = mach_info->xres.defval;
fbinfo->var.xres_virtual = mach_info->xres.defval;
fbinfo->var.yres = mach_info->yres.defval;
fbinfo->var.yres_virtual = mach_info->yres.defval;
fbinfo->var.bits_per_pixel = mach_info->bpp.defval;
fbinfo->var.upper_margin = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) + 1;
fbinfo->var.lower_margin = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) + 1;
fbinfo->var.vsync_len = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;
fbinfo->var.left_margin = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
fbinfo->var.right_margin = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
fbinfo->var.hsync_len = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;
fbinfo->var.red.offset = 11;
fbinfo->var.green.offset = 5;
fbinfo->var.blue.offset = 0;
fbinfo->var.transp.offset = 0;
fbinfo->var.red.length = 5;
fbinfo->var.green.length = 6;
fbinfo->var.blue.length = 5;
fbinfo->var.transp.length = 0;
fbinfo->fix.smem_len = mach_info->xres.max *
mach_info->yres.max *
mach_info->bpp.max / 8;
........
/* Initialize video memory */
ret = s3c2410fb_map_video_memory(info);
if (ret) {
printk( KERN_ERR "Failed to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto release_clock;
}
.........
return 0;
.........
release_clock:
clk_disable(info->clk);
clk_put(info->clk);
}
要得到LCD的分辨率信息:从上往下看(fbmem.c)
fbmem.c看".ioctl"
fbmem_init->fb_ops->ioctl(fb_ioctl)
static int
fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
......
case FBIOGET_VSCREENINFO:/*GET 获得.V(var)可变的.SCREEN 屏幕.INFO 信
息*/
return copy_to_user(argp, &info->var,
sizeof(var)) ? -EFAULT : 0;
......
}
info->var中存在屏幕的信息;
二、LCD硬件参数设置
正式开始写驱动之前,先了解硬件的具体操作,如下;
设置LCD控制器时,不同的LCD的"VCLK"不一样,要调整LCD控制器来设置频率的快和慢,硬件操作过程:
1,根据 LCD 手册设置 LCD 控制器。(时钟参数等)
2,分配显存,并将显存的地址告诉 LCD 控制器。 还要说明颜色格式。一个像素用多少个字节来表示等。
3, 2440 相关的操作:配置引脚用于 LCD,芯片的管脚可以配置成输入输出,还可以配置为 LCD 引脚。
static struct fb_info *s3c_led;
static void lcd_setting(void)
{
/* 2.1 设置固定的参数 */
strcpy(s3c_led->fix.id,"mylcd"); //名字
s3c_led->fix.smem_len = 240*320*16/8; //显存的长度
s3c_led->fix.typ = FB_TYPE_PACKED_PIXELS;
s3c_led->fix.visual = FB_VISUAL_TRUECOLOR; /*TFT*/
s3c_led->fix.line_length = 240*16/8; //固定信息里面,一行的长度
/* 2.2 设置可变的参数 */
s3c_led->var.xres = 240; //x方向的分辨率240
s3c_led->var.yres = 320; //y方向的分辨率240
s3c_led->var.xres_virtual = 240; //x方向的虚拟分辨率240
s3c_led->var.yres_virtual = 320; //y方向的虚拟分辨率240
s3c_led->bits_per_pixel = 16; //BPP
/* RGB:565 */
s3c_lcd->var.red.offset = 11; //RGB---565:红色的偏移值从bit11开始
s3c_lcd->var.red.length = 5; //RGB---565,红色占5位
s3c_lcd->var.green.offset = 5; //RGB---565:红色的偏移值从bit5开始
s3c_lcd->var.green.length = 6; //RGB---565,红色占6位
s3c_lcd->var.blue.offset = 0; //RGB---565:红色的偏移值从bit0开始
s3c_lcd->var.blue.length = 5; //RGB---565,红色占5位
s3c_lcd->var.activate = FB_ACTIVATE_NOW; //默认值
/* 2.3 设置操作函数 */
s3c_lcd->fbops = &s3c_lcdfb_ops;
/* 2.4 其他的设置 */
//s3c_lcd->pseudo_palette =; //
//s3c_lcd->screen_base = ; /* 显存的虚拟地址 */
s3c_lcd->screen_size = 240*324*16/8; //显存的大小
}
三、硬件相关操作
3.1硬件引脚
屏幕使用的接口如下:
上面的原理图可知LCD使用的寄存器:GPCx GPGx GPDx,配置引脚使用
背光电路:
“KEYBOARD”引脚:KEYBOARD --- J6 TOUT0/GPB0 (GPB0 要配置成输出引脚:GPBCON |=
GPB0_out;)
3.2配置硬件引脚
1.定义映射引脚
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;
2.映射物理地址:ioremap()
void * __ioremap(unsigned long phys_addr, unsigned long size)
ioremap()将一个 IO 地址空间映射到内核的虚拟地址空间上去,便于访问;
gpbcon = ioremap(0x56000010, 8);
gpbdat = gpbcon+1;
gpccon = ioremap(0x56000020, 4);
gpdcon = ioremap(0x56000030, 4);
gpgcon = ioremap(0x56000060, 4);
3.配置引脚
*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 */
a.配置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; // 0X4D000028 STN:蓝色查找表
unsigned long reserved[9]; //保留字节
unsigned long dithmode; // 0X4D00004C STN:抖动模式
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));
b.分配显存
从内存里分配显存(framebuffer),并把地址告诉 LCD 控制器.设置好了 LCD 控制器后,就会自动从显存里取一个像素的值通过 VD0~VD23 发送出像素数据到 LCD 屏上去。然后再取下一个,周而复始,取到显存中最后一个时(显存就那么大)就再到显存开始处取。显存的地址需要连续,不能使用kmolloc()分配显存->dma_alloc_writecombine()
static inline
void dma_alloc_writecombine(struct device *dev, size_t size,dma_addr_t *dma_addr, gfp_t gfp)
{
return dma_alloc_attrs(dev, size, dma_addr, gfp,
DMA_ATTR_WRITE_COMBINE);
}
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);//lcdsaddr1显存地址
lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start +
s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;lcdsaddr2帧缓冲器地址
lcd_regs->lcdsaddr3 = (240*16/16);
/* 启动LCD */
lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
*gpbdat |= 1; /* 输出高电平, 使能背光 */
3.3调色板
调色板是一块内存,LCD 控制器从显存里得到的 8bit 数据,此时并不是直接发给 LCD 屏。而是发给调色板,调色板是块内存,里面存放了真正的 16bit 数据。
LCD 控制器从显存得到 8bit 数据后,以它作为 索引,假如一个 8 位是 100,这时就从“调色板”(像个数组)中找到 100 项,把这个第 100 项处的 16bit 取出来。发送给 LCD 屏。
调色板就像一个里面放好颜色的颜料盒,要取哪种颜色就通过 索引来检索出来哪个颜色在调色板这个颜色盒的哪里,找到后拿出来。这里调色板是块内存,事先配置好几种颜色,LCD 控制器要用时,从显存中读到一个 8 bit 数据,以此 8bit 数据作为一个“索引”值,从“调色板”这个数组里取出一个真正的颜色,再把这个真正的颜色发送给 LCD 硬件。
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 inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16 - bf->length;
return chan << bf->offset;
}
五、参考链接
https://gitee.com/change-ly/linux_project