title: lcd驱动框架

tags: linux

date: 2018/12/3 15:43:23

toc: true

lcd驱动框架

参考文档 cnblog 鱼树笔记 韦老师2期视频

框图

LCD设备驱动程序应该编写成frambuffer接口, frambuffer设备层是对图像设备的一种抽象,它代表了视频硬件的帧缓存,使得应用程序通过定义好的接口就可以访问硬件。应用程序不需要考虑底层的(寄存器级)的操作。

这里的lcd驱动框架,也可以理解是fb总线下面挂接了lcd设备,默认的是一种总线-平台-设备模型.

完整的程序流程图在这里

程序分析

入口

函数入口在drivers/video/fbmem.c中的fbmem_init

static int __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;
}
  1. create_proc_read_entry/proc下也会有fb文件

    # cat /proc/fb
    0 s3c2410fb
  2. register_chrdev注册驱动告知内核,主设备号是FB_MAJOR=29

  3. class_create注册了一个类graphics,具体的设备文件并不在这里创建

    # ls /sys/class/graphics/
    fb0 fbcon # 这个并不是在这里创建的

打开open

这里注册的是字符设备驱动,结构是默认的file_operations=fb_fops,从open=fb_open入手分析.

  1. 可以看到这里有一个新的结构fb_info,这个结构存储在以次设备号为索引的数组registered_fb[]中,这里次设备号最大为32,应该就是支持最多32个fb设备了,这里的fb_info应该就是管理结构了.
  2. 也就是根据次设备号在registered_fb中寻找对应的fb_info中的fb_ops中的open
  3. 注册了一个字符设备驱动fb_fops结构,open=fb_fops.open > registered_fb[次设备号].fb_ops.open
static int
fb_open(struct inode *inode, struct file *file)
{
int fbidx = iminor(inode);
struct fb_info *info; //这个是fb信息结构
int res = 0; if (fbidx >= FB_MAX)
return -ENODEV;
if (!(info = registered_fb[fbidx]))
return -ENODEV;
if (!try_module_get(info->fbops->owner))
return -ENODEV;
file->private_data = info;
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1); //registered_fb[fbidx]->fbops->fb_open
if (res)
module_put(info->fbops->owner);
}
return res;
}

read

已经抽象出read的算法部分,根据lcd的参数读取具体的frambuf

static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
// 从全局数组中按照次设备号获取具体的 fb_info 结构
struct inode *inode = file->f_path.dentry->d_inode;
int fbidx = iminor(inode);
//调用 registered_fb[次设备号].fb_read
struct fb_info *info = registered_fb[fbidx];
u32 *buffer, *dst;
u32 __iomem *src;
int c, i, cnt = 0, err = 0;
unsigned long total_size; if (!info || ! info->screen_base)
return -ENODEV; if (info->state != FBINFO_STATE_RUNNING)
return -EPERM; // 如果自定义了驱动层的read,则调用自定义的read,否则执行默认的
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos); //total_size = 屏幕大小
total_size = info->screen_size; if (total_size == 0)
total_size = info->fix.smem_len; if (p >= total_size)
return 0; if (count >= total_size)
count = total_size; if (count + p > total_size)
count = total_size - p; //分配大小,如果读取的大小大于页面大小则读取页面大小,否则读取指定大小,也就是从 页面大小和指定大小中取小值
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
if (!buffer)
return -ENOMEM;
//获得需要读取的地址=基地址+offset
src = (u32 __iomem *) (info->screen_base + p); //这个应该用于等待同步如果有自定义的话
if (info->fbops->fb_sync)
info->fbops->fb_sync(info); //读取数据到buf
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
dst = buffer;
for (i = c >> 2; i--; )
*dst++ = fb_readl(src++);
if (c & 3) {
u8 *dst8 = (u8 *) dst;
u8 __iomem *src8 = (u8 __iomem *) src; for (i = c & 3; i--;)
*dst8++ = fb_readb(src8++); src = (u32 __iomem *) src8;
} if (copy_to_user(buf, buffer, c)) {
err = -EFAULT;
break;
}
*ppos += c;
buf += c;
cnt += c;
count -= c;
} kfree(buffer); return (err) ? err : cnt;
}

初始化registered_fb

从上述可得知,具体的操作实际上是在registered_fb这个全局数组中的,肯定有函数来注册初始化它.si中搜索register很快找到,这里会寻找到空的数组元素,并使用device_create创建类下的设备

int
register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode; // 寻找一个空的registered_fb[] ,可以发现fb_info->node 也就是索引也是设备号
if (num_registered_fb == FB_MAX)
return -ENXIO;
num_registered_fb++;
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i; // 在类下创建设备文件,名字为fb次设备号
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), "fb%d", i);
if (IS_ERR(fb_info->dev)) {
/* Not fatal */
printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
fb_info->dev = NULL;
} else
fb_init_device(fb_info); if (fb_info->pixmap.addr == NULL) {
fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
if (fb_info->pixmap.addr) {
fb_info->pixmap.size = FBPIXMAPSIZE;
fb_info->pixmap.buf_align = 1;
fb_info->pixmap.scan_align = 1;
fb_info->pixmap.access_align = 32;
fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
}
}
fb_info->pixmap.offset = 0; if (!fb_info->pixmap.blit_x)
fb_info->pixmap.blit_x = ~(u32)0; if (!fb_info->pixmap.blit_y)
fb_info->pixmap.blit_y = ~(u32)0; if (!fb_info->modelist.prev || !fb_info->modelist.next)
INIT_LIST_HEAD(&fb_info->modelist); fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
registered_fb[i] = fb_info; event.info = fb_info;
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
return 0;
}

注册

搜索这个注册函数register_framebuffer,可以看到在s3c2410fb_probe中调用,再搜索则有如下,.看到了类型是platform_driver,这就是平台platform框架程序了,可以搜索"s3c2410-lcd"来查找它的设备文件也就是来查看资源.s3c24xx_fb_set_platdata可以为这个资源文件再分配私有数据s3c2410fb_mach_info

//drivers\video\s3c2410fb.c
static struct platform_driver s3c2410fb_driver = {
.probe = s3c2410fb_probe,
.remove = s3c2410fb_remove,
.suspend = s3c2410fb_suspend,
.resume = s3c2410fb_resume,
.driver = {
.name = "s3c2410-lcd",
.owner = THIS_MODULE,
},
}; //arch\arm\plat-s3c24xx\devs.c
struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
{
struct s3c2410fb_mach_info *npd; npd = kmalloc(sizeof(*npd), GFP_KERNEL);
if (npd) {
memcpy(npd, pd, sizeof(*npd));
s3c_device_lcd.dev.platform_data = npd;
} else {
printk(KERN_ERR "no memory for LCD platform data\n");
}
}

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; //获取LCD设备信息(长宽、类型等) if (mach_info == NULL) {
dev_err(&pdev->dev,"no platform data for lcd, cannot attach\n");
return -EINVAL;
}
mregs = &mach_info->regs; irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "no irq for device\n");
return -ENOENT;
} fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); //1.分配一个fb_info结构体
if (!fbinfo) {
return -ENOMEM;
} /*2.设置fb_info*/
info = fbinfo->par;
info->fb = fbinfo;
info->dev = &pdev->dev;
... ... /*3.硬件相关的操作,设置中断,LCD时钟频率,显存地址, 配置引脚... ...*/
ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info); //设置中断
info->clk = clk_get(NULL, "lcd"); //获取时钟
clk_enable(info->clk); //使能时钟
ret = s3c2410fb_map_video_memory(info); //显存地址
ret = s3c2410fb_init_registers(info); //设置寄存器,配置引脚
... ... ret = register_framebuffer(fbinfo); //4.注册一个fb_info结构体
if (ret < 0) {
printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
goto free_video_memory;
}
... ...
return ret;
}

小结

也就是说,fbmem.c已经抽象出读写的操作,能够根据提供的基地址,页面大小来读写内部ram类型frambuf,我们需要写驱动来操作硬件,并告知其具体lcd的信息

程序设计

参照drivers\video\s3c2410fb.c来设计这个fb总线下的platform平台驱动,我们这里不使用platform设计,而是直接写驱动.参考s3c2410fb_probe来进行初始化设置

  • 入口
  1. 分配一个fb_info,使用framebuffer_alloc,具体的参数设置可以参考s3c2410fb_probe,其中mach_infobast_init>s3c24xx_fb_set_platdata(&bast_lcd_info)

    1. 设置固定的参数fb_info-> fix
    2. 设置可变的参数fb_info-> var
    3. 设置具体的文件操作指针fb_info->fbops
  2. 设置GPIO引脚

  3. 分配显存fb_info>screen_base,这里使用dma_alloc_writecombine,这里注意函数返回值是虚拟地址,有个参数*handle返回实际物理地址,这个物理地址需要设置到lcd的寄存器

    void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);  //分配DMA缓存区给显存
    //返回值为:申请到的DMA缓冲区的虚拟地址,若为NULL,表示分配失败,则需要使用dma_free_writecombine()释放内存,避免内存泄漏
    //参数如下: //*dev:指针,这里填0,表示这个申请的缓冲区里没有内容 //size:分配的地址大小(字节单位) //*handle:申请到的物理起始地址 //gfp:分配出来的内存参数,标志定义在<linux/gfp.h>,常用标志如下:
    //GFP_ATOMIC 用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠.
    //GFP_KERNEL 内核内存的正常分配. 可能睡眠.
    //GFP_USER 用来为用户空间页来分配内存; 它可能睡眠.
  4. 注册fb_info结构体,register_framebuffer

  • 出口
  1. 卸载内核的fb_info,unregister_framebuffer
  2. 释放申请的显存dma_free_writecombine
  3. 寄存器操作以及ioremap
  4. 释放fb_info的内存framebuffer_release
  • fb_ops结构注册,这里需要我们实现设置调色板的功能

    static struct fb_ops my_lcdfb_ops = {
    .owner = THIS_MODULE,
    .fb_setcolreg = my_lcdfb_setcolreg,//调用my_lcdfb_setcolreg()函数,来设置调色板fb_info-> pseudo_palette
    .fb_fillrect = cfb_fillrect, //填充矩形
    .fb_copyarea = cfb_copyarea, //复制数据
    .fb_imageblit = cfb_imageblit, //绘画图形,
    };
    //这里颜色表示为565 red green blue
    my_lcd->var.red.offset = 11; //红色的最低bit
    my_lcd->var.red.length = 5; //红色的长度
    my_lcd->var.green.offset = 5;
    my_lcd->var.green.length = 6;
    my_lcd->var.blue.offset = 0;
    my_lcd->var.blue.length = 5; //填充颜色到16位数据中
    static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
    {
    /*内核中的单色都是16位,默认从左到右排列,比如G颜色[0x1f],那么chan就等于0XF800*/
    chan &= 0xffff;
    chan >>= 16 - bf->length; //右移,将数据靠到位0上
    return chan << bf->offset; //左移一定偏移值,放入16色数据中对应的位置
    } static int my_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) //调色板数组不能大于15
    return 1; /* 用red,green,blue三个颜色值构造出16色数据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; //放到调色板数组中
    return 0;
    }

测试

  1. 去掉内核的2410lcd模块Device Drivers>Graphics support 编译为模块(M选项),为了使用以下三个

    static struct fb_ops my_lcdfb_ops = {
    ...
    .fb_fillrect = cfb_fillrect, //填充矩形
    .fb_copyarea = cfb_copyarea, //复制数据
    .fb_imageblit = cfb_imageblit, //绘画图形,
    };
  2. make uImage

  3. make modules得到需要的模块

    /drivers/video
    insmod cfbcopyarea.ko && insmod cfbfillrect.ko && insmod cfbimgblt.ko
    insmod cfbfillrect.ko
    insmod cfbimgblt.ko
  4. uboot使用nfs烧写指定内核nfs 0x30008000 192.168.137.222:/work/nfs_root/u-boot.bin

  5. 启动bootm 30000000

方式一操作fb0

使用cat lcd.ko>/dev/fb0这个直接写设备文件,可以看到lcd花屏

方式二操作tty

显示文件,这里第一次测试需要使用自带的那个qt的文件系统,但是我做完方式三后发现精简的文件系统也可以,后来重试了也可以,没明白为什么

echo 123> /dev/tty1
cat Makefile>/dev/tty1

方式三操作终端

  1. 使用lcd显示sh,修改/etc/inittab,添加tty1::askfirst:-/bin/sh ,重启后加载lcd驱动,当加载lcd.ko就能发现 LCD 上有提示输入回车激活终端

     mount -o nolock,rsize=1024,wsize=1024  172.16.45.222:/home/book/stu  /mnt
    insmod cfbcopyarea.ko && insmod cfbfillrect.ko && insmod cfbimgblt.ko
    insmod lcd.ko
  2. 再使用输入子系统中的按键驱动insmod button.ko,按下按键ent11也就是代表enter会激活 LCD 的终端

  3. 这个时候可以使用按键输入ls,激活命令

  4. 在同时在串口中输入ps可以看到有两个sh

      767 0          3096 S   -sh
    768 0 3096 S -sh
  5. 查看sh对应的文件

    # ls /proc/768/fd -l
    lrwx------ 1 0 0 64 Jan 1 00:48 0 -> /dev/tty1
    lrwx------ 1 0 0 64 Jan 1 00:48 1 -> /dev/tty1
    lrwx------ 1 0 0 64 Jan 1 00:48 10 -> /dev/tty
    lrwx------ 1 0 0 64 Jan 1 00:48 2 -> /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> /*LCD : 480*272 */
#define LCD_xres 480 //LCD 行分辨率
#define LCD_yres 272 //LCD列分辨率 /* GPIO prot */
static unsigned long *GPBcon;
static unsigned long *GPCcon;
static unsigned long *GPDcon;
static unsigned long *GPGcon; //GPG4:控制LCD信号
static unsigned long *GPBdat; //GPB0: 控制背光 /* LCD control */
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;
}; static struct lcd_reg *lcd_reg; static struct fb_info *my_lcd; //定义一个全局变量
static u32 pseudo_palette[16]; //调色板数组,被fb_info->pseudo_palette调用 static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
/*内核中的单色都是16位,默认从左到右排列,比如G颜色[0x1f],那么chan就等于0XF800*/
chan &= 0xffff;
chan >>= 16 - bf->length; //右移,将数据靠到位0上
return chan << bf->offset; //左移一定偏移值,放入16色数据中对应的位置
} static int my_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) //调色板数组不能大于15
return 1; /* 用red,green,blue三个颜色值构造出16色数据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; //放到调色板数组中
return 0;
} static struct fb_ops my_lcdfb_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = my_lcdfb_setcolreg,//调用my_lcdfb_setcolreg()函数,来设置调色板fb_info-> pseudo_palette
.fb_fillrect = cfb_fillrect, //填充矩形
.fb_copyarea = cfb_copyarea, //复制数据
.fb_imageblit = cfb_imageblit, //绘画图形,
}; static int lcd_init(void)
{
/*1.申请一个fb_info结构体*/
my_lcd= framebuffer_alloc(0,0); /*2.设置fb_info*/ /* 2.1设置固定的参数fb_info-> fix */
/*my_lcd->fix.smem_start 物理地址后面注册MDA缓存区设置*/
strcpy(my_lcd->fix.id, "mylcd"); //名字
my_lcd->fix.smem_len =LCD_xres*LCD_yres*2; //地址长
my_lcd->fix.type =FB_TYPE_PACKED_PIXELS;
my_lcd->fix.visual =FB_VISUAL_TRUECOLOR; //真彩色
my_lcd->fix.line_length =LCD_xres*2; //LCD 一行的字节 /* 2.2 设置可变的参数fb_info-> var */
my_lcd->var.xres =LCD_xres; //可见屏X 分辨率
my_lcd->var.yres =LCD_yres; //可见屏y 分辨率
my_lcd->var.xres_virtual =LCD_xres; //虚拟屏x分辨率
my_lcd->var.yres_virtual =LCD_yres; //虚拟屏y分辨率
my_lcd->var.xoffset = 0; //虚拟到可见屏幕之间的行偏移
my_lcd->var.yoffset =0; //虚拟到可见屏幕之间的行偏移 my_lcd->var.bits_per_pixel=16; //像素为16BPP
my_lcd->var.grayscale = 0; //灰色比例 my_lcd->var.red.offset = 11;
my_lcd->var.red.length = 5;
my_lcd->var.green.offset = 5;
my_lcd->var.green.length = 6;
my_lcd->var.blue.offset = 0;
my_lcd->var.blue.length = 5; /* 2.3 设置操作函数fb_info-> fbops */
my_lcd->fbops = &my_lcdfb_ops; /* 2.4 设置fb_info 其它的成员 */
/*my_lcd->screen_base 虚拟地址在后面注册MDA缓存区设置*/
my_lcd->pseudo_palette =pseudo_palette; //保存调色板数组
my_lcd->screen_size =LCD_xres * LCD_yres *2; //虚拟地址长 /*3 设置硬件相关的操作*/
/*3.1 配置LCD引脚*/
GPBcon = ioremap(0x56000010, 8);
GPBdat = GPBcon+1;
GPCcon = ioremap(0x56000020, 4);
GPDcon     = ioremap(0x56000030, 4);
GPGcon      = ioremap(0x56000060, 4); *GPBcon &=~(0x03<<(0*2));
*GPBcon |= (0x01<<(0*2)); //PGB0背光
*GPBdat &=~(0X1<<0); //关背光
*GPCcon =0xaaaaaaaa;
*GPDcon =0xaaaaaaaa;
*GPGcon |=(0x03<<(4*2)); //GPG4:LCD信号 /*3.2 根据LCD手册设置LCD控制器,参考之前的裸机驱动*/
lcd_reg=ioremap(0X4D000000, sizeof( lcd_reg) );
/*HCLK:100Mhz */
lcd_reg->lcdcon1 = (4<<8) | (0X3<<5) | (0x0C<<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 分配显存(framebuffer),把地址告诉LCD控制器和fb_info*/
my_lcd->screen_base=dma_alloc_writecombine(0,my_lcd->fix.smem_len, &my_lcd->fix.smem_start, GFP_KERNEL); /*lcd控制器的地址必须是物理地址*/
lcd_reg->lcdsaddr1 =(my_lcd->fix.smem_start>>1)&0X3FFFFFFF; //保存缓冲起始地址A[30:1]
lcd_reg->lcdsaddr2 =((my_lcd->fix.smem_start+my_lcd->screen_size)>>1)&0X1FFFFF; //保存存缓冲结束地址A[21:1]
lcd_reg->lcdsaddr3 =LCD_xres& 0x3ff;        //OFFSIZE[21:11]:保存LCD上一行结尾和下一行开头的地址之间的差
                               //PAGEWIDTH [10:0]:保存LCD一行占的宽度(半字数为单位) /*4开启LCD,并注册fb_info: register_framebuffer()*/
/*4.1 直接在init函数中开启LCD(后面讲到电源管理,再来优化)*/
lcd_reg->lcdcon1 |=1<<0; //输出PWREN信号
lcd_reg->lcdcon5 |=1<<3; //允许PWREN信号
*GPBdat |=(0X1<<0); //开背光 /*4.2 注册fb_info*/
register_framebuffer(my_lcd);
return 0;
} static int lcd_exit(void)
{
/* 1卸载内核中的fb_info*/
unregister_framebuffer(my_lcd); /*2 控制LCDCON1关闭PWREN信号,关背光,iounmap注销地址*/
lcd_reg->lcdcon1 &=~(1<<0); // 关闭PWREN信号输出
lcd_reg->lcdcon5 &=~(1<<3); //禁止PWREN信号
*GPBdat &=~(0X1<<4); //关背光
iounmap(GPBcon);
iounmap(GPCcon);
iounmap(GPDcon);
iounmap(GPGcon); /*3.释放DMA缓存地址dma_free_writecombine()*/
dma_free_writecombine(0,my_lcd->screen_size,my_lcd->screen_base,my_lcd->fix.smem_start); /*4.释放注册的fb_info*/
framebuffer_release(my_lcd); return 0;
} module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");

lcd驱动框架的更多相关文章

  1. Linux驱动:LCD驱动框架分析

    一直想花时间来整理一下Linux内核LCD驱动,却一直都忙着做其他事情去了,这些天特意抽出时间来整理之前落下的笔记,故事就这样开始了.LCD驱动也是字符设备驱动的一种,框架上相对于字符设备驱动稍微复杂 ...

  2. 【Linux高级驱动】LCD驱动框架分析

    1.framebuffer接口层(fbmem.c) 功能:给用户提供接口 fbmem_init  ),"fb",&fb_fops)  /*2.创建一个设备类*/ fb_cl ...

  3. sc7731 Android 5.1 LCD驱动简明笔记之一

    基于展讯sc7731 - Android 5.1 代码分析浏览.将屏蔽细节,把握整体,并且不涉及其他设备和LCD的交互. 以下对sc7731 lcd大体流程进行简要说明. 第一,lcd 的两个阶段 1 ...

  4. 高通 android平台LCD驱动分析

    目前手机芯片厂家提供的源码里包含整个LCD驱动框架,一般厂家会定义一个xxx_fb.c的源文件,注册一个平台设备和平台驱动,在驱动的probe函数中来调用register_framebuffer(), ...

  5. Linux学习: LCD驱动

    一.LCD驱动框架: 1.分配一个fb_info结构体:s3c_lcd = framebuffer_alloc(0,NULL); 2.设置fb_info(s3c_lcd): ID.固定参数.可变参数. ...

  6. LCD驱动应该怎么写?–基于stm32F407 [复制链接]

    够用的硬件能用的代码使用的教程 (拷贝过来的代码有点乱,请下载附件查看文档) 资料下载地址:https://pan.baidu.com/s/1bHUVe6X6tymktUHk_z91cA 网络上配套S ...

  7. 10. LCD驱动程序 ——框架分析

    引言: 由LCD的硬件原理及操作(可参看韦哥博客:第017课 LCD原理详解及裸机程序分析) 我们知道只要LCD控制器的相关寄存器正确配置好,就可以在LCD面板上显示framebuffer中的内容. ...

  8. Linux的LCD驱动分析及移植

    测试平台 宿主机平台:Ubuntu 12.04.4 LTS 目标机:Easy-ARM IMX283 目标机内核:Linux 2.6.35.3 LCD驱动分析 LCD屏的驱动总体上分成两块,一块是GUI ...

  9. Linux Framebuffer 驱动框架之一概念介绍及LCD硬件原理【转】

    本文转载自:http://blog.csdn.net/liuxd3000/article/details/17464779 一.基本概念 帧缓冲(Framebuffer)是Linux系统为显示设备提供 ...

随机推荐

  1. 企业业务数据处理用“work”还是“MQ”

    近期公司在做架构梳理已经项目架构方向,不知不觉就引起了使用“work”跑数据还是用“MQ”进行跑数据的争论! 对于争论这件事在各行各业都有,其实我觉得针对“争论”这个词的根源在于一件事情有很多解决方案 ...

  2. configparser_配置解析器

    configparser:配置解析器 import configparser config = configparser.ConfigParser() #配置文件 config[', 'Compres ...

  3. Eclipse编写代码时设置属于自己的注释

    翻看硬盘文件,偶然发现以前存的这么一个小操作,给大家分享一下 1.打开Eclipse,按照以下步骤进行设置: Window -->Preferences->Java->Editor- ...

  4. kernel笔记——进程调度

    调度器完成以下任务: 时钟中断(或类似的定时器)时间内刷新进程的时间片,设置进程调度标志 系统调用返回或中断完成时检查调度标志 schedule函数 内核代码中完成进程调度的函数为schedule() ...

  5. 在dialog的content中嵌入select的获取选中值方法

    var shortNameList = "<select><option value='1'>1</option><option value='2' ...

  6. Ubuntu 安装 Docker CE(社区版)

    参考自 https://yeasy.gitbooks.io/docker_practice/install/ubuntu.html#ubuntu-1604- docker-io 是以前早期的版本,版本 ...

  7. Example of DenseCRF with non-RGB data

    本笔记本通过一个示例说明如何在非rgb数据上使用DenseCRFs.同时,它将解释基本概念并通过一个示例进行演示,因此即使您正在处理RGB数据,它也可能是有用的,不过也请查看PyDenseCRF's ...

  8. uWSGI、WSGI和uwsgi

    WSGI wsgi server (比如uWSGI) 要和 wsgi application(比如django )交互,uwsgi需要将过来的请求转给django 处理,那么uWSGI 和 djang ...

  9. mybatis 使用事务处理

    mybatis默认开启事务 以前使用JDBC的时候,如果要开启事务,我们需要调用conn.setAutoCommit(false)方法来关闭自动提交,之后才能进行事务操作,否则每一次对数据库的操作都会 ...

  10. 给hMailServer添加DKIM图文教程

    https://www.hmailserver.org/viewtopic.php?f=4&t=12