framebuffer驱动详解

一、framebuffer介绍

1、什么是framebuffer

(1)裸机中如何操作LCD
  LCD的显示原理:DDR中分出一块内存,将要显示的内容放到显存中,硬件自动将显存数据放到驱动器中,驱动器操控LCD显示内容。裸机操作LCD的方法由LCD的本身的工作原理决定。
在这里插入图片描述

(2)OS下操作LCD的难点
   内核(驱动)做底层硬件操作相关的那部分(初始化LCD控制器的寄存器、内存,建立显存与LCD之间的映射关系),应用做让LCD显示具体内容的那部分(把显示的内容丢到显存中去,让LCD显示具体的内容)。

  应用与内核的显存的虚拟地址不同,应用使用的是应用层的虚拟地址空间,而内核使用的是内核层的虚拟地址空间,但是二者可以对应同一块物理地址,这样就可以提升效率。

  内核与应用进行数据交换:
    小容量数据:copy_to_user,copy_from_user
    大容量数据:mmap(可用于显示的情况下,显示画面时数据量较大)

(3)framebuffer帧缓冲(简称fb)是linux内核中虚拟出的一个设备,使用代码构建出的一个设备,具有设备文件可以去读写,代替LCD显示器这个硬件设施以及LCD显示器所需的软件设施和硬件设施,如显卡驱动和显卡。framebuffer可以进行不同的配置从而支持不同的接口如VGA、HDMI等等。

ls /dev/fb*
一般为fb0,fb为设备名称,数字为序号,有几个显示设备就有几个fbX,X表示从零开始的整数
一般只有一个屏幕但是却有好多个fbX文件,是因为这些表示的是虚拟屏幕

(4)framebuffer向应用层提供一个统一标准接口的显示设备,忽略不同显示设备的差异,屏蔽了不同的硬件的差异

(5)从驱动来看,fb是一个典型的字符设备,而且创建了一个类/sys/class/graphics

2、framebuffer的使用

(1)设备文件 /dev/fb0(我的开发板是这个,不同开发板不同内核驱动可能会有所差异)

(2)获取设备信息(显示设备的大小,分辨率等) #include <linux/fb.h>

(3)mmap做映射(详解:https://www.jianshu.com/p/755338d11865)

  在LINUX中我们可以使用mmap用来在进程虚拟内存地址空间中分配地址空间,创建和物理内存的映射关系。mmap将一个文件或者其它对象映射进内存。

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

  mmap只是在虚拟内存分配了地址空间,只有在第一次访问虚拟内存的时候才分配物理内存。

  驱动在内核中申请一块显存,多个进程可同时操控LCD,可进行多对一映射,后来的覆盖前面的。

(4)填充framebuffer(即将显示的内容放到framebuffer中)

二、framebuffer应用编程实践

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <unistd.h>
#include "image.h"

#define FBDEVICE   "/dev/fb0"

//旧开发板
//#define WIDTH 800
//#define HEIGHT 480

//新开发板
#define WIDTH 1024
#define HEIGHT 600

//背景颜色数据
#define WHITE		0xffffffff
#define BLACK		0x00000000
#define RED			0xffff0000
#define GREEN		0xff00ff00
#define BLUE		0xff0000ff	

unsigned int *pfb = NULL;//用于保存mmp映射返回的显存的地址

void draw_back(unsigned int width, unsigned height, unsigned int color);//刷新屏幕背景函数
void draw_line(unsigned int color);//画直线函数
void lcd_draw_picture(const unsigned char *pData);//显示图片函数

void draw_back(unsigned int width, unsigned int height, unsigned int color)
{
	unsigned int x, y;
	
	for (y=0; y<height; y++)
	{
		for (x=0; x<width; x++)
		{
			*(pfb + y * WIDTH + x) = color;
		}
	}
}

void draw_line(unsigned int color)
{
	unsigned int x, y;
	
	for (x=50; x<600; x++)
	{
		*(pfb + 200 * WIDTH + x) = color;
	}
}

//画1024*600像素的图片,图像数据存在pData所指向的数组中
void lcd_draw_picture(const unsigned char *pData)
{
	unsigned int x, y, color, p = 0;

		for(y = 0; y < 600;y++)
		{
			for(x = 0;x < 1024; x++)
			{
				// 在这里将坐标点(x, y)的那个像素填充上相应的颜色值即可
				
				color = ((pData[p+2] << 0)|(pData[p+1] << 8)|(pData[p+0] << 16));
				*(pfb + y * WIDTH + x) = color;
				p += 3;
				
			}
		}
	
}

/*
显示图片的函数并未测试成功,经过分析后,发现虚拟分辨率的大小和图片的大小不同,导致在显存中存放的颜色数据的显示时位置发生错乱,尝试修改mmap参数中的finfo.smem_len,结果在执行时出现了段错误,访问到了不该访问的内存,对于实验时出现的显示错误,经初步分析与底层驱动相关,驱动的分辨率未设置正确,经过更换内核源码可以确定是底层驱动出错的问题
*/

int main(int argv, char*argc[])
{
	int fd = -1, ret = -1;
	
	struct fb_fix_screeninfo finfo = {0};
	struct fb_var_screeninfo vinfo = {0};
	
	
	//第一步:打开设备	
	fd = open(FBDEVICE, O_RDWR);
	if (fd < 0)
	{
		perror("open fb0 error");
		return -1;
	}
	printf("open fb0 successfully.\n");
	
	//第二步:获取设备的硬件信息
	ret = ioctl(fd, FBIOGET_FSCREENINFO, &finfo);//获取不可变信息
	if (ret < 0)
	{
		perror("ioctl");
		return -1;
	}
	printf("smem_start = 0x%x, smem_len = %u.\n", finfo.smem_start, finfo.smem_len);
	
	ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);//获取可变信息
	if (ret < 0)
	{
		perror("ioctl");
		return -1;
	}
	printf("xres = %u, yres = %u.\n", vinfo.xres, vinfo.yres);
	printf("xres_virtual = %u, yres_virtual = %u.\n", vinfo.xres_virtual, vinfo.yres_virtual);
	printf("bpp = %u.\n", vinfo.bits_per_pixel);
	
	//第三步:进行mmp映射
	//finfo.smem_len以虚拟分辨率进行计算的
	pfb = mmap(NULL, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (NULL == pfb)
	{
		perror("mmap fail");
		return -1;
	}
	printf("pfb = %p.\n", pfb);
	
	//第四步:填充framebuffer
	//draw_back(WIDTH, HEIGHT, WHITE);
	//sleep(1);
	//draw_back(700, 480, BLACK);
	//draw_back(1024, 600, BLACK);
	//sleep(3);
	//draw_back(WIDTH, HEIGHT, BLACK);
	//draw_line(BLACK);
	lcd_draw_picture(gImage_1024600);
	
	//最后一步:关闭设备文件
	close(fd);
	
	return 0;
}

1、打开设备

open("/dev/fb0", O_RDWR);

2、获取设备信息

  #include <linux/fb.h>是内核源码中的一个文件,但制作交叉编译链时也将这个文件加入了进去。分析查看文件该文件,可得知相关的设备信息。

  kernel/include/linux/fb.h该文件11行,152行左右开始的内容

(1)不可变信息FSCREENINFO,使用ioctl的FBIOGET_FSCREENINFO命令获得,操作命令码类似于上篇文章操作buzzer的那些命令码。

从fb.h文件摘录部分命令码
/* ioctls
   0x46 is 'F'								*/
#define FBIOGET_VSCREENINFO	0x4600
#define FBIOPUT_VSCREENINFO	0x4601
#define FBIOGET_FSCREENINFO	0x4602
#define FBIOGETCMAP		0x4604
#define FBIOPUTCMAP		0x4605
#define FBIOPAN_DISPLAY		0x4606
#ifdef __KERNEL__
struct fb_fix_screeninfo {
	char id[16];			/* identification string eg "TT Builtin" */
	unsigned long smem_start;	/* Start of frame buffer mem */
					/* (physical address) */
	__u32 smem_len;			/* Length of frame buffer mem */
	__u32 type;			/* see FB_TYPE_*		*/
	__u32 type_aux;			/* Interleave for interleaved Planes */
	__u32 visual;			/* see FB_VISUAL_*		*/ 
	__u16 xpanstep;			/* zero if no hardware panning  */
	__u16 ypanstep;			/* zero if no hardware panning  */
	__u16 ywrapstep;		/* zero if no hardware ywrap    */
	__u32 line_length;		/* length of a line in bytes    */
	unsigned long mmio_start;	/* Start of Memory Mapped I/O   */
					/* (physical address) */
	__u32 mmio_len;			/* Length of Memory Mapped I/O  */
	__u32 accel;			/* Indicate to driver which	*/
					/*  specific chip/card we have	*/
	__u16 reserved[3];		/* Reserved for future compatibility */
};

计算方法:smem_len = xres*yres*bpp/8

(2)可变信息VSCREENINFO,使用ioctl的FBIOGET_VSCREENINFO命令获得

struct fb_var_screeninfo {
	__u32 xres;			/* visible resolution	真实分辨率即屏幕分辨率*/
	__u32 yres;
	__u32 xres_virtual;		/* virtual resolution	虚拟分辨率,即实际图片的分辨率	*/
	__u32 yres_virtual;
	__u32 xoffset;			/* offset from virtual to visible,偏移量,开始显示的左上角坐标 */
	__u32 yoffset;			/* resolution			*/

	__u32 bits_per_pixel;		/* guess what,bpp,每个像素用多少个字节来表示			*/
	__u32 grayscale;		/* != 0 Graylevels instead of colors,灰度等级 */

	struct fb_bitfield red;		/* bitfield in fb mem if true color, */
	struct fb_bitfield green;	/* else only length is significant */
	struct fb_bitfield blue;	//struct fb_bitfield各个颜色以及透明度的阈值
	struct fb_bitfield transp;	/* transparency,透明度			*/	

	__u32 nonstd;			/* != 0 Non standard pixel format */

	__u32 activate;			/* see FB_ACTIVATE_*		*/

	__u32 height;			/* height of picture in mm,屏幕的物理尺寸大小    */
	__u32 width;			/* width of picture in mm     */

	__u32 accel_flags;		/* (OBSOLETE) see fb_info.flags */

	/* Timing: All values in pixclocks, except pixclock (of course) */
	__u32 pixclock;			/* pixel clock in ps (pico seconds) ,像素时钟*/
	__u32 left_margin;		/* time from sync to picture	*/
	__u32 right_margin;		/* time from picture to sync	*/
	__u32 upper_margin;		/* time from sync to picture	*/
	__u32 lower_margin;		//初始化LCD时序的六个参数
	__u32 hsync_len;		/* length of horizontal sync	*/
	__u32 vsync_len;		/* length of vertical sync	*/
	__u32 sync;			/* see FB_SYNC_*		*/

	__u32 vmode;			/* see FB_VMODE_*		*/
	__u32 rotate;			/* angle we rotate counter clockwise */
	__u32 reserved[5];		/* Reserved for future compatibility */
};

  虚拟分辨率与真实分辨率最好的理解就是一张分辨率高于屏幕的图片无法在这个屏幕完全显示,需要通过滑动左右栏或者上下栏来查看整张图片。

   一个像素点数据由四个字节组成,包括RGB(RGB888颜色编码)以及透明度,各占一个字节。

   三星驱动的虚拟分辨率是真是分辨率的两倍,采用了双缓冲结构(有时也称乒乓结构),屏幕首先从(0,0)开始显示,当上半部分显示完后,可以直接切换到(0, 480),显示剩下的内容。
在这里插入图片描述

	struct fb_fix_screeninfo finfo = {0};
	struct fb_var_screeninfo vinfo = {0};

	ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);//获取可变信息
	if (ret < 0)
	{
		perror("ioctl");
		return -1;
	}
	printf("xres = %u, yres = %u.\n", vinfo.xres, vinfo.yres);
	printf("xres_virtual = %u, yres_virtual = %u.\n", vinfo.xres_virtual, vinfo.yres_virtual);
	printf("bpp = %u.\n", vinfo.bits_per_pixel);

  理解本部分内容,主要还是看所写的程序,可参考我上边提供的例程。

3、mmap做映射

mmap返回值指向一段内存。做完了mmap后fb在当前进程中就已经就绪了,随时可以去读写LCD显示器了。

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
        是否指定映射地址   显存大小       权限   是否可共享         偏移量
                                               多线程同时
                                                操作
void *addr:传入NULL,让自动分配地址 

示例程序:
	//finfo.smem_len以虚拟分辨率进行计算的
	pfb = mmap(NULL, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (NULL == pfb)
	{
		perror("mmap fail");
		return -1;
	}
	printf("pfb = %p.\n", pfb);                 

4、fb显示之刷背景

  一个像素点的数据包括RGB三种颜色和透明度,共4个字节大小。
故而可使用unsigned int类型的变量作为颜色数据的变量类型。

//背景颜色数据
#define WHITE		0xffffffff
#define BLACK		0x00000000
#define RED			0xffff0000
#define GREEN		0xff00ff00
#define BLUE		0xff0000ff	

void draw_back(unsigned int width, unsigned int height, unsigned int color)
{
	unsigned int x, y;
	
	for (y=0; y<height; y++)
	{
		for (x=0; x<width; x++)
		{
			*(pfb + y * WIDTH + x) = color;
		}
	}
}

5、设置分辨率

(1)实验失败,实验结果是只能修改虚拟分辨率,不能修改可视分辨率。原因要去驱动里找。

(2)正确的做法是在驱动中去修改参数,然后重新编译运行,才能解决。

6、写字、画线、图片显示等

void draw_line(unsigned int color)
{
	unsigned int x, y;
	
	for (x=50; x<600; x++)
	{
		*(pfb + 200 * WIDTH + x) = color;
	}
}

//画1024*600像素的图片,图像数据存在pData所指向的数组中,图片数据可用专用的软件生成
//在下方我会给出我使用的软件的链接:Image2Lcd.rar
//https://www.aliyundrive.com/s/D4WwdKcfg63
void lcd_draw_picture(const unsigned char *pData)
{
	unsigned int x, y, color, p = 0;

		for(y = 0; y < 600;y++)
		{
			for(x = 0;x < 1024; x++)
			{
				// 在这里将坐标点(x, y)的那个像素填充上相应的颜色值即可
				
				color = ((pData[p+2] << 0)|(pData[p+1] << 8)|(pData[p+0] << 16));
				*(pfb + y * WIDTH + x) = color;
				p += 3;
				
			}
		}
	
}

  上述的这些程序以及实验效果是无法在ubuntu实现的,因为ubuntu做了一些限制,是无法操作framebuffer的,要去开发板执行程序。

三、framebuffer驱动框架总览

1、驱动框架部分(kernel/drivers/video/fbmem.c)

(1)drivers/video/fbmem.c。
主要任务:

1、创建graphics类(/sys/class/graphics/fb0)
2、注册FB的字符设备驱动
3、提供register_framebuffer(内部调用device_create创建某个类的设备)接口给具体framebuffer驱动编写着来注册fb设备的。
	int register_framebuffer(struct fb_info *fb_info);
	int unregister_framebuffer(struct fb_info *fb_info);
fbmem_init(void)
{
	proc_create("fb", 0, NULL, &fb_proc_fops);

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

fbmem_exit(void)
{
	remove_proc_entry("fb", NULL);
	class_destroy(fb_class);
	unregister_chrdev(FB_MAJOR, "fb");
}

  本文件相对于fb来说,地位和作用和misc.c文件相对于杂散类设备来说一样的,结构和分析方法也是类似的。

(2)drivers/video/fbsys.c。这个文件是处理fb在/sys目录下的一些属性文件的。

(3)drivers/video/modedb.c。这个文件是管理显示模式的(譬如VGA、720P、刷新率等就是显示模式)

(4)drivers/video/fb_notify.c,进行反向唤醒,管理了一个链表,链表发生变动时告知链表中的各个成员

2、驱动部分

(1)drivers/video/samsung/s3cfb.c,驱动主体
(2)drivers/video/samsung/s3cfb_fimd6x.c,里面有很多LCD硬件操作的函数
(2)arch/arm/mach-s5pv210/mach-x210.c,负责提供platform_device的
(3)arch/arm/plat-s5p/devs.c,为platform_device提供一些硬件描述信息的

3、如何分析

(1)经验
(2)分析menuconfig、Makefile、Kconfig等

#
# Makefile for the s3c framebuffer driver
#

ifeq ($(CONFIG_FB_S3C),y)
obj-y                           += s3cfb.o
obj-$(CONFIG_ARCH_S5PV210)      += s3cfb_fimd6x.o

obj-$(CONFIG_FB_S3C_LTE480WV)   += s3cfb_lte480wv.o
obj-$(CONFIG_FB_S3C_LVDS)       += s3cfb_lvds.o
obj-$(CONFIG_FB_S3C_HT101HD1)   += s3cfb_ht101hd1.o
obj-$(CONFIG_FB_S3C_TL2796)     += s3cfb_tl2796.o
obj-$(CONFIG_FB_S3C_AMS701KA)   += s3cfb_ams701ka.o
obj-$(CONFIG_FB_S3C_MDNIE)      += s3cfb_mdnie.o s3cfb_ielcd.o
obj-$(CONFIG_FB_S3C_CMC623)     += tune_cmc623.o
endif

ifeq ($(CONFIG_FB_S3C_DEBUG),y)
EXTRA_CFLAGS += -DDEBUG
endif
#
# S3C Video configuration
#

config FB_S3C
        tristate "S3C Framebuffer support"
        select FB_CFB_FILLRECT
        select FB_CFB_COPYAREA
        select FB_CFB_IMAGEBLIT
        select FRAMEBUFFER_CONSOLE_DETECT_PRIMARY
        depends on FB && ARCH_S5PV210
        default n
        ---help---
          This enables support for Samsung Display Controller (FIMD)

config FB_S3C_SPLASH_SCREEN
        bool "SPLASH_SCREEN on LCD"
        depends on FB_S3C
        default n

config FB_S3C_LCD_INIT
        bool "LCD init in kernel"
        depends on FB_S3C
        default y

config FB_S3C_DEBUG
        bool "S3C Framebuffer debug messages"
        depends on FB_S3C

config FB_S3C_TRACE_UNDERRUN
        bool "S3C Framebuffer FIFO underrun trace"
        depends on FB_S3C

config FB_S3C_DEFAULT_WINDOW
        int "Default Window (0-4)"
        depends on FB_S3C
        default "2"
        ---help---
          This indicates the default window number, and which is used as console framebuffer

config FB_S3C_NR_BUFFERS
        int "Number of frame buffers (1-3)"
        depends on FB_S3C
        default "1"
        ---help---
          This indicates the number of buffers for pan display,
          1 means no pan display and
          2 means the double size of video buffer will be allocated for default window

config FB_S3C_VIRTUAL
        bool "Virtual Screen"
        depends on FB_S3C
        default n
        ---help---
          This uses 1920 x 1080 virtual screen.

config FB_S3C_X_VRES
        int "xres_virtual"
        depends on FB_S3C && FB_S3C_VIRTUAL
        default "1920"
        help
          This indicates var.xres_virtual which has to be bigger than var.xres.

config FB_S3C_Y_VRES
        int "yres_virtual"
        depends on FB_S3C && FB_S3C_VIRTUAL
        default "1080"
        help
          This indicates var.yres_virtual which has to be bigger than var.yres.

choice
depends on FB_S3C
prompt "Select LCD Type"
default FB_S3C_LTE480WV
config FB_S3C_LTE480WV
        bool "LTE480WV"
        depends on (MACH_SMDKV210 || MACH_SMDKC110)
        ---help---
          This enables support for Samsung LTE480WV 4.8\" WVGA LCD panel

config FB_S3C_HT101HD1
        bool "HT101HD1"
        depends on MACH_SMDKV210
        ---help---
          This enables support for HT101HD1 WXVGA(1366*768) LCD panel

config FB_S3C_EK070TN93
        bool "EK070TN93"
        depends on MACH_SMDKV210 || MACH_SMDKC110
        select BACKLIGHT_PWM
        help
          This enables support for EK070TN93 WVGA LCD panel
endchoice

config FB_S3C_TL2796
        bool "TL2796"
        depends on FB_S3C
        select SPI
        select SPI_GPIO
        select SPI_BITBANG
        ---help---
          This enables support for Samsung TL2796 WVGA LCD panel

config FB_S3C_LVDS
        bool "LVDS"
        depends on FB_S3C && (ARCH_S5PV210)
        default y
        ---help---
          This enables support for Samsung LVDS LCD panel

config FB_S3C_AMS701KA
        bool "AMS701KA"
        depends on FB_S3C && (ARCH_S5PV210)
        select SPI
        select SPI_GPIO
        default n
        ---help---
          This enables support for Samsung AMS701KA LCD panel

config FB_S3C_MDNIE
        bool "Samsung MDNIE"
        depends on FB_S3C
        depends on FB_S3C && (ARCH_S5PV210)
        default "0"
        ---help---
          This enables support for Samsung MDNIE feature

config FB_S3C_CMC623
        bool "Samsung CMC623"
        depends on FB_S3C && (ARCH_S5PV210)
        default "0"
        ---help---
        This enables support for Samsung CMC623 feature

(3)内核编译后检查编译结果中的.o文件
在这里插入图片描述

四、framebuffer驱动框架分析

从kernel/drivers/video/fbmem.c开始分析

1、fbmem_init函数

static int __init fbmem_init(void)
{
	proc_create("fb", 0, NULL, &fb_proc_fops);

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

#ifdef MODULE
module_init(fbmem_init);//其声明的可以去安装和卸载,内核作为一个模块
static void __exit
fbmem_exit(void)
{
	remove_proc_entry("fb", NULL);
	class_destroy(fb_class);
	unregister_chrdev(FB_MAJOR, "fb");
}

module_exit(fbmem_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Framebuffer base");
#else
subsys_initcall(fbmem_init);//其声明的则是被编译进内核
#endif

(1)#ifdef MODULE
经过分析,发现并未定义该宏MODULE

(2)fb_proc_fops和fb在proc文件系统中的表现

proc_create("fb", 0, NULL, &fb_proc_fops);
static const struct file_operations fb_proc_fops = {
.owner		= THIS_MODULE,
.open		= proc_fb_open,
.read		= seq_read,
.ll seek		= seq_lseek,
.release	= seq_release,
};

static int proc_fb_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &proc_fb_seq_ops);
}
	
static const struct seq_operations proc_fb_seq_ops = {
.start	= fb_seq_start,
.next	= fb_seq_next,
.stop	= fb_seq_stop,
.show	= fb_seq_show,
};
static int fb_seq_show(struct seq_file *m, void *v)
{
	int i = *(loff_t *)v;
	struct fb_info *fi = registered_fb[i];

	if (fi)
	seq_printf(m, "%d %s\n", fi->node, fi->fix.id);
	return 0;
}
cat /proc/fb

在驱动框架并不进行真正的硬件操作,而是通过函数指针指向一个真正可用的函数

(3)register_chrdev注册fb设备

(4)class_create创建graphics类

(5)fbmem_exit的对应:当以非模块的方式即以内核一部分集成编译进内核时,开机自动加载执行后是无法像模块卸载的,故而这种非模块方式并不需要fbmem_exit函数

2、fb_fops结构体

所有的framebuffer设备共用一个主设备号,用不同的次设备号进行区分,类似于misc类设备。

static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write,
	.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = fb_compat_ioctl,
#endif
	.mmap =		fb_mmap,
	.open =		fb_open,
	.release =	fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
	.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	.fsync =	fb_deferred_io_fsync,
#endif
};

(1)read/write/mmap/ioctl函数
(2)registered_fb和num_registered_fb
(3)struct fb_info描述一个framebuffer设备

kernel/include/linux/fb.h 45行 附近
/* Definitions of frame buffers						*/

#define FB_MAX			32	/* sufficient for now */
struct fb_info *registered_fb[FB_MAX] __read_mostly;//最大数量
int num_registered_fb __read_mostly;//已经注册的数量
__read_mostly:表示其修饰的变量大多数状态是在读
struct fb_info {
	int node;//当前这个framebuffer设备在framebuffer设备数组中的下表
	int flags;
	struct mutex lock;		/* Lock for open/release/ioctl funcs */
	struct mutex mm_lock;		/* Lock for fb_mmap and smem_* fields */
	struct fb_var_screeninfo var;	/* Current var */
	struct fb_fix_screeninfo fix;	/* Current fix */ 
	struct fb_monspecs monspecs;	/* Current Monitor specs */
	struct work_struct queue;	/* Framebuffer event queue */
	struct fb_pixmap pixmap;	/* Image hardware mapper */
	struct fb_pixmap sprite;	/* Cursor hardware mapper */
	struct fb_cmap cmap;		/* Current cmap */
	struct list_head modelist;      /* mode list */
	struct fb_videomode *mode;	/* current mode */
该结构体部分成员

3、register_framebuffer函数解析

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;

	remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,
					 fb_is_primary_device(fb_info));//防止冲突机制

	num_registered_fb++;
	for (i = 0 ; i < FB_MAX; i++)//找到一个空的可用的
		if (!registered_fb[i])
			break;
	fb_info->node = i; 
	mutex_init(&fb_info->lock);
	mutex_init(&fb_info->mm_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_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;
	if (!lock_fb_info(fb_info))
		return -ENODEV;
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	unlock_fb_info(fb_info);
	return 0;
}

(1)fb驱动框架开放给驱动编写着的注册接口
(2)fb_check_foreignness

static int fb_check_foreignness(struct fb_info *fi)
{
	const bool foreign_endian = fi->flags & FBINFO_FOREIGN_ENDIAN;

	fi->flags &= ~FBINFO_FOREIGN_ENDIAN;

#ifdef __BIG_ENDIAN
	fi->flags |= foreign_endian ? 0 : FBINFO_BE_MATH;
#else
	fi->flags |= foreign_endian ? FBINFO_BE_MATH : 0;
#endif /* __BIG_ENDIAN */

	if (fi->flags & FBINFO_BE_MATH && !fb_be_math(fi)) {
		pr_err("%s: enable CONFIG_FB_BIG_ENDIAN to "
		       "support this framebuffer\n", fi->fix.id);
		return -ENOSYS;
	} else if (!(fi->flags & FBINFO_BE_MATH) && fb_be_math(fi)) {
		pr_err("%s: enable CONFIG_FB_LITTLE_ENDIAN to "
		       "support this framebuffer\n", fi->fix.id);
		return -ENOSYS;
	}

	return 0;
}

(3)device_create

struct device *device_create(struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...)
{
	va_list vargs;
	struct device *dev;

	va_start(vargs, fmt);
	dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
	va_end(vargs);
	return dev;
}

(4)fb_init_device:创建/sys/目录下的那些文件

void dev_set_drvdata(struct device *dev, void *data)
{
	int error;

	if (!dev)
		return;
	if (!dev->p) {
		error = device_private_init(dev);
		if (error)
			return;
	}
	dev->p->driver_data = data;
}
void *dev_get_drvdata(const struct device *dev)
{
	if (dev && dev->p)
		return dev->p->driver_data;
	return NULL;
}
位于:kernel/drivers/base/dd.c,base目录下的这些文件是内核提供的底层驱动的框架支持,我们是不需要去修改的。
dev->p->driver_data:struct device->struct device_private *p(该结构体保存设备的一些私有信息)->void *driver_data;

dev_set_drvdata(fb_info->dev, fb_info);//该函数
dev->p->driver_data = data;将fb_info结构体包含的数据保存到该设备的一个结构体成员中,结合dev_get_drvdata()函数实现数据的传递在不同函数中使用,从而避免使用过多的全局
变量,破坏程序的结构
创建的/sys/class/graphics/fb0目录下的文件:
static struct device_attribute device_attrs[] = {
	__ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp),
	__ATTR(blank, S_IRUGO|S_IWUSR, show_blank, store_blank),
	__ATTR(console, S_IRUGO|S_IWUSR, show_console, store_console),
	__ATTR(cursor, S_IRUGO|S_IWUSR, show_cursor, store_cursor),
	__ATTR(mode, S_IRUGO|S_IWUSR, show_mode, store_mode),
	__ATTR(modes, S_IRUGO|S_IWUSR, show_modes, store_modes),
	__ATTR(pan, S_IRUGO|S_IWUSR, show_pan, store_pan),
	__ATTR(virtual_size, S_IRUGO|S_IWUSR, show_virtual, store_virtual),
	__ATTR(name, S_IRUGO, show_name, NULL),
	__ATTR(stride, S_IRUGO, show_stride, NULL),
	__ATTR(rotate, S_IRUGO|S_IWUSR, show_rotate, store_rotate),
	__ATTR(state, S_IRUGO|S_IWUSR, show_fbstate, store_fbstate),
#ifdef CONFIG_FB_BACKLIGHT
	__ATTR(bl_curve, S_IRUGO|S_IWUSR, show_bl_curve, store_bl_curve),
#endif
};

分析示例:
__ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp),
static ssize_t show_bpp(struct device *device, struct device_attribute *attr,
			char *buf)
{
	struct fb_info *fb_info = dev_get_drvdata(device);
	return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->var.bits_per_pixel);
	//bits_per_pixel值的来源
}
使用:echo 16 > bits_per_pixel可以进行写值通过调用store_bpp方法

在这里插入图片描述
在这里插入图片描述

int fb_init_device(struct fb_info *fb_info)
{
	int i, error = 0;

	dev_set_drvdata(fb_info->dev, fb_info);//该函数

	fb_info->class_flag |= FB_SYSFS_FLAG_ATTR;

	for (i = 0; i < ARRAY_SIZE(device_attrs); i++) {
		error = device_create_file(fb_info->dev, &device_attrs[i]);//创建/sys/目录下的文件

		if (error)
			break;
	}

	if (error) {
		while (--i >= 0)
			device_remove_file(fb_info->dev, &device_attrs[i]);
		fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR;
	}

	return 0;
}

4、fb的mode

register_framebuffer函数:

	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);	

(1)什么是mode
刷新率,色彩,像素时钟等设置

(2)fb_var_to_videomode(kernel/drivers/video/modedb.c)

void fb_var_to_videomode(struct fb_videomode *mode,
			 const struct fb_var_screeninfo *var)
{
	u32 pixclock, hfreq, htotal, vtotal;

	mode->name = NULL;
	mode->xres = var->xres;
	mode->yres = var->yres;
	mode->pixclock = var->pixclock;
	mode->hsync_len = var->hsync_len;
	mode->vsync_len = var->vsync_len;
	mode->left_margin = var->left_margin;
	mode->right_margin = var->right_margin;
	mode->upper_margin = var->upper_margin;
	mode->lower_margin = var->lower_margin;
	mode->sync = var->sync;
	mode->vmode = var->vmode & FB_VMODE_MASK;
	mode->flag = FB_MODE_IS_FROM_VAR;
	mode->refresh = 0;

	if (!var->pixclock)
		return;

	pixclock = PICOS2KHZ(var->pixclock) * 1000;

	htotal = var->xres + var->right_margin + var->hsync_len +
		var->left_margin;
	vtotal = var->yres + var->lower_margin + var->vsync_len +
		var->upper_margin;

	if (var->vmode & FB_VMODE_INTERLACED)
		vtotal /= 2;
	if (var->vmode & FB_VMODE_DOUBLE)
		vtotal *= 2;

	hfreq = pixclock/htotal;
	mode->refresh = hfreq/vtotal;
}
填充struct fb_videomode *mode结构体并且返回回来

(3)fb_add_videomode(kernel/drivers/video/modedb.c)

int fb_add_videomode(const struct fb_videomode *mode, struct list_head *head)
{
	struct list_head *pos;
	struct fb_modelist *modelist;
	struct fb_videomode *m;
	int found = 0;

	list_for_each(pos, head) {
		modelist = list_entry(pos, struct fb_modelist, list);
		m = &modelist->mode;
		if (fb_mode_is_equal(m, mode)) {
			found = 1;
			break;
		}
	}
	if (!found) {
		modelist = kmalloc(sizeof(struct fb_modelist),
						  GFP_KERNEL);

		if (!modelist)
			return -ENOMEM;
		modelist->mode = *mode;
		list_add(&modelist->list, head);
	}
	return 0;
}fb_var_to_videomode()得到的mode添加到mode列表中,若已存在相同的mode则不添加,若无则添加。

5、注册登记该fb设备

(1)registered_fb[i] = fb_info;(register_framebuffer函数剩余代码)

(2)结合fb_read等函数中对fb_info的使用

(3)关键点:数据如何封装、数据由谁准备由谁消费、数据如何传递

7、fb_notifier_call_chain(register_framebuffer函数剩余代码)

	event.info = fb_info;
	if (!lock_fb_info(fb_info))//锁定
		return -ENODEV;
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);//阻塞通知
	//当阻塞结束时进行通知,发送FB_EVENT_FB_REGISTERED消息给等待的人(也可以理解为其他线程或进程或任务)

	unlock_fb_info(fb_info);//解锁

五、framebuffer驱动分析

注:本节的内容基本都是以s3cfb_probe为主要核心展开的,从3开始到本节结束都是分析讲解该函数中的代码。

1、s3cfb.c

kernel/drivers/video/samsung/s3cfb.c
以下为不同文件的函数的调用层次及流程,便于理解驱动程序

//使用平台总线框架进行LCD的驱动注册,故理解此部分流程可参考学习之前的LED框架注册的流
//程,都是match函数先进行设备和驱动的匹配,然后再调用probe函数

#define S3CFB_NAME		"s3cfb"
static struct platform_driver s3cfb_driver = {
	.probe = s3cfb_probe,//初始化硬件,安装硬件
	.remove = __devexit_p(s3cfb_remove),
	.driver = {
		   .name = S3CFB_NAME,//platform查找device的一种手段,通过名称匹配进行查找
		   .owner = THIS_MODULE,
	},
};

static int __init s3cfb_register(void)
{
	platform_driver_register(&s3cfb_driver);

	return 0;
}
module_init(s3cfb_register);	

使用平台总线框架进行驱动注册的设备都维护在一个平台总线设备数组中,若某个设备要使用
平台总线框架进行注册,则需再该设备数组中添加该设备。

(1)注意目录结构的组织
(2)s3cfb_driver
  LCD作为platform_device进行注册的,故而在kernel/arch/arm/mach-s5pv210/mach-x210.c中的:

static struct platform_device *smdkc110_devices[] 数组中找到和它相关的元素:&s3c_device_fb

kernel/arch/arm/plat-s5p/devs.c
struct platform_device s3c_device_fb = {
	.name		  = "s3cfb",
	.id		  = -1,//表示不指定设备号,让其自动分配
	.num_resources	  = ARRAY_SIZE(s3cfb_resource),
	.resource	  = s3cfb_resource,//资源数组,寄存器所使用的一些地址
	.dev		  = {
		.dma_mask		= &fb_dma_mask,
		.coherent_dma_mask	= 0xffffffffUL
	}
};

kernel/arch/arm/plat-s5p/devs.c
static struct resource s3cfb_resource[] = {
	[0] = {
		.start = S5P_PA_LCD,PA表示使用了物理地址,具体理解可参考数据手册显示部分的寄存器讲解
		.end   = S5P_PA_LCD + S5P_SZ_LCD - 1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = IRQ_LCD1,
		.end   = IRQ_LCD1,
		.flags = IORESOURCE_IRQ,
	},
	[2] = {
		.start = IRQ_LCD0,
		.end   = IRQ_LCD0,
		.flags = IORESOURCE_IRQ,
	},
};
static struct platform_device *smdkc110_devices[] __initdata = {
#ifdef CONFIG_FIQ_DEBUGGER
	&s5pv210_device_fiqdbg_uart2,
#endif
#ifdef CONFIG_MTD_ONENAND
	&s5pc110_device_onenand,
#endif
#ifdef CONFIG_MTD_NAND
	&s3c_device_nand,
#endif
	&s5p_device_rtc,
#ifdef CONFIG_SND_S3C64XX_SOC_I2S_V4
	&s5pv210_device_iis0,
	&s5pv210_device_iis1,
#endif
#ifdef CONFIG_SND_S3C_SOC_AC97
	&s5pv210_device_ac97,
#endif
#ifdef CONFIG_SND_S3C_SOC_PCM
	&s5pv210_device_pcm0,
#endif
#ifdef CONFIG_SND_SOC_SPDIF
	&s5pv210_device_spdif,
#endif
	&s3c_device_wdt,

#ifdef CONFIG_FB_S3C
	&s3c_device_fb,
#endif
#ifdef CONFIG_DM9000
	&s5p_device_dm9000,
#endif

#ifdef CONFIG_VIDEO_MFC50
	&s3c_device_mfc,
#endif
#ifdef CONFIG_TOUCHSCREEN_S3C
	&s3c_device_ts,
#endif
	&s3c_device_keypad,
#ifdef CONFIG_S5P_ADC
	&s3c_device_adc,
#endif
#ifdef CONFIG_VIDEO_FIMC
	&s3c_device_fimc0,
	&s3c_device_fimc1,
	&s3c_device_fimc2,
#endif
#ifdef CONFIG_VIDEO_FIMC_MIPI
	&s3c_device_csis,
#endif
#ifdef CONFIG_VIDEO_JPEG_V2
	&s3c_device_jpeg,
#endif
#ifdef CONFIG_VIDEO_G2D
	&s3c_device_g2d,
#endif
#ifdef CONFIG_VIDEO_TV20
	&s5p_device_tvout,
	&s5p_device_cec,
	&s5p_device_hpd,
#endif

	&s3c_device_g3d,
	&s3c_device_lcd,

	&s3c_device_i2c0,
#ifdef CONFIG_S3C_DEV_I2C1
	&s3c_device_i2c1,
#endif
#ifdef CONFIG_S3C_DEV_I2C2
	&s3c_device_i2c2,
#endif

#ifdef CONFIG_USB_EHCI_HCD
	&s3c_device_usb_ehci,
#endif
#ifdef CONFIG_USB_OHCI_HCD
	&s3c_device_usb_ohci,
#endif

#ifdef CONFIG_USB_GADGET
	&s3c_device_usbgadget,
#endif
#ifdef CONFIG_USB_ANDROID
	&s3c_device_android_usb,
#ifdef CONFIG_USB_ANDROID_MASS_STORAGE
	&s3c_device_usb_mass_storage,
#endif
#ifdef CONFIG_USB_ANDROID_RNDIS
	&s3c_device_rndis,
#endif
#endif
#ifdef CONFIG_BATTERY_S3C
	&sec_device_battery,
#endif
#ifdef CONFIG_S3C_DEV_HSMMC
	&s3c_device_hsmmc0,
#endif
#ifdef CONFIG_S3C_DEV_HSMMC1
	&s3c_device_hsmmc1,
#endif
#ifdef CONFIG_S3C_DEV_HSMMC2
	&s3c_device_hsmmc2,
#endif
#ifdef CONFIG_S3C_DEV_HSMMC3
	&s3c_device_hsmmc3,
#endif

#ifdef CONFIG_S3C64XX_DEV_SPI
	&s5pv210_device_spi0,
	&s5pv210_device_spi1,
#endif
#ifdef CONFIG_S5PV210_POWER_DOMAIN
	&s5pv210_pd_audio,
	&s5pv210_pd_cam,
	&s5pv210_pd_tv,
	&s5pv210_pd_lcd,
	&s5pv210_pd_g3d,
	&s5pv210_pd_mfc,
#endif

#ifdef CONFIG_HAVE_PWM
	&s3c_device_timer[0],
	&s3c_device_timer[1],
	&s3c_device_timer[2],
	&s3c_device_timer[3],
#endif

//	&timed_gpio_device,

	&headset_switch_device,

//Mrs.Zhang
	&x210_led0,
	&x210_led1,
	&x210_led2,
};

2、probe(s3cfb_probe)函数分析

static int __devinit s3cfb_probe(struct platform_device *pdev)
{
	struct s3c_platform_fb *pdata;
	struct s3cfb_global *fbdev;
	struct resource *res;
	int i, j, ret = 0;

	fbdev = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL);
	if (!fbdev) {
		dev_err(&pdev->dev, "failed to allocate for "
			"global fb structure\n");
		ret = -ENOMEM;
		goto err_global;
	}
	fbdev->dev = &pdev->dev;//挂接

	fbdev->regulator = regulator_get(&pdev->dev, "pd");//控制屏幕功耗
	if (!fbdev->regulator) {
		dev_err(fbdev->dev, "failed to get regulator\n");
		ret = -EINVAL;
		goto err_regulator;
	}
	ret = regulator_enable(fbdev->regulator);
	if (ret < 0) {
		dev_err(fbdev->dev, "failed to enable regulator\n");
		ret = -EINVAL;
		goto err_regulator;
	}
	pdata = to_fb_plat(&pdev->dev);//取出platform_device中放进去的platform_data数据
	if (!pdata) {
		dev_err(fbdev->dev, "failed to get platform data\n");
		ret = -EINVAL;
		goto err_pdata;
	}

	fbdev->lcd = (struct s3cfb_lcd *)pdata->lcd;

	if (pdata->cfg_gpio)
		pdata->cfg_gpio(pdev);

	if (pdata->clk_on)
		pdata->clk_on(pdev, &fbdev->clock);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(fbdev->dev, "failed to get io memory region\n");
		ret = -EINVAL;
		goto err_io;
	}

	res = request_mem_region(res->start,
				 res->end - res->start + 1, pdev->name);
	if (!res) {
		dev_err(fbdev->dev, "failed to request io memory region\n");
		ret = -EINVAL;
		goto err_io;
	}
	//io动态映射
	fbdev->regs = ioremap(res->start, res->end - res->start + 1);
	if (!fbdev->regs) {
		dev_err(fbdev->dev, "failed to remap io region\n");
		ret = -EINVAL;
		goto err_mem;
	}

	s3cfb_set_vsync_interrupt(fbdev, 1);
	s3cfb_set_global_interrupt(fbdev, 1);
	s3cfb_init_global(fbdev);

	if (s3cfb_alloc_framebuffer(fbdev)) {
		ret = -ENOMEM;
		goto err_alloc;
	}

	if (s3cfb_register_framebuffer(fbdev)) {
		ret = -EINVAL;
		goto err_register;
	}

	s3cfb_set_clock(fbdev);
	s3cfb_set_window(fbdev, pdata->default_win, 1);

	s3cfb_display_on(fbdev);

	fbdev->irq = platform_get_irq(pdev, 0);
	if (request_irq(fbdev->irq, s3cfb_irq_frame, IRQF_SHARED,
			pdev->name, fbdev)) {
		dev_err(fbdev->dev, "request_irq failed\n");
		ret = -EINVAL;
		goto err_irq;
	}

#ifdef CONFIG_FB_S3C_LCD_INIT//查看内核.config文件是否有或者搜索该宏定义,注意
	if (pdata->backlight_on)//有些宏定义是在编译过程中生成的
		pdata->backlight_on(pdev);

	if (!bootloaderfb && pdata->reset_lcd)
		pdata->reset_lcd(pdev);
#endif

#ifdef CONFIG_HAS_EARLYSUSPEND
	fbdev->early_suspend.suspend = s3cfb_early_suspend;//挂起恢复相关的
	fbdev->early_suspend.resume = s3cfb_late_resume;
	fbdev->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB;
	register_early_suspend(&fbdev->early_suspend);
#endif

	ret = device_create_file(&(pdev->dev), &dev_attr_win_power);
	if (ret < 0)
		dev_err(fbdev->dev, "failed to add sysfs entries\n");//sys文件系统相关的

	dev_info(fbdev->dev, "registered successfully\n");

#if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO)//屏幕logo
	if (fb_prepare_logo( fbdev->fb[pdata->default_win], FB_ROTATE_UR)) {
		printk("Start display and show logo\n");
		/* Start display and show logo on boot */
		fb_set_cmap(&fbdev->fb[pdata->default_win]->cmap, fbdev->fb[pdata->default_win]);
		fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR);//可修改logo的位置
	}
#endif
	mdelay(100);
	if (pdata->backlight_on)
		pdata->backlight_on(pdev);

	return 0;

err_irq:
	s3cfb_display_off(fbdev);
	s3cfb_set_window(fbdev, pdata->default_win, 0);
	for (i = pdata->default_win;
			i < pdata->nr_wins + pdata->default_win; i++) {
		j = i % pdata->nr_wins;
		unregister_framebuffer(fbdev->fb[j]);
	}
err_register:
	for (i = 0; i < pdata->nr_wins; i++) {
		if (i == pdata->default_win)
			s3cfb_unmap_default_video_memory(fbdev->fb[i]);
		framebuffer_release(fbdev->fb[i]);
	}
	kfree(fbdev->fb);

err_alloc:
	iounmap(fbdev->regs);

err_mem:
	release_mem_region(res->start,
				 res->end - res->start + 1);

err_io:
	pdata->clk_off(pdev, &fbdev->clock);

err_pdata:
	regulator_disable(fbdev->regulator);

err_regulator:
	kfree(fbdev);

err_global:
	return ret;
}

(1)struct s3c_platform_fb
  这个结构体是fb的platform_data结构体,这个结构体变量就是platform设备的私有数据,这个数据在platform_device.device.platform_data中存储。在mach文件中去准备并填充这些数据,在probe函数中通过传参的platform_device指针取出来。

struct s3c_platform_fb *pdata;
pdata = to_fb_plat(&pdev->dev);

(2)struct s3cfb_global
  这个结构体主要作用是在驱动部分的2个文件(s3cfb.c和s3cfb_fimd6x.c)的函数中做数据传递用的。

struct s3cfb_global {
	/* general */
	void __iomem		*regs;
	struct mutex		lock;
	struct device		*dev;
	struct clk		*clock;
	struct regulator	*regulator;
	int			irq;
	struct fb_info		**fb;
	struct completion	fb_complete;

	/* fimd */
	int			enabled;
	int			dsi;
	int			interlace;
	enum s3cfb_output_t	output;
	enum s3cfb_rgb_mode_t	rgb_mode;
	struct s3cfb_lcd	*lcd;

#ifdef CONFIG_HAS_WAKELOCK
	struct early_suspend	early_suspend;
	struct wake_lock	idle_lock;
#endif

#ifdef CONFIG_CPU_FREQ
	struct notifier_block	freq_transition;
	struct notifier_block	freq_policy;
#endif
};

(3)struct resource

struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	struct resource *parent, *sibling, *child;
};

(4)regulator
功耗控制相关的

3、platform_data的传递过程

(1)to_fb_plat

pdata = to_fb_plat(&pdev->dev);
	
struct s3c_platform_fb *to_fb_plat(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);

	return (struct s3c_platform_fb *)pdev->dev.platform_data;
}

(2)s3cfb_set_platdata
kernel/arch/arm/plat-s5p/devs.c

void __init s3cfb_set_platdata(struct s3c_platform_fb *pd)
{
	struct s3c_platform_fb *npd;
	int i;

	if (!pd)
		pd = &default_fb_data;

	npd = kmemdup(pd, sizeof(struct s3c_platform_fb), GFP_KERNEL);
	if (!npd)
		printk(KERN_ERR "%s: no memory for platform data\n", __func__);
	else {
		for (i = 0; i < npd->nr_wins; i++)
			npd->nr_buffers[i] = 1;

		npd->nr_buffers[npd->default_win] = CONFIG_FB_S3C_NR_BUFFERS;

		s3cfb_get_clk_name(npd->clk_name);
		npd->clk_on = s3cfb_clk_on;
		npd->clk_off = s3cfb_clk_off;

		/* starting physical address of memory region */
		npd->pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMD, 1);
		/* size of memory region */
		npd->pmem_size = s5p_get_media_memsize_bank(S5P_MDEV_FIMD, 1);

		s3c_device_fb.dev.platform_data = npd;
	}
}

(3)smdkc110_machine_init(内核启动后执行的硬件初始化函数)
kernel/arch/arm/mach-s5pv210/mach-x210.c

static void __init smdkc110_machine_init(void)
#ifdef CONFIG_FB_S3C_EK070TN93
	smdkv210_backlight_off();
	s3cfb_set_platdata(&ek070tn93_fb_data);
#endif

4、struct s3cfb_lcd(设置屏幕的分辨率)

若想移植一个新的开发板,修改这个结构体的参数即可

//kernel/drivers/video/samsung/s3cfb.c  942行左右
fbdev->lcd = (struct s3cfb_lcd *)pdata->lcd; 

/*
 * struct s3cfb_lcd
 * @width:		horizontal resolution
 * @height:		vertical resolution
 * @p_width:	        width  lcd in mm
 * @p_height:	        height of lcd in mm
 * @bpp:		bits per pixel
 * @freq:		vframe frequency
 * @timing:		timing values
 * @polarity:		polarity settings
 * @init_ldi:		pointer to LDI init function
 *
*/
struct s3cfb_lcd {
	int	width;
	int	height;
	int	p_width;
	int	p_height;
	int	bpp;
	int	freq;
	struct	s3cfb_lcd_timing timing;
	struct	s3cfb_lcd_polarity polarity;

	void	(*init_ldi)(void);
	void	(*deinit_ldi)(void);
};
//kernel/arch/arm/mach-s5pv210/mach-x210.c 2103行
static void __init smdkc110_machine_init(void)
	s3cfb_set_platdata(&ek070tn93_fb_data);

static struct s3c_platform_fb ek070tn93_fb_data __initdata = {//mach-x210.c1107行
	.hw_ver	= 0x62,
	.nr_wins = 5,
	.default_win = CONFIG_FB_S3C_DEFAULT_WINDOW,
	.swap = FB_SWAP_WORD | FB_SWAP_HWORD,

	.lcd = &ek070tn93,//注意
	.cfg_gpio	= ek070tn93_cfg_gpio,
	.backlight_on	= ek070tn93_backlight_on,
	.backlight_onoff    = ek070tn93_backlight_off,
	.reset_lcd	= ek070tn93_reset_lcd,
};

static struct s3cfb_lcd ek070tn93 = {//mach-x210.c 947行
	.width = S5PV210_LCD_WIDTH,
	.height = S5PV210_LCD_HEIGHT,
	.bpp = 32,
	.freq = 60,

	.timing = {//时序参数
		.h_fp	= 40,
		.h_bp	= 88,
		.h_sw	= 128,
		.v_fp	= 1,
		.v_fpe	= 1,
		.v_bp	= 23,
		.v_bpe	= 1,
		.v_sw	= 4,
	},
	.polarity = {
		.rise_vclk = 1,
		.inv_hsync = 1,
		.inv_vsync = 1,
		.inv_vden = 0,
	},
};
#define AT070TN92       1//mach-x210.c 118行
#define VGA_800X600     2
#define VGA_1024X768    3
#define DISP_MODE       AT070TN92
//#define DISP_MODE       VGA_800X600
//#define DISP_MODE       VGA_1024X768

#if(DISP_MODE==VGA_800X600)//249行
#define S5PV210_LCD_WIDTH 800
#define S5PV210_LCD_HEIGHT 600
#elif(DISP_MODE==VGA_1024X768)
#define S5PV210_LCD_WIDTH 1024
#define S5PV210_LCD_HEIGHT 768
#else
#define S5PV210_LCD_WIDTH 800
#define S5PV210_LCD_HEIGHT 480
#endif
如果开发板屏幕分辨率参数不正确,修改上述的宏定义即可

5、pdata->cfg_gpio

if (pdata->cfg_gpio)
		pdata->cfg_gpio(pdev);
static struct s3c_platform_fb ek070tn93_fb_data		
		.cfg_gpio	= ek070tn93_cfg_gpio,

static void ek070tn93_cfg_gpio(struct platform_device *pdev)
{
	int i;

	for (i = 0; i < 8; i++) {
		s3c_gpio_cfgpin(S5PV210_GPF0(i), S3C_GPIO_SFN(2));
		s3c_gpio_setpull(S5PV210_GPF0(i), S3C_GPIO_PULL_NONE);
	}

	for (i = 0; i < 8; i++) {
		s3c_gpio_cfgpin(S5PV210_GPF1(i), S3C_GPIO_SFN(2));
		s3c_gpio_setpull(S5PV210_GPF1(i), S3C_GPIO_PULL_NONE);
	}

	for (i = 0; i < 8; i++) {
		s3c_gpio_cfgpin(S5PV210_GPF2(i), S3C_GPIO_SFN(2));
		s3c_gpio_setpull(S5PV210_GPF2(i), S3C_GPIO_PULL_NONE);
	}

	for (i = 0; i < 4; i++) {
		s3c_gpio_cfgpin(S5PV210_GPF3(i), S3C_GPIO_SFN(2));
		s3c_gpio_setpull(S5PV210_GPF3(i), S3C_GPIO_PULL_NONE);
	}

	/* mDNIe SEL: why we shall write 0x2 ? */
	writel(0x2, S5P_MDNIE_SEL);

	/* drive strength to max */
	writel(0xffffffff, S5PV210_GPF0_BASE + 0xc);
	writel(0xffffffff, S5PV210_GPF1_BASE + 0xc);
	writel(0xffffffff, S5PV210_GPF2_BASE + 0xc);
	writel(0x000000ff, S5PV210_GPF3_BASE + 0xc);
}		

6、pdata->clk_on

	if (pdata->clk_on)
		pdata->clk_on(pdev, &fbdev->clock);

这个参数是否为空查看:

static struct s3c_platform_fb ek070tn93_fb_data __initdata = {//1107行
	.hw_ver	= 0x62,
	.nr_wins = 5,
	.default_win = CONFIG_FB_S3C_DEFAULT_WINDOW,
	.swap = FB_SWAP_WORD | FB_SWAP_HWORD,

	.lcd = &ek070tn93,//注意
	.cfg_gpio	= ek070tn93_cfg_gpio,
	.backlight_on	= ek070tn93_backlight_on,
	.backlight_onoff    = ek070tn93_backlight_off,
	.reset_lcd	= ek070tn93_reset_lcd,
};

故得知是空的,所以LCD时钟设置在前面就已经打开了

7、resource的处理

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(fbdev->dev, "failed to get io memory region\n");
		ret = -EINVAL;
		goto err_io;
	}

(1)platform_device中提供resource结构体数组

(2)probe中platform_get_resource取出resource并且按FLAG分头处理

8、kernel/drivers/video/samsung/s3cfb_fimd6x.c

  是x210的lcd寄存器操作的所有相关函数,这些函数的参数都包括了struct s3cfb_global *ctrl结构体指针。
(1)s3cfb_set_vsync_interrupt

int s3cfb_set_vsync_interrupt(struct s3cfb_global *ctrl, int enable)
	s3cfb_set_vsync_interrupt(fbdev, 1);

(2)s3cfb_set_global_interrupt

int s3cfb_set_global_interrupt(struct s3cfb_global *ctrl, int enable)
	s3cfb_set_global_interrupt(fbdev, 1);

(3)s3cfb_init_global
s3cfb_init_global(fbdev);
上述两个进行了一些填充,未初始化完,在调用该函数初始化fbdev

static int s3cfb_init_global(struct s3cfb_global *ctrl)
{
	ctrl->output = OUTPUT_RGB;
	ctrl->rgb_mode = MODE_RGB_P;

	init_completion(&ctrl->fb_complete);
	mutex_init(&ctrl->lock);

	s3cfb_set_output(ctrl);
	s3cfb_set_display_mode(ctrl);
	s3cfb_set_polarity(ctrl);
	s3cfb_set_timing(ctrl);
	s3cfb_set_lcd_size(ctrl);

	return 0;
}

9、向框架注册该fb设备

kernel/drivers/video/samsung/s3cfb.c
(1)s3cfb_alloc_framebuffer//进行内存分配,分配后进行初始化

static int s3cfb_alloc_framebuffer(struct s3cfb_global *ctrl)
{
		for (i = 0; i < pdata->nr_wins; i++) {
			ctrl->fb[i] = framebuffer_alloc(sizeof(*ctrl->fb),
}							

(2)s3cfb_register_framebuffer//注册5个虚拟屏幕,有5个framebuffer设备

static int s3cfb_register_framebuffer(struct s3cfb_global *ctrl)
{
		for (i = pdata->default_win;
			i < pdata->nr_wins + pdata->default_win; i++) {//有5个虚拟屏幕
				j = i % pdata->nr_wins;
				ret = register_framebuffer(ctrl->fb[j]);
				if (ret) {
					dev_err(ctrl->dev, "failed to register "
							"framebuffer device\n");
					ret = -EINVAL;
					goto err_register_fb;
				}
	#ifndef CONFIG_FRAMEBUFFER_CONSOLE
				if (j == pdata->default_win) {
					s3cfb_check_var(&ctrl->fb[j]->var, ctrl->fb[j]);
					s3cfb_set_par(ctrl->fb[j]);
					s3cfb_draw_logo(ctrl->fb[j]);
				}
			}
}

虚拟屏幕:可理解为一个画面的显示由多个虚拟屏幕的画面叠加共同显示。

10、一些硬件操作

(1)s3cfb_set_clock,设置时钟相关的

s3cfb_set_clock(fbdev);

(2)s3cfb_set_window

s3cfb_set_window(fbdev, pdata->default_win, 1);//打开默认的那个window

(3)s3cfb_display_on

s3cfb_display_on(fbdev);

11、驱动中处理中断

fbdev->irq = platform_get_irq(pdev, 0);
if (request_irq(fbdev->irq, s3cfb_irq_frame, IRQF_SHARED,申请要使用的中断号
		pdev->name, fbdev)) {
	dev_err(fbdev->dev, "request_irq failed\n");
	ret = -EINVAL;
	goto err_irq;
}

12、logo显示

(1)kernel/drivers/video/samsung/s3cfb.c s3cfb_probe函数(1020行)

#if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO)//屏幕logo
	if (fb_prepare_logo( fbdev->fb[pdata->default_win], FB_ROTATE_UR)) {
		printk("Start display and show logo\n");
		/* Start display and show logo on boot */
		fb_set_cmap(&fbdev->fb[pdata->default_win]->cmap, fbdev->fb[pdata->default_win]);
		fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR);
	}
#endif

(2)

int fb_prepare_logo(struct fb_info *info, int rotate)
{
	int depth = fb_get_color_depth(&info->var, &info->fix);//颜色由几个字节组成
	unsigned int yres;

	memset(&fb_logo, 0, sizeof(struct logo_data));

	if (info->flags & FBINFO_MISC_TILEBLITTING ||//错误校验
	    info->flags & FBINFO_MODULE)
		return 0;

	if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) {//错误校验
		depth = info->var.blue.length;
		if (info->var.red.length < depth)
			depth = info->var.red.length;
		if (info->var.green.length < depth)
			depth = info->var.green.length;
	}

	if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR && depth > 4) {
		/* assume console colormap */
		depth = 4;
	}

	/* Return if no suitable logo was found */
	fb_logo.logo = fb_find_logo(depth);//找到真正的logo,该函数内部是关键

	if (!fb_logo.logo) {
		return 0;
	}

	if (rotate == FB_ROTATE_UR || rotate == FB_ROTATE_UD)
		yres = info->var.yres;
	else
		yres = info->var.xres;

	if (fb_logo.logo->height > yres) {
		fb_logo.logo = NULL;
		return 0;
	}

	/* What depth we asked for might be different from what we get */
	if (fb_logo.logo->type == LINUX_LOGO_CLUT224)
		fb_logo.depth = 8;
	else if (fb_logo.logo->type == LINUX_LOGO_VGA16)
		fb_logo.depth = 4;
	else
		fb_logo.depth = 1;


 	if (fb_logo.depth > 4 && depth > 4) {
 		switch (info->fix.visual) {
 		case FB_VISUAL_TRUECOLOR:
 			fb_logo.needs_truepalette = 1;
 			break;
 		case FB_VISUAL_DIRECTCOLOR:
 			fb_logo.needs_directpalette = 1;
 			fb_logo.needs_cmapreset = 1;
 			break;
 		case FB_VISUAL_PSEUDOCOLOR:
 			fb_logo.needs_cmapreset = 1;
 			break;
 		}
 	}

	return fb_prepare_extra_logos(info, fb_logo.logo->height, yres);
}
const struct linux_logo * __init_refok fb_find_logo(int depth)
{
	if (depth >= 8) {
#ifdef CONFIG_LOGO_LINUX_CLUT224
		/* Generic Linux logo */
		logo = &logo_linux_clut224;
#endif
#ifdef CONFIG_LOGO_LINUX_FULL_CLUT224
		/* Full screen Linux logo */
		logo = &logo_linux_full_clut224;//这个就是我们使用的那个
#endif
// lqm added
#ifdef CONFIG_LOGO_X210_CLUT224
/* x210 android logo */
logo = &logo_x210_clut224;
#endif
}

  logo的数据是不在内核源码中的,而是在一个文件夹中以文件的形式单独存在的。kernel/drivers/video/logo以.ppm文件格式存在

(3)

fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR);也要重点分析
											logo旋转角度数
int fb_show_logo(struct fb_info *info, int rotate)
{
	int y;

	y = fb_show_logo_line(info, rotate, fb_logo.logo, 0,
			      num_online_cpus());
	y = fb_show_extra_logos(info, y, rotate);

	return y;
}

13、backlight点亮

	if (pdata->backlight_on)
		pdata->backlight_on(pdev);

static struct s3c_platform_fb ek070tn93_fb_data
		.backlight_on	= ek070tn93_backlight_on,
		.backlight_onoff    = ek070tn93_backlight_off,
		
static int ek070tn93_backlight_on(struct platform_device *pdev)
{
	/* backlight enable pin */
	s3c_gpio_cfgpin(S5PV210_GPF3(5), S3C_GPIO_OUTPUT);
	s3c_gpio_setpull(S5PV210_GPF3(5), S3C_GPIO_PULL_UP);
	gpio_set_value(S5PV210_GPF3(5), 1);

	return 0;
}

static int ek070tn93_backlight_off(struct platform_device *pdev, int onoff)
{
	/* backlight enable pin */
	s3c_gpio_cfgpin(S5PV210_GPF3(5), S3C_GPIO_OUTPUT);
	s3c_gpio_setpull(S5PV210_GPF3(5), S3C_GPIO_PULL_DOWN);
	gpio_set_value(S5PV210_GPF3(5), 0);

	/* LCD_5V */
	s3c_gpio_cfgpin(S5PV210_GPH0(5), S3C_GPIO_OUTPUT);
	s3c_gpio_setpull(S5PV210_GPH0(5), S3C_GPIO_PULL_DOWN);
	gpio_set_value(S5PV210_GPH0(5), 0);

	/* LCD_33 */
	s3c_gpio_cfgpin(S5PV210_GPH2(0), S3C_GPIO_OUTPUT);
	s3c_gpio_setpull(S5PV210_GPH2(0), S3C_GPIO_PULL_UP);
	gpio_set_value(S5PV210_GPH2(0), 1);


	return 0;
}

六、应用层为何不能设置分辨率

kernel/include/linux/fb.h 253行

struct fb_var_screeninfo {
	__u32 xres;			/* visible resolution		*/
	__u32 yres;
	__u32 xres_virtual;		/* virtual resolution		*/
	__u32 yres_virtual;
	__u32 xoffset;			/* offset from virtual to visible */
	__u32 yoffset;			/* resolution			*/

	__u32 bits_per_pixel;		/* guess what			*/
	__u32 grayscale;		/* != 0 Graylevels instead of colors */

	struct fb_bitfield red;		/* bitfield in fb mem if true color, */
	struct fb_bitfield green;	/* else only length is significant */
	struct fb_bitfield blue;
	struct fb_bitfield transp;	/* transparency			*/	

	__u32 nonstd;			/* != 0 Non standard pixel format */

	__u32 activate;			/* see FB_ACTIVATE_*		*/

	__u32 height;			/* height of picture in mm    */
	__u32 width;			/* width of picture in mm     */

	__u32 accel_flags;		/* (OBSOLETE) see fb_info.flags */

	/* Timing: All values in pixclocks, except pixclock (of course) */
	__u32 pixclock;			/* pixel clock in ps (pico seconds) */
	__u32 left_margin;		/* time from sync to picture	*/
	__u32 right_margin;		/* time from picture to sync	*/
	__u32 upper_margin;		/* time from sync to picture	*/
	__u32 lower_margin;
	__u32 hsync_len;		/* length of horizontal sync	*/
	__u32 vsync_len;		/* length of vertical sync	*/
	__u32 sync;			/* see FB_SYNC_*		*/
	__u32 vmode;			/* see FB_VMODE_*		*/
	__u32 rotate;			/* angle we rotate counter clockwise */
	__u32 reserved[5];		/* Reserved for future compatibility */
};

1、问题描述

(1)在上边开头的内容中我们试图在应用层设置分辨率失败了,原因何在?

(2)定位问题:肯定是驱动的事儿

(3)进一步驱动中定位:ioctl部分的事儿

2、fb的ioctl部分

kernel/drivers/video/fbmem.c 1423行

static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write,
	.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = fb_compat_ioctl,
#endif
	.mmap =		fb_mmap,
	.open =		fb_open,
	.release =	fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
	.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	.fsync =	fb_deferred_io_fsync,
#endif
};

static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct inode *inode = file->f_path.dentry->d_inode;
	int fbidx = iminor(inode);//得到次设备号
	struct fb_info *info = registered_fb[fbidx];//registered_fb其内都是已注册的设备

	return do_fb_ioctl(info, cmd, arg);
}

static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
			unsigned long arg)
{
	struct fb_ops *fb;
	struct fb_var_screeninfo var;
	struct fb_fix_screeninfo fix;
	struct fb_con2fbmap con2fb;
	struct fb_cmap cmap_from;
	struct fb_cmap_user cmap;
	struct fb_event event;
	void __user *argp = (void __user *)arg;
	long ret = 0;

	switch (cmd) {//不同的命令对应不同的功能
	case FBIOGET_VSCREENINFO:
		if (!lock_fb_info(info))
			return -ENODEV;
		var = info->var;
		unlock_fb_info(info);

		ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
		break;
	case FBIOPUT_VSCREENINFO:
		if (copy_from_user(&var, argp, sizeof(var)))
			return -EFAULT;
		if (!lock_fb_info(info))
			return -ENODEV;
		acquire_console_sem();//获取,进行锁定
		info->flags |= FBINFO_MISC_USEREVENT;
		ret = fb_set_var(info, &var);
		info->flags &= ~FBINFO_MISC_USEREVENT;
		release_console_sem();//释放
		unlock_fb_info(info);
		if (!ret && copy_to_user(argp, &var, sizeof(var)))
			ret = -EFAULT;//我们给的参数若非法,则不会进行修改
		break;			//即使应用修改了,再次读取还是未修改的	
	case FBIOGET_FSCREENINFO:
		if (!lock_fb_info(info))
			return -ENODEV;
		fix = info->fix;
		unlock_fb_info(info);

		ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
		break;
	case FBIOPUTCMAP:
		if (copy_from_user(&cmap, argp, sizeof(cmap)))
			return -EFAULT;
		ret = fb_set_user_cmap(&cmap, info);
		break;
	case FBIOGETCMAP:
		if (copy_from_user(&cmap, argp, sizeof(cmap)))
			return -EFAULT;
		if (!lock_fb_info(info))
			return -ENODEV;
		cmap_from = info->cmap;
		unlock_fb_info(info);
		ret = fb_cmap_to_user(&cmap_from, &cmap);
		break;
	case FBIOPAN_DISPLAY:
		if (copy_from_user(&var, argp, sizeof(var)))
			return -EFAULT;
		if (!lock_fb_info(info))
			return -ENODEV;
		acquire_console_sem();
		ret = fb_pan_display(info, &var);
		release_console_sem();
		unlock_fb_info(info);
		if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))
			return -EFAULT;
		break;
	case FBIO_CURSOR:
		ret = -EINVAL;
		break;
	case FBIOGET_CON2FBMAP:
		if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
			return -EFAULT;
		if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
			return -EINVAL;
		con2fb.framebuffer = -1;
		event.data = &con2fb;
		if (!lock_fb_info(info))
			return -ENODEV;
		event.info = info;
		fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);
		unlock_fb_info(info);
		ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;
		break;
	case FBIOPUT_CON2FBMAP:
		if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
			return -EFAULT;
		if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
			return -EINVAL;
		if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
			return -EINVAL;
		if (!registered_fb[con2fb.framebuffer])
			request_module("fb%d", con2fb.framebuffer);
		if (!registered_fb[con2fb.framebuffer]) {
			ret = -EINVAL;
			break;
		}
		event.data = &con2fb;
		if (!lock_fb_info(info))
			return -ENODEV;
		event.info = info;
		ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event);
		unlock_fb_info(info);
		break;
	case FBIOBLANK:
		if (!lock_fb_info(info))
			return -ENODEV;
		acquire_console_sem();
		info->flags |= FBINFO_MISC_USEREVENT;
		ret = fb_blank(info, arg);
		info->flags &= ~FBINFO_MISC_USEREVENT;
		release_console_sem();
		unlock_fb_info(info);
		break;
	default:
		if (!lock_fb_info(info))
			return -ENODEV;
		fb = info->fbops;
		if (fb->fb_ioctl)
			ret = fb->fb_ioctl(info, cmd, arg);
		else
			ret = -ENOTTY;
		unlock_fb_info(info);
	}
	return ret;
}

(1)fb是典型的字符设备驱动

(2)ioctl功能实现分为2部分,在驱动框架部分和驱动部分各有一半

(3)一路追踪找问题

fbmem.c文件中:
fbmem_init(void)//1706行
	register_chrdev(FB_MAJOR,"fb",&fb_fops)//被调用
		static const struct file_operations fb_fops
			.unlocked_ioctl = fb_ioctl,
			static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
				do_fb_ioctl(info, cmd, arg);
					ret = fb_set_var(info, &var);
					fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var)
						info->fbops->fb_check_var(s3cfb.c中的s3cfb_check_var)//检查是否合法

kernel/kernel/include/linux/fb.h

struct fb_ops {
	/* open/release and usage marking */
	struct module *owner;
	int (*fb_open)(struct fb_info *info, int user);
	int (*fb_release)(struct fb_info *info, int user);

	/* For framebuffers with strange non linear layouts or that do not
	 * work with normal memory mapped access
	 */
	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);

	/* checks var and eventually tweaks it to something supported,
	 * DO NOT MODIFY PAR */
	int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);

	/* set the video mode according to info->var */
	int (*fb_set_par)(struct fb_info *info);

	/* set color register */
	int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
			    unsigned blue, unsigned transp, struct fb_info *info);

	/* set color registers in batch */
	int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);

	/* blank display */
	int (*fb_blank)(int blank, struct fb_info *info);

	/* pan display */
	int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);

	/* Draws a rectangle */
	void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
	/* Copy data from area to another */
	void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
	/* Draws a image to the display */
	void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);

	/* Draws cursor */
	int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);

	/* Rotates the display */
	void (*fb_rotate)(struct fb_info *info, int angle);

	/* wait for blit idle, optional */
	int (*fb_sync)(struct fb_info *info);

	/* perform fb specific ioctl (optional) */
	int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
			unsigned long arg);

	/* Handle 32bit compat ioctl (optional) */
	int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
			unsigned long arg);

	/* perform fb specific mmap */
	int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);

	/* get capability given var */
	void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
			    struct fb_var_screeninfo *var);

	/* teardown any resources to do with this framebuffer */
	void (*fb_destroy)(struct fb_info *info);
};

修改分辨率:
kernel/arch/arm/mach-s5pv210/mach-x210.c 249行开始

#if(DISP_MODE==VGA_800X600)
#define S5PV210_LCD_WIDTH 800
#define S5PV210_LCD_HEIGHT 600
#elif(DISP_MODE==VGA_1024X768)
#define S5PV210_LCD_WIDTH 1024
#define S5PV210_LCD_HEIGHT 768
#else
#define S5PV210_LCD_WIDTH 800
#define S5PV210_LCD_HEIGHT 480
#endif

在这里插入图片描述

七、折腾内核的启动logo

参考教程:https://blog.csdn.net/fengyuwuzu0519/article/details/73469807

1、自定义内核启动logo

  kernel/drivers/video/samsung/s3cfb.c 1020行
  除了参考教程使用新的文件名称,修改内核配置,也可以将自己生成的.ppm文件改成与原文件名字相同的文件,替换掉该文件,然后直接编译烧录内核即可,也可参考该目录(kernel/drivers/video/logo)下的Kconfig文件,自己编写生成新的一个make menuconfig选项。
在这里插入图片描述
Kconfig文件:
在这里插入图片描述
Makefile文件:
在这里插入图片描述

#if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO)//屏幕logo
	if (fb_prepare_logo( fbdev->fb[pdata->default_win], FB_ROTATE_UR)) {
		printk("Start display and show logo\n");
		/* Start display and show logo on boot */
		fb_set_cmap(&fbdev->fb[pdata->default_win]->cmap, fbdev->fb[pdata->default_win]);
		fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR);
	}
#endif

看fb_prepare_logo函数,内有注释,其内的fb_logo.logo = fb_find_logo(depth);//找到真正的logo

logo的数据是不在内核源码中的,而是在一个文件夹中以文件的形式单独存在的。
kernel/drivers/video/logo/以.ppm文件格式存在

fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR);也要重点分析
logo旋转角度数

2、让logo显示在屏幕中央

fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR);//可修改logo的位置

y = fb_show_logo_line(info, rotate, fb_logo.logo, 0, num_online_cpus());
				  
kernel/drivers/video/fbmem.c 471行
image.dx = 0;	//logo显示的起始坐标的x值
image.dy = y;   //logo显示的起始坐标的y值
image.width = logo->width;//logo的大小
image.height = logo->height;

若要显示到中央,则坐标计算:屏幕的x,y值减去图像的x,y值除以二即可
.ppm文件开头的第二行两个数字即为图片的分辨率

image.dx = (info->var.xres - logo->width) / 2;
image.dy = (info->var.yres - logo->height) / 2;
image.width = logo->width;//logo的大小
image.height = logo->height;

注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来并且引用了部分他人博客的内容,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。

  • 12
    点赞
  • 85
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小嵌同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值