9.LCD驱动架构
1.小结
1.分配一个fb_info
2.设置
- 2.1固定的参数
- 2.2设置可变的参数
- 2.3设置具体的文件操作指针
fb_info->fbops
- 2.4其他设置
3.硬件相关操作
- 3.1配置
GPIO
用于LCD
- 3.2根据
LCD
手册设置LCD
控制器,如VCLK
的频率等 - 3.3分配显存
framebuffer
并把地址告诉LCD
控制器
4.注册
- 4.1分配显存
fb_info->screnn_base
,使用dma_alloc_writecombine()
注:函数返回值是虚拟地址,有参数*handler
返回实际实际物理地址,这个物理地址需要设置到LCD
控制器 - 4.2注册
fb_info
结构体,register_framebuffer
5.出口
- 5.1卸载内核中的
fb_info
- 5.2控制
LCDCON1
关闭PWREN
信号,关背光,iounmap
注销地址 - 5.3释放
DMA
缓冲地址dma_free_write()
- 5.4释放
fb_info
2.关键函数和数据结构
-
dma_alloc_writecombine
分配内存空间void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp) 解析: 返回值:申请到的DMA缓冲区虚拟地址,若为NULL则表示分配失败,此时需要使用dma_free_writecombine释放内存 *dev: 这里填0,表示这个申请的缓冲区里没有内容 size: 分配的地址大小(字节单位) *handle: 申请到的物理起始地址 gfp: 分配出来的内存参数,标志定义在<linux/gfp.h>常用如下: GFP_ATOMIC:用来从中断处理和进程上下文之外的其他代码中分配内存,从不睡眠 GFP_KERNEL:内核内存的正常分配,可能会睡眠 GFP_USER: 用来为用户空间也分配,可能会睡眠
framebuffer_alloc
—创建一个新的帧缓冲区信息结构
struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
fb_info
struct fb_info {
atomic_t count;
int node;
int flags;
struct mutex lock; /* Lock for open/release/ioctl funcs */
struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */
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;
/* we need the PCI or similar aperture base/size not
smem_start/size as smem_start may just be an object
allocated inside the aperture so may not actually overlap */
struct apertures_struct {
unsigned int count;
struct aperture {
resource_size_t base;
resource_size_t size;
} ranges[0];
} *apertures;
};
-
register_framebuffer
注册一个帧缓冲设备int register_framebuffer(struct fb_info *fb_info)
3.源码解析
lcd.c
#define LCD_xres 480
#define LCD_yres 270
static u32 pseudo_palette[16];
static struct fb_info *mylcd_info;
static volatile unsigned long *GPBcon;
static volatile unsigned long *GPCcon;
static volatile unsigned long *GPDcon;
static volatile unsigned long *GPGcon; //GPG4:控制LCD信号
static volatile unsigned long *GPBdat; //GPB0: 控制背光
/* lcd control */
static struct lcd_reg {
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 tconsel;
} *lcd_reg;
/* 把单色提出来,放到rgb中对应的位置 */
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 mylcd_fb_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; //调色板数组不能大于15
val = chan_to_field(red, &info->var.red);
val |= chan_to_field(green, &info->var.green);
val |= chan_to_field(blue, &info->var.blue);
pseudo_palette[regno] = val;
return 0;
}
static struct fb_ops mylcd_fb_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = mylcd_fb_setcolreg,
.fb_fillrect = cfb_fillrect, //填充矩形
.fb_copyarea = cfb_copyarea, //复制数据
.fb_imageblit = cfb_imageblit, //绘画图像
};
static int lcd_init(void)
{
printk(KERN_DEBUG"%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 1.申请fb_info结构体 */
mylcd_info = framebuffer_alloc(0, 0);
/* 2.设置 */
/* 2.1设置固定参数 */
strcpy(mylcd_info->fix.id, "mylcd"); //名字
mylcd_info->fix.smem_len = LCD_xres * LCD_yres * 2; //长度(像素为16bit[2字节],所以乘以2)
mylcd_info->fix.type = FB_TYPE_PACKED_PIXELS; //支持的类型
mylcd_info->fix.visual = FB_VISUAL_TRUECOLOR; //真彩色
mylcd_info->fix.line_length = LCD_xres * 2; //同上
printk(KERN_DEBUG"%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 2.2设置可变参数 */
mylcd_info->var.xres = 480; //可见屏x分辨率
mylcd_info->var.yres = 270; //可见屏y分辨率
mylcd_info->var.xres_virtual = 480; //虚拟屏x分辨率
mylcd_info->var.yres_virtual = 270; //虚拟屏y分辨率
mylcd_info->var.xoffset = 0; //虚拟屏到可见屏之间x偏移
mylcd_info->var.yoffset = 0; //y偏移
mylcd_info->var.bits_per_pixel = 16; //像素
mylcd_info->var.grayscale = 0; //灰度(透明度)
printk(KERN_DEBUG"%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* rgb:565 */
mylcd_info->var.red.offset = 11; //偏移位置
mylcd_info->var.red.length = 5; //长度
mylcd_info->var.green.offset = 5;
mylcd_info->var.green.length = 6;
mylcd_info->var.blue.offset = 0;
mylcd_info->var.blue.length = 5;
/* 2.3设置操作函数 */
mylcd_info->fbops = &mylcd_fb_ops;
printk(KERN_DEBUG"%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 2.4设置其他 */
mylcd_info->pseudo_palette = pseudo_palette; //保存调色板数组
mylcd_info->screen_size = LCD_xres * LCD_yres * 2; //虚拟地址长
/* 3.硬件操作 */
/* 3.1设置gpio为lcd */
GPBcon = ioremap(0x56000010, 8);
GPBdat = GPBcon + 1;
GPCcon = ioremap(0x56000020, 4);
GPDcon = ioremap(0x56000030, 4);
GPGcon = ioremap(0x56000060, 4);
*GPBcon &= ~(0X3 << 0);
*GPBcon |= (0X1 << 0); //输出
*GPBdat &= ~(0X1 << 0); //关背光
*GPCcon = 0Xaaaaaaaa;
*GPDcon = 0Xaaaaaaaa;
*GPGcon |= (0x3 << (4 * 2)); //lcd_power
/* 3.2设置lcd控制器 */
lcd_reg->lcdcon1 = (4<<8) | (3<<5) | (0xc<<1);
lcd_reg->lcdcon2 = (3<<24) | (271<<14) | (1<<6) |(0<<0);
lcd_reg->lcdcon3 = ((16)<<19) | (479<<8) | ((10));
lcd_reg->lcdcon4 = (4);
lcd_reg->lcdcon5 = (1<<11) | (1<<9) | (1<<8)|(1<<0);
lcd_reg->lcdcon1 &= ~(1<<0); //关闭PWREN信号输出
lcd_reg->lcdcon5 &= ~(1<<3); //禁止PWREN信号
/* 3.3分配显存 */
mylcd_info->screen_base = dma_alloc_writecombine(0, mylcd_info->fix.smem_len, mylcd_info->fix.smem_start, GFP_KERNEL); //显存虚拟地址
lcd_reg->lcdsaddr1 = (mylcd_info->fix.smem_start >> 1) & (0x3fffffff);
lcd_reg->lcdsaddr2 = ((mylcd_info->fix.smem_start + mylcd_info->screen_size) >> 1) & 0x1fffff;
lcd_reg->lcdsaddr3 = LCD_xres & 0x3ff;
/* 4.1开启lcd */
lcd_reg->lcdcon1 |= (1 << 0); //输出power信号
lcd_reg->lcdcon2 |= (1 << 3); //允许power信号
*GPBdat |= (1 << 0); //开背光
/* 4.2注册fb_info */
register_framebuffer(mylcd_info);
return 0;
}
static int lcd_exit(void)
{
printk(KERN_DEBUG"%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 1.卸载fb_info */
unregister_framebuffer(mylcd_info);
/* 2.控制lcd关闭,释放io */
lcd_reg->lcdcon1 &= ~(1 << 0);
lcd_reg->lcdcon5 &= ~(1 << 3);
*GPBdat &= ~(1 << 0);
iounmap(GPBcon);
iounmap(GPCcon);
iounmap(GPDcon);
iounmap(GPGcon);
/* 3.释放显存 */
dma_free_writecombine(0, mylcd_info->screen_size, mylcd_info->screen_base, mylcd_info->fix.smem_start);
/* 4.释放fb_info */
framebuffer_release(mylcd_info);
return 0;
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
Makefile
KERN_DIR = /home/book/linux-2.6.22.6
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += lcd.o