Linux驱动-LCD驱动
扫描二维码
随时随地手机看文章
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 devsn", FB_MAJOR);
fb_class = class_create(THIS_MODULE, "graphics");
if (IS_ERR(fb_class)) {
printk(KERN_WARNING "Unable to create fb class; errno = %ldn", PTR_ERR(fb_class));
fb_class = NULL;
}
return 0;
}
② file_operations fb_fbmem_init里面注册字符设备fb,其主设备号为29.并且创建了类class_create,注意在此没有创建设备节点
fops
:
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.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
};
Ⅰ、 我们假设 app: open(“/dev/fb0”, …) 主设备号: 29, 次设备号: 0 //应用程序打开 /dev/fb0,主设备号29 次设备号0;那么就会调用内核中的 fb_open:
fb_open(struct inode *inode, struct file *file)
{
int fbidx = iminor(inode); // 得到次设备号 0
struct fb_info *info = registered_fb[fbidx(0)]; //fb_info 这个结构体等于registered_fb数组里面的次设备号检索出来。
}
Ⅱ、 我们假设 app: read() //应用程序read的时候;那么就会调用内核中的 fb_read:
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
int fbidx = iminor(inode);//得到次设备号0
struct fb_info *info = registered_fb[fbidx];//在registered_fb数组里得到一个fb_info 结构体
}
由I、Ⅱ可知open read都依赖fb_info结构体,从registered_fb数组中得到fb_info结构体。
也就是说内核中主设备号为29的设备可能有很多,open的时候根据次设备号从registered_fb数组得到一个fb_info。
registered_fb数组在register_framebuffer被设置
register_framebuffer(struct fb_info*fb_info)
{
fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), "fb%d", i); //创建设备节点
registered_fb[i]= fb_info;
}
小结:到此已经可以很清晰看出LCD的框架。
首先内核帮助我们实现了一个主设备号为29的设备,此时只创建了类,并没有在类下创建设备节点。
当我们的下层硬件驱动调用注册函数的时候,会初始化registered_fb结构体,并创建设备节点。
此时应用程序可以来打开一个设备节点了,比如open(“/dev/fb0”, …),最终会调用到fbmem核心层提供的open函数,这个open函数中根据次设备号,registered_fb数组中取出硬件注册进来的结构体,调用里面的open函数,或者使用一些属性。这样内核可以方便管理类似的设备了。
来列举一下写底层框架函数将用到的结构体: fb_info 结构体:上层fbmem.c内核已经写好,并完成上层驱动注册。我们要做的是写出硬件部分的函数,来初始化registered_fb:
1. 分配一个fb_info结构体: 怎么分配:framebuffer_alloc
2. 设置fb_info里面的相关参数
3. 注册:register_framebuffer
4. 硬件相关的设置
struct fb_info {
int node;/* 序号索引值,/dev/fb0,/dev/fb1 其中0,1 就是从这里获得的*/
int flags;
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;/* fb_ops,各种帧缓冲操作函数,很重要 */
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 */ /* 16位假的调色板,重要 */
#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; /* 这个用来存放私有数据 */
};
Ⅰ、 fb_var_screeninfo 结构体:将用到下面6个内容:
struct fb_var_screeninfo var; /* Current var 可变参数,很重要 */
struct fb_fix_screeninfo fix; /* Current fix 固定参数,很重要 */
struct fb_ops fbops;/ fb_ops,各种帧缓冲操作函数,很重要 */
char __iomem screen_base; / Virtual address “显存“的基地址 */
unsigned long screen_size; /* Amount of ioremapped VRAM or 0 ”显存“的大小,重要 */
void pseudo_palette; / Fake palette of 16 colors / / 16位假的调色板,重要 */
struct fb_var_screeninfo {
__u32 xres; /* visible resolution */
__u32 yres;
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible */
__u32 yoffset; /* resolution */
__u32 bits_per_pixel; /* guess what */
__u32 grayscale; /* != 0 Graylevels instead of colors */
struct fb_bitfield red; /* bitfield in fb mem if true color, */
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 */
__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 */
};
Ⅱ、fb_fix_screeninfo fix结构体:
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */
unsigned long smem_start; /* Start of frame buffer mem */
/* (physical address) */
__u32 smem_len; /* Length of frame buffer mem */
__u32 type; /* see FB_TYPE_* */
__u32 type_aux; /* Interleave for interleaved Planes */
__u32 visual; /* see FB_VISUAL_* */
__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 */
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 */
};
Ⅲ、fb_ops 结构体:
struct fb_ops {
struct module *owner;
/* 打开/释放 */
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
/* 对于非线性布局的/常规内存映射无法工作的帧缓冲设备需要 */
ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
size_t count, loff_t *ppos);
/* 检测可变参数,并调整到支持的值*/
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
/* 根据 info->var 设置 video 模式 */
int (*fb_set_par)(struct fb_info *info);
/* 设置 color 寄存器 */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
/* 批量设置 color 寄存器,设置颜色表 */
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
/*显示空白 */
int (*fb_blank)(int blank, struct fb_info *info);
/* pan 显示 */
int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
/* 矩形填充 */
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
/* 数据复制 */
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
/* 图形填充 */
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
/* 绘制光标 */
int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
/* 旋转显示 */
void (*fb_rotate)(struct fb_info *info, int angle);
/* 等待 blit 空闲 (可选) */
int (*fb_sync)(struct fb_info *info);
/* fb 特定的 ioctl (可选) */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg);
/* 处理 32 位的 compat ioctl (可选) */
int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
unsigned long arg);
/* fb 特定的 mmap */
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
/* 保存目前的硬件状态 */
void (*fb_save_state)(struct fb_info *info);
/* 恢复被保存的硬件状态 */
void (*fb_restore_state)(struct fb_info *info);
/* get capability given var */
void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
struct fb_var_screeninfo *var);
};
二、代码:
底层驱动lcd.c:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// 设置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];
unsigned long dithmode;
unsigned long tpal;
unsigned long lcdintpnd;
unsigned long lcdsrcpnd;
unsigned long lcdintmsk;
unsigned long lpcsel;
};
//设置fb_ops 结构体
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 struct fb_ops s3c_lcdfb_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = s3c_lcdfb_setcolreg, // 设置 color 寄存器
.fb_fillrect = cfb_fillrect, // 矩形填充
.fb_copyarea = cfb_copyarea, // 数据复制
.fb_imageblit = cfb_imageblit, // 图形填充
};
/* 定义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;
//定义fb_info结构体
static struct fb_info *s3c_lcd;
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 设置固定参数fix */
strcpy(s3c_lcd->fix.id,"czg_led");
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 设置可变参数var */
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 设置帧缓冲操作函数fobps */
s3c_lcd->fbops = &s3c_lcdfb_ops;
/* 2.4 设置“显存“的基地址 */
//s3c_lcd->screen_base; /* 显存的虚拟地址 */ //在3.3设置
/* 2.5 设置”显存“的大小 */
s3c_lcd->screen_size = 480*272*16/8; /* 显存的大小 */
/* 2.6 设置6位伪调色板 */
s3c_lcd->pseudo_palette = pseudo_palette;
/* 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<<0);// KEYBOARD output pin
*GPBCON |= 1;
*GPBDAT &= ~(1<<0); // KEYBOARD off
*GPGCON |= (3<<(2*4));
/* 3.2 根据LCD手册设置LCD控制器,比如VCLK的频率等等 */
lcd_regs = ioremap(0X4D000000,sizeof(struct lcd_regs));
/* bit[17:8] : TFT: VCLK = HCLK / [(CLKVAL+1) x 2] ( CLKVAL >= 0 ) */
/* HCLK通过dmesg命令查看为100MHZ*/
/* VCLK在LCD芯片手册P11页中取典型值9MHZ */
/* CLKVAL = 100/9/2 -1 = 4.5 = 4 */
/* bit[6:5] = 0b11 TFT LCD panel */
/* 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] : LCD芯片手册P11页和2440数据手册时序图对比 */
/* VBPD = tvb - 1 = 2-1 = 1 */
/* bit[23:14] : LINEVAL = tvd - 1 = 272-1 = 271 */
/* bit[13:6] : VFPD = tvf - 1 = 2-1 = 1 */
/* bit[5:0] : VSPW = tvp - 1 = 10-1 = 9*/
lcd_regs->lcdcon2 = (1<<24) | (271<<14) | (1<<6)| (9<<0);
/* 水平方向时间参数 */
/* bit[[25:19] : LCD芯片手册P11页和2440数据手册时序图对比 */
/* HBPD (TFT) = thb - 1 = 2-1 = 1 */
/* bit[18:8] : HOZVAL = thd - 1 = 480-1 = 479 */
/* bit[7:0] : HFPD (TFT) = thf - 1 = 2-1 = 1 */
lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1<<0);
/* 水平方向的同步信号 */
/* bit[7:0] : HSPW(TFT) = thp - 1 = 41-1 = 40 */
lcd_regs->lcdcon4 = (40<<0);
/* bit[11] : FRM565 = 1 5:6:5 Format */
/* bit[10] : INVVCLK = 0 The video data is fetched at VCLK falling edge */
/* bit[9] : INVVLINE = 1 Inverted VLINE/HSYNC 低电平有效 */
/* bit[8] : INVVFRAME = 1 Inverted VFRAME/VSYNC 低电平有效 */
/* bit[7] : INVVD = 0 Normal VD (video data) */
/* bit[6] : INVVDEN = 0 normal VDEN数据信号 高电平有效不需要反转 */
/* bit[5] : INVPWREN = 0 normal PWREN 电源信号 高电平有效不需要反转 */
/* bit[3] : LCD_PWREN output = 0 Disable PWREN signal 设置完再使能 */
/* bit[1:0] : BSWP-HWSWP:0b01 = 0x01,数据手册P413 像素排列P1-P2-P3-P4 */
lcd_regs->lcdcon5 = (1<<11) | (1<<9) | (1<<8) | (1<<0);
/* 3.3 分配显存(framebuffer),并把地址告诉L CD控制器 */
//s3c_lcd->fix.smem_start /* 显存的物理地址 */
s3c_lcd->screen_base = dma_alloc_writecombine(NULL,s3c_lcd->fix.smem_len,(u32 *)&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);
/* 3.4 启动LCD */
*GPBDAT |= (1<<0); /* 输出高电平,使能背光 */
lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身电源 */
/* 4.注册 */
register_framebuffer(s3c_lcd);
return 0;
}
static void lcd_exit(void)
{
iounmap(lcd_regs);
iounmap(GPBCON);
iounmap(GPCCON);
iounmap(GPDCON);
iounmap(GPGCON);
*GPBDAT &= ~(1<<0); /* 输出低电平,关闭背光 */
lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD控制器 */
lcd_regs->lcdcon5 &= ~(1<<3); /* 关闭LCD本身电源 */
framebuffer_release(s3c_lcd); // 释放fb_info
dma_free_writecombine(NULL,s3c_lcd->fix.smem_len,s3c_lcd->screen_base,s3c_lcd->fix.smem_start); // 释放显存
unregister_framebuffer(s3c_lcd); // 卸载
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
Makefile:
KERN_DIR = /work/system/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
三、编译测试:
① 需要重新编译内核, make menuconfig
去掉原来的驱动程序
-> Device Drivers
-> Graphics support
S3C2410 LCD framebuffer support
② make uImage
使用新的内核启动开发板
cp arch/arm/boot/uImage /work/nfs_root/czg/uImage_nolcd
nfs 30000000 192.168.2.3:/work/nfs_root/czg/uImage_nolcd
bootm 30000000
make modules
cp drivers/video/cfb*.ko /work/nfs_root/czg
insmod cfbcopyarea.ko
insmod cfbfillrect.ko
insmod cfbimgblt.ko
insmod lcd.ko
④ 测试:
第一种方式:
ls /dev/fb*
echo hello_czg > /dev/tty1 //显示屏上显示
cat lcd.ko > /dev/fb0
第二种方式:
① echo tty1::askfirst:-/bin/sh >> /etc/inittab
② 用新内核重启开发板-重复上面②
③ insmod cfbcopyarea.ko
insmod cfbfillrect.ko
insmod cfbimgblt.ko
insmod lcd.ko
insmod buttons_drv.ko
④ 通过按键在LCD显示屏上看到ls指令
⑤ 查看进程buttons_drv.ko模块点我跳转
ps
ls /proc/*/fd -l