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

百问网驱动大全学习(一)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>;
					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", "st,stm32mp157";
	/*LCD Panel*/

	panel {
		status = "disabled";
		compatible = "myir,070tft";
        	interrupts = <11 IRQ_TYPE_EDGE_FALLING>;
        	interrupt-parent = <&gpioe>;
        	pinctrl-names = "default", "sleep";
        	pinctrl-0 = <&ltdc_pins_a>;
        	pinctrl-1 = <&ltdc_pins_sleep_a>;
        	//reset-gpios = <&gpioe 12 GPIO_ACTIVE_LOW>;

		backlight = <&panel_backlight>;
		
		port {
			panel_in: endpoint {
				remote-endpoint = <&ltdc_ep0_out>;			
			};
	
		};
	};
	
	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>;
					hsync-len = <20>;
					vback-porch = <20>;
					vfront-porch = <12>;
					vsync-len = <3>;
					
					hsync-active = <0>;
					vsync-active = <0>;
					de-active = <1>;
					pixelclk-active = <0>;
				};
			};
		};
	};
};

&spi5 {
        pinctrl-names = "default", "sleep";
        pinctrl-0 = <&spi5_pins_a>;
        pinctrl-1 = <&spi5_sleep_pins_a>;
        status = "okay";
        cs-gpios = <&gpioh 5 GPIO_ACTIVE_LOW>;
        spidev: icm20608@0{
                compatible = "invensense,icm20608";
                interrupts = <0 IRQ_TYPE_EDGE_FALLING>;
                interrupt-parent = <&gpioz>;
                spi-max-frequency = <8000000>;
                reg = <0>;
        };
};

/* test HDMI*/
&ltdc {
    status = "okay";
    port {
       #address-cells = <1>;
       #size-cells = <0>;

	ltdc_ep1_out: endpoint@1 {
       		reg = <1>;
        	remote-endpoint = <&sii9022_in>;
        };
   };
};


/*HDMI*/
&i2c4 {
        clock-frequency = <100000>;
       hdmi-transmitter@40 { // use a dummy device
                compatible = "sil,sii9022";
                reg = <0x40>;
                reset-gpios = <&gpiob 10 GPIO_ACTIVE_LOW>;
                interrupts = <13 IRQ_TYPE_EDGE_FALLING>;
                interrupt-parent = <&gpiob>;
                //pinctrl-names = "default", "sleep";
                //pinctrl-0 = <&ltdc_pins_a>;
                //pinctrl-1 = <&ltdc_pins_sleep_a>;
                status = "disabled";

                ports {
                        #address-cells = <1>;
                        #size-cells = <0>;

                        port@0 {
                                reg = <0>;
                                sii9022_in: endpoint {
                                        remote-endpoint = <&ltdc_ep1_out>;
                                };
                        };

                };
        };

#if 1
	sii902x: sii902x@39 {
              compatible = "SiI,sii902x";
              reset-gpios = <&gpiob 10 GPIO_ACTIVE_LOW>;
              //pinctrl-names = "default";
              //pinctrl-0 = <&pinctrl_sii902x>;
              interrupts = <13 IRQ_TYPE_EDGE_FALLING>;
              interrupt-parent = <&gpiob>;
              mode_str ="1280x720M@60";
              bits-per-pixel = <16>;
              reg = <0x39>;
              status = "okay";

	};
#endif
};

&i2s2 {
	status = "disable";

};

/*test LCD*/
&ltdc {
        status = "okay";

        port {
                #address-cells = <1>;
                #size-cells = <0>;

                ltdc_ep0_out: endpoint@0 {
                                reg = <0>;
                                remote-endpoint = <&panel_in>;
                                };
           };
};



附录二、stm32mp157_lcdif结构体

struct stm32mp157_lcdif{
	volatile unsigned int LTDC_IDR;                          
	volatile unsigned int LTDC_LCR;                        
	volatile unsigned int LTDC_SSCR;                         
	volatile unsigned int LTDC_BPCR;       
	volatile unsigned int LTDC_AWCR;                             
	volatile unsigned int LTDC_TWCR;                         
	volatile unsigned int LTDC_GCR;                       
	volatile unsigned int LTDC_GC1R;   
	volatile unsigned int LTDC_GC2R;                            
	volatile unsigned int LTDC_SRCR;    
		unsigned char RESERVED_0[4];
	volatile unsigned int LTDC_BCCR;     
	 	unsigned char RESERVED_1[4];
	volatile unsigned int LTDC_IER;                        
	volatile unsigned int LTDC_ISR;   
	volatile unsigned int LTDC_ICR;                          
	volatile unsigned int LTDC_LIPCR;                        
	volatile unsigned int LTDC_CPSR;                          
	volatile unsigned int LTDC_CDSR; 
	  	unsigned char RESERVED_2[56];   
	volatile unsigned int LTDC_L1CR;                      
	volatile unsigned int LTDC_L1WHPCR;                     
	volatile unsigned int LTDC_L1WVPCR;                    
	volatile unsigned int LTDC_L1CKCR;                          
	volatile unsigned int LTDC_L1PFCR;                          
	volatile unsigned int LTDC_L1CACR;                          
	volatile unsigned int LTDC_L1DCCR;                           
	volatile unsigned int LTDC_L1BFCR;
		unsigned char RESERVED_3[8]; 
	volatile unsigned int LTDC_L1CFBAR;   
	volatile unsigned int LTDC_L1CFBLR;                        
	volatile unsigned int LTDC_L1CFBLNR;
	  	unsigned char RESERVED_4[12];  
	volatile unsigned int LTDC_L1CLUTWR; 
	  	unsigned char RESERVED_5[60];   
	volatile unsigned int LTDC_L2CR;  
	volatile unsigned int LTDC_L2WHPCR;                        
	volatile unsigned int LTDC_L2WVPCR;                        
	volatile unsigned int LTDC_L2CKCR;                        
	volatile unsigned int LTDC_L2PFCR;   
	volatile unsigned int LTDC_L2CACR;  
	volatile unsigned int LTDC_L2DCCR;  
	volatile unsigned int LTDC_L2BFCR;  
	  	unsigned char RESERVED_6[8];   
	volatile unsigned int LTDC_L2CFBAR;                        
	volatile unsigned int LTDC_L2CFBLR;                        
	volatile  unsigned int LTDC_L2CFBLNR;
	  	unsigned char RESERVED_7[12]; 
	volatile unsigned int LTDC_L2CLUTWR;    
};

附录三、驱动完整代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include <linux/io.h>
#include <linux/gpio/consumer.h>
#include <video/display_timing.h>
#include <video/of_display_timing.h>


struct stm32mp157_lcdif{
	volatile unsigned int LTDC_IDR;                          
	volatile unsigned int LTDC_LCR;                        
	volatile unsigned int LTDC_SSCR;                         
	volatile unsigned int LTDC_BPCR;       
	volatile unsigned int LTDC_AWCR;                             
	volatile unsigned int LTDC_TWCR;                         
	volatile unsigned int LTDC_GCR;                       
	volatile unsigned int LTDC_GC1R;   
	volatile unsigned int LTDC_GC2R;                            
	volatile unsigned int LTDC_SRCR;    
		unsigned char RESERVED_0[4];
	volatile unsigned int LTDC_BCCR;     
	 	unsigned char RESERVED_1[4];
	volatile unsigned int LTDC_IER;                        
	volatile unsigned int LTDC_ISR;   
	volatile unsigned int LTDC_ICR;                          
	volatile unsigned int LTDC_LIPCR;                        
	volatile unsigned int LTDC_CPSR;                          
	volatile unsigned int LTDC_CDSR; 
	  	unsigned char RESERVED_2[56];   
	volatile unsigned int LTDC_L1CR;                      
	volatile unsigned int LTDC_L1WHPCR;                     
	volatile unsigned int LTDC_L1WVPCR;                    
	volatile unsigned int LTDC_L1CKCR;                          
	volatile unsigned int LTDC_L1PFCR;                          
	volatile unsigned int LTDC_L1CACR;                          
	volatile unsigned int LTDC_L1DCCR;                           
	volatile unsigned int LTDC_L1BFCR;
		unsigned char RESERVED_3[8]; 
	volatile unsigned int LTDC_L1CFBAR;   
	volatile unsigned int LTDC_L1CFBLR;                        
	volatile unsigned int LTDC_L1CFBLNR;
	  	unsigned char RESERVED_4[12];  
	volatile unsigned int LTDC_L1CLUTWR; 
	  	unsigned char RESERVED_5[60];   
	volatile unsigned int LTDC_L2CR;  
	volatile unsigned int LTDC_L2WHPCR;                        
	volatile unsigned int LTDC_L2WVPCR;                        
	volatile unsigned int LTDC_L2CKCR;                        
	volatile unsigned int LTDC_L2PFCR;   
	volatile unsigned int LTDC_L2CACR;  
	volatile unsigned int LTDC_L2DCCR;  
	volatile unsigned int LTDC_L2BFCR;  
	  	unsigned char RESERVED_6[8];   
	volatile unsigned int LTDC_L2CFBAR;                        
	volatile unsigned int LTDC_L2CFBLR;                        
	volatile  unsigned int LTDC_L2CFBLNR;
	  	unsigned char RESERVED_7[12]; 
	volatile unsigned int LTDC_L2CLUTWR;    
};

static struct fb_info *g_ptFbInfo = NULL;
static struct clk *g_ptFbClk = NULL;
static struct gpio_desc *g_ptBLGpio = NULL;
static struct stm32mp157_lcdif *g_ptLcdIf = NULL;
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,
};

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


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


static int mylcd_probe(struct platform_device *pdev)
{
	int ret = 0;
	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;

	unsigned map_size;
	dma_addr_t phy_dma;
	

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	
	// 配置时序参数
	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];

//	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	/* 1. 分配fb_info */	
	g_ptFbInfo = framebuffer_alloc(0, NULL);
	if(!g_ptFbInfo)
	{
		printk("framebuffer_alloc error\n");
		return -1;
	}

	/* 2. 设置fb_info */
	
	/* 2.1 var */
	printk("var.xres = %d\n", timing->hactive.typ);
	printk("var.yres = %d\n", timing->vactive.typ);
	printk("bits_per_pixel = %d\n", bits_per_pixel);
	
	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; 

	/* RGB565 */
	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;
	}
	
//	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	/* 2.2 fix */

	strcpy(g_ptFbInfo->fix.id, "100ask,lcd_drv");
	
	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;

	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;

	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;	
	
	/* 2.3 fbops */
	g_ptFbInfo->fbops = &lcd_drv_fbops;
	g_ptFbInfo->pseudo_palette = pseudo_palette;

	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);


	/* 3. 注册fb_info */
	ret = register_framebuffer(g_ptFbInfo);
	if(ret)
	{
		printk("register_framebuffer error\n");
		return ret;
	}

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	/* 配置时钟 */
	g_ptFbClk = devm_clk_get(&pdev->dev, "lcd");
	if (IS_ERR(g_ptFbClk)) {
		printk("failed to get lcd clock source\n");
		return -1;
	}

	// 设置时钟频率
	clk_set_rate(g_ptFbClk, timing->pixelclock.typ);

	clk_prepare_enable(g_ptFbClk);

	g_ptBLGpio = gpiod_get(&pdev->dev, "backlight", GPIOD_OUT_LOW);
	if(!g_ptBLGpio)
	{
		printk("gpiod_get error\n");
		ret = -1;
		goto fail_gpiod_get;
	}
	
	// 设置输入输出方向
	gpiod_direction_output(g_ptBLGpio, 1);

	// 设置输出低电平
	gpiod_set_value(g_ptBLGpio, 0);

	/* 接下来就是配置寄存器了 */

	
	// 获取寄存器地址
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		printk("failed to get memory registers\n");
		ret = -1;
		goto fail_platform_get_resource;
	}
	
	size = resource_size(res);
	printk("stm32mp157_lcdif address is %x size is %x\n", res->start, size);
	
	g_ptLcdIf = devm_ioremap_resource(&pdev->dev, res);
	
	lcd_controller_init(g_ptLcdIf, timing, width, bits_per_pixel, phy_dma);
	Stm32mp157_lcd_controller_enable(g_ptLcdIf);

	return 0;

fail_devm_ioremap:
fail_platform_get_resource:
	gpiod_put(g_ptBLGpio);

fail_gpiod_get:
	clk_disable_unprepare(g_ptFbClk);
	clk_put(g_ptFbClk);

	return ret;

	
	
}


static int mylcd_remove(struct platform_device *pdev)
{

	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	/* 释放gpio */
	if(g_ptBLGpio)
		gpiod_put(g_ptBLGpio);

	/* 禁止时钟 */
	if(g_ptFbClk)
	{
		clk_disable_unprepare(g_ptFbClk);
		clk_put(g_ptFbClk);
	}

	unregister_framebuffer(g_ptFbInfo);
	framebuffer_release(g_ptFbInfo);
	
	
	return 0;
}

static const struct of_device_id mylcd_of_match[] = {
	{ .compatible = "100ask,mylcd", },
	{ },
};
MODULE_DEVICE_TABLE(of, mylcd_of_match);

static struct platform_driver mylcd_driver = {
	.driver = {
		.name = "100ask,mylcd",
		.of_match_table = mylcd_of_match,
	},
	.probe = mylcd_probe,
	.remove = mylcd_remove,
};



/* 1. 入口函数 */
static int __init lcd_drv_init(void)
{
	int ret;

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



module_init(lcd_drv_init);
module_exit(lcd_drv_exit);

MODULE_AUTHOR("100ask");
MODULE_DESCRIPTION("Framebuffer driver for the linux");
MODULE_LICENSE("GPL");



  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值