1、LCD基础知识
分辨率
2K的由来:指的是水平方向上的像素数量大约在2000个左右。这是一种基于数量级的粗略分类,就像4K指水平像素约4000个,8K指约8000个一样。"2K"最"正统"的来源是数字电影联盟(DCI)在2005年为专业数字电影制作与放映制定了标准,将 2048 × 1080 定义为 DCI 2K。它的水平像素(2048)严格符合"约2000"的字面定义,长宽比约为1.90:1,比常见的16:9屏幕更宽。
像素格式
ARGB888表示一个像素点需要4个字节,分别是R、G、B、Alpha(透明)。

其中bit32~bit24是Alpha通道。
RGB888就是不需要Alpha通道,只需要3个字节。
LCD屏幕接口
I.MX6U-ALPHA支持RGB 接口的LCD,下图J1就是对外接口,采用RGB888格式。RGB LCD接口的信号线包括8根红色数据线R7:0、8根绿色数据线G7:0、8根蓝色数据线B7:0、数据使能线DE、垂直同步信号线VSYNC、水平同步信号线HSYNC、像素时钟信号线PCLK。

I.MX6ULL的eLCDIF支持RGB接口
The enhanced Liquid Crystal Display Interface (eLCDIF) is a general pupose display controller used to drive a wide range of display devices varying in size and capability.
Displays that support moving pictures and require the RGB interface mode (DOTCLK interface).
DOTCLK Interface include the VSYNC, HSYNC, DOTCLK and (optional) ENABLE signals. The interface is popularly called the RGB interface if the ENABLE signal is present.

从上图可见一帧的周期是由VSYNC控制的,其中包括了一组HSYNC,由此也可想到LCD的工作方式,从上到下,从左到右。

LCD时间参数和时序
HSPW
也叫thp,是HSYNC信号宽度,也即是行同步信号HSYNC持续时间。HSYNC信号不是一个脉冲,而是需要持续一段时间才是有效的,单位为 CLK。
HBP
也叫 thb,行同步信号后肩,单位CLK。
HFP
也叫thf,行同步信号前肩,单位CLK。
HSYC
行同步信号,当此信号有效的话就表示开始显示新的一行数据。
HOZVAL
也叫thd,显示一行数据所需的时间,屏幕分辨率如果为1024*600,那么HOZVAL就是1024,单位是CLK。
显示一行所要的时间是: HSPW + HBP + HOZVAL + HFP

VSPW
也叫tvp,是帧同步信号VSYNC的宽度,也就是VSYNC信号持续时间,单位为1行的时间。
VBP
也叫tvb,帧同步信号后肩,单位为1行的时间。
VFP
也叫tvf,帧同步信号前肩,单位为1行的时间。
VSYNC
帧同步信号,当此信号有效的话就表示开始显示新的一帧数据。
LINE
也叫tvd,显示一帧有效数据所需的时间,屏幕分辨率如果为1024*600,那么LINE就是600行的时间。
一帧就有(VSPW + VBP + LINE + VFP)*行时间

像素时钟频率
像素时钟就是RGB LCD的时钟信号,显示一帧图像所需的时钟数是:
T =(VSPW+VBP+LINE+VFP)* (HSPW+HBP+HOZVAL+HFP)
=(3 + 20 + 600 + 12) * (20 + 140 + 1024 + 160) = 635 * 1344 = 853440
显示60帧就是: 853440 * 60 = 51206400 ≈ 51.2 M
时钟树中的eLCD配置以及CCM时钟根源

首先要对PLL5相关寄存器进行配置、确定输出频率。参考手册有如下描述:


可见要对PLL_VIEDOn、CCM_ANALOG_MISC2n这两个寄存器配置。
CCM Memory Map/Register Definition and CCM Analog Memory Map/Register
Definition contains detailed descriptions of the memory mapped registers and control
functions of the clock generation sub-module.
Video PLL:{0h0A0, 0h0A4,0h0A8, 0h0AC}, 0h0B0, 0h0C0
CCM_ANALOG_PLL_VIDEOn就是PLL_VIEDOn的全称了。注意到20C_80A0h、20C_80A4h、20C_80A8h、20C_80A4h是同一个寄存器,只是后面三个是可以原子操作的。

bit6~0 :

CCM_ANALOG_MISC2n的地址是20C_8170h、20C_8174h、20C_8178h、20C_817Ch,后面三个寄存器也是可以原子操作的。

如果VIDEO_DIV配置为00b,POST_DIV_SELECT配置为10b,DIV_SELECT配置为32,那么pll5_main_clk = 24*32 = 768MHz。
接下来配置LCDIF时钟
CCM Serial Clock Divider Register 2 (CCM_CSCDR2 )这个寄存器的bit17-15可配置时钟源。

来源选择PLL5,LCDIF1_PRE_CLK_SEL配置成010b。
CCM_CSCDR2 寄存器的bit14-12可配置前置分频。设置为010b,即3分频。

CCM Bus Clock Multiplexer Register (CCM_CBCMR)寄存器的bit25-23可配置后置分频,设置为100b,即5分频。


CSCDR2的bit11~9选择LCDIF的最终的根时钟,设置为000b,即选择pll5_main_clk。

因此最终LCDIF1_CLK_ROOT的频率就是768/3/5 = 51.2 MHz。
2、FrameBuffer设备驱动
FrameBuffer简称fb。fb 是一种机制,将系统中所有跟显示有关的硬件以及软件集合起来,虚拟出一个 fb 设备,当我们编写好 LCD 驱动以后会生成一个名为/dev/fbX(X=0~n)的设备,应用程序通过访问/dev/fbX 这个设备就可以访问 LCD。输入命令"ls /dev/fb* -l" 可以看到/dev/fb0这样一个设备。
不同分辩率的LCD屏幕其eLCDIF控制器驱动代码都是相同的,只需修改对应的屏蔽参数。也就是修改设备树中的LCD设备节点。
正点原子的ALPHA开发板的LCD接口用了三个SGM3157模拟开关,为防止模拟开关影响到网络,需要降低LCD数据线的驱动能力,即将LCD的电气属性设置由原配置的0x79改为0x49。以IOMUXC_SW_PAD_CTL_PAD_LCD_DATA00为例,查看一下该寄存器的DSE配置

原来配置是0x79,那bit5-3就是111,改成0x49后,bit5-3就是001。查一下I.MX6ULL的技术手册,可以找到DSE配置后的阻抗值,可见DSE的数值越高,Impedance越小,驱动能力越强。


屏幕参数信息设置
/arch/arm/boot/dts/imx6ull.dtsi文件找到lcdif节点:

/drivers/video/fbdev/mxsfb.c


Framebuffer驱动的编写流程
Linux内核将所有的Framebuffer抽象为一个叫fb_info的结构体,fb_info结构体包含了Framebuffer设备的完整属性和操作集合,所以每一个Framebuffer设备都必须有一个fb_info。编写LCD的驱动就是构建fb_ifno, 注册fb_info。 fb_info 结构体定义在include/linux/fb.h文件中。

mxsfb_probe()函数主要完成:
a) 申请fb_info;
b) 初始化fb_info结构体中的各个成员变量;
c) 初始化eLCDIF控制器;
d) 使用 register_framebuffer 函数向 Linux 内核注册初始化好的 fb_info。
在mxsfb_probe函数中从设备树中获取到寄存器首地址后要进行内存映射,得到虚拟地址。

mxsfb.c中定义了eLCDIF各寄存器相对于基地址的偏移值

其中的xxx_V3_xxx与xxx_V4_xxx是对应的硬件版本不同,mxsfb 驱动定义了一个描述硬件特性的结构体,并为每个硬件版本(如 MXSFB_V3、MXSFB_V4)初始化不同的函数指针或数据成员。

调用 mxsfb_init_fbinfo 函数初始化 fb_info,重点是 fb_info 的 var、 fix、 fbops,screen_base 和 screen_size。
objectivec
static int mxsfb_init_fbinfo(struct mxsfb_info *host)
{
struct fb_info *fb_info = host->fb_info;
struct fb_var_screeninfo *var = &fb_info->var;
struct fb_modelist *modelist;
int ret;
fb_info->fbops = &mxsfb_ops;
fb_info->flags = FBINFO_FLAG_DEFAULT | FBINFO_READS_FAST;
fb_info->fix.type = FB_TYPE_PACKED_PIXELS;
fb_info->fix.ypanstep = 1;
fb_info->fix.ywrapstep = 1;
fb_info->fix.visual = FB_VISUAL_TRUECOLOR,
fb_info->fix.accel = FB_ACCEL_NONE;
ret = mxsfb_init_fbinfo_dt(host);
if (ret)
return ret;
if (host->id < 0)
sprintf(fb_info->fix.id, "mxs-lcdif");
else
sprintf(fb_info->fix.id, "mxs-lcdif%d", host->id);
if (!list_empty(&fb_info->modelist)) {
/* first video mode in the modelist as default video mode */
modelist = list_first_entry(&fb_info->modelist,
struct fb_modelist, list);
fb_videomode_to_var(var, &modelist->mode);
}
/* save the sync value getting from dtb */
host->sync = fb_info->var.sync;
var->nonstd = 0;
var->activate = FB_ACTIVATE_NOW;
var->accel_flags = 0;
var->vmode = FB_VMODE_NONINTERLACED;
/* init the color fields */
mxsfb_check_var(var, fb_info);
fb_info->fix.line_length =
fb_info->var.xres * (fb_info->var.bits_per_pixel >> 3);
fb_info->fix.smem_len = SZ_32M;
/* Memory allocation for framebuffer */
if (mxsfb_map_videomem(fb_info) < 0)
return -ENOMEM;
if (mxsfb_restore_mode(host))
memset((char *)fb_info->screen_base, 0, fb_info->fix.smem_len);
return 0;
}
mxsfb_init_fbinfo_dt从imx6ull-alientek--emmc.dts中读取LCD的属性。
objectivec
static int mxsfb_init_fbinfo_dt(struct mxsfb_info *host)
{
struct fb_info *fb_info = host->fb_info;
struct fb_var_screeninfo *var = &fb_info->var;
struct device *dev = &host->pdev->dev;
struct device_node *np = host->pdev->dev.of_node;
struct device_node *display_np;
struct device_node *timings_np;
struct display_timings *timings = NULL;
const char *disp_dev;
u32 width;
int i;
int ret = 0;
host->id = of_alias_get_id(np, "lcdif");
display_np = of_parse_phandle(np, "display", 0);
if (!display_np) {
dev_err(dev, "failed to find display phandle\n");
return -ENOENT;
}
ret = of_property_read_u32(display_np, "bus-width", &width);
if (ret < 0) {
dev_err(dev, "failed to get property bus-width\n");
goto put_display_node;
}
switch (width) {
case 8:
host->ld_intf_width = STMLCDIF_8BIT;
break;
case 16:
host->ld_intf_width = STMLCDIF_16BIT;
break;
case 18:
host->ld_intf_width = STMLCDIF_18BIT;
break;
case 24:
host->ld_intf_width = STMLCDIF_24BIT;
break;
default:
dev_err(dev, "invalid bus-width value\n");
ret = -EINVAL;
goto put_display_node;
}
ret = of_property_read_u32(display_np, "bits-per-pixel",
&var->bits_per_pixel);
if (ret < 0) {
dev_err(dev, "failed to get property bits-per-pixel\n");
goto put_display_node;
}
ret = of_property_read_string(np, "disp-dev", &disp_dev);
if (!ret) {
memcpy(host->disp_dev, disp_dev, strlen(disp_dev));
/* Timing is from encoder driver */
goto put_display_node;
}
timings = of_get_display_timings(display_np);
if (!timings) {
dev_err(dev, "failed to get display timings\n");
ret = -ENOENT;
goto put_display_node;
}
timings_np = of_find_node_by_name(display_np,
"display-timings");
if (!timings_np) {
dev_err(dev, "failed to find display-timings node\n");
ret = -ENOENT;
goto put_display_node;
}
for (i = 0; i < of_get_child_count(timings_np); i++) {
struct videomode vm;
struct fb_videomode fb_vm;
ret = videomode_from_timings(timings, &vm, i);
if (ret < 0)
goto put_timings_node;
ret = fb_videomode_from_videomode(&vm, &fb_vm);
if (ret < 0)
goto put_timings_node;
if (!(vm.flags & DISPLAY_FLAGS_DE_HIGH))
fb_vm.sync |= FB_SYNC_OE_LOW_ACT;
if (vm.flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
fb_vm.sync |= FB_SYNC_CLK_LAT_FALL;
fb_add_videomode(&fb_vm, &fb_info->modelist);
}
put_timings_node:
of_node_put(timings_np);
put_display_node:
if (timings)
kfree(timings);
of_node_put(display_np);
return ret;
}
imx6ull-alientek-emmc.dts中的lcdif节点描述:

3、应用程序对Framebuffer设备编程
fb的file_operations操作集定义在drivers/video/fbdev/core/fbmem.c文件中。主要有fb_read,fb_write, fb_ioctl, fb_open, fb_release等这些操作。

在应用程序中,操作/dev/fbX 的一般步骤:
a) 首先打开/dev/fbX 设备文件。
b) 使用 ioctl()函数获取到当前显示设备的参数信息,譬如屏幕的分辨率大小、像素格式,根据屏幕参数计算显示缓冲区的大小。
c) 通过存储映射 I/O 方式将屏幕的显示缓冲区映射到用户空间(mmap)。
d) 映射成功后就可以直接读写屏幕的显示缓冲区,进行绘图或图片显示等操作了。
e) 完成显示后,调用 munmap()取消映射、并调用 close()关闭设备文件。
参考资料
1.I.MX6Uq嵌入式Linux驱动开发指南V2.0.1正点原子
-
Linux设备驱动开发 详解:基于最新的Linux 4.0内核
-
I.MX6U嵌入式Linux C应用编程指南V1.6 正点原子