锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

百问网驱动大全学习(一)LCD驱动

时间:2022-12-06 16:30:00 tvb25sl传感器

百问网驱动大全学习(1)LCD驱动

LCD驱动是由字符设备驱动的,但因为LCD驱动是一个常用的驱动程序,所以内核把框架性的一些东西给封装好了,它就是fbmem.c,它完成了一些字符设备的驱动框架,最终将通过fb_info与硬件相关的驱动程序调用结构体。

因此,我们必须写一个LCD驱动程序,要做的就是

1. 修改设备树 2. 分析驱动程序中的设备树 3. 分配、设置、注册fb_info结构体 4. 硬件相关操作 

一、修改设备树

该设备的树节点相对固定,无需记忆stm32mp157c-100ask-512d-lcd-v1.dts添加的设备节点如下

mylcd { 
           status = "okay";   compatible = "100ask,mylcd";   pinctrl-names = "default";   pinctrl-0 = <&ltdc_pins_a>;   clocks = <&rcc LTDC_PX>;   clock-names = "lcd";   backlight-gpios = <&gpioe 11 GPIO_ACTIVE_LOW>;   reg = <0x5a001000 0x400>;      display = <&display0>;      display0: display { 
            bits-per-pixel = <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/span>>; hsync-len = <20>; vback-porch = <20>; vfront-porch = <12>; vsync-len = <3>; hsync-active = <0>; vsync-active = <0>; de-active = <1>; pixelclk-active = <0>; }; }; }; }; 

这里要把原来的pannel节点状态设置为disabled,在文末会给出完整的设备树文件

这里也有需要注意的地方,背光引脚已经被使用了,我们需要禁用使用该引脚的节点

二、在驱动程序中解析设备树

添加了设备树节点,下一步我们要做的就是在驱动程序代码中解析设备树节点,获取硬件相关的信息。

static int mylcd_probe(struct platform_device *pdev)
{ 
        
    struct resource *res;
    struct device_node *display_np;
    struct display_timings *timings = NULL;
    struct display_timing *timing = NULL;
    int size;
    int width;
    int bits_per_pixel;

    display_np = of_parse_phandle(pdev->dev.of_node, "display", 0);

    ret = of_property_read_u32(display_np, "bus-width", &width);
    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];
}

这样,我们就能够匹配到第一步添加的设备树节点,并可以从display_timing结构体变量中获得设备相关的时序、引脚极性等信息

三、分配、设置、注册一个fb_info结构体

(一)分配fb_info结构体

static struct fb_info *g_ptFbInfo = NULL;
static int mylcd_probe(struct platform_device *pdev)
{ 
        
    ...
    g_ptFbInfo = framebuffer_alloc(0, NULL);
    ...
}

使用上面这几行简洁的代码就可以分配一个fb_info结构体了,并且会把它初始化为0

(二)设置fb_info结构体

设置fb_info结构体就比较重要了,主要有三个方面:

1. var可变参数
2. fix固定参数
3. fbops等其他变量

我们先来看看var可变参数要设置哪些吧

(1)fb_info.var可变参数

首先是LCD的分辨率和bpp,这两个属性我们在上面解析设备树时就已经获取到了,这里直接赋值即可

g_ptFbInfo->var.xres_virtual = g_ptFbInfo->var.xres = timing->hactive.typ;
g_ptFbInfo->var.yres_virtual = g_ptFbInfo->var.yres = timing->vactive.typ;
g_ptFbInfo->var.bits_per_pixel = bits_per_pixel;

这个xres_virtual和yres_virtual如果不设置,在fb-test时会无效参数的错误

然后是红绿蓝三原色的偏移和大小,也比较简单,根据bpp的值来配置

if(bits_per_pixel == 16)
{ 
        
    g_ptFbInfo->var.red.offset = 11;
    g_ptFbInfo->var.red.length = 5;

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

    g_ptFbInfo->var.blue.offset = 0;
    g_ptFbInfo->var.blue.length = 5;
}
else if(bits_per_pixel == 24 || bits_per_pixel == 32)
{ 
        
    g_ptFbInfo->var.red.offset = 16;
    g_ptFbInfo->var.red.length = 8;

    g_ptFbInfo->var.green.offset = 8;
    g_ptFbInfo->var.green.length = 8;

    g_ptFbInfo->var.blue.offset = 0;
    g_ptFbInfo->var.blue.length = 8;
}
else
{ 
        
    printk("don't support bits_per_pixel\n");
    return -1;
}

这样,我们的可变参数就设置完毕了,是不是很简单。

(2)fb_info.fix固定参数

接下来是fix固定参数

fix.id

首先是fix.id,这个参数在register_framebuffer时会打印这个变量,如果不设置的话可能会打印乱码啥的,但是fb_info结构体在分配时清零了,这里不设置的话可能只会打印空字符串。

strcpy(g_ptFbInfo->fix.id, "100ask,lcd_drv");
fix.smem_len

接着是framebuffer的占用内存的长度,这个也很好理解,直接看代码,有一个需要注意的地方是,我们这个framebuffer的内存必须使用一块连续的物理内存

g_ptFbInfo->fix.smem_len = g_ptFbInfo->var.xres * 
    g_ptFbInfo->var.yres * g_ptFbInfo->var.bits_per_pixel / 8;

if(g_ptFbInfo->var.bits_per_pixel == 24)
    g_ptFbInfo->fix.smem_len = g_ptFbInfo->var.xres * 
    g_ptFbInfo->var.yres * 4;
fix.line_length

然后是line_length,是framebuffer一行的长度,也很好理解,如果不设置这个,在使用fb_test时,也会报无效参数的错误

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

最后是设置framebuffer的物理地址和虚拟地址映射以及一些 类型信息

unsigned map_size;
dma_addr_t phy_dma;

map_size = PAGE_ALIGN(g_ptFbInfo->fix.smem_len);
g_ptFbInfo->screen_base = dma_alloc_wc(&pdev->dev, map_size, &phy_dma,
                                       GFP_KERNEL);// framebuffer虚拟地址

g_ptFbInfo->fix.smem_start = phy_dma; // framebuffer物理地址

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

这里使用了一个新的函数dma_alloc_wc,用这个函数就可以分配一块连续的物理内存,并且把这一块物理内存的起始地址保存在phy_dma变量中,并且返回这一块内存映射的虚拟地址,关于这一个函数具体的作用,我还是存在一些疑问,必应直接搜索这个函数得到的信息不是很多。

(3)fbops等其他变量

最后就是设置fbops等其他变量了

static unsigned int pseudo_palette[16];

static inline u_int chan_to_field(u_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;

	/* dprintk("setcol: regno=%d, rgb=%d,%d,%d\n", regno, red, green, blue); */

	switch (info->fix.visual) { 
        
	case FB_VISUAL_TRUECOLOR:
		/* true-colour, use pseudo-palette */

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


static struct fb_ops lcd_drv_fbops = { 
        
	.owner		= THIS_MODULE,
	.fb_setcolreg	= mylcd_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};



g_ptFbInfo->fbops = &lcd_drv_fbops;
g_ptFbInfo->pseudo_palette = pseudo_palette;

fbops里面的mylcd_setcolreg是设置调色板函数,这里直接参考2410的代码复制过来修改的

并且在设置调色板函数里面,会调用到fb_info里的pseudo_palette,所以要给fb_info设置一个pseudo_palette

(三)注册fb_info结构体

搞定了这些,我们就要注册fb_info结构体了

ret = register_framebuffer(g_ptFbInfo);

四、硬件相关操作

在注册了fb_info结构体之后,要做的就是硬件相关的配置了

主要分为四个步骤

  1. 配置时钟
  2. 配置引脚
  3. 根据设备树节点配置寄存器
  4. 使能LCD控制器

(一)配置时钟

在设备树节点里面我们已经声明了需要使用的时钟

clocks = <&rcc LTDC_PX>;
clock-names = "lcd";

我们需要在驱动程序中使用内核提供的函数来使能这个时钟

static struct clk *g_ptFbClk = NULL;
g_ptFbClk = devm_clk_get(&pdev->dev, "lcd");
clk_set_rate(g_ptFbClk, timing->pixelclock.typ);
clk_prepare_enable(g_ptFbClk);

上面的四行代码所表达的含义已经很清晰了,需要注意的一点是设置时钟频率这里,我们使用的是第二步、解析设备树里面解析得到的display_timing结构体变量里的典型值

(二)配置引脚

这一步其实相对来说比较简单,在我们的设备树节点里使用了下面两行属性

pinctrl-names = "default";
pinctrl-0 = <&ltdc_pins_a>;

这个就是100ask STM32MP157板子的LCD控制器的引脚配置了,Linux的PinCtrl子系统已经帮我们配置好了

我们要做的就是配置背光引脚,在设备树节点里有背光引脚的声明

backlight-gpios = <&gpioe 11 GPIO_ACTIVE_LOW>;

使用的是PE11引脚,这就需要我们在代码中手动配置了,不过也简单

static struct gpio_desc *g_ptBLGpio = NULL;
g_ptBLGpio = gpiod_get(&pdev->dev, "backlight", GPIOD_OUT_LOW);
// 设置输入输出方向
gpiod_direction_output(g_ptBLGpio, 1);
// 设置输出低电平
gpiod_set_value(g_ptBLGpio, 0);

这样我们的背光引脚就配置好了

(三)根据设备树节点配置寄存器

这里已经是具体单板的寄存器操作了,只要根据解析设备树得到的display_timing结构体变量配置就好

分为两步:

1. 获取寄存器的物理地址,并且把物理地址映射成虚拟地址
2. 根据设备树配置具体的寄存器
(1)获取寄存器地址

我们在设备树节点里有这么一个属性

reg = <0x5a001000 0x400>;

这里保存的是我们157的LCD控制器的起始地址和大小,在驱动程序中通过如下几行代码获取

int size;
struct resource *res;
static struct stm32mp157_lcdif *g_ptLcdIf = NULL;

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
size = resource_size(res);
g_ptLcdIf = devm_ioremap_resource(&pdev->dev, res);

通过这几行代码,我们就能够通过结构体变量访问到157LCD控制器里的寄存器了,这里的stm32mp157_lcdif结构体是157LCD控制器的寄存器结构体,会在附录中给出。

(2)根据设备树配置具体的寄存器

这一步我们修改100ask STM32MP157开发板的裸机LCD驱动里面的初始化函数,在probe函数中直接调用即可

lcd_controller_init(g_ptLcdIf, timing, width, bits_per_pixel, phy_dma);
static int lcd_controller_init(struct stm32mp157_lcdif *lcdif, struct display_timing *dt, int lcd_bpp, int fb_bpp, unsigned int fb_phy)
{ 
        
	int bpp_mode;
	int pol_vclk = 0;
	int pol_vsync = 0;
	int pol_hsync = 0;
	int pol_de = 0;

	/*[11:0]垂直同步信号宽度tvp,[27:16]水平同步信号宽度thp*/
	lcdif->LTDC_SSCR = (dt->vsync_len.typ << 0) | (dt->hsync_len.typ << 16);
	
	/*清空LTDC_BPCR寄存器*/
	lcdif->LTDC_BPCR = 0 ;
	/*[11:0] VSYNC宽度tvp + 上黑框tvb - 1*/
	lcdif->LTDC_BPCR |= (dt->vsync_len.typ + dt->vback_porch.typ - 1) << 0 ;
	/*[27:16]HSYNC宽度thp + 左黑框thb - 1*/
	lcdif->LTDC_BPCR |=	(dt->hsync_len.typ + dt->hback_porch.typ - 1) << 16;
	
	/*清空LTDC_AWCR寄存器*/
	lcdif->LTDC_AWCR = 0 ;
	/*[11:0] VSYNC宽度tvp + 上黑框tvb + 垂直有效高度yres - 1*/
	lcdif->LTDC_AWCR |= (dt->vsync_len.typ + dt->vback_porch.typ + dt->vactive.typ - 1) << 0;
	/*[27:16] HSYNC宽度thp + 左黑框thb + 水平有效高度xres - 1*/ 
	lcdif->LTDC_AWCR |= (dt->hsync_len.typ + dt->hback_porch.typ + dt->hactive.typ - 1) << 16;
	
	/*清空LTDC_TWCR寄存器*/
	lcdif->LTDC_TWCR = 0;
	/*[11:0] VSYNC宽度tvp + 上黑框tvb + 垂直有效高度yres + 下黑框tvf - 1 , 即垂直方向上的总周期*/
	lcdif->LTDC_TWCR |= (dt->vsync_len.typ + dt->vback_porch.typ + dt->vactive.typ + dt->vfront_porch.typ - 1) << 0;
	/*[27:16] HSYNC宽度thp + 左黑框thb + 垂直有效高度xres + 右黑框thf - 1 , 即水平方向上的总周期*/
	lcdif->LTDC_TWCR |= (dt->hsync_len.typ + dt->hback_porch.typ + dt->hactive.typ + dt->hfront_porch.typ - 1) << 16;

	// vclk 上升沿有效
	if(dt->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)
		pol_vclk = 1;

	if(dt->flags & DISPLAY_FLAGS_DE_HIGH)
		pol_de = 1;

	if(dt->flags & DISPLAY_FLAGS_VSYNC_HIGH)
		pol_vsync = 1;

	if(dt->flags & DISPLAY_FLAGS_HSYNC_HIGH)
		pol_hsync = 1;

	
	/*清空LTDC_GCR寄存器*/
	lcdif->LTDC_GCR &= ~(0xF << 28);
	/* 1 : DOTCLK下降沿有效 ,根据屏幕配置文件将其设置为1 */
	lcdif->LTDC_GCR |= pol_vclk  << 28;
	/* 1 : ENABLE信号高电平有效,根据屏幕配置文件将其设置为1 */
	lcdif->LTDC_GCR |= pol_de    << 29;
	/* 0 : VSYNC低电平有效 ,根据屏幕配置文件将其设置为0 */
	lcdif->LTDC_GCR |= pol_vsync << 30 ;
	/* 0 : HSYNC低电平有效 , 根据屏幕配置文件将其设置为0 */
	lcdif->LTDC_GCR |= pol_hsync << 31 ;

	/*layer 1的相关设置如下*/
	lcdif->LTDC_L1WHPCR |= (dt->hsync_len.typ + dt->hback_porch.typ + dt->hactive.typ - 1) << 16 | (dt->hsync_len.typ + dt->hback_porch.typ ) ;

	lcdif->LTDC_L1WVPCR |= (dt->vsync_len.typ + dt->vback_porch.typ + dt->vactive.typ - 1) << 16 | (dt->vsync_len.typ + dt->vback_porch.typ ) ;

	lcdif->LTDC_L1CFBLR = (dt->hactive.typ * (fb_bpp>>3) + 7) | (dt->hactive.typ * (fb_bpp>>3))<< 16;
	 
	lcdif->LTDC_L1CFBLNR = dt->vactive.typ;/*显存总共的行数*/
	
	/*透明度填充值,当选的bpp格式是ARGB8888,ARGB1555等会使用到,如选的是RGB565,RBG888等者不设置也可以*/
	lcdif->LTDC_L1CACR = 0xff;

	/* *BC = BF1 x C + BF2 x Cs *BF1为LTDC_L1BFCR设置的[10:8]值,设置为100:constant alpha即LTDC_L1CACR设置的值0xff,表示完全不透明 *BF2为LTDC_L1BFCR设置的[2:0]值,设置为101:constant alpha即LTDC_L1CACR设置的值0xff,表示完全不透明 *C为当前层的颜色, *Cs为背景色,不设置,默认值为0,即黑色 *LTDC_L1BFCR寄存器也是针对有透明度的像素格式而设置,如用RGB565等也可不设置 */
	lcdif->LTDC_L1BFCR = (4<<8) | (5<<0);
	
	 /*当bpp为16时,数据格式为RGB565 , 当bpp为32时,数据格式为ARGB8888*/
     switch(fb_bpp)
	{ 
        
		case 16:{ 
        
			bpp_mode = 0x2;break;}
		case 32:{ 
        
			bpp_mode = 0x0;break;}
		default:{ 
        
			bpp_mode = 0x0;break;}
	}
	 
	lcdif->LTDC_L1PFCR  =       0 ;
	lcdif->LTDC_L1PFCR |= bpp_mode; /*设置像素格式*/

	lcdif->LTDC_L1CFBAR = fb_phy; /*设置显存地址*/

	lcdif->LTDC_L1CR |= 0x1;/*1 layer 使能*/
	return 0;
}

(四)使能LCD控制器

这里也使用100ask提供的裸机代码,也很简单

static void Stm32mp157_lcd_controller_enable(struct stm32mp157_lcdif *lcdif)
{ 
        	
	lcdif->LTDC_SRCR |= 1;         /*加载LAYER的参数*/
	lcdif->LTDC_GCR  |= 1<<0;      /* 使能STM32MP157的LCD控制器 */
}

在probe函数中直接调用就可以使能LCD控制器了

Stm32mp157_lcd_controller_enable(g_ptLcdIf);

附录一、设备树文件 stm32mp157c-100ask-512d-lcd-v1.dts

// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/* * Copyright (C) 100ASK 2020 - All Rights Reserved * Author: 100ask * support: weidongshan@qq.com */

/dts-v1/;

#include "stm32mp157c-100ask-512d-v1.dts"

/ { 
        
        model = "100ASK YA157C v2 www.100ask.com";
        compatible = "st,stm32mp157c-100ask-512d-v1",  

相关文章