文章目录

  • 一、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,每个像素用多少位来表示

韦东山 emmc_韦东山 emmc


假设每个像素的颜色用16位来表示,那么一个LCD的所有像素点假设有xresy res个,需要的内存为:xresyres*16 / 8,也就是要设置所有像素的颜色,需要这么大小的内存。这块内存就被称为framebuffer:

  • Framebuffer中每块数据对应一个像素
  • 每块数据的大小可能是16位、32位,这跟LCD上像素的颜色格式有关
  • 设置好LCD硬件后,只需要把颜色数据写入Framebuffer即可

统一的LCD硬件模型

韦东山 emmc_驱动_02


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驱动程序框架

字符设备驱动程序框架

韦东山 emmc_底层_03


① 驱动主设备号 ② 构造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

韦东山 emmc_嵌入式_04

1-3 结合APP分析LCD驱动程序框架

参考:04_fb_test文件夹

open函数分析:Linux根据主设备号找到驱动程序,根据次设备号来确定驱动程序中的那一个设备。此时会调用fbmen.c文件中fb_open函数,从registered_fb[i]数组中去获取fb_info。底层驱动在此前已经注册设备驱动,并registered_fb[i] = 定义的fb_info结构体。

韦东山 emmc_韦东山 emmc_05


韦东山 emmc_驱动_06


韦东山 emmc_嵌入式_07

/*设备驱动程序的注册信息*/
static struct fb_info *myfb_info;
register_framebuffer(myfb_info);
registered_fb[i] = fb_info;

ioctl函数分析:ioctl对应于底层fb_ioctl函数再调用到do_fb_ioctl

韦东山 emmc_驱动_08


韦东山 emmc_linux_09


韦东山 emmc_底层_10

1-4 LCD硬件时序图

韦东山 emmc_韦东山 emmc_11


HSD:水平方向同步信号

VSD:垂直方向同步信号

thd:每行像素个数

LCD控制器时序图

韦东山 emmc_韦东山 emmc_12

1-5 分析内核自带的LCD驱动程序

(1) 打开mxsfb.c文件,首先,platform_driver结构体中的属性与内核中设备树进行匹配。

韦东山 emmc_驱动_13


ubuntu系统中,通过grep查找(第一个属性的值没找到),第二个属性找到内核的imx6ull.dtsi文件的第1017行,并打开。

Dtsi:可以理解为dts的公共部分,添加、变更非常灵活。Dtsi包含在dts中。

grep "fsl,imx28-lcdif" * -nr

设备树通过compatible属性来匹配驱动程序

vi imx6ull.dtsi +1017

韦东山 emmc_韦东山 emmc_14


而单板的设备树需要访问100ask_imx6ull-14x14.dts设备树文件,里面有硬件的配置。

韦东山 emmc_驱动_15


(2) 匹配成功,则调用probe函数。并在里面分配、设置、注册fb_info

分配

韦东山 emmc_驱动_16


韦东山 emmc_嵌入式_17


设置

韦东山 emmc_驱动_18


注册

韦东山 emmc_驱动_19

(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引脚和背光引脚。

韦东山 emmc_linux_20


韦东山 emmc_底层_21


设备树节点信息和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时钟

韦东山 emmc_驱动_22

设备树添加时钟属性

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结构体里:

韦东山 emmc_驱动_23

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