和输入子系统一样LCD内核也有写好的框架,在fbmem.c中
fbmem.c中做了字符设备驱动都会做的事情
1.分配设置注册file_opration结构体
2.构建好了open,read,write,等函数
但是这些所以的函数都会调用到一个fb_info结构体中的数据,这个结构体中是LCD硬件相关的信息,需要其他文件来设置这个结构体,这样层次就出来了,fbmem.c中代码是稳定的通用的,也就是软件相关层,而不同的LCD通过不同的文件来设置fb_info结构体,这是硬件相关层
软件层和硬件层是怎样联系起来的呢??硬件层构造好fb_info结构体后会调用函数register_framebuffer()注册fb_info结构体,register_framebuffer函数会把fb_info结构体放到一个数组里,然后再以这个数组的下标为次设备号创建设备。软件层会在这个数组中根据打开设备文件的次设备号找到对应的fb_info结构体,这样联系就建立起来了
现在来写一个s3c_lcd.c
1.分配一个fb_info结构体
2.设置fb_info结构体
3.硬件相关操作
4.分配显存
5.注册
先来看看第一步,分配fb_info结构体的函数在fbmem.c中有写,调用它
s3c_lcd_info = framebuffer_alloc(0,NULL);
第二步,fb_info结构体中有很多项,不用都设置的,只设置其中一部分就行,需要设置的有
</pre><pre name="code" class="cpp">struct fb_info {
struct fb_var_screeninfo var; /* Current var */
struct fb_fix_screeninfo fix; /* Current fix */
struct fb_ops *fbops;
char __iomem *screen_base; /* Virtual address */
unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */
void *pseudo_palette; /* Fake palette of 16 colors */
};
其中,var和fix是液晶屏的参数,
screen_base是显存的虚拟地址
screen_size是显存大小
fbops是关于对显存的一些操作函数,可以参考根据其他文件来写
pseudo_palette是假调色板,大概是需要兼容以前的东西,所以要有一个假调色板,这里的代码也可以参考其他文件来写
看一下操作函数
s3c_lcd_info->fbops = &s3c_lcd_fb_ops;
static struct fb_ops s3c_lcd_fb_ops={
.fb_setcolreg = s3c_lcdfb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
一般来说操作函数只需要设置这四个就行了,第一个是关于假调色板,后面三个看着名字眼熟,原来装载LCD驱动程序之前需要先装载三个模块的名字和这个差不多,猜想通过这里驱动会调用到那三个模块里的内容来操作显存。写到这里想到,每次装载LCD驱动程序都要先装载那三个模块,能不能把那三个模块融合到lcd.ko中呢?修改Makefile应该能做到吧,
第三步,显存因为要物理地址连续,所以有专门的分配显存的函数,看以看看别人用什么函数分配显存的
s3c_lcd_info->screen_base = dma_alloc_writecombine(NULL, s3c_lcd_info->fix.smem_len, &s3c_lcd_info->fix.smem_start, GFP_KERNEL);
第四步,硬件相关的操作分为两部分
1.设置相关引脚用与LCD,这个简单,把相关的引脚iomap然后设只寄存器用于LCD就行了
2.设置LCD控制寄存器,LCD控制寄存器有很多,但也不需要全部设置,只设置下面几个寄存器就行了
lcdcon1,主要是关于LCD的一些参数的设置,要看LCD手册
lcdcon2,时间参数设置,要对照LCD手册和2440手册计算出需要设置的数值
lcdcon3,时间参数设置
lcdcon4,时间参数设置
lcdcon5,关于信号反转的设置,LCD手册上的信号和2440手册上的信号有可能是相反的,这时要设置信号反转
lcdsaddr1
lcdsaddr2
lcdsaddr3,这三个寄存器是关于显存的一些设置
第五步,注册fb_info结构体,把fb_info结构体告诉fbmen.c,这样就能用起来了
需要注意的地方,
LCD的横纵坐标,一开始我把x分辨率写成320,把y分辨率写成240,结果刚好写反了
一开始把lcdcon1的某一位该设成16bpp的设成24bpp了,结果屏幕分成四个同样的小块,找了好半天才找出毛病
测试
echo hello > /dev/tty1
#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);
static volatile unsigned long *gpbcon;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile unsigned long *gpbdat;
static volatile struct lcd_regs * lcd_con;
static u32 pseudo_palette[16];
static struct fb_info * s3c_lcd_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_lcd_fb_ops={
.owner = THIS_MODULE,
.fb_setcolreg = s3c_lcdfb_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;
}
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_info = framebuffer_alloc(0,NULL);
/*2.设置*/
strcpy(s3c_lcd_info->fix.id,"lcd");
s3c_lcd_info->fix.smem_len = 240*320*16/8;
s3c_lcd_info->fix.type = FB_TYPE_PACKED_PIXELS;
s3c_lcd_info->fix.visual = FB_VISUAL_TRUECOLOR;
s3c_lcd_info->fix.line_length = 240*16/8;
s3c_lcd_info->var.xres = 240;
s3c_lcd_info->var.yres = 320;
s3c_lcd_info->var.xres_virtual = 240;
s3c_lcd_info->var.yres_virtual = 320;
s3c_lcd_info->var.bits_per_pixel = 16;
s3c_lcd_info->var.red.length =5;
s3c_lcd_info->var.red.offset =11;
s3c_lcd_info->var.green.length =6;
s3c_lcd_info->var.green.offset =5;
s3c_lcd_info->var.blue.length =5;
s3c_lcd_info->var.blue.offset =0;
s3c_lcd_info->var.activate = FB_ACTIVATE_NOW;
s3c_lcd_info->fbops = &s3c_lcd_fb_ops;
s3c_lcd_info->pseudo_palette = pseudo_palette;
//s3c_lcd->screen_base = ; /* 显存的虚拟地址 */
s3c_lcd_info->screen_size = 240*324*16/8;
/*3.硬件相关操作*/
gpbcon = ioremap(0x56000010,8);
gpbdat = gpbcon + 1;
*gpbcon &= ~(3<<0);
*gpbcon |= (1<<0);
*gpbdat &= ~1; /* 输出低电平 */
gpccon = ioremap(0x56000020,4);
*gpccon = 0xaaaaaaaa;
gpdcon = ioremap(0x56000030,4);
*gpdcon = 0xaaaaaaaa;
gpgcon = ioremap(0x56000060,4);
*gpgcon |= (3<<8);
lcd_con = ioremap(0X4D000000,sizeof(struct lcd_regs));
/*
*[27:18] 0
*[17:8] 4
* VCLK = HCLK / [(CLKVAL+1) x 2]
* VCLK=10MHz HCLK=100MHz
* 10 = 100 / [(CLKVAL+1) x 2]
* CLKVAL=4
[6:5] 11
[4:1] 1100
[0] 0
*/
lcd_con->lcdcon1 = (4<<8)|(3<<5)|(12<<1);
/*
VBPD [31:24] 3
LINEVAL [23:14] 319
VFPD [13:6] 1
VSPW [5:0] 0
*/
lcd_con->lcdcon2 = (1<<6)|(319<<14)|(3<<24)| (0<<0);
/*
HBPD [25:19] 16
HOZVAL [18:8] 239
HFPD [7:0] 10
*/
lcd_con->lcdcon3 = (16<<19)|(239<<8)|(10<<0);
/*
HSPW [7:0] 4
*/
lcd_con->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_con->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
/*4.分配显存*/
s3c_lcd_info->screen_base = dma_alloc_writecombine(NULL, s3c_lcd_info->fix.smem_len, &s3c_lcd_info->fix.smem_start, GFP_KERNEL);
lcd_con->lcdsaddr1 = (s3c_lcd_info->fix.smem_start >> 1) & ~(3<<30);
lcd_con->lcdsaddr2 = ((s3c_lcd_info->fix.smem_start + s3c_lcd_info->fix.smem_len) >> 1) & 0x1fffff;
lcd_con->lcdsaddr3 = (240*16/16); /* 一行的长度(单位: 2字节) */
//s3c_lcd->fix.smem_start = xxx; /* 显存的物理地址 */
/* 启动LCD */
lcd_con->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
lcd_con->lcdcon5 |= (1<<3); /* 使能LCD本身 */
*gpbdat |= 1; /* 输出高电平, 使能背光 */
/*5.注册*/
register_framebuffer(s3c_lcd_info);
return 0;
}
static void lcd_exit(void)
{
unregister_framebuffer(s3c_lcd_info);
lcd_con->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
*gpbdat &= ~1; /* 关闭背光 */
dma_free_writecombine(NULL, s3c_lcd_info->fix.smem_len, s3c_lcd_info->screen_base, s3c_lcd_info->fix.smem_start);
iounmap(lcd_con);
iounmap(gpbcon);
iounmap(gpccon);
iounmap(gpdcon);
iounmap(gpgcon);
framebuffer_release(s3c_lcd_info);
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");