一、Framebuffer概述
Frame 是帧的意思,buffer 是缓冲的意思,在 Linux 系统中通过 Framebuffer 驱动程序来控制 LCD。LCD由一个一个像素组成,假设他的分辨率为xres*yres,那么就是每行有xres个像素,总共有yres行。假设每个像素的颜色用16位来表示,一个LCD的所有像素点有xres * y res个,那么需要的内存为:xres * yres * 16 / 8,这块内存就被称为framebuffer。
framebuffer存放在内存里面,LCD控制器会按照一定频率把framebuffer上的数据刷到LCD硬件上面去,从而点亮LCD。
二、Framebuffer驱动程序编写
分为上下两层:
上层: fbmem.c (Linux内核自带)
承上启下,实现、注册file_operations结构体,把APP的调用向下转发到具体的硬件驱动程序
下层: xxx_fb.c(硬件相关的驱动程序)
分配、设置、注册fb_info结构体,LCD硬件操作(引脚设置、 时钟设置、LCD控制器设置)
2.1、xxx_fb.c硬件驱动框架
xxx_fb.c驱动框架采用平台总线设备驱动框架,驱动程序+设备树的形式,设备树描述硬件信息,驱动程序根据设备树的硬件信息设置fb_info结构体信息,初始化硬件,注册fb_info结构体。
平台总线驱动框架代码大致如下:
static int lcd_probe(struct platform_device *pdev)
{
return 0;
}
static int lcd_remove(struct platform_device *pdev)
{
return 0;
}
static const struct of_device_id lcd_of_match[] = {
{ .compatible = "wyq,lcd", },
{ },
};
static struct platform_driver lcd_driver = {
.driver = {
.name = "lcd",
.of_match_table = lcd_of_match,
},
.probe = lcd_probe,
.remove = lcd_remove,
};
static int __init lcd_drv_init(void)
{
int ret;
ret = platform_driver_register(&lcd_driver);
if (ret)
return ret;
return 0;
}
static void __exit lcd_drv_exit(void)
{
platform_driver_unregister(&lcd_driver);
}
module_init(lcd_drv_init);
module_exit(lcd_drv_exit);
MODULE_LICENSE("GPL");
首先创建一个平台总线驱动lcd_driver,在入口函数里面去注册它,在出口函数里面去注销,如果lcd_driver里面的of_match_table里面的compatible属性与设备树里面的compatible属性一致,那么内核就会自动调用lcd_driver里面的probe函数。
2.2、编写设备树
我这里使用的LCD为RGB888接口的LCD显示屏(24根数据传输线,红绿蓝各8根,垂直同步信号VSYNC,行同步信号线HSYNC,时钟CLK,DE线,背光backlight,还有其他引脚,但是根据原理图不需要设置),开发板使用的是stm32mp157,开发板支持pinctrl系统,所以引脚设置使用ST官方提供的STM32CubeMX,然后根据开发板原理图设置LCD引脚,最后在pinctrl节点设置如下。
ltdc_pins_a: ltdc-a-0 {
pins {
pinmux = <STM32_PINMUX('G', 7, AF14)>, /* LCD_CLK */时钟
<STM32_PINMUX('I', 10, AF14)>, /* LCD_HSYNC */垂直信号
<STM32_PINMUX('I', 9, AF14)>, /* LCD_VSYNC */水平信号
<STM32_PINMUX('F', 10, AF14)>, /* LCD_DE */数据使能
<STM32_PINMUX('H', 2, AF14)>, /* LCD_R0 */
<STM32_PINMUX('H', 3, AF14)>, /* LCD_R1 */
<STM32_PINMUX('H', 8, AF14)>, /* LCD_R2 */
<STM32_PINMUX('H', 9, AF14)>, /* LCD_R3 */
<STM32_PINMUX('H', 10, AF14)>, /* LCD_R4 */
<STM32_PINMUX('C', 0, AF14)>, /* LCD_R5 */
<STM32_PINMUX('H', 12, AF14)>, /* LCD_R6 */
<STM32_PINMUX('E', 15, AF14)>, /* LCD_R7 */R0-R7表示红色
<STM32_PINMUX('E', 5, AF14)>, /* LCD_G0 */
<STM32_PINMUX('E', 6, AF14)>, /* LCD_G1 */
<STM32_PINMUX('H', 13, AF14)>, /* LCD_G2 */
<STM32_PINMUX('H', 14, AF14)>, /* LCD_G3 */
<STM32_PINMUX('H', 15, AF14)>, /* LCD_G4 */
<STM32_PINMUX('I', 0, AF14)>, /* LCD_G5 */
<STM32_PINMUX('I', 1, AF14)>, /* LCD_G6 */
<STM32_PINMUX('I', 2, AF14)>, /* LCD_G7 */G0-G7表示绿色
<STM32_PINMUX('D', 9, AF14)>, /* LCD_B0 */
<STM32_PINMUX('G', 12, AF14)>, /* LCD_B1 */
<STM32_PINMUX('G', 10, AF14)>, /* LCD_B2 */
<STM32_PINMUX('D', 10, AF14)>, /* LCD_B3 */
<STM32_PINMUX('I', 4, AF14)>, /* LCD_B4 */
<STM32_PINMUX('A', 3, AF14)>, /* LCD_B5 */
<STM32_PINMUX('B', 8, AF14)>, /* LCD_B6 */
<STM32_PINMUX('D', 8, AF14)>; /* LCD_B7 */B0-B7表示蓝色
bias-disable;
drive-push-pull;
slew-rate = <1>;
};
};
对应原理图
背光引脚需要复用为GPIO功能,但是该开发板不需要使用pinctrl系统把该引脚复用为GPIO功能,所以不需要在pinctrl系统里面设置。然后把这个节点添加到stm32mp15-pinctrl.dtsi中。
然后还要把LCD的一些硬件信息和时序参数在设备树中描述出来,在具体单板加入的子节点如下:
framebuffer-lcd {
compatible = "wyq,lcd";
reg = <0x5a001000 0x400>;
pinctrl-names = "default";
pinctrl-0 = <<dc_pins_a>;/*pinctrl信息引用刚才上面的节点*/
backlight-gpios = <&gpioe 11 GPIO_ACTIVE_HIGH>;/*背光引脚*/
clocks = <&rcc LTDC_PX>;/*时钟引用rcc*/
clock-names = "lcd";
display = <&display0>;/*时序节点,驱动程序根据这个节点获取LCD相关参数*/
display0: display {
bits-per-pixel = <16>;/*一个像素16位*/
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>;/*低电平有效*/
};
};
};
};
2.3、分配、设置、注册fb_info
首先创建一个fb_info 结构体。
struct fb_info *lcd_info;
然后去设置填充fb_info,主要填充信息有fb_info->var,主要是一些可变参数,分辨率像素值等。fb_info ->fix中的一些固定参数,还有操作函数fbops;设置填充完后就可以注册了。
/*从设备树中获取节点*/
display_np = of_parse_phandle(pdev->dev.of_node, "display", 0);
/* 依据节点获取像素值/
ret = of_property_read_u32(display_np, "bits-per-pixel",&bits_per_pixel);
/* 获取时序 */
timings = of_get_display_timings(display_np);
timing = timings->timings[timings->native_mode];
/* 分配fb_info */
lcd_info = framebuffer_alloc(0, NULL);
/* 设置fb_info */
/* 设置 LCD分辨率*/
lcd_info->var.xres = timing->hactive.typ;
lcd_info->var.yres = timing->vactive.typ;
lcd_info->var.bits_per_pixel = bits_per_pixel;
/* 设置 LCD分辨率、颜色格式 */
myfb_info->var.red.offset = 16;
myfb_info->var.red.length = 8;/*红色*/
myfb_info->var.green.offset = 8;
myfb_info->var.green.length = 8;/*绿色*/
myfb_info->var.blue.offset = 0;
myfb_info->var.blue.length = 8;、/*蓝色*/
/* 设置fix固定参数*/
lcd_info->fix.smem_len = lcd_info->var.xres * lcd_info->var.yres * lcd_info- >var.bits_per_pixel / 8;
lcd_info->screen_base = dma_alloc_wc(&pdev->dev, lcd_info->fix.smem_len, &phy_addr,
GFP_KERNEL);
lcd_info->fix.smem_start = phy_addr; /* fb的物理地址 */
lcd_info->fix.type = FB_TYPE_PACKED_PIXELS;
lcd_info->fix.visual = FB_VISUAL_TRUECOLOR;
lcd_info->fix.line_length = lcd_info->var.xres * lcd_info->var.bits_per_pixel / 8;
/* 设置 fbops */
lcd_info->fbops = &myfb_ops;
lcd_info->pseudo_palette = pseudo_palette;
/* 最后注册fb_info */
register_framebuffer(lcd_info);
2.4、硬件相关操作
一、引脚设置
引脚相关设置大部分已经使用pinctrl系统在设备树里面设置好了,驱动程序里面无需设置,但还需要设置背光引脚。
在设备树里面已经指定了背光引脚,在驱动程序里面直接使用gpiod_get就可以获取该引脚,然后在对该引脚进行设置。
bl_gpio = gpiod_get(&pdev->dev, "backlight", 0);//获得背后引脚
gpiod_direction_output(bl_gpio, 1);//配置为输出
gpiod_set_value(bl_gpio, 1);//在lcd寄存器初始化完毕并使能后就可以使能背光引脚了
二、时钟设置
在设备树中指定了引用哪个时钟源,也指定了时钟频率,但是这两个还无法相互直接进行操作,需要在驱动程序里面根据设备树信息获取时钟,然后根据设备树信息指定的频率设置它,最后使能它。
pixel_clk = devm_clk_get(&pdev->dev, "lcd");//获取时钟
clk_set_rate(pixel_clk, timing->pixelclock.typ);//设置频率
clk_prepare_enable(pixel_clk);//使能时钟
三、LCD控制器设置
这一块主要是根据芯片手册完成相关寄存器的设置,把寄存器的物理地址映射成虚拟地址。
三、参考资料
驱动源码:
LCD相关:Linux-5.4\drivers\gpu\drm\panel\panel-myir070tft.c
LCD控制器相关:Linux-5.4\drivers\gpu\drm\stm\ltdc.c
GPU相关:Linux-5.4\drivers\gpu\drm\stm\drv.c
设备树:
Linux-5.4/arch/arm/boot/dts/stm32mp151.dtsi
Linux-5.4/arch/arm/boot/dts/stm32mp15-pinctrl.dtsi
视频:韦东山《驱动大全》