您的位置:首页 > 运维架构 > Linux

显存fb和LCD驱动程序

2017-05-17 14:51 211 查看


fbmem.c 处于Framebuffer设备驱动技术的中心位置.它为上层应用程序提供系统调用也为下一层的特定硬件驱动提供接口;那些底层硬件驱动需要用到这儿的接口来向系统内核注册它们自己. fbmem.c 为所有支持FrameBuffer的设备驱动提供了通用的接口,避免重复工作.

1) 全局变量

  struct fb_info *registered_fb[FB_MAX];

  int num_registered_fb;

  这两变量记录了所有fb_info 结构的实例,fb_info 结构描述显卡的当前状态,所有设备对应的fb_info 结构都保存在这个数组中,当一个FrameBuffer设备驱动向系统注册自己时,其对应的fb_info 结构就会添加到这个结构中,同时num_registered_fb 为自动加1.

  static struct {

  const char *name;

  int (*init)(void);

  int (*setup)(void);

  } fb_drivers[] __initdata= { ….};

  如果FrameBuffer设备被静态链接到内核,其对应的入口就会添加到这个表中;如果是动态加载的,即使用insmod/rmmod,就不需要关心这个表。

  static struct file_operations fb_ops ={

  owner: THIS_MODULE,

  read: fb_read,

  write: fb_write,

  ioctl: fb_ioctl,

  mmap: fb_mmap,

  open: fb_open,

  release: fb_release

  };

  这是一个提供给应用程序的接口.

  2)fbmem.c 实现了如下函数.

  register_framebuffer(struct fb_info *fb_info);

  unregister_framebuffer(struct fb_info *fb_info);

  这两个是提供给下层FrameBuffer设备驱动的接口,设备驱动通过这两函数向系统注册或注销自己。几乎底层设备驱动所要做的所有事情就是填充fb_info结构然后向系统注册或注销它。

  1)在系统内存中分配显存

  在fbmem.c文件中可以看到, file_operations 结构中的open()和release()操作不需底层支持,但read()、write()和 mmap()操作需要函数fb_get_fix()的支持.因此需要重新实现函数fb_get_fix()。另外还需要在系统内存中分配显存空间,大多数的LCD控制器都没有自己的显存空间,被分配的地址空间的起始地址与长度将会被填充到fb_fix_screeninfo 结构的smem_start 和smem_len 的两个变量中.被分配的空间必须是物理连续的。

  2)实现 fb_ops 中的函数

  用户应用程序通过ioctl()系统调用操作硬件,fb_ops 中的函数就用于支持这些操作。(注: fb_ops结构与file_operations 结构不同,fb_ops是底层操作的抽象,而file_operations是提供给上层系统调用的接口,可以直接调用.

主设备号,file_operations,register_chrdev已经写好。我们只要做硬件相关的驱动。

怎么写LCD程序?

1.分配一个fb_info结构体:framebuffer_alloc

2.设置

2.1 设置固定参数 struct fb_fix_screeninfo fb_fix;

2.2设置可变参数 struct fb_var_screeninfo fb_var;

2.3设置操作参数

s3c_lcd->fbops = &s3c_lcdfb_ops;

2.4其他设置

假的调色板 s3c_lcd->pseudo_palette = pseudo_palette

3.注册:register_framebuffer

4硬件相关操作

4.1 根据LCD手册,设置LCD控制器

4.2 分配显存,并把地址告诉LCD控制器

分配显存 dma_alloc_writecombina

把地址告诉LCD控制器 lcdsaddr1、lcdsaddr2、lcdsaddr3

4.3配置引脚用于LCD

1.分配一个fb_info结构体

s3c_lcd = framebuffer_alloc(0, NULL);


3.注册:register_framebuffer

register_framebuffer(s3c_lcd);


2.3设置操作参数

s3c_lcd->fbops = &s3c_lcdfb_ops;`

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 int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
unsigned int green, unsigned int blue,
unsigned int transp, struct fb_info *info)      /*regno指明是哪个碟子,参数为(碟子,红,绿,蓝,透明,info)*/
{
unsigned int val;
/*只有16个碟子,如果大于16,返回某个自带的错误*/
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;      //把 val值放到假的碟子里面去
return 0;
}


相关函数chan_to_field()

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


4.2分配显存

s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);

完整代码

#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); /*设置调色板*/

struct lcd_regs { /*设置一个结构体数组,定义LCD控制器信息 15.3.2 11分钟*/
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]; /*保留9个字节,每个寄存器之间相差4个字节,如果相差太大,之间要插入保留字*/
unsigned long dithmode;
unsigned long tpal;
unsigned long lcdintpnd;
unsigned long lcdsrcpnd;
unsigned long lcdintmsk;
unsigned long lpcsel;
};

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 *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;/*后面的lcd_regs是一个指向lcd_regs结构体指针变量*/
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) /*regno指明是哪个碟子,参数为(碟子,红,绿,蓝,透明,info)*/ { unsigned int val; /*只有16个碟子,如果大于16,返回某个自带的错误*/ 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; //把 val值放到假的碟子里面去 return 0; }static int lcd_init(void)
{
/* 1. 分配一个fb_info */
s3c_lcd = framebuffer_alloc(0, NULL);

/* 2. 设置 */
/* 2.1 设置固定的参数 */
strcpy(s3c_lcd->fix.id, "mylcd");
s3c_lcd->fix.smem_len = 240*320*16/8;
s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS;
s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; /* TFT */
s3c_lcd->fix.line_length = 240*2;

/* 2.2 设置可变的参数 */
s3c_lcd->var.xres = 240;
s3c_lcd->var.yres = 320;
s3c_lcd->var.xres_virtual = 240;
s3c_lcd->var.yres_virtual = 320;
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 设置操作函数 */
s3c_lcd->fbops = &s3c_lcdfb_ops;

/* 2.4 其他的设置 */
s3c_lcd->pseudo_palette = pseudo_palette; /* 假的调色板*/
//s3c_lcd->screen_base = ; /* 显存的虚拟地址 */
s3c_lcd->screen_size = 240*324*16/8;

/* 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); /* GPB0设置为输出引脚 */
*gpbcon |= 1; //GPB0:01输出
*gpbdat &= ~1; /* *gpbdat=0,输出低电平 */

*gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */

/* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs)); /*基地址0x4D000000,大小sizeof(struct lcd_regs)*/

/* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P14
* 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);

#if 1
/* 垂直方向的时间参数lcdcon2
* bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据
* LCD手册 T0-T2-T1=4
* VBPD=3
* bit[23:14]: 多少行, 320, 所以LINEVAL=320-1=319
* bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC
* LCD手册T2-T5=322-320=2, 所以VFPD=2-1=1
* bit[5:0] : VSPW, 即VSYNC信号的脉冲宽度, LCD手册T1=1, 所以VSPW=1-1=0
*/
lcd_regs->lcdcon2 = (3<<24) | (319<<14) | (1<<6) | (0<<0);

/* 水平方向的时间参数lcdcon3
* bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据
* LCD手册 T6-T7-T8=17
* HBPD=16
* bit[18:8]: 多少列, 240, 所以HOZVAL=240-1=239
* bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
* LCD手册T8-T11=251-240=11, 所以HFPD=11-1=10
*/
lcd_regs->lcdcon3 = (16<<19) | (239<<8) | (10<<0);

/* 水平方向的同步信号
* bit[7:0] : HSPW, HSYNC信号的脉冲宽度, LCD手册T7=5, 所以HSPW=5-1=4
*/
lcd_regs->lcdcon4 = 4;

#else
lcd_regs->lcdcon2 = S3C2410_LCDCON2_VBPD(5) | \
S3C2410_LCDCON2_LINEVAL(319) | \
S3C2410_LCDCON2_VFPD(3) | \
S3C2410_LCDCON2_VSPW(1);

lcd_regs->lcdcon3 = S3C2410_LCDCON3_HBPD(10) | \
S3C2410_LCDCON3_HOZVAL(239) | \
S3C2410_LCDCON3_HFPD(1);

lcd_regs->lcdcon4 = S3C2410_LCDCON4_MVAL(13) | \
S3C2410_LCDCON4_HSPW(0);

#endif
/* 信号的极性
* 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_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);

/* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器*/
// dma_alloc_writecombine() 其中,NULL代表设备,s3c_lcd->fix.smem_len为大小,&s3c_lcd->fix.smem_start是物理地址,GFP_KERNEL 设备号
// 返回虚拟地址s3c_lcd->screen_base
s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);

//把地址告诉LCD控制器,设置lcdsaddr1,lcdsaddr2,lcdsaddr3
//LCDSADDR1的bit[29:21]相当于缓冲区起始地址 的bit[30:22],我们采用单扫描模式,起始地址的bit[21:1]相当于LCDSADDR1的bit[20:0]
//,要把地址告诉LCD控制器,也就是把bit[30:1]中的数据给LCDSADDR1的bit[29:0],起始地址右移一位。bit[31:30]我们不需要,让他清零
lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);/*物理起始地址右移一位,最高2位清0*/
//单扫描模式下,LCD缓存 帧 的结束地址的bit[21:1]是LCDSADDR2的bit[20:0],所以让结束地址右移一位
lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;/*lcdsaddr2:[20 :0], 只要21位,与上 0x1fffff,1个f是4位,5个f是20位,还有一个1,总计21位*/
lcd_regs->lcdsaddr3 = (240*16/16); /* 一行的长度(单位: 2字节) */

//s3c_lcd->fix.smem_start = xxx; /* 显存的物理地址 */
/* 启动LCD */
lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
*gpbdat |= 1; /* 输出高电平, 使能背光 */

/* 4. 注册 */
register_framebuffer(s3c_lcd);

return 0;
}

static void lcd_exit(void)
{
unregister_framebuffer(s3c_lcd);
lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
*gpbdat &= ~1; /* 关闭背光 */
dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
iounmap(lcd_regs);
iounmap(gpbcon);
iounmap(gpccon);
iounmap(gpdcon);
iounmap(gpgcon);
framebuffer_release(s3c_lcd);
}

module_init(lcd_init);
module_exit(lcd_exit);

MODULE_LICENSE("GPL");



问1. registered_fb在哪里被设置?

答1. register_framebuffer

怎么写LCD驱动程序?

1. 分配一个fb_info结构体: framebuffer_alloc

2. 设置

3. 注册: register_framebuffer

4. 硬件相关的操作

测试:(1,2是在192.168.31.105里面执行,3在板子即com口里执行)

1. make menuconfig去掉原来的驱动程序 ,否则和原来的2个会冲突

-> Device Drivers

-> Graphics support

S3C2410 LCD framebuffer support

make uImage //编译内核,配置成模式的驱动模块不参与编译

cp arch/arm/boot/uImage /work/nfs_root/uImage_nolcd 这里nolcd指的是没有lcd

make modules //编译make menuconfig中配置成模式的驱动模块

主要是想得到.fb_fillrect = cfb_fillrect,

.fb_copyarea = cfb_copyarea,

.fb_imageblit = cfb_imageblit,

这3个函数所对应的3个文件,也得把他编译成.ko,等会再加载进去

使用新的uImage启动开发板:

reboot

烧写uImage 通过NFS nfs 30000000 192.168.31.105:/work/nfs_root/uImage_nolock(烧写到30000000,后面是烧写的文件,192.168.31.105是虚拟机地址)

bootm 30000000 bootm主要是对内存(ram)中的数据进行引导,并且只起到引导的作用。你需要提前将uboot下载到指定位置,可以使用tftp,nfs,或者nand flash读写

192.168.31.105下

make 生成.文件 book@book-desktop:/work/drivers_and_test/10th_lcd/4thmakebook@book−desktop:/work/driversandtest/10thlcd/4th cp lcd.ko /work/nfs_root/first_fs

book@book-desktop:/work/system/linux-2.6.22.6$ cp drivers/video/cfb*.ko /work/nfs_root/first_fs

板子上装载

insmod cfbcopyarea.ko

insmod cfbfillrect.ko

insmod cfbimgblt.ko

insmod lcd.ko

echo hello > /dev/tty1 // 可以在LCD上看见hello

cat lcd.ko > /dev/fb0 // 花屏 把lcd.ko的数据直接放到显存上

修改 /etc/inittab

tty1::askfirst:-/bin/sh

用新内核重启开发板

遇到问题

1. #make menuconfig(基于文本选单的配置界面,字符终端下推荐使用)

2. make modules

今天在编译Linux kernel的时候发现make menuconfig中配置成模式的驱动模块没有加入编译之,下面命令编译内核的:

make uImage

通过查看kernel的makefile发些了一些蛛丝马迹,现在将工作笔记记录如下:在linux kernel下运行终端,输入如下命令查看kernel编译相关的一些信息

发现并没有把make menuconfig中配置成模式的驱动模块没有加入编译,分析才发现还得执行如下命令就OK了。

make modules

相关的*.ko驱动模块就在kernel目录下!

原文地址:http://blog.csdn.NET/qq_21792169/article/details/50360139

3 、使用新的uImage启动开发板:reboot

烧写uImage 通过NFS nfs 30000000 192.168.31.105:/work/nfs_root/uImage_nolock(烧写到30000000,后面是烧写的文件,192.168.31.105是虚拟机地址)

bootm命令

bootm 30000000 bootm主要是对内存(ram)中的数据进行引导,并且只起到引导的作用。你需要提前将uboot下载到指定位置,可以使用tftp,nfs,或者nand flash读写
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux驱动