文章目录
- 一、LCD
- 1-1 不同接口的LCD硬件操作原理
- 1-2 LCD驱动程序框架
- 1-3 结合APP分析LCD驱动程序框架
- 1-4 LCD硬件时序图
- 1-5 分析内核自带的LCD驱动程序
- 1-6 编程LCD驱动程序框架_使用设备树
- 1-7 LCD驱动程序框架_引脚配置
- 1-8 LCD驱动程序框架_时钟配置
- 1-9 LCD驱动程序框架_LCD控制器配置
- 1-10 LCD驱动程序框架_寄存器操作
本人学习完韦老师的视频,因此来复习巩固,写以笔记记之。
韦老师的课比较难,第一遍不知道在说什么,但是坚持看完一遍,再来复习,基本上就水到渠成了。
看完视频复习的同学观看最佳!
基于 IMX6ULL-PRO
一、LCD
1-1 不同接口的LCD硬件操作原理
bpp:bits per pixel,每个像素用多少位来表示
假设每个像素的颜色用16位来表示,那么一个LCD的所有像素点假设有xresy res个,需要的内存为:xresyres*16 / 8,也就是要设置所有像素的颜色,需要这么大小的内存。这块内存就被称为framebuffer:
- Framebuffer中每块数据对应一个像素
- 每块数据的大小可能是16位、32位,这跟LCD上像素的颜色格式有关
- 设置好LCD硬件后,只需要把颜色数据写入Framebuffer即可
统一的LCD硬件模型
MIPI表示Mobile Industry Processor Interface
,即移动产业处理器接口。是MIPI联盟发起的为移动应用处理器制定的开放标准和一个规范。
MIPI接口可以分为3类:MIPI-DBI (Display Bus Interface) ,既能发送数据,也能发送命令,常用的8080接口就属于DBI接口;MIPI-DPI (Display Pixel Interface) ,Pixel(像素),强调的是操作单个像素,在MPU上的LCD控制器就是这种接口
1-2 LCD驱动程序框架
字符设备驱动程序框架
① 驱动主设备号 ② 构造file_operations结构体,填充open/read/write等成员函数
③ 注册驱动:register_chrdev(major, name, &fops) ④ 入口函数 ⑤出口函数
Framebuffer驱动程序框架
分为上下两层:
(1) fbmem.c:承上启下
实现、注册file_operations结构体;把APP的调用向下转发到具体的硬件驱动程序
(2) xxx_fb.c:硬件相关的驱动程序
实现、注册fb_info结构体;实现硬件操作
编写硬件程序如下
分配fb_info:framebuffer_alloc
设置fb_info:var、fbops、硬件相关操作
注册fb_info:register_framebuffer
1-3 结合APP分析LCD驱动程序框架
参考:04_fb_test文件夹
open函数分析:Linux根据主设备号找到驱动程序,根据次设备号来确定驱动程序中的那一个设备。此时会调用fbmen.c文件中fb_open函数,从registered_fb[i]数组中去获取fb_info。底层驱动在此前已经注册设备驱动,并registered_fb[i] = 定义的fb_info结构体。
/*设备驱动程序的注册信息*/
static struct fb_info *myfb_info;
register_framebuffer(myfb_info);
registered_fb[i] = fb_info;
ioctl函数分析:ioctl对应于底层fb_ioctl函数再调用到do_fb_ioctl
1-4 LCD硬件时序图
HSD:水平方向同步信号
VSD:垂直方向同步信号
thd:每行像素个数
LCD控制器时序图
1-5 分析内核自带的LCD驱动程序
(1) 打开mxsfb.c文件,首先,platform_driver结构体中的属性与内核中设备树进行匹配。
ubuntu系统中,通过grep查找(第一个属性的值没找到),第二个属性找到内核的imx6ull.dtsi文件的第1017行,并打开。
Dtsi:可以理解为dts的公共部分,添加、变更非常灵活。Dtsi包含在dts中。
grep "fsl,imx28-lcdif" * -nr
设备树通过compatible属性来匹配驱动程序
vi imx6ull.dtsi +1017
而单板的设备树需要访问100ask_imx6ull-14x14.dts设备树文件,里面有硬件的配置。
(2) 匹配成功,则调用probe函数。并在里面分配、设置、注册fb_info
分配
设置
注册
(3) 硬件操作
我们只需要针对IMX6ULL的编写硬件相关的代码,涉及3部分:
①GPIO设置
- LCD引脚
- 背光引脚
- GPIO设置使用设备树,在设备树中设置pinctrl。
②时钟设置
- 确定LCD控制器的时钟
- 根据LCD的DCLK计算相关时钟
③LCD控制器本身的设置
- 比如设置Framebuffer的地址
- 设置Framebuffer中数据格式、LCD数据格式
- 设置时序
1-6 编程LCD驱动程序框架_使用设备树
将03文件的驱动程序进行改进
使用platform_driver注册,在probe函数里分配fb_info、设置fb_info、注册fb_info、硬件相关的设置。设备树中需要有对应的节点
驱动框架
static int mylcd_probe(struct platform_device *pdev)
{
/*简要代码*/
/* 1.1 分配fb_info */
myfb_info = framebuffer_alloc(0, NULL);
/* 1.2 设置fb_info */
/* a. var : LCD分辨率、颜色格式 */
myfb_info->var.xres_virtual = myfb_info->var.xres = 500;
myfb_info->var.yres_virtual = myfb_info->var.yres = 300
/* 1.3 注册fb_info */
register_framebuffer(myfb_info);
/* 1.4 硬件操作 */
mylcd_regs = ioremap(0x021C8000, sizeof(struct lcd_regs));
mylcd_regs->fb_base_phys = phy_addr;
mylcd_regs->fb_xres = 500;
mylcd_regs->fb_yres = 300;
mylcd_regs->fb_bpp = 16;
return 0;
}
static int mylcd_remove(struct platform_device *pdev)
{
/* 反过来操作 */
/* 2.1 反注册fb_info */
unregister_framebuffer(myfb_info);
/* 2.2 释放fb_info */
framebuffer_release(myfb_info);
iounmap(mylcd_regs);
return 0;
}
static const struct of_device_id mylcd_of_match[] = {
{ .compatible = "100ask,lcd_drv", },
{ },
};
MODULE_DEVICE_TABLE(of, simplefb_of_match);
static struct platform_driver mylcd_driver = {
.driver = {
.name = "mylcd",
.of_match_table = mylcd_of_match,
},
.probe = mylcd_probe,
.remove = mylcd_remove,
};
static int __init lcd_drv_init(void)
{
int ret;
struct device_node *np;
ret = platform_driver_register(&mylcd_driver);
if (ret)
return ret;
return 0;
}
/* 2. 出口 */
static void __exit lcd_drv_exit(void)
{
platform_driver_unregister(&mylcd_driver);
}
设备树信息
framebuffer-mylcd {
compatible = "100ask,lcd_drv"; /*未待完续*/
};
1-7 LCD驱动程序框架_引脚配置
GPIO设置:配置LCD引脚和背光引脚GPIO,在设备树中设置pinctrl。
根据原理图确定需要配置那些引脚,打开引脚配置工具/设备树生成工具,把引脚配置成LCD引脚和背光引脚。
设备树节点信息和pinctrl信息(省略,如上图所示)
framebuffer-mylcd {
compatible = "100ask,lcd_drv";
pinctrl-names = "default";
pinctrl-0 = <&mylcd_pinctrl>;
backlight-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>;
};
/*probe函数获取GPIO*/
/* get gpio from device tree */
bl_gpio = gpiod_get(&pdev->dev, "backlight", 0);
/* config bl_gpio as output */
gpiod_direction_output(bl_gpio, 1);
/* set val: gpiod_set_value(bl_gpio, status); */
1-8 LCD驱动程序框架_时钟配置
时钟设置:确定LCD控制器的时钟和根据LCD的DCLK计算相关时钟
翻阅芯片手册Chapter 34 Enhanced LCD Interface (eLCDIF),查看LCD时钟
设备树添加时钟属性
framebuffer-mylcd {
compatible = "100ask,lcd_drv";
pinctrl-names = "default";
pinctrl-0 = <&mylcd_pinctrl>;
backlight-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>;
clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
<&clks IMX6UL_CLK_LCDIF_APB>,
clock-names = "pix", "axi";
};
驱动代码添加设置时钟
/*probe函数中设置CLK*/
/*get clk form device tree*/
clk_pix = devm_clk_get(&pdev->dev,"pix");
clk_axi = devm_clk_get(&pdev->dev,"axi");
/*set clk rate*/
/*clk_axi系统启动后自动设置
*50Mhz以后会由设备树来设置
*/
clk_set_rate(clk_pix, 50000000);
/*enable clk*/
clk_prepare_enable(clk_pix);
clk_prepare_enable(clk_axi);
1-9 LCD驱动程序框架_LCD控制器配置
LCD控制器本身的设置:设置Framebuffer的地址、设置Framebuffer中数据格式、LCD数据格式、设置时序
设备树
display = <&display0>;
display0: display {
bits-per-pixel = <24>;
bus-width = <24>;
display-timings {
native-mode = <&timing0>;
timing0: timing0_1024x600 {
clock-frequency = <50000000>;
hactive = <1024>;
vactive = <600>;
hfront-porch = <160>;
hback-porch = <140>;
hsync-len = <20>;
vback-porch = <20>;
vfront-porch = <12>;
vsync-len = <3>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <1>;
pixelclk-active = <0>;
};
};
};
驱动程序
struct device_node *display_np;
int ret;
int bits_per_pixel;
struct display_timings *timings = NULL;
display_np = of_parse_phandle(pdev->dev.of_node, "display", 0);
/*get common info*/
ret = of_property_read_u32(display_np, "bus-width", &width);
ret = of_property_read_u32(display_np, "bits-per-pixel",
&bits_per_pixel);
/*get timming*/
timings = of_get_display_timings(display_np);
时序参数、引脚极性等信息,都被保存在一个display_timing结构体里:
1-10 LCD驱动程序框架_寄存器操作
设备树添加lcd物理地址
reg = <0x021c8000 0x4000>;
驱动程序在设备树中获取地址信息
/* 1.4 硬件操作 */
//lcdif = ioremap(0x021C8000, sizeof(*lcdif));
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
lcdif = devm_ioremap_resource(&pdev->dev, res);