学习本章前,给大家打一下预防针, LCD 设备驱动本身属于字符设备驱动的范畴,难度属于一般难度。 在层次架构方面较输入子系统或平台总线驱动设备模型要简单,但是它有复杂的硬件操作。所以,建议大家在学习本章内容之前,要先学习第一部分的第 11 章---LCD 控制器原理与应用。 因为, Linux 下的 LCD 设备驱动引用了很大一部分裸机下的硬件相关的代码。
9.1 帧缓冲(framebuffer)
帧缓冲( framebuffer)是 Linux 为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由 Framebuffer 设备驱动来完成的。
Linux FrameBuffer 本质上只是提供了对图形设备的硬件抽象,在开发者看来, FrameBuffer 是一块显示缓存,往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。所以说 FrameBuffer 就是一块白板。例如对于初始化为 16 位色的 FrameBuffer 来说, FrameBuffer 中的两个字节代表屏幕上一个点,从上到下,从左至右,屏幕位置与内存地址是顺序的线性关系。 帧缓存有个地址,是在内存里。我们通过不停的向 frame buffer 中写入数据, 显示控制器就自动的从 frame buffer 中取数据并显示出来。
帧缓冲设备对应的设备文件为/dev/fb*,如果系统有多个显示卡, Linux 下还可支持多个帧缓冲设备,最多可达 32 个,分别为/dev/fb0 到/dev/fb31 ,而/dev/fb则为当前缺省的帧缓冲设备,通常指向/dev/fb0。当然在嵌入式系统中支持一个显示设备就够了。帧缓冲设备为标准字符设备,主设备号为 29,次设备号则从0 到 31 。分别对应/dev/fb0-/dev/fb31 。
9.1.1 帧缓冲与应用程序的交互
对于用户程序而言,它和其他的设备并没有什么区别,用户可以把 fb 看成是一块内存,既可以向内存中写数据,也可以读数据。 fb 的显示缓冲区位于内核空间,应用程序可以把此空间映射到自己的用户空间,在进行操作。 在应用程序中,操作/dev/fbn 的一般步骤如下:
(1) 打开/dev/fbn 设备文件。
(2) 用 ioctl()操作取得当前显示屏幕的参数,如屏幕分辨率、每个像素点的比特数。根据屏幕参数可计算屏幕缓冲区的大小。
(3) 用 mmap()函数,将屏幕缓冲区映射到用户空间。(4) 映射后就可以直接读/写屏幕缓冲区,进行绘图和图片显示了。
9.1. 2 帧缓冲显示原理
帧缓冲类似一个蓄水池,存放来自用户进程的数据,然后把这些数据再输入显示设备中。对于用户而言,帧缓冲就是内存中的一块区域,可以读、写、映射。只要在初始化阶段把显示设备映射到用户进程空间,可以理解为将屏幕中的每一点和帧缓冲的每一点一一对应起来。这样接下来就可以对这块内存区域填充任何已经定义的像素以及颜色,而屏幕也就可以根据刚才写入的像素及颜色呈现出五彩缤纷的画面。
9.1.3 Linux 帧缓冲设备驱动架构
一个帧缓冲区对应一个 struct fb_info 结构,它包括了帧缓冲设备的属性和操作的完整集合。 具体的 lcd 设备驱动(xxxfb.c)通过 register_framrbuffer()向内核注册帧缓冲设备。
帧缓冲设备提供给用户空间的 file_operations 结构体由 fbmem.c 中的file_operations 提供,而特定的帧缓冲设备 fb_info 结构体的注册、注销以及其中的成员的实现,则由具体的 lcd 设备驱动(xxxfb.c)实现。
9.1.3.1 应用系统调用到 LCD 寄存器的流程
如图 9.2 所示,当应用程序通过系统调用(ioctl/mmap/write)打开/dev/fbX时,就 会 调 用 到 主 设 备 号 为 29 , 次 设 备 号 为 X 的 字 符 设 备 驱 动 里 的(fb_ioctl/fb_mmap/fb_write) 函 数 , 而 这 些 函 数 是 由 framebuffer 驱 动 核 心fbmem.c 文件实现的,并且它提供了一个 register_framebuffer()注册接口函数。当有具体的 LCD 设备驱动时,就会使用 register_framebuffer() 函数向内核注册一个帧缓冲设备。 这时候应用程序的系统调用会进一步调用到具体 LCD 设备驱动里的(xxx_lcd_ioctl/ xxx_lcd_mmap/ xxx_lcd_write)函数,最后操作到 LCD 硬件寄存器等。
9.1. 4 fb_info 结构体
无论是帧缓冲设备驱动(fbmem.c)还是具体的 LCD 设备驱动(xxxfb.c)都离不开 fb_info 结构体,它包括了帧缓冲设备属性和操作的完整描述,包括设备的设置参数、状态以及操作函数指针。
/* 参考 include\linux\fb.h */
struct fb_info {
atomic_t count; //原子变量
int node; //用作次设备号索引
int flags;
struct mutex lock; //用于 open/release/ioctl 函数的锁
struct fb_var_screeninfo var; //可变参数,重点关注
struct fb_fix_screeninfo fix; //固定参数,重点关注
struct fb_monspecs monspecs; //显示器标准
struct work_struct queue; //帧缓冲区队列
struct fb_pixmap pixmap; //图像硬件映射
struct fb_pixmap sprite; //光标硬件映射
struct fb_cmap cmap; //当前颜色表
struct list_head modelist; //模式链表
struct fb_videomode *mode; //当前 video 模式
struct fb_ops *fbops; //帧缓冲操作函数集
char __iomem *screen_base; //显存基地址
unsigned long screen_size; //显存大小
void *pseudo_palette; //伪 16 色颜色表
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; //硬件状态,如挂起
void *fbcon_par; //用作私有数据区
void *par; //info->par 指向了额外多申请内存空间的首地址
};
9.1. 4.1 可变参数(fb_var_screeninfo)
fb_info结构体有个重要的成员,那就是 fb_var_screeninfo可变参数结构体,fb_var_screeninfo 结构体定义了一些在系统运行期间可以改变的信息。例如像素深度、灰度级、颜色格式、时序,屏幕边缘空白区等。
/* 参考 include\uapi\linux\fb.h */
struct fb_var_screeninfo {
__u32 xres; /* 物理分辨率的 X 坐标 */
__u32 yres; /* 物理分辨率的 Y 坐标 */
__u32 xres_virtual; /* 虚拟分辨率的 X 坐标 */
__u32 yres_virtual; /* 虚拟分辨率的 Y 坐标 */
__u32 xoffset; /* 虚拟分辨率与物理分辨率的 X 坐标的偏移量*/
__u32 yoffset; /* 虚拟分辨率与物理分辨率的 Y 坐标的偏移量*/
__u32 bits_per_pixel; /* 每个像素占据多少位 */
struct fb_bitfield red; /* 红色位域 */
struct fb_bitfield green; /* 绿色位域 */
struct fb_bitfield blue; /* 蓝色位域 */
__u32 activate; /* 活动模式 */
__u32 grayscale; /* 灰度模式 */
struct fb_bitfield transp; /* 透明色 */
__u32 nonstd; /* 非标准像素格式 */
...
__u32 pixclock; /* 像素时钟,单位是皮秒 */
__u32 left_margin; /* 左侧边缘区 */
__u32 right_margin; /* 右侧边缘区 */
__u32 upper_margin; /* 顶部边缘区 */
__u32 lower_margin;
__u32 hsync_len; /* 水平扫描边缘区 */
__u32 vsync_len; /* 垂直扫描边缘区 */
...
};
其中 fb_bitfield 结构体表示位域,其定义如下:
/* 参考 include\uapi\linux\fb.h */
struct fb_bitfield {
__u32 offset; /* 位域偏移值 */
__u32 length; /* 位域长度 */
__u32 msb_right; /* 等于 1 ,表示最重要一位在右 */
};
图 9.3 标出了各种边缘区在整个屏幕上的位置:
/* 参考 include\uapi\linux\fb.h */
struct fb_fix_screeninfo {
char id[16]; /* 设备名 */
unsigned long smem_start; /* 缓冲区物理地址起始地址 */
__u32 smem_len; /* 缓冲区长度 */
__u32 type; /* 设备类型,例如 TFT 或 STN */
...
__u32 visual; /* 色彩类型,真彩色、假彩色或单色 */
...
__u32 line_length; /* 屏幕上每行的字节数 */
unsigned long mmio_start; /* IO 映射区起始地址(物理地址) */
__u32 mmio_len; /* IO 映射区长度 */
...
__u16 reserved[3]; /* 系统保留*/
};
9.1.4.3 fb_ops
/* 参考 include\linux\fb.h */
struct fb_ops {
struct module *owner;
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
...
/* 检测可变参数,并调整到支持的值 */
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);
...
/* 画一个矩形 */
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);
...
};
9.2 帧缓冲设备驱动浅析(fbmem.c)
9.2.1 入口函数分析一个函数,从它的入口函数开始分析。
/* 参考 drivers\video\fbmem.c */
#define FB_MAJOR 29 /* /dev/fb* framebuffers */
static int __init fbmem_init(void)
{
…
/* 注册一个主设备号为 29,文件操作函数为 fb_fops 的字符设备 */
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
printk("unable to get major %d for fb devs\n", FB_MAJOR);
/* 创建一个 graphics 类 */
fb_class = class_create(THIS_MODULE, "graphics");
…
return 0;
}
9.2.2 fb_fops 实例
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.unlocked_ioctl = fb_ioctl,
……
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
……
.llseek = default_llseek,
};
9.2.3 fb_open
/* 参考 drivers\video\fbmem.c */
static int fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
/* 从设备节点中获取次设备号 */
int fbidx = iminor(inode);
struct fb_info *info;
int res = 0;
/* 通过次设备号来获取一个 fb_info 实例 */
info = get_fb_info(fbidx);
if (!info) {
request_module("fb%d", fbidx);
info = get_fb_info(fbidx);
if (!info)
return -ENODEV;
}
if (IS_ERR(info))
return PTR_ERR(info);
mutex_lock(&info->lock);
if (!try_module_get(info->fbops->owner)) {
res = -ENODEV;
goto out;
}
/* 将 fb_info 实例赋给文件私有数据,以供其他函数使用 */
file->private_data = info;
/* 如果新的 fb_info 实例中的 fbops->fb_open 存在的话就调用它 */
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1);
if (res)
module_put(info->fbops->owner);
}
#ifdef CONFIG_FB_DEFERRED_IO
if (info->fbdefio)
fb_deferred_io_open(info, inode, file);
#endif
out:
mutex_unlock(&info->lock);
if (res)
put_fb_info(info);
return res;
}
9.2.3.1
/* 参考 drivers\video\fbmem.c */
static struct fb_info *get_fb_info(unsigned int idx)
{
struct fb_info *fb_info;
/* 如果次设备号大于 32,出错 */
if (idx >= FB_MAX)
return ERR_PTR(-ENODEV);
mutex_lock(®istration_lock);
/* 以次设备号为下标,在 registered_fb 数组里找到一项 fb_ino 实例 */
fb_info = registered_fb[idx];
if (fb_info)
atomic_inc(&fb_info->count);
mutex_unlock(®istration_lock);
return fb_info;
}
9.2. 4
/* 参考 drivers\video\fbmem.c */
static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
/* 以次设备号为下标,在 registered_fb 数组里找到一项 fb_ino 实例 */
struct fb_info *info = file_fb_info(file);
u8 *buffer, *dst;
u8 __iomem *src;
int c, cnt = 0, err = 0;
unsigned long total_size;
if (!info || ! info->screen_base)
return -ENODEV;
if (info->state != FBINFO_STATE_RUNNING)
return -EPERM;
/* 如果新的 fb_info 实例中的 fbops->fb_read 存在的话就调用它 */
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos);
/* 没有默认的读函数,就从显存的虚拟起始
* 地址读数据,大小为虚拟显存大小
*/
total_size = info->screen_size;
/* 如果虚拟显存大小为了,则设置大小为 fb 缓冲区长度 */
if (total_size == 0)
total_size = info->fix.smem_len;
/* 如果读的偏移值大于 total_size,则返回 0 个字节 */
if (p >= total_size)
return 0;
/* 如果读的总量大于 total_size,则调整
* 最多读 total_size 个字节的数据
*/
if (count >= total_size)
count = total_size;
/* 调整读的位置及能读多少字节 */
if (count + p > total_size)
count = total_size - p;
/* 分配内存,最大分配 4K 的大小 */
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
if (!buffer)
return -ENOMEM;
/* 源虚拟基地址 */
src = (u8 __iomem *) (info->screen_base + p);
/* 如果新的 fb_info 实例中的 fbops->fb_sync 存在的话就调用它 */
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
while (count) {
/* 读多少计数变量,单位为 byte */
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
/* dst 指向刚分配 buffer 内存的首地址 */
dst = buffer;
/* 从源地址里拷贝数据到目的地址 */
fb_memcpy_fromfb(dst, src, c);
/* 调整位置 */
dst += c;
src += c;
/* 从内核刚申请内存的地址 buffer 拷贝
* c 长度的数据到用户空间的 buf 里去
*/
if (copy_to_user(buf, buffer, c)) {
err = -EFAULT;
break;
}
/* 调整偏移位置 */
*ppos += c;
buf += c;
cnt += c;
/* count 变量减去已经读取的 c 数量,用于判断 while(count)是否为真*/
count -= c;
}
/* 释放内存 */
kfree(buffer);
/* err = 0 时,返回被拷贝成功的数量 cnt */
return (err) ? err : cnt;
}
9.2.4.1
static struct fb_info *file_fb_info(struct file *file)
{
/* 获取次设备号 */
struct inode *inode = file->f_path.dentry->d_inode;
int fbidx = iminor(inode);
/* 以次设备号为下标,在 registered_fb 数组里找到一项 fb_ino 实例 */
struct fb_info *info = registered_fb[fbidx];
/* 如果获取的 fb_info 实例与 fb_open 函数
* 获取的 fb_info 实例不一致,置为 NULL
*/
if (info != file->private_data)
info = NULL;
return info;
}
9.2.5 registered_fb
register_framebuffer
-->do_register_framebuffer
-->registered_fb[i] = fb_info;
/* 参考 drivers\video\fbmem.c */
int register_framebuffer(struct fb_info *fb_info)
{
int ret;
mutex_lock(®istration_lock);
ret = do_register_framebuffer(fb_info);
mutex_unlock(®istration_lock);
return ret;
}
9.2.7 do_register_framebuffer
/* 参考 drivers\video\fbmem.c */
static int do_register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode;
......
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;
atomic_set(&fb_info->count, 1);
mutex_init(&fb_info->lock);
mutex_init(&fb_info->mm_lock);
/* 在 graphics 类下创建设备,设备节点为/dev/fb0--/dev/fb31 */
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
...
/* 简单初始化 fb_info 结构体部分成员 */
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);
/* 将初始化好的 fb_info 结构体赋给 registered_fb[] */
registered_fb[i] = fb_info;
event.info = fb_info;
if (!lock_fb_info(fb_info))
return -ENODEV;
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
unlock_fb_info(fb_info);
return 0;
}
9.2. 8 fb_mmap
static int fb_mmap(struct file *file, struct vm_area_struct * vma)
{
/* 以次设备号为下标找到一项 fb_info 结构体 */
struct fb_info *info = file_fb_info(file);
struct fb_ops *fb;
unsigned long off;
unsigned long start;
u32 len;
if (!info)
return -ENODEV;
if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
return -EINVAL;
off = vma->vm_pgoff << PAGE_SHIFT;
fb = info->fbops;
if (!fb)
return -ENODEV;
mutex_lock(&info->mm_lock);
/* 如果新的 fb_info 实例中的 fbops->fb_mmap 存在的话就调用它 */
if (fb->fb_mmap) {
int res;
res = fb->fb_mmap(info, vma);
mutex_unlock(&info->mm_lock);
return res;
}
/* 源:fb 缓冲内存的开始位置(物理地址) */
start = info->fix.smem_start;
/* 长度 */
len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
/* 如果 off 大于 len,则要调整源起始地址、长度 */
if (off >= len) {
/* memory mapped io */
off -= len;
if (info->var.accel_flags) {
mutex_unlock(&info->mm_lock);
return -EINVAL;
}
start = info->fix.mmio_start;
len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
}
mutex_unlock(&info->mm_lock);
start &= PAGE_MASK;
if ((vma->vm_end - vma->vm_start + off) > len)
return -EINVAL;
off += start;
vma->vm_pgoff = off >> PAGE_SHIFT;
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
fb_pgprotect(file, vma, off);
/* 正式映射物理内存到用户空间虚拟地址 */
if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, vma->vm_page_prot))
return -EAGAIN;
return 0;
}
9.2.9 fb_ioctl
static long fb_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
{
struct fb_info *info = file_fb_info(file);
if (!info)
return -ENODEV;
return do_fb_ioctl(info, cmd, arg);
}
9.3 从零编写 LCD 设备驱动
一眼扫过去, s3c2410fb.c 貌似看起来相对比较熟悉。 进去咋一看,确实这是三星公司提供的基于 S3C2410/S3C2440 架构来编写的 LCD 设备驱动。 它属于我们第八章讲的平台总线驱动设备模型,入口函数注册了一个平台驱动,如果系统中存在有相同名字的平台设备,就会调用 platform_driver 的 probe 函数。
(4) 通过平台设备 platform_device 获得内存资源。
9.3.1 图说 LCD 设备驱动编写流程
9.3.2 从零编写 LCD 设备驱动分析
9.3.2.1 入口函数
/* 第一个参数为 0,表示只需要分配结构体本身大小,无需分配额外空间
* 第二个参数为 NULL,表示无 device 结构
*/
webee_fbinfo = framebuffer_alloc(0 , NULL);
2.1 设置固定的参数
/* 关于这些参数的含义,请参考 9.1.4.2 小节 */
strcpy(webee_fbinfo->fix.id, "webee210_lcd");
webee_fbinfo->fix.smem_len = 800 * 480 * 32/8;
webee_fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
webee_fbinfo->fix.visual = FB_VISUAL_TRUECOLOR;
webee_fbinfo->fix.line_length = 800 * 32/8;
/* 关于这些参数的含义,请参考 9.1.4.1 小节 */
webee_fbinfo->var.xres = 800;
webee_fbinfo->var.yres = 480;
webee_fbinfo->var.xres_virtual = 800;
webee_fbinfo->var.yres_virtual = 480;
webee_fbinfo->var.bits_per_pixel = 32;
/*RGB:888*/
webee_fbinfo->var.red.offset = 16;
webee_fbinfo->var.red.length = 8;
webee_fbinfo->var.green.offset = 8;
webee_fbinfo->var.green.length = 8;
webee_fbinfo->var.blue.offset = 0;
webee_fbinfo->var.blue.length = 8;
webee_fbinfo->var.activate = FB_ACTIVATE_NOW
/* 后面分析这个 fops */
webee_fbinfo->fbops = &webee210_lcdfb_ops;
webee_fbinfo->screen_size = 800 * 480 * 32/8;
static u32 pseudo_palette[16];
……
/* pseudo_palette 是一个 u32 型的数组,在 webee210_lcdfb_setcolreg
* 函数中被设置, 如何设置看后面 webee210_lcdfb_setcolreg 函数分析。
*/
webee_fbinfo->pseudo_palette = pseudo_palette;
webee_fbinfo->screen_base = dma_alloc_writecombine(NULL,webee_fbinfo->fix.smem_len,(u32*)&(webee_fbinfo->fix.smem_start), GFP_KERNEL);
/* 函数原型如下:
* 第一个参数为: device 结构体,设置为 NULL,表示无 device 结构
* 第二个参数为: 需分配帧缓冲大小,这里为 800 * 480 * 32/8
* 第三个参数为: 返回的帧缓冲的物理起始地址,表示 DMA 可用
* 第四个参数为:分配标志,如 GFP_KERNEL
*/
static inline void *dma_alloc_writecombine(struct device *dev,size_t size,dma_addr_t *dma_handle, gfp_t flag)
3. 硬件相关的操作3.1 获取 lcd 时钟,使能时钟
lcd_clk = clk_get(NULL, "lcd");
if (!lcd_clk || IS_ERR(lcd_clk)) {
printk(KERN_INFO "failed to get lcd clock source\n");
}
clk_enable(lcd_clk);3.2 配置 GPIO 用于 LCD
gpf0con = ioremap(0xE0200120, 4);
gpf1con = ioremap(0xE0200140, 4);
gpf2con = ioremap(0xE0200160, 4);
gpf3con = ioremap(0xE0200180, 4);
gpd0con = ioremap(0xE02000A0, 4);
gpd0dat = ioremap(0xE02000A4, 4);
display_control = ioremap(0xe0107008, 4);
/* 设置相关 GPIO 引脚用于 LCD */
*gpf0con = 0x22222222;
*gpf1con = 0x22222222;
*gpf2con = 0x22222222;
*gpf3con = 0x22222222;
/* 使能 LCD 本身 */
*gpd0con |= 1<<4;
*gpd0dat |= 1<<1;
/* 显示路径的选择, 0b10: RGB=FIMD I80=FIMD ITU=FIMD */
*display_control = 2<<0;
3.3 映射 LCD 控制器对应寄存器
lcd_regs = ioremap(0xF8000000, sizeof(struct s5pv210_lcd_regs));
vidw00add0b0 = ioremap(0xF80000A0, 4);
vidw00add1b0 = ioremap(0xF80000D0, 4);
lcd_regs->vidcon0 &= ~((3<<26) | (1<<18) | (0xff<<6) | (1<<2));
lcd_regs->vidcon0 |= ((5<<6) | (1<<4) );
/* 在 vclk 的下降沿获取数据 */
lcd_regs->vidcon1 &= ~(1<<7);
/* HSYNC 极性反转, VSYNC 极性反转 */
lcd_regs->vidcon1 |= ((1<<6) | (1<<5));
lcd_regs->vidtcon0 = (VBPD << 16) | (VFPD << 8) | (VSPW << 0);
lcd_regs->vidtcon1 = (HBPD << 16) | (HFPD << 8) | (HSPW << 0);
lcd_regs->vidtcon2 = (LINEVAL << 11) | (HOZVAL << 0);
lcd_regs->wincon0 &= ~(0xf << 2);
lcd_regs->wincon0 |= (0xB<<2)|(1<<15);
lcd_regs->vidosd0a = (LeftTopX<<11) | (LeftTopY << 0);
lcd_regs->vidosd0b = (RightBotX<<11) | (RightBotY << 0);
lcd_regs->vidosd0c = (LINEVAL + 1) * (HOZVAL + 1);
*vidw00add0b0 = webee_fbinfo->fix.smem_start;
*vidw00add1b0 =
webee_fbinfo->fix.smem_start + webee_fbinfo->fix.smem_len;
lcd_regs->shadowcon = 0x1; /* 使能通道 0 */
lcd_regs->vidcon0 |= 0x3; /* 开启总控制器 */
lcd_regs->wincon0 |= 1; /* 开启窗口 0 */
register_framebuffer(webee_fbinfo);
9.3.2.2 出口函数
static void __exit webee210_lcd_exit(void)
{
unregister_framebuffer(webee_fbinfo);
dma_free_writecombine(NULL,webee_fbinfo->fix.smem_len,webee_fbinfo->screen_base, webee_fbinfo->fix.smem_start);
iounmap(gpf0con);
iounmap(gpf1con);
iounmap(gpf2con);
iounmap(gpf3con);
iounmap(gpd0con);
iounmap(gpd0dat);
iounmap(display_control);
iounmap(lcd_regs);
iounmap(vidw00add0b0);
iounmap(vidw00add1b0);
framebuffer_release(webee_fbinfo);
}
9.3.2.3 webee210_lcdfb_ops
static struct fb_ops webee210_lcdfb_ops =
{
.owner = THIS_MODULE,
.fb_setcolreg = webee210_lcdfb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
webee210_lcd.ko 时,需要先加载这三个函数对应的模块。
9.3.2.4 webee210_lcdfb_setcolreg
static int webee210_lcdfb_setcolreg(unsigned regno,
unsigned red, unsigned green, unsigned blue,
unsigned 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);
pseudo_palette[regno] = val;
return 0;
}
9.3.2.5 chan_to_field
static unsigned int chan_to_field(unsigned int chan,struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16 - bf->length;
return chan << bf->offset;
}
9.3. 3 从零编写 LCD 设备驱动的测试
/* 下面的操作都在虚拟机下, Webee 的文件系统路径可能与你们的不一样 */
[root@localhost etc]# cd /home/webee210v2/rootfs/etc
[root@localhost etc]# vim profile
/* etc/profile 文件部分内容 */
export PS1 HOSTNAME
insmod ko/cfbimgblt.ko
insmod ko/cfbfillrect.ko
insmod ko/cfbcopyarea.ko
#insmod ko/lcd.ko
insmod ko/webee210_lcd.ko
insmod ko/bus.ko
insmod ko/dev.ko
insmod ko/drv.ko
……
#qt4 &改为:
qt4 &
9.4 本章小结