Linux驱动修炼之道-framebuffer
[日期:2011-06-19] |
来源:CSDN 作者:李万鹏 |
From:http://www.linuxidc.com/Linux/2011-06/37494.htm
帧缓冲(frame buffer)是Linux视频系统的核心概念,因此先了解一下他的功能。
因为视频适配器可能基于不同的硬件体系架构,较高内核层和应用程序的实现可能会
因视频卡的不同而不同,这会导致在使用不同视频卡的时需要采用不同的方案。随之而来的低可移植性和冗余的代码需要大量的投入和维护开销。帧缓冲的概念解决
了这个问题,它进行了一般化的抽象并规定编程接口,从而开发人员可以以与平台无关的方式编写应用层和较高内核层程序。因此,内核的帧缓冲接口允许应用程序
与底层图形硬件的变化无关,如果应用和显示器驱动程序遵循帧缓冲接口,应用程序不用改变就可以在不同类型的视频硬件上运行。
s3c2410在Linux系统中的framebuffer驱动框架:
再来看framebuffer中用到的各种数据结构:
- struct fb_fix_screeninfo {
- char id[16]; /*字符串形式的标识符*/
- unsigned long smem_start; /*fb缓冲内存的开始位置(物理地址)*/
-
__u32 smem_len; /*fb缓冲的长度*/
-
__u32 type; /*FB_TYPE_* */
-
__u32 type_aux; /*Interleave*/
-
__u32 visual; /*FB_VISUAL_* */
-
__u16 xpanstep; /*如果没有硬件panning,赋0*/
-
__u16 ypanstep;
-
__u16 ywrapstep;
-
__u32 line_length; /*一行的字节数*/
- unsigned long mmio_start; /*内存映射I/O的开始位置*/
-
__u32 mmio_len; /*内存映射I/O的长度*/
-
__u32 accel;
-
__u16 reserved[3]; /*保留*/
- };
- struct fb_var_screeninfo {
- /*可见解析度*/
-
__u32 xres;
-
__u32 yres;
- /*虚拟解析度*/
-
__u32 xres_virtual;
-
__u32 yres_virtual;
- /*虚拟到可见之间的偏移*/
-
__u32 xoffset;
-
__u32 yoffset;
-
__u32 bits_per_pixel; /*每像素的位数,BPP*/
-
__u32 grayscale; /*非0时指灰度*/
- /*fb缓存的R\G\B位域*/
- struct fb_bitfield red;
- struct fb_bitfield green;
- struct fb_bitfield blue;
- struct fb_bitfield transp; /*透明度*/
-
__u32 nonstd; /*!=0非标准像素格式*/
-
__u32 activate;
-
__u32 height; /*高度*/
-
__u32 width; /*宽度*/
-
__u32 accel_flags;
- /*除pixclock本身外,其他都以像素时钟为单位*/
-
__u32 pixclock; /*像素时钟(皮秒)*/
-
__u32 left_margin; /*行切换:从同步到绘图之间的延迟*/
-
__u32 right_margin; /*行切换:从绘图到同步之间的延迟*/
-
__u32 upper_margin; /*帧切换:从同步到绘图之间的延迟*/
-
__u32 lower_margin; /*帧切换:从绘图到同步之间的延迟*/
-
__u32 hsync_len; /*水平同步的长度*/
-
__u32 vsync_len; /*垂直同步的长度*/
-
__u32 sync;
-
__u32 vmode;
-
__u32 rotate; /*顺时针旋转的角度*/
-
__u32 reserved[5]; /*保留*/
- };
看下图可能会对fb_var_screeninfo中涉及的时序更清楚一些了。
- struct fb_cmap {
-
__u32 start; /*第1个元素入口*/
-
__u32 len; /*元素数量*/
- /*R,G,B,透明度*/
-
__u16 *red;
-
__u16 *green;
-
__u16 *blue;
-
__u16 *transp;
- };
- struct fb_bitfield {
-
__u32 offset; /*位域的偏移*/
-
__u32 length; /*位域的长度*/
-
__u32 msb_right; /*!=0 MSB在右边*/
- };
- 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);
- /**/
- void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
-
struct fb_var_screeninfo *var);
- };
- struct fb_info {
- 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; /*图像硬件mapper*/
- struct fb_pixmap sprite; /*光标硬件mapper*/
- struct fb_cmap cmap; /*目前的颜色表*/
- struct list_head modelist;
- struct fb_videomode *mode; /*目前的video模式*/
-
-
#ifdef CONFIG_FB_BACKLIGHT
- struct backlight_device *bl_dev; /*对应的背光设备*/
- 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; /*帧缓冲操作*/
- struct device *device; /*父设备*/
- struct device *dev; /*fb设备*/
- int class_flag; /*私有sysfs标志*/
-
#ifdef CONFIG_FB_TILEBLITTING
- struct fb_tile_ops *tileops; /*图块blitting*/
-
#endif
- char __iomem *screen_base; /*虚拟基地址*/
- unsigned long screen_size; /*ioremapped的虚拟内存大小*/
- void *pseudo_palette; /*伪16色颜色表*/
-
#define FBINFO_STATE_RUNNING 0
-
#define FBINFO_STATE_SUSPENDED 1
-
u32 state; /*硬件状态,如挂起*/
- void *fbcon_par;
- void *par;
- };
下面详细分析一下framebuffer的驱动源码,framebuffer作为一个平台驱动注册进内核:
- 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,
- },
- };
-
- int __init s3c2410fb_init(void)
- {
- int ret = platform_driver_register(&s3c2410fb_driver);
-
- if (ret == 0)
-
ret = platform_driver_register(&s3c2412fb_driver);;
-
- return ret;
- }
-
- static void __exit s3c2410fb_cleanup(void)
- {
-
platform_driver_unregister(&s3c2410fb_driver);
-
platform_driver_unregister(&s3c2412fb_driver);
- }
-
-
module_init(s3c2410fb_init);
-
module_exit(s3c2410fb_cleanup);
在arch/arm/plat-s3c24xx/devs.c中定义了framebuffer的平台设备:
- /* LCD Controller */
- static struct resource s3c_lcd_resource[] = {
- [0] = {
-
.start = S3C24XX_PA_LCD, //IO内存的物理起始地址
-
.end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1, //IO内存的物理结束地址
-
.flags = IORESOURCE_MEM,
- },
- [1] = {
-
.start = IRQ_LCD, //LCD的中断号
-
.end = IRQ_LCD,
-
.flags = IORESOURCE_IRQ,
- }
-
- };
- static u64 s3c_device_lcd_dmamask = 0xffffffffUL;
- 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
- }
- };
-
-
EXPORT_SYMBOL(s3c_device_lcd);
devs.c中的这个函数把s3c2410fb_mach_info存放到s3c_device_lcd.dev.platform_data,probe函数中会用到的。
- 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");
- }
- }
这个函数是在arch/arm/mach-s3c2440/mach-smdk2440.c中的smdk2440_machine_init中调用的,所以在系统启动后会自动调用。
- static void __init smdk2440_machine_init(void)
- {
-
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
-
s3c_i2c0_set_platdata(NULL);
-
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
-
smdk_machine_init();
- }
s3c2410fb_display表示屏的显示参数,这个结构体在我们移植LCD驱动的时候需要根据我们屏的参数重新设置。
- /* LCD driver info */
- static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {
-
-
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
-
S3C2410_LCDCON5_INVVLINE |
-
S3C2410_LCDCON5_INVVFRAME |
-
S3C2410_LCDCON5_PWREN |
-
S3C2410_LCDCON5_HWSWP,
-
-
.type = S3C2410_LCDCON1_TFT,
-
-
.width = 240,
-
.height = 320,
-
-
.pixclock = 270000,
-
.xres = 320,
-
.yres = 240,
-
.bpp = 16,
-
.left_margin = 8,
-
.right_margin = 5,
-
.hsync_len = 63,
-
.upper_margin = 15,
-
.lower_margin = 3,
-
.vsync_len = 5,
- };
将s3c2410fb_display结构体存于s3c2410fb_mach_info的displays域。
- static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
-
.displays = &smdk2440_lcd_cfg,
-
.num_displays = 1,
-
.default_display = 0,
-
.lpcsel = 0,
- };
下面来看看当lcd驱动和设备匹配成功后会调用的探测函数:
- static int __init s3c2410fb_probe(struct platform_device *pdev)
- {
- return s3c24xxfb_probe(pdev, DRV_S3C2410);
- }
这里调用了s3c24xxfb_probe(pdev, DRV_S3C2410),进行了一层封装,因为这样这部分代码可以与s3c2412进行复用。
- static int __init s3c24xxfb_probe(struct platform_device *pdev,
-
enum s3c_drv_type drv_type)
- {
- struct s3c2410fb_info *info;
- struct s3c2410fb_display *display;
- struct fb_info *fbinfo;
- struct s3c2410fb_mach_info *mach_info;
- struct resource *res;
- int ret;
- int irq;
- int i;
- int size;
-
u32 lcdcon1;
- /*这就获得了刚才保存的s3c2410fb_mach_info*/
-
mach_info = pdev->dev.platform_data;
- if (mach_info == NULL) {
-
dev_err(&pdev->dev,
- "no platform data for lcd, cannot attach\n");
- return -EINVAL;
- }
- if (mach_info->default_display >= mach_info->num_displays) {
-
dev_err(&pdev->dev, "default is %d but only %d displays\n",
-
mach_info->default_display, mach_info->num_displays);
- return -EINVAL;
- }
- /*获取显示屏的相关参数*/
-
display = mach_info->displays + mach_info->default_display;
- /*获得中断号*/
-
irq = platform_get_irq(pdev, 0);
- if (irq < 0) {
-
dev_err(&pdev->dev, "no irq for device\n");
- return -ENOENT;
- }
- /*分配一个fb_info结构体*/
-
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
- if (!fbinfo)
- return -ENOMEM;
- /*设置pdev->dev->driver_data保存fbinfo的地址*/
-
platform_set_drvdata(pdev, fbinfo);
-
info = fbinfo->par;
-
info->dev = &pdev->dev;
-
info->drv_type = drv_type;
- 这4句构建的关系图如下:
-
- /*获得IO内存*/
-
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (res == NULL) {
-
dev_err(&pdev->dev, "failed to get memory registers\n");
-
ret = -ENXIO;
- goto dealloc_fb;
- }
-
-
size = (res->end - res->start) + 1;
- /*申请IO内存*/
-
info->mem = request_mem_region(res->start, size, pdev->name);
- if (info->mem == NULL) {
-
dev_err(&pdev->dev, "failed to get memory region\n");
-
ret = -ENOENT;
- goto dealloc_fb;
- }
- /*映射IO内存*/
-
info->io = ioremap(res->start, size);
- if (info->io == NULL) {
-
dev_err(&pdev->dev, "ioremap() of registers failed\n");
-
ret = -ENXIO;
- goto release_mem;
- }
- /*获得LCD中断挂起寄存器的基地址*/
- info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);
-
- dprintk("devinit\n");
-
-
strcpy(fbinfo->fix.id, driver_name);
-
- /*暂时关闭LCD控制器*/
-
lcdcon1 = readl(info->io + S3C2410_LCDCON1);
-
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
-
-
fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
-
fbinfo->fix.type_aux = 0;
-
fbinfo->fix.xpanstep = 0;
-
fbinfo->fix.ypanstep = 0;
-
fbinfo->fix.ywrapstep = 0;
-
fbinfo->fix.accel = FB_ACCEL_NONE;
-
-
fbinfo->var.nonstd = 0;
-
fbinfo->var.activate = FB_ACTIVATE_NOW;
-
fbinfo->var.accel_flags = 0;
-
fbinfo->var.vmode = FB_VMODE_NONINTERLACED;
- /*将底层操作函数与上层联系起来*/
-
fbinfo->fbops = &s3c2410fb_ops;
-
fbinfo->flags = FBINFO_FLAG_DEFAULT;
-
fbinfo->pseudo_palette = &info->pseudo_pal;
- /*用于填充调色板*/
- for (i = 0; i < 256; i++)
-
info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
- /*注册中断处理函数*/
-
ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);
- if (ret) {
-
dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
-
ret = -EBUSY;
- goto release_regs;
- }
- /*获得LCD时钟*/
-
info->clk = clk_get(NULL, "lcd");
- if (!info->clk || IS_ERR(info->clk)) {
-
printk(KERN_ERR "failed to get lcd clock source\n");
-
ret = -ENOENT;
- goto release_irq;
- }
- /*使能LCD时钟*/
-
clk_enable(info->clk);
- dprintk("got and enabled clock\n");
- /*初始化LCD控制器之前要延时一段时间*/
- msleep(1);
-
- /*计算缓冲区需要的最大内存,就是缓冲区一共占多少字节,xres*yres*bpp/8 */
- for (i = 0; i < mach_info->num_displays; i++) {
-
unsigned long smem_len = mach_info->displays[i].xres;
-
-
smem_len *= mach_info->displays[i].yres;
-
smem_len *= mach_info->displays[i].bpp;
-
smem_len >>= 3;
- if (fbinfo->fix.smem_len < smem_len)
-
fbinfo->fix.smem_len = smem_len;
- }
- /*申请帧缓冲内存*/
-
ret = s3c2410fb_map_video_memory(fbinfo);
- if (ret) {
-
printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);
-
ret = -ENOMEM;
- goto release_clock;
- }
-
- dprintk("got video memory\n");
-
-
fbinfo->var.xres = display->xres;
-
fbinfo->var.yres = display->yres;
-
fbinfo->var.bits_per_pixel = display->bpp;
- /*初始化相关寄存器*/
-
s3c2410fb_init_registers(fbinfo);
- /*检查fb_info中的可变参数*/
-
s3c2410fb_check_var(&fbinfo->var, fbinfo);
-
-
ret = register_framebuffer(fbinfo); //注册帧缓冲设备
- if (ret < 0) {
-
printk(KERN_ERR "Failed to register framebuffer device: %d\n",
-
ret);
- goto free_video_memory;
- }
-
- /* create device files */
-
ret = device_create_file(&pdev->dev, &dev_attr_debug); //创建设备文件
- if (ret) {
-
printk(KERN_ERR "failed to add debug attribute\n");
- }
-
-
printk(KERN_INFO "fb%d: %s frame buffer device\n",
-
fbinfo->node, fbinfo->fix.id);
-
- return 0;
-
-
free_video_memory:
-
s3c2410fb_unmap_video_memory(fbinfo);
- release_clock:
-
clk_disable(info->clk);
-
clk_put(info->clk);
- release_irq:
-
free_irq(irq, info);
- release_regs:
-
iounmap(info->io);
- release_mem:
-
release_resource(info->mem);
-
kfree(info->mem);
- dealloc_fb:
-
platform_set_drvdata(pdev, NULL);
-
framebuffer_release(fbinfo);
- return ret;
- }
总结一下探测函数完成的任务:
1)申请fb_info结构体的内存空间,初始化fb_info结构中固定和可变的内存参数,即填充fb_info中的fb_var_screeninfo var和struct fb_fix_screeninfo fix成员。
2)申请帧缓冲设备的显示缓冲区空间
3)注册帧缓冲设备
- struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
- {
-
#define BYTES_PER_LONG (BITS_PER_LONG/8)
-
#define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG))
- int fb_info_size = sizeof(struct fb_info);
- struct fb_info *info;
- char *p;
- if (size)
-
fb_info_size += PADDING;
- /*这里开辟的堆空间用来存储struct fb_info结构体和struct s3c2410fb_info结构体*/
-
p = kzalloc(fb_info_size + size, GFP_KERNEL);
- if (!p)
- return NULL;
- info = (struct fb_info *) p;
- /*在这里将par成员赋值,以后用于存储struct s3c2410fb_info结构*/
- if (size)
-
info->par = p + fb_info_size;
-
info->device = dev;
-
-
#ifdef CONFIG_FB_BACKLIGHT
-
mutex_init(&info->bl_curve_mutex);
-
#endif
-
- return info;
-
#undef PADDING
-
#undef BYTES_PER_LONG
- }
中断处理函数:
- static irqreturn_t s3c2410fb_irq(int irq, void *dev_id)
- {
- struct s3c2410fb_info *fbi = dev_id;
- /*LCD中断挂起寄存器基地址*/
- void __iomem *irq_base = fbi->irq_base;
- /*读取LCD中断挂起寄存器值*/
- unsigned long lcdirq = readl(irq_base + S3C24XX_LCDINTPND);
- /*如果framebuffer发出了中断请求*/
- if (lcdirq & S3C2410_LCDINT_FRSYNC) {
- /*填充调色板*/
- if (fbi->palette_ready)
-
s3c2410fb_write_palette(fbi);
- /*设置帧已插入中断请求*/
-
writel(S3C2410_LCDINT_FRSYNC, irq_base + S3C24XX_LCDINTPND);
-
writel(S3C2410_LCDINT_FRSYNC, irq_base + S3C24XX_LCDSRCPND);
- }
-
- return IRQ_HANDLED;
- }
填充调色板:
- static void s3c2410fb_write_palette(struct s3c2410fb_info *fbi)
- {
- unsigned int i;
- void __iomem *regs = fbi->io;
-
-
fbi->palette_ready = 0;
-
- for (i = 0; i < 256; i++) {
-
unsigned long ent = fbi->palette_buffer[i];
- if (ent == PALETTE_BUFF_CLEAR)
- continue;
-
-
writel(ent, regs + S3C2410_TFTPAL(i));
-
- /* it seems the only way to know exactly
- * if the palette wrote ok, is to check
- * to see if the value verifies ok
- */
-
- if (readw(regs + S3C2410_TFTPAL(i)) == ent)
-
fbi->palette_buffer[i] = PALETTE_BUFF_CLEAR;
- else
-
fbi->palette_ready = 1; /* retry */
- }
- }
申请帧缓冲设备fb_info的缓冲区空间:
- static int __init s3c2410fb_map_video_memory(struct fb_info *info)
- {
- struct s3c2410fb_info *fbi = info->par;
-
dma_addr_t map_dma;
- /*获得帧缓冲区的大小*/
-
unsigned map_size = PAGE_ALIGN(info->fix.smem_len);
-
- dprintk("map_video_memory(fbi=%p) map_size %u\n", fbi, map_size);
- /*分配一个写合并缓冲区来设置帧缓冲的虚拟地址*/
-
info->screen_base = dma_alloc_writecombine(fbi->dev, map_size,
-
&map_dma, GFP_KERNEL);
- if (info->screen_base) {
- /* prevent initial garbage on screen */
-
dprintk("map_video_memory: clear %p:%08x\n",
-
info->screen_base, map_size);
- /*初始化为0*/
-
memset(info->screen_base, 0x00, map_size);
- /*设置物理地址*/
-
info->fix.smem_start = map_dma;
-
dprintk("map_video_memory: dma=%08lx cpu=%p size=%08x\n",
-
info->fix.smem_start, info->screen_base, map_size);
- }
- /*返回虚拟地址*/
- return info->screen_base ? 0 : -ENOMEM;
- }
初始化相关寄存器:
- static int s3c2410fb_init_registers(struct fb_info *info)
- {
- struct s3c2410fb_info *fbi = info->par;
- struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;
- unsigned long flags;
- /*获得LCD寄存器基地址,这个在probe中获得*/
- void __iomem *regs = fbi->io;
- void __iomem *tpal;
- void __iomem *lpcsel;
-
- if (is_s3c2412(fbi)) {
-
tpal = regs + S3C2412_TPAL;
-
lpcsel = regs + S3C2412_TCONSEL;
- } else {
- /*获得LCD调色板寄存器基地址,注意对于lpcsel这是一个针对三星TFT屏的一个专用寄存器,如果用的不是三星的屏就不用管它*/
-
tpal = regs + S3C2410_TPAL;
-
lpcsel = regs + S3C2410_LPCSEL;
- }
-
- /* Initialise LCD with values from haret */
- /*关中断*/
-
local_irq_save(flags);
-
- /* modify the gpio(s) with interrupts set (bjd) */
- /*把IO端口C和D设置成LCD模式*/
-
modify_gpio(S3C2410_GPCUP, mach_info->gpcup, mach_info->gpcup_mask);
-
modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask);
-
modify_gpio(S3C2410_GPDUP, mach_info->gpdup, mach_info->gpdup_mask);
-
modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask);
- /*恢复被屏蔽的中断*/
-
local_irq_restore(flags);
-
- dprintk("LPCSEL = 0x%08lx\n", mach_info->lpcsel);
-
writel(mach_info->lpcsel, lpcsel);
-
- dprintk("replacing TPAL %08x\n", readl(tpal));
-
- /*临时调色板使能禁止*/
-
writel(0x00, tpal);
-
- return 0;
- }
设置fb_info中的可变参数:
- static int s3c2410fb_check_var(struct fb_var_screeninfo *var,
-
struct fb_info *info)
- {
- struct s3c2410fb_info *fbi = info->par;
- struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;
- struct s3c2410fb_display *display = NULL;
- struct s3c2410fb_display *default_display = mach_info->displays +
-
mach_info->default_display;
- /*LCD的类型,S3C2410_LCDCON1_TFT*/
- int type = default_display->type;
-
unsigned i;
-
- dprintk("check_var(var=%p, info=%p)\n", var, info);
-
- /*获取与LCD屏有关的参数,封装在s3c2410fb_display中*/
- if (var->yres == default_display->yres &&
-
var->xres == default_display->xres &&
-
var->bits_per_pixel == default_display->bpp)
-
display = default_display;
- else
- for (i = 0; i < mach_info->num_displays; i++)
- if (type == mach_info->displays[i].type &&
-
var->yres == mach_info->displays[i].yres &&
-
var->xres == mach_info->displays[i].xres &&
-
var->bits_per_pixel == mach_info->displays[i].bpp) {
-
display = mach_info->displays + i;
-
break;
- }
-
- if (!display) {
-
dprintk("wrong resolution or depth %dx%d at %d bpp\n",
-
var->xres, var->yres, var->bits_per_pixel);
- return -EINVAL;
- }
-
- /*配置屏的虚拟解析度和高度宽度*/
-
var->xres_virtual = display->xres;
-
var->yres_virtual = display->yres;
-
var->height = display->height;
-
var->width = display->width;
-
- /*这里是时序了,设置时钟像素,行帧切换值,水平同步,垂直同步切换值*/
-
var->pixclock = display->pixclock;
-
var->left_margin = display->left_margin;
-
var->right_margin = display->right_margin;
-
var->upper_margin = display->upper_margin;
-
var->lower_margin = display->lower_margin;
-
var->vsync_len = display->vsync_len;
-
var->hsync_len = display->hsync_len;
-
- /*配置LCD控制寄存器1中5-6位(配置成TFT类型),配置寄存器5*/
-
fbi->regs.lcdcon5 = display->lcdcon5;
- /* set display type */
-
fbi->regs.lcdcon1 = display->type;
-
- /*设置透明度*/
-
var->transp.offset = 0;
-
var->transp.length = 0;
- /*根据色位模式设置(BPP)来设置可变参数中R,G,B的颜色位域,显示缓冲区与显示点对应如下图:*/
- switch (var->bits_per_pixel) {
- case 1:
- case 2:
- case 4:
-
var->red.offset = 0;
-
var->red.length = var->bits_per_pixel;
-
var->green = var->red;
-
var->blue = var->red;
- break;
- case 8:
- if (display->type != S3C2410_LCDCON1_TFT) {
- /* 8 bpp 332 */
-
var->red.length = 3;
-
var->red.offset = 5;
-
var->green.length = 3;
-
var->green.offset = 2;
-
var->blue.length = 2;
-
var->blue.offset = 0;
- } else {
-
var->red.offset = 0;
-
var->red.length = 8;
-
var->green = var->red;
-
var->blue = var->red;
- }
- break;
- case 12:
- /* 12 bpp 444 */
-
var->red.length = 4;
-
var->red.offset = 8;
-
var->green.length = 4;
-
var->green.offset = 4;
-
var->blue.length = 4;
-
var->blue.offset = 0;
- break;
-
- default:
- case 16:
- if (display->lcdcon5 & S3C2410_LCDCON5_FRM565) {
- /* 16 bpp, 565 format */
-
var->red.offset = 11;
-
var->green.offset = 5;
-
var->blue.offset = 0;
-
var->red.length = 5;
-
var->green.length = 6;
-
var->blue.length = 5;
- } else {
- /* 16 bpp, 5551 format */
-
var->red.offset = 11;
-
var->green.offset = 6;
-
var->blue.offset = 1;
-
var->red.length = 5;
-
var->green.length = 5;
-
var->blue.length = 5;
- }
- break;
- case 32:
- /* 24 bpp 888 and 8 dummy */
-
var->red.length = 8;
-
var->red.offset = 16;
-
var->green.length = 8;
-
var->green.offset = 8;
-
var->blue.length = 8;
-
var->blue.offset = 0;
- break;
- }
- return 0;
- }
注册帧缓冲设备:
- int
-
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;
-
- if (fb_check_foreignness(fb_info))
- return -ENOSYS;
- /*
- *每一个注册的fb_info,都会分配一个下标"i",对应的就是registered_fb[i]
- *最多能注册的fb_info个数为FB_MAX,若新注册FB则num_registered_fb++
- */
-
num_registered_fb++;
- for (i = 0 ; i < FB_MAX; i++)
- if (!registered_fb[i])
- break;
- /*找到空闲的i,赋值给fb_info->node,这个node相当于次设备号了,以后通过这个i找到fb_info*/
-
fb_info->node = i;
-
mutex_init(&fb_info->lock);
- /*创建设备文件*/
-
fb_info->dev = device_create(fb_class, fb_info->device,
-
MKDEV(FB_MAJOR, i), NULL, "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的属性文件*/
-
fb_init_device(fb_info);
- 。。。。。。。。。。。。。。
- return 0;
- }
-
- static struct fb_ops s3c2410fb_ops = {
-
.owner = THIS_MODULE,
-
.fb_check_var = s3c2410fb_check_var,
-
.fb_set_par = s3c2410fb_set_par,
-
.fb_blank = s3c2410fb_blank,
-
.fb_setcolreg = s3c2410fb_setcolreg,
-
.fb_fillrect = cfb_fillrect,
-
.fb_copyarea = cfb_copyarea,
-
.fb_imageblit = cfb_imageblit,
- };
设置参数,根据可变参数设置固定参数:
- static int s3c2410fb_set_par(struct fb_info *info)
- {
- struct fb_var_screeninfo *var = &info->var;
- /*根据可变参数的位色模式*/
- switch (var->bits_per_pixel) {
- case 32:
- case 16:
- case 12://设置成真彩,分红,绿,蓝三基色
-
info->fix.visual = FB_VISUAL_TRUECOLOR;
- break;
- case 1://设置为黑白,FB_VISUAL_MONO01代表黑,FB_VISUAL_MONO10代表白
-
info->fix.visual = FB_VISUAL_MONO01;
- break;
- default://默认设置为伪彩色,采用索引颜色显示
-
info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
- break;
- }
- /*设置fb_info中固定参数一行的字节数*/
-
info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8;
-
- /*激活新的参数配置,设置控制寄存器的值*/
-
s3c2410fb_activate_var(info);
- return 0;
- }
激活设置:
- static void s3c2410fb_activate_var(struct fb_info *info)
- {
- struct s3c2410fb_info *fbi = info->par;
- void __iomem *regs = fbi->io;
- /*获得屏的类型*/
- int type = fbi->regs.lcdcon1 & S3C2410_LCDCON1_TFT;
- struct fb_var_screeninfo *var = &info->var;
- /*获得CLKVAL*/
- int clkdiv = s3c2410fb_calc_pixclk(fbi, var->pixclock) / 2;
-
- dprintk("%s: var->xres = %d\n", __func__, var->xres);
- dprintk("%s: var->yres = %d\n", __func__, var->yres);
- dprintk("%s: var->bpp = %d\n", __func__, var->bits_per_pixel);
-
- if (type == S3C2410_LCDCON1_TFT) {
- /*就是根据可变参数结构设置lcdcon1~5*/
-
s3c2410fb_calculate_tft_lcd_regs(info, &fbi->regs);
-
--clkdiv;
- if (clkdiv < 0)
-
clkdiv = 0;
- } else {
-
s3c2410fb_calculate_stn_lcd_regs(info, &fbi->regs);
- if (clkdiv < 2)
-
clkdiv = 2;
- }
- /*设置分频值*/
-
fbi->regs.lcdcon1 |= S3C2410_LCDCON1_CLKVAL(clkdiv);
-
- /* write new registers */
-
- dprintk("new register set:\n");
- dprintk("lcdcon[1] = 0x%08lx\n", fbi->regs.lcdcon1);
- dprintk("lcdcon[2] = 0x%08lx\n", fbi->regs.lcdcon2);
- dprintk("lcdcon[3] = 0x%08lx\n", fbi->regs.lcdcon3);
- dprintk("lcdcon[4] = 0x%08lx\n", fbi->regs.lcdcon4);
- dprintk("lcdcon[5] = 0x%08lx\n", fbi->regs.lcdcon5);
- /*设置寄存器前先把LCD使能关闭,然后将刚才设置的值写入真正的寄存器*/
-
writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID,
-
regs + S3C2410_LCDCON1);
-
writel(fbi->regs.lcdcon2, regs + S3C2410_LCDCON2);
-
writel(fbi->regs.lcdcon3, regs + S3C2410_LCDCON3);
-
writel(fbi->regs.lcdcon4, regs + S3C2410_LCDCON4);
-
writel(fbi->regs.lcdcon5, regs + S3C2410_LCDCON5);
-
- /*设置LCDSADDR1~3*/
-
s3c2410fb_set_lcdaddr(info);
- /*使能LCD*/
-
fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID,
-
writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);
- }
显示空白:blank_mode有5中模式,是一个枚举,定义在include/linux/fb.h中:
- static int s3c2410fb_blank(int blank_mode, struct fb_info *info)
- {
- struct s3c2410fb_info *fbi = info->par;
- void __iomem *tpal_reg = fbi->io;
-
- dprintk("blank(mode=%d, info=%p)\n", blank_mode, info);
-
-
tpal_reg += is_s3c2412(fbi) ? S3C2412_TPAL : S3C2410_TPAL;
-
- if (blank_mode == FB_BLANK_POWERDOWN) { //如果是空白模式,则关闭LCD
-
s3c2410fb_lcd_enable(fbi, 0);
- } else {
-
s3c2410fb_lcd_enable(fbi, 1);
- }
- if (blank_mode == FB_BLANK_UNBLANK)
- /*临时调色板无效*/
-
writel(0x0, tpal_reg);
- else {
- /*临时调色板有效*/
-
dprintk("setting TPAL to output 0x000000\n");
-
writel(S3C2410_TPAL_EN, tpal_reg);
- }
- return 0;
- }
设置颜色表:
- static int s3c2410fb_setcolreg(unsigned regno,
-
unsigned red, unsigned green, unsigned blue,
-
unsigned transp, struct fb_info *info)
- {
- struct s3c2410fb_info *fbi = info->par;
- void __iomem *regs = fbi->io;
- unsigned int val;
-
- /* dprintk("setcol: regno=%d, rgb=%d,%d,%d\n",
- regno, red, green, blue); */
-
- switch (info->fix.visual) {
- /*真彩色*/
- case FB_VISUAL_TRUECOLOR:
- /* true-colour, use pseudo-palette */
-
- if (regno < 16) {
-
u32 *pal = info->pseudo_palette;
-
-
val = chan_to_field(red, &info->var.red);
-
val |= chan_to_field(green, &info->var.green);
-
val |= chan_to_field(blue, &info->var.blue);
-
-
pal[regno] = val;
- }
- break;
- /*伪彩色*/
- case FB_VISUAL_PSEUDOCOLOR:
- if (regno < 256) {
- /* currently assume RGB 5-6-5 mode */
-
-
val = (red >> 0) & 0xf800;
-
val |= (green >> 5) & 0x07e0;
-
val |= (blue >> 11) & 0x001f;
-
-
writel(val, regs + S3C2410_TFTPAL(regno));
- /*修改调色板*/
-
schedule_palette_update(fbi, regno, val);
- }
- break;
- default:
- return 1; /* unknown type */
- }
- return 0;
- }
- 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 void schedule_palette_update(struct s3c2410fb_info *fbi,
-
unsigned int regno, unsigned int val)
- {
- unsigned long flags;
- unsigned long irqen;
- /*LCD中断挂起寄存器基地址*/
- void __iomem *irq_base = fbi->irq_base;
-
- /*屏蔽中断,将中断状态保存在flags中*/
-
local_irq_save(flags);
-
-
fbi->palette_buffer[regno] = val;
- /*判断调色板是否准备就绪*/
- if (!fbi->palette_ready) {
-
fbi->palette_ready = 1;
- /*使能中断屏蔽寄存器*/
-
irqen = readl(irq_base + S3C24XX_LCDINTMSK);
-
irqen &= ~S3C2410_LCDINT_FRSYNC;
-
writel(irqen, irq_base + S3C24XX_LCDINTMSK);
- }
- /*回复被屏蔽的中断*/
-
local_irq_restore(flags);
- }
用户写屏程序:
-
#include <stdlib.h>
-
#include <unistd.h>
-
#include <stdio.h>
-
#include <fcntl.h>
-
#include <linux/fb.h>
-
#include <sys/mman.h>
-
#include <sys/ioctl.h>
-
- int main()
- {
- int fbfd = 0;
- struct fb_var_screeninfo vinfo;
- struct fb_fix_screeninfo finfo;
- long int screensize = 0;
- char *fbp = 0;
- int x = 0, y = 0;
- long int location = 0;
-
- // Open the file for reading and writing
-
fbfd = open("/dev/fb0", O_RDWR);
- if (fbfd == -1) {
-
perror("Error: cannot open framebuffer device");
-
exit(1);
- }
- printf("The framebuffer device was opened successfully.\n");
- // Get fixed screen information
- if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo) == -1) {
-
perror("Error reading fixed information");
-
exit(2);
- }
- // Get variable screen information
-
- if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
-
perror("Error reading variable information");
-
exit(3);
- }
-
- printf("%dx%d, %dbpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
- // Figure out the size of the screen in bytes
-
screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
-
- // Map the device to memory
-
- fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED,
-
fbfd, 0);
-
- if ((int)fbp == -1) {
-
perror("Error: failed to map framebuffer device to memory");
-
exit(4);
- }
- printf("The framebuffer device was mapped to memory successfully.\n");
-
-
x = 100; y = 100; // Where we are going to put the pixel
-
- // Figure out where in memory to put the pixel
- for (y = 100; y < 300; y++)
- for (x = 100; x < 300; x++) {
-
location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) +
-
(y+vinfo.yoffset) * finfo.line_length;
-
- if (vinfo.bits_per_pixel == 32) {
-
*(fbp + location) = 100; // Some blue
-
*(fbp + location + 1) = 15+(x-100)/2; // A little green
-
*(fbp + location + 2) = 200-(y-100)/5; // A lot of red
-
*(fbp + location + 3) = 0; // No transparency
-
} else { //assume 16bpp
-
int b = 10;
-
int g = (x-100)/6; // A little green
-
int r = 31-(y-100)/16; // A lot of red
-
unsigned short int t = r<<11 | g << 5 | b;
-
*((unsigned short int*)(fbp + location)) = t;
- }
- }
-
munmap(fbp, screensize);
-
close(fbfd);
- return 0;
- }
这个是网上流行的用户测试程序,总结一下用户程序写屏步骤:
1)打开设备节点
2)获得fb_info的固定参数与可变参数结构体
3)计算帧缓冲区大小
4)调用mmap将缓冲区映射到进程的地址空间
5)写屏
这里注意fb_var_screeninfo中的可见分辨率xres,yres是屏的实际大小,即320,240。而虚拟分辨率xres_virtual,yres_virtual是虚拟窗口的大小。但是在s3c2440中被设为相等的。看源码:
s3c24xxfb_probe函数中:
-
fbinfo->var.xres = display->xres;
-
fbinfo->var.yres = display->yres;
-
fbinfo->var.bits_per_pixel = display->bpp;
s3c2410fb_check_var函数中:
-
var->xres_virtual = display->xres;
-
var->yres_virtual = display->yres;
所以xres=xres_virtual,yres=yres_virtual。
而实际的xres_virtual=xres + 2 * x_offset,yres_virtual = yres
+ 2 * y_offset。可以看s3c2440的datasheet,有一个图,
看那个for循环,如果是在PC机上当然就是32BPP,R,G,B分别一个字节,另外一个字节空着或者干其他的事情,比如这里就用来表示透明度。如果在开发板上运行就是16BPP,在开发板上运行要改一下x,y的范围。在开发板上运行清屏命令:
- dd if=/dev/zero of=/dev/fb0 bs=320 count=240
然后运行效果如下: