linux 驱动-fb子系统

    本文用于描述 linux 下的 LCD Frambuffer 框架,本文的所有试验均使用 imx6ull 进行,内核版本 Linux4.8

1. 基本概念

1.1 像素(pixel)

    像素是图像显示的基本单位。像素是 lcd 显示的最小单位,每一个像素可以显示不同的颜色,颜色的亮度等。像素的颜色由三个最基本的颜色绿来表示,常见的编码方式有 RGB888/RGB565/RGB555

  • RGB888: (red: 8bitgreen: 8bitblue: 8bit)
  • RGB565: (red: 5bitgreen: 6bitblue: 5bit)
  • RGB555: (red: 5bitgreen: 5bitblue: 5bit)

1.2 显示接口

1.2.1 MPU(MIPI DBI) 接口

    也称为 MCU 接口,由于其常用于单片机领域而得名。 MCU-LCD 接口的标准术语是 Intel 提出的 8080 总线标准,因此在很多文档中用 I80 来指 MCU-LCD 屏。8080 是一种并行接, 别称很多 DBI(Data Bus interface) 数据总线接口, 微处理器 MPU 接口,MCU 接口,CPU 接口的,实际上都是一回事。它是一种并行、异步、半双工通信协议,作用是用于外扩 RAM、ROM 后来又将其应用到了 LCD 接口。数据传输位宽有 8 位、9 位、16 位、18 位、24 位。通常该接口的显示模块被称为LCM, 它由 LCD 图形显存(GRAM) + LCD 控制器 + LCD 屏幕组成。

  • 优点: 控制简单方便,无需时钟和同步信号。
  • 缺点:GRAM 很贵 ,所以难以做到大屏( 3.8 以上)

    其接口时序如下:

8080接口时序

引脚 名称 说明
CS 片选信号 低电平有效
RS 数据命令 1:数据 0:命令
WR 读数据/命令 上升沿读取数据/命令
RD 写数据/命令 上升沿写入数据/命令

1.2.2 RGB(MIPI DPI) 接口

    是一种并行接口,采用普通的同步、时钟、信号线来传输特定数据,采用 SPI/I2C 等控制线辅助进行命令控制。采用这种接口的 LCD 内部无 GRAM. LCD 图形显存(GRAM) + LCD 控制器 集成在 soc 端。因此 LCD 模组可以将成本控制的更低,屏幕做的更大,多用于手机平板行业。采用同步实时传输的方式,也使得它的显示帧率更快。本文所用 imx6ull 的接口也是该接口. 其接口时序如下:

RGB接口时序

名称 含义
Vsync 垂直/场同步信号
VBP 垂直后沿,在垂直同步信号之后的无效行数,显示的上边框
VFP 垂直前沿,下一次垂直同步信号来之前的无效行数,显示下边框
Hsync 水平/行同步信号
HBP 水平前肩,水平同步信号之后的无效像素个数,显示左边框框
HFP 水平后肩,下一次水平同步信号来之前的无效像素个数,显示右边边框
DE 有效数据选通信号也称数据使能信号

1.2.3 MIPI DSI 接口

    当我们口述mipi接口的时候,大多数描述的都是这个接口。显示串行接口 (DSI) 规范定义了主机处理器和外围设备之间的协议,这些协议符合 MIPI 联盟的移动设备接口规范。为了让我们的图像传输的更快,更稳定,MIPI 联盟定义了该接口,其中包括 DBI-2 [2]、DPI-2 [3] 和 DCS [1] 定义的像素格式和命令集。可以简单理解为 DPI 信号的又一层封装。当前我们用到的所有便携式设备的屏幕,手机平板等都是采用该接口来传输. 关于其详细描述可阅读 MIPI Alliance Standard for Display Serial Interface 手册。由于其泛用性后续会专门出一篇文章用于描述该接口.除此之外还有 i2c/spi、lvds、edp、dp 等接口,由于笔者未使用或不常使用这些接口就不多介绍了。

1.3 Linux FrameBuffer 子系统

    FrameBuffer 也叫帧缓冲,即一帧图片所需要的内存空间。Framebuffer 是一个抽象的概念,在 GPU 能够访问的空间范围内( GPU 的物理地址空间),任意分配一段内存(或显存),都可以作为 Framebuffer 使用,只需要在分配后将该内存区域信息,设置到显卡相关的寄存器中即可。在 linux 中我们将要显示的数据写入显存,FB 框架就会帮我们显示出对应的图片。需要注意的是,FB 框架并不会直接显示 FremFuffer 中设置的像素颜色,而是会根据设置的值到调色板中去找到对应颜色再进行显示。Linux FrameBuffer 子系统的本质就是一个主设备号为 29 字符设备,在文件系统中描述为 /dev/fbn。整体框架如下所示.

RGB接口时序

    对于 fb 驱动,分配 fb_info, 设置 fb_info, 平台硬件相关操作,注册 fb_info。可见驱动的核心在 fb_info.相关数据结构如下所述。

2. 重要数据结构

2.1 fb_info

    frambuffer 子系统核心数据接口,每一个 fb 设备就是一个 fb_info 结构, 包含该 fb 设备的全部信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct fb_info {
atomic_t count; //打开计数
int node; //存放该 fb 在 fb 数组中的下标,也可以理解为次设备信息
int flags; //标志位

struct fb_var_screeninfo var; /* 可变的参数信息 */
struct fb_fix_screeninfo fix; /* 固定的参数信息*/
struct fb_monspecs monspecs;
struct work_struct queue;
struct fb_pixmap pixmap; // 用于表示一个像素图像(pixmap)的信息
struct fb_pixmap sprite;
struct fb_cmap cmap;
struct list_head modelist;
struct fb_videomode *mode; /* 当前的显示模式*/

#if IS_ENABLED(CONFIG_FB_BACKLIGHT)
struct backlight_device *bl_dev; //对应的背光设备

struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS]; //背光调整
#endif

struct fb_ops *fbops; //基于各个平台正真的硬件操作函数接口
struct device *device; //父设备节点
struct device *dev; //设备节点

union {
char __iomem *screen_base; // 虚拟地址
char *screen_buffer;
};
unsigned long screen_size; // 虚拟内存大小
void *pseudo_palette; // 伪16色颜色表
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; //当前状态,挂起或者复位
......

2.2 fb_var_screeninfo

    可以通过该结构获取到 LCD 的可变参数信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// struct fb_info的成员(可变参数),其记录用户可修改的显示控制器的参数,包括分
// 辨率和每个像素点的比特数,其成员需要在驱动程序中初始化和设置
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缓存的RGB位域**********/
struct fb_bitfield red; /* fb缓存的红色位域 */
struct fb_bitfield green; /* fb缓存的绿色位域 */
struct fb_bitfield blue; /* fb缓存的蓝色位域 */
struct fb_bitfield transp; /* 透明度 =0 */

__u32 nonstd; /* 非标准像素格式时应该为非0值 (标志像素格式时 nonstd=0) */

__u32 activate; /* 查看宏 FB_ACTIVATE_NOW */

__u32 height; /* 高度 */
__u32 width; /* 宽度 */

__u32 accel_flags; /* 查看fb_info.flags */


/************这参数必须通过查看LCD数据手册得到**************/
/* Timing: All values in pixclocks, except pixclock (of course) */
__u32 pixclock; /*像素时钟(皮秒),pixclock=1/Dclk=... */

/* 行切换,从同步到绘图之间的延迟即HFPD(有效数据之后无效的像素的个数) ,对应于LCD数据手册的Hsyn的front-porch*/
__u32 left_margin;

/*行切换,从绘图到同步之间的延迟即HBPD(Hsyn脉冲下降沿之后的无效像素的个数) ,对应于LCD数据手册的Hsyn的back-porch*/
__u32 right_margin;

/*帧切换,从同步到绘图之间的延迟即VFPD(有效数据之后还要经历的无效行数(之后是下一帧数据)) ,对应于LCD数据手册的Vsyn的front-porch*/
__u32 upper_margin;

/*帧切换,从绘图到同步之间的延迟即VBPD(Vsyn脉冲下降沿之后还要经历的无效行数) ,对应于LCD数据手册的Vsyn的back-porch */
__u32 lower_margin;

/*水平同步的长度即HSPW(Hsyn信号的脉冲宽度),对应于LCD数据手册的 Hsyn 的 pulse Width */
__u32 hsync_len;

/*垂直同步的长度即VSPW(Vsyn信号的脉冲宽度),对应于LCD数据手册的Vsyn的pulse Width */
__u32 vsync_len;

__u32 sync; /* 查看宏FB_SYNC_*/
__u32 vmode; /* 查看宏FB_VMODE_ */
__u32 rotate; /* 顺时钟旋转的角度 */
__u32 reserved[5];
};
1
2
3
4
5
struct fb_bitfield {        
__u32 offset; /* 像素的起始位置 */
__u32 length; /* 像素的长度 */
__u32 msb_right; /* !0 则从高位往低位获取数据*/
}; /* right */

2.3 fb_fix_screeninfo

    lcd 硬件的固定参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// struct fb_info 的成员(固定参数),其记录用户不能修改的显示控制器的参数,如屏幕缓冲区物理地址,长度
struct fb_fix_screeninfo {

char id[16]; // 字符串形式的标识符

// fb 缓冲内存的开始地址(物理地址),它一般是作为 dma_alloc_writecombine 的参数,该函数会将物理地址存放在该变量中
unsigned long smem_start;

__u32 smem_len; /* fb缓冲的长度,等于max_xres *max_yres*max_bpp/8 */
__u32 type; /* 查看宏 FB_TYPE_ FB_TYPE_PACKED_PIXELS=0 */
__u32 type_aux; /* 分界,=0 */
__u32 visual; /* 查看宏 FB_VISUAL_,用于记录屏幕使用的色彩模式,一般是FB_VISUAL_TRUECOLOR(真彩色) */
__u16 xpanstep; /* 如果没有硬件 panning,=0 */
__u16 ypanstep; /* 如果没有硬件 panning,=0 */
__u16 ywrapstep; /* 如果没有硬件 panning,=0 */
__u32 line_length; /* 一行的字节数 */
unsigned long mmio_start; /* 内存映射的I/O的开始位置 */

/* (physical address) */
__u32 mmio_len; /* 内存映射的I/O的长度 */
__u32 accel; /* = FB_ACCEL_NONE */

__u16 reserved[3]; /* Reserved for future compatibility */
};

2.4 fb_ops

    基于各个平台正真的操作函数接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct fb_ops {
struct module *owner;
......
/* 调色版 */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);

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

/* 画一个矩形 */
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
/* 把一个地方的数据 copy 到另一个地方 */
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
/* 画一张图片 */
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
......
};

2.5 fb_pixmap

用于表示一个像素图像 pixmap 的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct fb_pixmap {
unsigned int bpp; /* 每个像素使用的位数(Bits per pixel) */
unsigned int depth; /* 每个像素使用的位数(包括额外的位数) */
unsigned int size; /* 像素数据缓冲区的大小(以字节为单位)。 */
unsigned int offset; /* 从像素数据缓冲区的开头到第一个像素的偏移量(以字节为单位) */

// 缓冲区的对齐方式(以字节为单位), 在常见的像素格式中,buf_align 可能会被设置为以下值:
// 1 字节对齐:对于 8 位和 16 位像素格式,通常设置为 1。
// 2 字节对齐:对于 24 位和 32 位像素格式,通常设置为 2。
// 4 字节对齐:对于 64 位像素格式,通常设置为 4。
unsigned int buf_align;


// 在常见的像素格式中,scan_align 可能会被设置为以下值:
// 1 字节对齐:对于 8 位像素格式,通常设置为 1。
// 2 字节对齐:对于 16 位像素格式,通常设置为 2。
// 4 字节对齐:对于 24 位和 32 位像素格式,通常设置为 4。
// 8 字节对齐:对于 64 位像素格式,通常设置为 8。
unsigned int scan_align; /* 扫描线的对齐方式(以字节为单位) */
unsigned int access_align; /* 访问的对齐方式(以字节为单位) */
unsigned int flags; /* 用于标识像素数据的标志位 */
void *addr; /* 指向像素数据缓冲区的虚拟地址 */
struct page **pages; /* 一个指针数组,每个指针指向一块物理内存页面的 struct page 结构体 */
};

2.6 fb_videomode

非常重要的结构体, 描述一个屏幕的参数, 如分辨率,前后肩等.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct fb_videomode {
const char *name; /* optional */
u32 refresh; // 刷新率
u32 xres; // 屏幕像素宽度
u32 yres; // 屏幕像素高度
u32 pixclock; // 像素时钟
u32 left_margin; // 左边框, 即 HBP
u32 right_margin; // 右边框, 即 HFP
u32 upper_margin; // 上边框, 即 VBP
u32 lower_margin; // 下边框, 即 VFP
u32 hsync_len; // 水平同步信号, hsync
u32 vsync_len; // 垂直同步信号, vsync
u32 sync; // 帧同步信息
u32 vmode; // 显示模式
u32 flag; // mode 标志位, 如果是上层设置的 mode , 则应该设置 FB_MODE_USER 标志位.
};

3. 源码阅读

3.1 fbmem_init

该函数的实现完成了 3 件事情:

  • 在 proc/ 下创建 fb 设备
  • 注册一个主设备号为 29 的字符设备
  • 创建一个设备类 /sys/graphics
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static int __init fbmem_init(void)
{
int ret;

// proc 子系统, 保留以后分析
if (!proc_create("fb", 0, NULL, &fb_proc_fops))
return -ENOMEM;

// 注册一个主设备号为 29 的字符设备
// #define FB_MAJOR 29
ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);
if (ret) {
printk("unable to get major %d for fb devs\n", FB_MAJOR);
goto err_chrdev;
}

// 创建一个设备类 graphics
fb_class = class_create(THIS_MODULE, "graphics");
if (IS_ERR(fb_class)) {
ret = PTR_ERR(fb_class);
pr_warn("Unable to create fb class; errno = %d\n", ret);
fb_class = NULL;
goto err_class;
}
return 0;

err_class:
unregister_chrdev(FB_MAJOR, "fb");
err_chrdev:
remove_proc_entry("fb", NULL);
return ret;
}

3.2 register_framebuffer

注册一个真缓冲设备, 其主体结构为 fb_info.

1
2
3
4
5
6
7
8
9
10
11
int register_framebuffer(struct fb_info *fb_info)
{
int ret;

mutex_lock(&registration_lock);
ret = do_register_framebuffer(fb_info);
mutex_unlock(&registration_lock);

return ret;
}
EXPORT_SYMBOL(register_framebuffer);

3.2.1 do_register_framebuffer

注册一个 fb_info, 流程如下

  • 1, 字节序检测根据, 返回结果查看当前设置的 fb 字节序是否有问题.
  • 2, 检测 pci 内存, 移除内存重叠的 fb 设备.
  • 3, 检测当前 fb_info 的注册是否超过上限
  • 4, 创建 fb 设备, /sys/class/fbx 以及 /dev/fbx, 设置私有数据, 创建一系列属性文件节点.
  • 5, 初始化像素信息, 包括像素缓冲区大小, 对齐方式等.
  • 6, 从可变参数中获取屏幕信息, 转换为 mode 结构之后, 注册到 fb_info->modlist
  • 7, 以次设备号为数组下标, 注册 fb_info 到 registered_fb 全局数组中
  • 8, 调用 fb 内核通知链并发出 FB_EVENT_FB_REGISTERED 事件.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
static int do_register_framebuffer(struct fb_info *fb_info)
{
int i, ret;
struct fb_event event;
struct fb_videomode mode;

// 字节序检测根据, 返回结果查看当前设置的 fb 字节序是否有问题.
if (fb_check_foreignness(fb_info))
return -ENOSYS;

// 移除内存重叠的 fb 设备.
ret = do_remove_conflicting_framebuffers(fb_info->apertures,
fb_info->fix.id,
fb_is_primary_device(fb_info));
if (ret)
return ret;

// 超过 fb 设备上限返回 err
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);

// 在 fb_class 创建 fb 设备, 即 /sys/class/fbx 以及 /dev/fbx
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); // 设置私有数据, 创建一系列属性文件等.

// 设置像素信息
// #define FBPIXMAPSIZE (1024 * 8)
if (fb_info->pixmap.addr == NULL) {
fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
if (fb_info->pixmap.addr) {
fb_info->pixmap.size = FBPIXMAPSIZE; // 像素数据缓冲区的大小(以字节为单位), 默认大小 (1024 * 8)
fb_info->pixmap.buf_align = 1; // 素数据缓冲区的对齐方式, 默认设置 1 字节对齐
fb_info->pixmap.scan_align = 1; // 素数据行在内存中的对齐方式, 默认设置为 1 字节对齐
fb_info->pixmap.access_align = 32; // 访问的对齐方式, 默认为 32 字节对齐
fb_info->pixmap.flags = FB_PIXMAP_DEFAULT; // 设置标志位表示该 pinxmap 的初始化采用默认的方式
}
}
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); // 初始化显示模式列表

// 终端切换(VT switch)时是否要暂停当前进程的执行。
if (fb_info->skip_vt_switch)
pm_vt_switch_required(fb_info->dev, false);
else
pm_vt_switch_required(fb_info->dev, true);

// 从可变参数结构体, 中将屏幕信息转换为 mode 结构
fb_var_to_videomode(&mode, &fb_info->var);
// 将 mode 结构挂接到 modelist
fb_add_videomode(&mode, &fb_info->modelist);

registered_fb[i] = fb_info; // 注册 fb_info 到 registered_fb

// event.info 初始化, 用于 fb 内核通知链
event.info = fb_info;
if (!lockless_register_fb)
console_lock();
if (!lock_fb_info(fb_info)) {
if (!lockless_register_fb)
console_unlock();
return -ENODEV;
}

// 调用 fb 内核通知链并发出 FB_EVENT_FB_REGISTERED 事件.
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
unlock_fb_info(fb_info);
if (!lockless_register_fb)
console_unlock();
return 0;
}

3.2.2 fb_check_foreignness

字节序检测根据, 返回结果查看当前设置的字节序是否有问题.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static int fb_check_foreignness(struct fb_info *fi)
{
// #define FBINFO_FOREIGN_ENDIAN 0x100000
// 这个宏在 Linux 内核中的 Framebuffer 驱动中被定义,用于指定当前 framebuffer 设备是否采用当前处理器的字节序
// 1,则表示 framebuffer 字节序与当前处理器的字节序相反。
// 0,则表示 framebuffer 字节序与当前处理器的字节序相同
const bool foreign_endian = fi->flags & FBINFO_FOREIGN_ENDIAN; // 获取该标志位状态, 并清空其他标志位

fi->flags &= ~FBINFO_FOREIGN_ENDIAN; // 清空该标志位

// FBINFO_BE_MATH 用于指定当前 framebuffer 设备是否采用大端字节序。
// 1,表示该设备采用大端字节序
// 0,表示该设备采用小端字节序

#ifdef __BIG_ENDIAN // __BIG_ENDIAN 用于检查当前处理器是否采用大端字节序。
// 当前处理器是大端字节序, FBINFO_FOREIGN_ENDIAN 被置 1, framebuffer 设备采用小字节序.
// 当前处理器是大端字节序, FBINFO_FOREIGN_ENDIAN 被置 0, framebuffer 设备采用大字节序
fi->flags |= foreign_endian ? 0 : FBINFO_BE_MATH;
#else
// 当前处理器是小端字节序, FBINFO_FOREIGN_ENDIAN 被置 1, framebuffer 设备采用大字节序.
// 当前处理器是小端字节序, FBINFO_FOREIGN_ENDIAN 被置 0, framebuffer 设备采用小字节序.
fi->flags |= foreign_endian ? FBINFO_BE_MATH : 0;
#endif /* __BIG_ENDIAN */

// CONFIG_FB_BIG_ENDIAN 用于指定 framebuffer 设备是否采用大端字节序
// CONFIG_FB_LITTLE_ENDIAN 用于指定 framebuffer 设备是否采用小端字节序

if (fi->flags & FBINFO_BE_MATH && !fb_be_math(fi)) { // fb 如果应该为大字节序, 但是未配置 CONFIG_FB_BIG_ENDIAN 则返回报错
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)) { // fb 如果应该为小字节序, 但是未配置 CONFIG_FB_LITTLE_ENDIAN 则返回报错
pr_err("%s: enable CONFIG_FB_LITTLE_ENDIAN to "
"support this framebuffer\n", fi->fix.id);
return -ENOSYS;
}

return 0;
}

该函数用来检测, fb 中数据的大小端配置是否正常:
1, 如果 FBINFO_FOREIGN_ENDIAN 指定采用当前处理器的字节序, 且当前字节处理器字节序为小端, 则应该设置 CONFIG_FB_LITTLE_ENDIAN 这个宏.
2, 如果 FBINFO_FOREIGN_ENDIAN 指定采用当前处理器的字节序, 且当前字节处理器字节序为大端, 则应该设置 CONFIG_FB_BIG_ENDIAN 这个宏.
3, 如果 FBINFO_FOREIGN_ENDIAN 字节序与当前处理器的字节序相反, 且当前字节处理器字节序为大端, 则应该设置 CONFIG_FB_LITTLE_ENDIAN 这个宏.
4, 如果 FBINFO_FOREIGN_ENDIAN 字节序与当前处理器的字节序相反, 且当前字节处理器字节序为小端, 则应该设置 CONFIG_FB_BIG_ENDIAN 这个宏.

FBINFO_FOREIGN_ENDIAN = 0 #ifdef __BIG_ENDIAN ==> CONFIG_FB_LITTLE_ENDIAN
FBINFO_FOREIGN_ENDIAN = 0 #unifdef __BIG_ENDIAN ==> CONFIG_FB_BIG_ENDIAN
FBINFO_FOREIGN_ENDIAN = 1 #ifdef __BIG_ENDIAN ==> CONFIG_FB_LITTLE_ENDIAN
FBINFO_FOREIGN_ENDIAN = 1 #unifdef __BIG_ENDIAN ==> CONFIG_FB_BIG_ENDIAN

3.2.3 do_remove_conflicting_framebuffers

遍历系统中所有已加载的 framebuffer 设备,检查它们的显存区域是否与当前 framebuffer 设备的显存区域存在重叠,如果存在重叠,则将其标记为冲突设备,并从系统中移除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#define VGA_FB_PHYS 0xA0000
static int do_remove_conflicting_framebuffers(struct apertures_struct *a, const char *name, bool primary)
{
int i, ret;

for (i = 0 ; i < FB_MAX; i++) { // 遍历 FB_MAX
struct apertures_struct *gen_aper;

// 找到非空的 registered_fb 数组项
if (!registered_fb[i])
continue;

// 判断该 fb_info 有固件信息, 则进行下一步
if (!(registered_fb[i]->flags & FBINFO_MISC_FIRMWARE))
continue;

// 获取 pcie 接口内存信息
gen_aper = registered_fb[i]->apertures;
// 如果内存映射重叠, 则注销该设备.
if (fb_do_apertures_overlap(gen_aper, a) ||
(primary && gen_aper && gen_aper->count &&
gen_aper->ranges[0].base == VGA_FB_PHYS)) {

printk(KERN_INFO "fb: switching to %s from %s\n",
name, registered_fb[i]->fix.id);
ret = do_unregister_framebuffer(registered_fb[i]);
if (ret)
return ret;
}
}

return 0;
}

3.2.4 fb_init_device

  • 设置私有数据
  • 设置 FB_SYSFS_FLAG_ATTR 标志
  • 创建一系列属性文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
int fb_init_device(struct fb_info *fb_info)
{
int i, error = 0;

// 设置设备数据.
dev_set_drvdata(fb_info->dev, fb_info);

// 设置 FB_SYSFS_FLAG_ATTR 标志
fb_info->class_flag |= FB_SYSFS_FLAG_ATTR;

for (i = 0; i < ARRAY_SIZE(device_attrs); i++) {
// 创建属性文件 device_attrs
error = device_create_file(fb_info->dev, &device_attrs[i]);

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

// 一系列属性文件.
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
};

3.2.5 fb_var_to_videomode

非常重要的内核接口, 将 fb_var_screeninfo 参数转换为 fb_videomode 中的参数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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; // 设置 hsync
mode->vsync_len = var->vsync_len; // 设置 vsync
mode->left_margin = var->left_margin; // 设置左边框, 即 HBP
mode->right_margin = var->right_margin; // 设置右边框, 即 HFP
mode->upper_margin = var->upper_margin; // 设置上边框, 即 VBP
mode->lower_margin = var->lower_margin; // 设置下边框, 即 VFP
mode->sync = var->sync; // 初始化帧同步信息
mode->vmode = var->vmode & FB_VMODE_MASK; // 设置显示模式 video
mode->flag = FB_MODE_IS_FROM_VAR; // 如果是用用户空间调用该接口, 这设置 FB_MODE_USER 标志位
mode->refresh = 0; // 设置刷新率

// 时钟检测, 无时钟直接返回
if (!var->pixclock)
return;

// 获取 pixclock
pixclock = PICOS2KHZ(var->pixclock) * 1000;

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

// 如果是交织模式则 vtotal 减半
if (var->vmode & FB_VMODE_INTERLACED)
vtotal /= 2;

// 如果使用双倍大小模式,这 vtotal * 2, 设置了这个标志,那么在显示该视频模式的图像时,
// 会将一幅图像放大到原来的两倍来显示。这种方式可以提高图像的清晰度,但会导致图像失真。
if (var->vmode & FB_VMODE_DOUBLE)
vtotal *= 2;

// 计算水平刷新率
hfreq = pixclock / htotal;

// 计算刷新率
mode->refresh = hfreq / vtotal;
}

3.4 fb_open

fb 框架的 open 函数, 通过次设备号获取到对应的 fb_info, 并检查其是否设置 info->fbops->owner , 未设置将返回 err, 最后调用真正的 fb 设备的 open 函数 info->fbops->fb_open. read 和 ioctl 等函数同理, 不再分析.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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;

// 通过次设备号, 找到对应的数组下标
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);

// 若未设置模块所有者返回 err
if (!try_module_get(info->fbops->owner)) {
res = -ENODEV;
goto out;
}

// 设置私有数据
file->private_data = info;

// 调用真正的对应的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;
}

4. 基于 imx6ull 编写一个 fb 驱动

该驱动根据伟东山视频编写, 有兴趣可自行购买 https://www.100ask.net/

4.1 数据手册阅读.

4.1.1 Overview

增强型液晶显示接口 Enhanced LCD Interface(eLCDIF)是一种通用的显示控制器,用于驱动各种大小和能力不同的显示设备. eLCDIF块支持以下功能:

  • 显示一个异步并行 MPU 接口,用于命令和数据传输到一个集成的帧缓冲区。
  • 显示支持移动图片和需要 RGB 接口模式(DOTCLK 界面)。
  • VSYNC 的数据传输高速模式
  • 接受 ITU-R BT 的数字视频编码器。656 格式 4:2:2 YCbCr数字组件视频,并将其转换为模拟电视信号

eLCDIF为所支持的接口提供了完全可编程的功能:

  • 总线主接口到源帧缓冲区数据,以进行显示刷新。这个接口也可以用于驱动“智能”显示器的数据。
  • PIO 接口,以管理“智能”显示器和 SoC 之间的数据传输
  • 根据I/O mux选项,可支持8/16/18/24/32位LCD数据总线。
  • 可编程的定时和参数的 MPU,VSYNC,和 DOTCLK LCD接口,以支持各种显示器。
  • ITU-R BT.656模式(这里称为数字视频接口或DVI模式),包括渐进到隔行功能以及 RGB 到 YCbCr 4:2:2 颜色空间转换,以支持525/60和625/50操作。

4.1.2 LCDIF_CTRL

位域 读写 描述
[31] SFTRST R/W 软件复位,正常工作时应设为 0;如果设为 1,它会复位整个 LCD 控制器
[30] CLKGATE R/W 时钟开关,0:正常工作时要设置为 0;1:关闭 LCD 控制器时钟
[29] YCBCR422_INPUT R/W 0 表示输入数据是 rgb 数据, 1 表示为 YCbCr 4:2:2 格式数据.
[28] READ_WRITEB R/W 设置为 1 则是8080读, 写模式时必须设置 0
[27] WAIT_FOR_VSYNC_EDGE R/W 设置为 1 则会等待 vsync 信号, 仅仅用于 vsync mode
[26] DATA_SHIFT_DIR R/W 0 数据为左移 SHIFT_NUM_BITS, 1 数据右移 SHIFT_NUM_BITS 位, DIV 模式下对时序不起作用
[25:21] SHIFT_NUM_BITS R/W 数据位移多少位
[20] DVI_MODE R/W 设置为 1 时为 DIV 模式接口, 即 ITU-R BT.656
[19] BYPASS_COUNT R/W DOTCLK 和 DVI 模式下需要设置为 1; MPU 和 VSYNC 模式时设为 0
[18] VSYNC_MODE R/W 使用 VSYNC 模式时,设置为 1
[17] DOTCLK_MODE R/W 使用 DOTCLK 模式时,设置为 1
[16] DATA_SELECT R/W 仅 MPU 下使用, 0 cmd mode; 1 date cmd
[15:14] INPUT_DATA_SWIZZLE R/W 显存中像素颜色的数据转给 LCD 控制器时,字节位置是否交换:
0x0: NO_SWAP,不交换;
0x0: LITTLE_ENDIAN , 小字节序,跟 NO_SWAP 一样;
0x1: BIG_ENDIAN_SWAP,字节 0、3 交换;字节 1、2 交换;
0x1: SWAP_ALL_BYTES,字节 0、3 交换;字节 1、2 交换;
0x2: HWD_SWAP,半字交换,即 0x12345678 转为 0x56781234
0x3: HWD_BYTE_SWAP,在每个半字内部放换字节,即 0x12345678 转换为 0x34127856
[13:12] CSC_DATA_SWIZZLE R/W 显存中的数据被传入 LCD 控制器内部并被转换为 24BPP 后,在它被转给 LCD 接口之前,字节位置是否交换:
0x0:NO_SWAP,不交换;
0x0:LITTLE_ENDIAN , 小字节序 , 跟NO_SWAP 一样;
0x1:BIG_ENDIAN_SWAP,字节 0、3 交换;字节 1、2 交换;
0x1:SWAP_ALL_BYTES,字节 0、3 交换;字节 1、2 交换;
0x2:HWD_SWAP,半字交换,即 0x12345678转为 0x56781234
0x3:HWD_BYTE_SWAP,在每个半字内部放换字节, 即 0x12345678 转换为 0x34127856
[13:12] CSC_DATA_SWIZZLE R/W 显存中的数据被传入 LCD 控制器内部并被转换为 24BPP 后,在它被转给 LCD 接口之前,字节位置是否交换:
0x0:NO_SWAP,不交换;
0x0:LITTLE_ENDIAN , 小字节序 , 跟NO_SWAP 一样;
0x1:BIG_ENDIAN_SWAP,字节 0、3 交换;字节 1、2 交换;
0x1:SWAP_ALL_BYTES,字节 0、3 交换;字节 1、2 交换;
0x2:HWD_SWAP,半字交换,即 0x12345678转为 0x56781234
0x3:HWD_BYTE_SWAP,在每个半字内部放换字节,即 0x12345678 转换为 0x34127856
[11:10] LCD_DATABUS_WIDTH R/W LCD 数据总线宽度,就是对外输出的 LCD 数据的位宽.有多少根线
0x0:16 位;
0x1:8 位;
0x2:18 位;
0x3:24 位
[9:8] WORD_LENGTH R/W 输入的数据格式,即显存中每个像素占多少位,
0x0:16 位;
0x1:8 位;
0x2:18 位;
0x3:24 位
[7] GB_TO_YCBCR422_CSC R/W 设置为 1 时,使能颜色空间转换:RGB 转为 YCbCr
[6] ENABLE_PXP_HANDSHAKE R/W 当 LCDIF_MASTER 设置为 1 时,再设置这位,则 LCD 控制器跟 PXP 之间的握手机制被关闭(我们不关心)
[5] MASTER R/W 设置为 1 时,LCD 控制器成为 bus master
[4] RSRVD0 R/W 保留
[3] DATA_FORMAT_16_BIT R/W WORD_LENGTH 为 0 时,表示一个像素用 16 位,此位作用如下:
0:数据格式为 ARGB565
1:数据格式为 ARGB555
[2] DATA_FORMAT_18_BIT R/W WORD_LENGTH 为 2 时,表示一个像素用 18位,RGB 数据还是保存在 32 位数据里,此位作用如下:
0:低 18 位用来表示 RGB666,高 14 位无效
1:高 18 位用来表示 RGB666,低 14 位无效
[1] DATA_FORMAT_24_BIT R/W WORD_LENGTH 为 3 时,表示一个像素用 24位,此位作用如下:
0:所有的 24 位数据都有效,格式为 RGB888
1:转给 LCD 控制器的数据是 24 位的,但只用到其中的 18 位,每个字节用来表示一个颜色,每字节中高 2 位无效

4.1.3 LCDIF_CTRL1

位域 读写 描述
[19:16] BYTE_PACKING_FORMAT R/W 用来表示一个 32 位的 word 中,哪些字节是有效的,即哪些字节是用来表示颜色的。bit16、17、18、19 分别对应 byte0、1、2、3;某位为 1,就表示对应的字节有效。默认值是 0xf,表示 32 位的 word 中,所有字节都有效。对于 8bpp,可以忽略本设置,所有的字节都是有效的;对于 16bpp,bit[1:0]、bit[3:2]分别对应一个字节,组合中的 2 位都为 1 时,对应的字节才有效;对于 24bpp,0x7 表示 32 位数据中只用到 3 个字节,这称为“24 bit unpacked format”,即 ARGB,其中的 A 字节被丢弃
[0] RESET R/W 复位 LCD,0:LCD_RESET 引脚输出低电平;1:LCD_RESET 引脚输出高电平

4.1.4 LCDIF_TRANSFER_COUNT

位域 读写 描述
[31:16] V_COUNT R/W 一帧中有多少行数据
[15:0] V_COUNT R/W 一帧中有多少列数据

4.1.5 LCDIF_VDCTRL0

位域 读写 描述
[29] VSYNC_OEB R/W 用来控制 VSYNC 信号,对于 DOTCLK 模式,设为 0,
0:VSYNC 是输出引脚,用 LCD 控制器产生;
1:VSYNC 是输入引脚
[28] ENABLE_PRESENT R/W 在 DOTCLK 模式下,硬件是否会产生数据使能信号 ENALBE:
0:不产生;
1:产生
[27] VSYNC_POL R/W 用来决定 VSYNC 脉冲的极性:
0:低脉冲;
1:高脉冲
[26] VSYNC_POL R/W 用来决定 HSYNC 脉冲的极性:
0:低脉冲;
1:高脉冲
[25] DOTCLK_POL R/W 用来决定 DOTCLK 的极性:
0:LCD 控制器在 DOTCLK 下降沿发送数据,LCD 在上升沿捕获数据;
1:反过来
[24] ENABLE_POL R/W 用来决定 ENABLE 信号的极性:
0:数据有效期间,ENABLE 信号为低;
1:反过来
[21] VSYNC_PERIOD_UNIT R/W 用来决定 VSYNC_PERIOD 的单位:
0:单位是像素时钟(pix_clk),这在 VSYNC 模式下使用;
1:单位是“整行”,这在 DOTCLK 模式下使用
[20] VSYNC_PULSE_WIDTH_UNIT R/W 用来决定 VSYNC_PULSE_WIDTH 的单位:
0:单位是像素时钟(pix_clk);
1:单位是“整行”
[19] HALF_LINE R/W VSYNC 周期是否周加上半行的时间:
0:VSYNC 周期=VSYNC_PERIOD;
1 : VSYNC 周 期=VSYNC_PERIOD+HORIZONTAL_PERIOD/2
[18] R/W 0:第 1 帧将在一行的中间结束,第 2 帧在一行的中间开始;
1:所有帧结束前都加上半行时间,这样所有帧都会起始于“行的开头”
[17:0] VSYNC_PULSE_WIDTH R/W VSYNC 脉冲的宽度

4.1.6 LCDIF_VDCTRL1

位域 读写 描述
[29] VSYNC_PERIOD R/W 两个垂直同步信号之间的间隔,即垂直方向同步信号的总周期;单位由 VSYNC_PERIOD_UNIT 决定

4.1.7 LCDIF_VDCTRL2

位域 读写 描述
[[31:18] HSYNC_PULSE_WIDTH R/W HSYNC 脉冲的宽度(单位:pix_clk)
[[17:0] HSYNC_PERIOD R/W 整行的宽度,即两个 HYSNC 信号之间的宽度(单位:pix_clk)

4.1.8 LCDIF_VDCTRL3

位域 读写 描述
[28] VSYNC_ONLY R/W 0:DOTCLK 模式时必须设置为 0;
1:VSYNC 模式时必须设置为 1
[27:16] HORIZONTAL_WAIT_CNT R/W 水平方向上的等待像素个数,等于 thp+thb
[[15:0] VERTICAL_WAIT_CNT R/W 垂直方向上的等待行数,等于 tvp+tvb

4.1.9 LCDIF_VDCTRL4

位域 读写 描述
[31:29] DOTCLK_DLY_SELs R/W 在 LCD 控制器内部的 DOTCLK 输出到LCD_DOTCK 引脚时,延时多久:
0:2ns;
1:4ns;
2:6ns;
3:8ns;
其他值保留
[18] SYNC_SIGNALS_ON R/W DOTCLK 模式下必须设为 1
[17:0] DOTCLK_H_VALID_DATA_CNT R/W 水 平 方 向 上 的 有 效 像素个数 (pix_clk),即分辨率的 y

4.1.10 LCDIF_CUR_BUF

位域 读写 描述
[31:0] ADDR R/W LCD 控制器正在传输的当前帧在显存中的地址

4.1.11 LCDIF_NEXT_BUF

位域 读写 描述
[31:0] ADDR R/W LCD 下一帧在显存中的地址

4.2 编写一个最简单的 fb 驱动

4.2.1 头文件

lcd 控制器寄存器对应结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#ifndef    _IMX6ULL_SOC_H_
#define _IMX6ULL_SOC_H_

struct imx6ull_lcdif {
volatile unsigned int CTRL;
volatile unsigned int CTRL_SET;
volatile unsigned int CTRL_CLR;
volatile unsigned int CTRL_TOG;
volatile unsigned int CTRL1;
volatile unsigned int CTRL1_SET;
volatile unsigned int CTRL1_CLR;
volatile unsigned int CTRL1_TOG;
volatile unsigned int CTRL2;
volatile unsigned int CTRL2_SET;
volatile unsigned int CTRL2_CLR;
volatile unsigned int CTRL2_TOG;
volatile unsigned int TRANSFER_COUNT;
unsigned char RESERVED_0[12];
volatile unsigned int CUR_BUF;
unsigned char RESERVED_1[12];
volatile unsigned int NEXT_BUF;
unsigned char RESERVED_2[12];
volatile unsigned int TIMING;
unsigned char RESERVED_3[12];
volatile unsigned int VDCTRL0;
volatile unsigned int VDCTRL0_SET;
volatile unsigned int VDCTRL0_CLR;
volatile unsigned int VDCTRL0_TOG;
volatile unsigned int VDCTRL1;
unsigned char RESERVED_4[12];
volatile unsigned int VDCTRL2;
unsigned char RESERVED_5[12];
volatile unsigned int VDCTRL3;
unsigned char RESERVED_6[12];
volatile unsigned int VDCTRL4;
unsigned char RESERVED_7[12];
volatile unsigned int DVICTRL0;
unsigned char RESERVED_8[12];
volatile unsigned int DVICTRL1;
unsigned char RESERVED_9[12];
volatile unsigned int DVICTRL2;
unsigned char RESERVED_10[12];
volatile unsigned int DVICTRL3;
unsigned char RESERVED_11[12];
volatile unsigned int DVICTRL4;
unsigned char RESERVED_12[12];
volatile unsigned int CSC_COEFF0;
unsigned char RESERVED_13[12];
volatile unsigned int CSC_COEFF1;
unsigned char RESERVED_14[12];
volatile unsigned int CSC_COEFF2;
unsigned char RESERVED_15[12];
volatile unsigned int CSC_COEFF3;
unsigned char RESERVED_16[12];
volatile unsigned int CSC_COEFF4;
unsigned char RESERVED_17[12];
volatile unsigned int CSC_OFFSET;
unsigned char RESERVED_18[12];
volatile unsigned int CSC_LIMIT;
unsigned char RESERVED_19[12];
volatile unsigned int DATA;
unsigned char RESERVED_20[12];
volatile unsigned int BM_ERROR_STAT;
unsigned char RESERVED_21[12];
volatile unsigned int CRC_STAT;
unsigned char RESERVED_22[12];
volatile unsigned int STAT;
unsigned char RESERVED_23[76];
volatile unsigned int THRES;
unsigned char RESERVED_24[12];
volatile unsigned int AS_CTRL;
unsigned char RESERVED_25[12];
volatile unsigned int AS_BUF;
unsigned char RESERVED_26[12];
volatile unsigned int AS_NEXT_BUF;
unsigned char RESERVED_27[12];
volatile unsigned int AS_CLRKEYLOW;
unsigned char RESERVED_28[12];
volatile unsigned int AS_CLRKEYHIGH;
unsigned char RESERVED_29[12];
volatile unsigned int SYNC_DELAY;
};

#endif // my_lcd_con.h

4.2.2 驱动实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include <linux/io.h>
#include <video/display_timing.h>
#include <video/of_display_timing.h>
#include <linux/gpio/consumer.h>
#include <asm/div64.h>
#include <asm/mach/map.h>

#include "my_lcd_con.h"

/* 定义 frame buffer 设备结构体 */
struct fb_device {
struct platform_device *pdev;
struct clk *clk_pix;
struct clk *clk_axi;
struct display_timing *dt;
struct imx6ull_lcdif* lcdif;
char fb_bbp;
char lcd_bbp;
struct resource * res;
};

// 调色板
static unsigned int pseudo_palette[16];

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 int mylcd_setcolreg(unsigned regno,
unsigned red, unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{
unsigned int val;

switch (info->fix.visual) {
case FB_VISUAL_TRUECOLOR:

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;

default:
return 1; /* unknown type */
}

return 0;
}

/* 实现属于我们自己的 fb_ops 函数 */
static struct fb_ops my_fb_ops = {
.owner = THIS_MODULE, // 必须设置, 如果不设置将无法 open
.fb_read = fb_sys_read,
.fb_write = fb_sys_write,
.fb_fillrect = sys_fillrect,
.fb_copyarea = sys_copyarea,
.fb_imageblit = sys_imageblit,
.fb_setcolreg = mylcd_setcolreg,
};

/*************************************************
Function: lcd_of_read_mode
Description: 从设备节点获取屏幕 timing
*************************************************/
int lcd_of_read_mode(struct platform_device *pdev)
{

struct fb_info *info = platform_get_drvdata(pdev);
struct fb_device *fb_dev = info->par;
struct device_node *display_np = NULL;
struct display_timings *timings = NULL;

// 获取屏幕 tinming
display_np = of_parse_phandle(pdev->dev.of_node, "display", 0);
if(!display_np){
dev_err(&pdev->dev, "Failed to read display node\n");
return -EINVAL;
}

timings = of_get_display_timings(display_np);
if(!timings){
dev_err(&pdev->dev, "Failed to read display timings\n");
return -EINVAL;
}

fb_dev->dt = timings->timings[timings->native_mode];

return 0;
}

/*************************************************
Function: set_fb_info_var
Description: 设置屏幕可变参数
*************************************************/
static int set_fb_info_var(struct platform_device *pdev)
{
struct fb_info *info = platform_get_drvdata(pdev);
struct fb_device *fb_dev = info->par;
struct display_timing *dt = fb_dev->dt;


info->var.xres = dt->hactive.typ;
info->var.yres = dt->vactive.typ;

info->var.xres_virtual = info->var.xres;
info->var.yres_virtual = info->var.yres;

// rgb 565
info->var.bits_per_pixel = 16;
info->var.red.offset = 11;
info->var.red.length = 5;

info->var.green.offset = 5;
info->var.green.length = 6;

info->var.blue.offset = 0;
info->var.blue.length = 5;


return 0;
}

/*************************************************
Function: set_fb_info_fix
Description: 设置屏幕固定参数
*************************************************/
static int set_fb_info_fix(struct platform_device *pdev)
{
struct fb_info *info = platform_get_drvdata(pdev);
dma_addr_t dma_addr;

strcpy(info->fix.id, "mylcd");

// 计算获取所需物理空间长度
info->fix.smem_len = info->var.xres * info->var.yres * info->var.bits_per_pixel / 8;
if(24 == info->var.bits_per_pixel)
info->fix.smem_len = info->var.xres * info->var.yres * 4;

// 申请一片连续的物理空间 dma_addr 并返回其虚拟地址 screen_base
info->screen_base = dma_alloc_wc(info->dev, info->fix.smem_len, &dma_addr,GFP_KERNEL);

info->fix.smem_start = dma_addr; // 设置物理地址

info->fix.type = FB_TYPE_PACKED_PIXELS;
info->fix.visual = FB_VISUAL_TRUECOLOR;

info->fix.line_length = info->var.xres * info->var.bits_per_pixel / 8;
if (info->var.bits_per_pixel == 24)
info->fix.line_length = info->var.xres * 4;

return 0;
}

static void lcd_controler_enable(struct platform_device *pdev)
{
struct fb_info *info = platform_get_drvdata(pdev);
struct fb_device *fb_dev = info->par;
struct imx6ull_lcdif *lcdif = fb_dev->lcdif;

lcdif->CTRL |= (1 << 0);
}

/*************************************************
Function: lcd_controler_init
Description: lcd controler 初始化
*************************************************/
static int lcd_controler_init(struct platform_device *pdev)
{ struct fb_info *info = platform_get_drvdata(pdev);
struct fb_device *fb_dev = info->par;
struct imx6ull_lcdif *lcdif = fb_dev->lcdif;
struct display_timing *dt = fb_dev->dt;
unsigned int hsync_pol = 0;
unsigned int vsync_pol = 0;
unsigned int de_pol = 0;
unsigned int clk_pol = 0;
int lcd_data_bus_width = 0;
int fb_width = 0;

if(fb_dev->lcd_bbp == 24)
lcd_data_bus_width = 3;
else if(fb_dev->lcd_bbp == 18)
lcd_data_bus_width = 2;
else if(fb_dev->lcd_bbp == 8)
lcd_data_bus_width = 1;
else if(fb_dev->lcd_bbp)
lcd_data_bus_width = 0;

if(fb_dev->lcd_bbp == 24)
fb_width = 3;
else if(fb_dev->lcd_bbp == 18)
fb_width = 2;
else if(fb_dev->lcd_bbp == 8)
fb_width = 1;
else if(fb_dev->lcd_bbp == 16)
fb_width = 0;

/*
* 初始化LCD控制器的CTRL寄存器
* [19] : 1 : DOTCLK和DVI modes需要设置为1
* [17] : 1 : 设置为1工作在DOTCLK模式
* [15:14] : 00 : 输入数据不交换(小端模式)默认就为0,不需设置
* [13:12] : 00 : CSC数据不交换(小端模式)默认就为0,不需设置
* [11:10] : 11 : 数据总线为24bit
* [9:8] 根据显示屏资源文件bpp来设置:8位0x1 , 16位0x0 ,24位0x3
* [5] : 1 : 设置elcdif工作在主机模式
* [1] : 0 : 24位数据均是有效数据,默认就为0,不需设置
*/
lcdif->CTRL = (1 << 19) | (1 << 17) | (lcd_data_bus_width << 10) | (fb_width << 8) | (1 << 5);


/*
* 设置ELCDIF的寄存器CTRL1
* 根据bpp设置,bpp为24或32才设置
* [19:16] : 111 :表示ARGB传输格式模式下,传输24位无压缩数据,A通道不用传输)
*/
if(fb_dev->fb_bbp == 32 || fb_dev->fb_bbp == 24){
lcdif->CTRL1 &= ~(0xf << 16);
lcdif->CTRL1 |= (0x7 << 16);
}else{
lcdif->CTRL1 |= (0xf << 16);
}

/*
* 设置ELCDIF的寄存器TRANSFER_COUNT寄存器
* [31:16] : 垂直方向上的像素个数
* [15:0] : 水平方向上的像素个数
*/
lcdif->TRANSFER_COUNT = (dt->vactive.typ << 16) | (dt->hactive.typ << 0);

/*
* 设置Elcdif的VDCTRL0寄存器
* [29] 0 : VSYNC输出 ,默认为0,无需设置
* [28] 1 : 在DOTCLK模式下,设置1硬件会产生使能ENABLE输出
* [27] 0 : VSYNC低电平有效 ,根据屏幕配置文件将其设置为0
* [26] 0 : HSYNC低电平有效 , 根据屏幕配置文件将其设置为0
* [25] 1 : DOTCLK下降沿有效 ,根据屏幕配置文件将其设置为1
* [24] 1 : ENABLE信号高电平有效,根据屏幕配置文件将其设置为1
* [21] 1 : 帧同步周期单位,DOTCLK mode设置为1
* [20] 1 : 帧同步脉冲宽度单位,DOTCLK mode设置为1
* [17:0] : vysnc脉冲宽度
*/

if(dt->flags & DISPLAY_FLAGS_HSYNC_HIGH)
hsync_pol = 1;
if(dt->flags & DISPLAY_FLAGS_VSYNC_HIGH)
vsync_pol = 1;
if(dt->flags & DISPLAY_FLAGS_DE_HIGH)
de_pol = 1;
if(dt->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)
clk_pol =1;

/*
* 设置ELCDIF的VDCTRL0寄存器
* [29] 0 : VSYNC输出 ,默认为0,无需设置
* [28] 1 : 在DOTCLK模式下,设置1硬件会产生使能ENABLE输出
* [27] 0 : VSYNC低电平有效 ,根据屏幕配置文件将其设置为0
* [26] 0 : HSYNC低电平有效 , 根据屏幕配置文件将其设置为0
* [25] 1 : DOTCLK下降沿有效 ,根据屏幕配置文件将其设置为1
* [24] 1 : ENABLE信号高电平有效,根据屏幕配置文件将其设置为1
* [21] 1 : 帧同步周期单位,DOTCLK mode设置为1
* [20] 1 : 帧同步脉冲宽度单位,DOTCLK mode设置为1
* [17:0] : vysnc脉冲宽度
*/
lcdif->VDCTRL0 = (1 << 28)|( vsync_pol << 27)\
|( hsync_pol << 26)\
|( clk_pol << 25)\
|( de_pol << 24)\
|(1 << 21)|(1 << 20)|( dt->vsync_len.typ << 0);

/*
* 设置Elcdif的VDCTRL1寄存器
* 设置垂直方向的总周期:上黑框tvb+垂直同步脉冲tvp+垂直有效高度yres+下黑框tvf
*/
lcdif->VDCTRL1 = (dt->vfront_porch.typ + dt->vback_porch.typ + dt->vsync_len.typ + dt->vactive.typ);

/*
* 设置Elcdif的VDCTRL2寄存器
* [18:31] : 水平同步信号脉冲宽度
* [17: 0] : 水平方向总周期
* 设置水平方向的总周期:左黑框thb+水平同步脉冲thp+水平有效高度xres+右黑框thf
*/

lcdif->VDCTRL2 = (dt->hsync_len.typ << 18) | (dt->hfront_porch.typ + dt->hback_porch.typ + dt->hsync_len.typ + dt->hactive.typ);

/*
* 设置Elcdif的VDCTRL3寄存器
* [27:16] :水平方向上的等待时钟数 =thb + thp
* [15:0] : 垂直方向上的等待时钟数 = tvb + tvp
*/

lcdif->VDCTRL3 = (( dt->hback_porch.typ + dt->hsync_len.typ) << 16) | (dt->vback_porch.typ + dt->vsync_len.typ);

/*
* 设置Elcdif的VDCTRL4寄存器
* [18] 使用VSHYNC、HSYNC、DOTCLK模式此为置1
* [17:0] : 水平方向的宽度
*/
lcdif->VDCTRL4 = (1<<18) | (dt->hactive.typ);

/*
* 设置Elcdif的CUR_BUF和NEXT_BUF寄存器
* CUR_BUF : 当前显存地址
* NEXT_BUF : 下一帧显存地址
* 方便运算,都设置为同一个显存地址
*/
lcdif->CUR_BUF = info->fix.smem_start;
lcdif->NEXT_BUF = info->fix.smem_start;
return 0;
}

/*************************************************
Function: lcd_clk_init
Description: 初始化 时钟
*************************************************/
static int lcd_clk_init(struct platform_device *pdev)
{
struct fb_info *info = platform_get_drvdata(pdev);
struct fb_device *fb_dev = info->par;

// get clock
fb_dev->clk_pix = devm_clk_get(&pdev->dev, "pix");
fb_dev->clk_axi = devm_clk_get(&pdev->dev, "axi");

// set and enable clock
clk_set_rate(fb_dev->clk_pix, fb_dev->dt->pixelclock.typ);
clk_prepare_enable(fb_dev->clk_pix);
clk_prepare_enable(fb_dev->clk_axi);

return 0;
}

static int myfb_probe(struct platform_device *pdev)
{
int ret = 0;
struct fb_info *info;
struct fb_device *fb_dev = NULL;

pr_err("my framebuffer device probe\n");

info = framebuffer_alloc(sizeof(struct fb_device), &pdev->dev);
if (!info)
return -ENOMEM;

fb_dev = info->par;
platform_set_drvdata(pdev, info);

// get display timing
lcd_of_read_mode(pdev);

// set and enable clock
lcd_clk_init(pdev);

// set fb_info
set_fb_info_var(pdev);
set_fb_info_fix(pdev);
info->fbops = &my_fb_ops;
info->pseudo_palette = pseudo_palette; // 设置调色板
info->flags = FBINFO_DEFAULT;

// map lcd control register
fb_dev->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
fb_dev->lcdif = devm_ioremap_resource(&pdev->dev, fb_dev->res);

// lcd control init
fb_dev->fb_bbp = 16;
fb_dev->lcd_bbp = 16;
lcd_controler_init(pdev);

// register fb
ret = register_framebuffer(info);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register framebuffer\n");
}

// enable control
lcd_controler_enable(pdev);

pr_err("myfb framebuffer device registered\n");

return 0;

}

static int myfb_remove(struct platform_device *pdev)
{
struct fb_info *info = platform_get_drvdata(pdev);

// 注销 framebuffer 设备
unregister_framebuffer(info);

// 释放显存
dma_free_wc(&pdev->dev, PAGE_ALIGN(info->fix.smem_len),
info->screen_base, info->fix.smem_start);

// 释放 framebuffer 设备
framebuffer_release(info);

printk("myfb framebuffer device unregistered\n");

return 0;
}

static const struct of_device_id myfb_dt_ids[] = {
{ .compatible = "mylcd", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, myfb_dt_ids);

static struct platform_driver myfb_driver = {
.probe = myfb_probe,
.remove = myfb_remove,
.driver = {
.name = "myfb",
.owner = THIS_MODULE,
.of_match_table = myfb_dt_ids,
},
};

module_platform_driver(myfb_driver);

MODULE_AUTHOR("baron");
MODULE_DESCRIPTION("Simple framebuffer driver");
MODULE_LICENSE("GPL");