lcd驱动框架
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;
}
-
create_proc_read_entry
在/proc
下也会有fb
文件# cat /proc/fb 0 s3c2410fb
-
register_chrdev
注册驱动告知内核,主设备号是FB_MAJOR=29
-
class_create
注册了一个类graphics
,具体的设备文件并不在这里创建# ls /sys/class/graphics/ fb0 fbcon # 这个并不是在这里创建的
打开open
这里注册的是字符设备驱动,结构是默认的file_operations=fb_fops
,从open=fb_open
入手分析.
- 可以看到这里有一个新的结构
fb_info
,这个结构存储在以次设备号为索引的数组registered_fb[]
中,这里次设备号最大为32
,应该就是支持最多32个fb
设备了,这里的fb_info
应该就是管理结构了. - 也就是根据次设备号在
registered_fb
中寻找对应的fb_info
中的fb_ops
中的open
- 注册了一个字符设备驱动
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
来进行初始化设置
- 入口
-
分配一个
fb_info
,使用framebuffer_alloc
,具体的参数设置可以参考s3c2410fb_probe
,其中mach_info
在bast_init>s3c24xx_fb_set_platdata(&bast_lcd_info)
- 设置固定的参数f
b_info-> fix
- 设置可变的参数
fb_info-> var
- 设置具体的文件操作指针
fb_info->fbops
- 设置固定的参数f
-
设置
GPIO
引脚 -
分配显存
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 用来为用户空间页来分配内存; 它可能睡眠.
-
注册
fb_info
结构体,register_framebuffer
- 出口
- 卸载内核的
fb_info
,unregister_framebuffer
- 释放申请的显存
dma_free_writecombine
- 寄存器操作以及
ioremap
- 释放
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; }
测试
-
去掉内核的
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, //绘画图形, };
-
make uImage
-
make modules
得到需要的模块/drivers/video insmod cfbcopyarea.ko && insmod cfbfillrect.ko && insmod cfbimgblt.ko insmod cfbfillrect.ko insmod cfbimgblt.ko
-
uboot
使用nfs
烧写指定内核nfs 0x30008000 192.168.137.222:/work/nfs_root/u-boot.bin
-
启动
bootm 30000000
方式一操作fb0
使用cat lcd.ko>/dev/fb0
这个直接写设备文件,可以看到lcd花屏
方式二操作tty
显示文件,这里第一次测试需要使用自带的那个qt
的文件系统,但是我做完方式三后发现精简的文件系统也可以,后来重试了也可以,没明白为什么
echo 123> /dev/tty1
cat Makefile>/dev/tty1
方式三操作终端
-
使用
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
-
再使用输入子系统中的按键驱动
insmod button.ko
,按下按键ent11
也就是代表enter
会激活 LCD 的终端 -
这个时候可以使用按键输入
ls
,激活命令 -
在同时在串口中输入
ps
可以看到有两个sh
767 0 3096 S -sh 768 0 3096 S -sh
-
查看
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");