SPI LCD驱动移植(基于fbtft)
bug不收敛
编辑于 2021年02月24日 09:09

对于大部分自带驱动的lcd来说,Linux内核中自带了驱动源码,由于一些申请接口不兼容原因,需要修改后才能正常驱动,下面以linux5.7内核移植ST7789V为例。

图1

在目录drivers/staging/fbtft中可以看到有st7789v的驱动代码,这就说明Linux内核原本就有该设备的驱动,减轻了很大的移植难度。现在打开fb_st7789v.c文件,然后找到屏幕初始化函数,修改如下:

代码块
clike
自动换行
复制代码
static int init_display(struct fbtft_par *par)
{
    par->fbtftops.reset(par);
    mdelay(50);
    write_reg(par,0x11);//Sleep exit 
    mdelay(12);
    write_reg(par,0x11); 
    mdelay(10);
    write_reg(par,0x3A,0x05); //65k mode
    write_reg(par,0xc5,0x1a); 
    write_reg(par,0x36,0x70); // 屏幕显示方向设置
//-------------ST7789V Frame rate setting-----------//
    write_reg(par,0xb2,0x05,0x05,0x00,0x33,0x33);  
    write_reg(par,0xb7,0x35);
//--------------ST7789V Power setting---------------//
    write_reg(par,0xbb,0x3f);
    write_reg(par,0xc0,0x2c);
    write_reg(par,0xc2,0x01);
    write_reg(par,0xc3,0x0f);
    write_reg(par,0xc4,0x20);
    write_reg(par,0xc6,0x11);
    write_reg(par,0xd0,0xa4,0xa1);
    write_reg(par,0xe8,0x03);
    write_reg(par,0xe9,0x09,0x09,0x08);
    write_reg(par,0xe0,0xd0,0x05,0x09,0x09,0x08,0x14,0x28,0x33,0x3f,0x07,0x13,0x14,0x28,0x30);
    write_reg(par,0xe1,0xd0,0x05,0x09,0x09,0x08,0x03,0x24,0x32,0x32,0x3b,0x14,0x13,0x28,0x2f);
    write_reg(par,0x21);
    write_reg(par,0x11);
    mdelay(120);      //Delay 120ms
    write_reg(par,0x29);
    mdelay(200);
    return 0;
}
复制成功

上面的初始化是根据中景园屏幕提供的STM32代码修改来的。

图2 ST7789V初始化代码

现在需要修改屏幕分辨率,这里我使用的是1.14寸135*240的液晶屏,找到fbtft_display display结构体,然后修改width和height,如图

图3 修改屏幕分辨率

然后修改fbtft-core.c文件,该文件需要修改,具体如下:

首先在最开始包含两个头文件:

代码块
JavaScript
自动换行
复制代码
#include "linux/gpio.h"
#include "linux/of_gpio.h"
复制成功

图4 fbtft-core.c添加头文件

添加头文件的目的是后面需要用到申请gpio函数。

然后找到fbtft_request_one_gpio和fbtft_request_gpios函数,这两个函数原本因为使用的gpio申请方式不同,导致无法申请,这里需要对这两个函数进行修改:

代码块
clike
自动换行
复制代码
static int fbtft_request_one_gpio(struct fbtft_par *par,
                  const char *name, int index,
                  struct gpio_desc **gpiop)
{
    struct device *dev = par->info->device;
    struct device_node *node = dev->of_node;
    int gpio, flags, ret = 0;
    enum of_gpio_flags of_flags;
    if (of_find_property(node, name, NULL)) {
        gpio = of_get_named_gpio_flags(node, name, index, &of_flags);
        if (gpio == -ENOENT)
            return 0;
        if (gpio == -EPROBE_DEFER)
            return gpio;
        if (gpio < 0) {
            dev_err(dev,
                "failed to get '%s' from DT\n", name);
            return gpio;
        }
         //active low translates to initially low 
        flags = (of_flags & OF_GPIO_ACTIVE_LOW) ? GPIOF_OUT_INIT_LOW :
                            GPIOF_OUT_INIT_HIGH;
        ret = devm_gpio_request_one(dev, gpio, flags,
                        dev->driver->name);
        if (ret) {
            dev_err(dev,
                "gpio_request_one('%s'=%d) failed with %d\n",
                name, gpio, ret);
            return ret;
        }

        *gpiop = gpio_to_desc(gpio);
        fbtft_par_dbg(DEBUG_REQUEST_GPIOS, par, "%s: '%s' = GPIO%d\n",
                            __func__, name, gpio);
    }

    return ret;
}
复制成功

图5 修改gpio申请函数

代码块
clike
自动换行
复制代码
static int fbtft_request_gpios(struct fbtft_par *par)
{
    int i;
    int ret;

    ret = fbtft_request_one_gpio(par, "reset-gpios", 0, &par->gpio.reset);
    if (ret)
        return ret;
    ret = fbtft_request_one_gpio(par, "dc-gpios", 0, &par->gpio.dc);
    if (ret)
        return ret;
    ret = fbtft_request_one_gpio(par, "rd-gpios", 0, &par->gpio.rd);
    if (ret)
        return ret;
    ret = fbtft_request_one_gpio(par, "wr-gpios", 0, &par->gpio.wr);
    if (ret)
        return ret;
    ret = fbtft_request_one_gpio(par, "cs-gpios", 0, &par->gpio.cs);
    if (ret)
        return ret;
    ret = fbtft_request_one_gpio(par, "latch-gpios", 0, &par->gpio.latch);
    if (ret)
        return ret;
    for (i = 0; i < 16; i++) {
        ret = fbtft_request_one_gpio(par, "db-gpios", i,
                         &par->gpio.db[i]);
        if (ret)
            return ret;
        ret = fbtft_request_one_gpio(par, "led-gpios", i,
                         &par->gpio.led[i]);
        if (ret)
            return ret;
        ret = fbtft_request_one_gpio(par, "aux-gpios", i,
                         &par->gpio.aux[i]);
        if (ret)
            return ret;
    }

    return 0;
}
复制成功

图6 修改设备树匹配字符串

修改gpio申请函数的原因在于这里一个不同版本之间的不兼容问题,因为内核版本移植在更新,但是有些驱动却没有即使更新,这就出现了一些内核接口已经更新了,而驱动却还在使用旧的方式,导致即使可以注册成功,但并不能对其操作。

然后修改fbtft复位函数,如下:

代码块
clike
自动换行
复制代码
static void fbtft_reset(struct fbtft_par *par)
{
    if (!par->gpio.reset)
        return;
    fbtft_par_dbg(DEBUG_RESET, par, "%s()\n", __func__);
    gpiod_set_value_cansleep(par->gpio.reset, 1);
    msleep(10);
    gpiod_set_value_cansleep(par->gpio.reset, 0);
    msleep(200);
    gpiod_set_value_cansleep(par->gpio.reset, 1);
    msleep(10);
}
复制成功

图6 修改复位函数

修改复位函数的原因在于原本的函数拉低复位引脚后并为拉高。

FBTFT的部分已经修改完毕,液晶屏使用的是SPI操作的,因此需要将fbtft驱动挂载在spi总线上,幸运的是对于F1C200S来说,内核已经有spi驱动了,因此我们只需要修改设备树就可以了,具体步骤如下:

打开arch/arm/boot/dts/suniv-f1c100s.dtsi文件,添加spi节点和pio节点

代码块
clike
自动换行
复制代码
		spi0:spi@1c05000 {
           compatible = "allwinner,suniv-spi", "allwinner,sun8i-h3-spi";
            reg = <0x1c05000 0x1000>;
            interrupts =<0xa>;
            clocks = <&ccu CLK_BUS_SPI0>, <&ccu CLK_BUS_SPI0>;
            clock-names = "ahb", "mod";
            resets = <&ccu RST_BUS_SPI0>;
            status = "okay";
            #address-cells =<1>;
            #size-cells =<0>;
            pinctrl-names = "default";
            pinctrl-0 = <&spi0_pins>;
        };

        spi1:spi@1c06000 {
            compatible = "allwinner,suniv-spi", "allwinner,sun8i-h3-spi";
            reg =<0x1c06000 0x1000>;
            interrupts =<0xb>;
            clocks = <&ccu CLK_BUS_SPI1>, <&ccu CLK_BUS_SPI1>;
            clock-names = "ahb", "mod";
            resets = <&ccu RST_BUS_SPI1>;
            status = "okay";
            #address-cells =<1>;
            #size-cells =<0>;
            bias-pull-up;
            pinctrl-names = "default";
            pinctrl-0 = <&spi1_pins>;
        };
复制成功

代码块
clike
自动换行
复制代码
		pio: pinctrl@1c20800 {
			compatible = "allwinner,suniv-f1c100s-pinctrl";
			reg = <0x01c20800 0x400>;
			interrupts =<38>,<39>,<40>;
			clocks = <&ccu CLK_BUS_PIO>, <&osc24M>, <&osc32k>;
			clock-names = "apb", "hosc", "losc";
			gpio-controller;
			interrupt-controller;
			#interrupt-cells =<3>;
			#gpio-cells =<3>;

			uart0_pe_pins: uart0-pe-pins {
				pins = "PE0", "PE1";
				function = "uart0";
			};
			
			lcd_rgb666_pins: lcd-rgb666-pins {
				pins = "PD0", "PD1", "PD2", "PD3", "PD4",
				       "PD5", "PD6", "PD7", "PD8", "PD9",
				       "PD10", "PD11", "PD12", "PD13", "PD14",
				       "PD15", "PD16", "PD17", "PD18", "PD19",
				       "PD20", "PD21";
				function = "lcd";
			};
			
			mmc0_pins: mmc0-pins {
				pins = "PF0", "PF1", "PF2", "PF3", "PF4", "PF5";
				function = "mmc0";
			};


			spi0_pins: spi0-pins{
               	 	pins = "PC0", "PC1", "PC2", "PC3";
                	function = "spi0";
            };

           	 spi1_pins: spi1-pins{
                	pins = "PA2","PA0","PA3","PA1";
                	function = "spi1";
            };


		};
复制成功

图7 添加SPI节点

图8 添加pio节点

然后打开arch/arm/boot/dts/suniv-f1c100s-licheepi-nano.dts在spi1中添加st7789v子节点

代码块
clike
自动换行
复制代码
&spi1 {
    st7789v@0 {
        status = "okay";
        compatible = "sitronix,st7789v";
               reg = <0>;
               spi-max-frequency =<32000000>;        //SPI时钟32M
               rotate =<90>;                    //屏幕旋转90度
               spi-cpol;
               spi-cpha;
               rgb;                           //颜色格式RGB
               fps =<30>;                      //刷新30帧率
               buswidth =<8>;                   //总线宽度8
	            reset-gpios=<&pio 4 3 GPIO_ACTIVE_LOW>;   //GPIOE3
               dc-gpios  =<&pio 4 5 GPIO_ACTIVE_LOW>;   //GPIOE5
               debug =<0>;                     //不开启调试
        };
}; 
复制成功

图9 添加st7789v子节点

现在所有的修改都完成了,剩下的就是编译内核了,在内核根目录下执行make menuconfig启动图形配置界面,由于FC1000S的SPI中有一个BUG,因此我们在开启SPI驱动的时候必须选择A31(Device Drivers -> SPI support)如图所示

图10 选择A31SPI控制器

现在选择ST7789V驱动并编译进内核中,如下:

Device Drivers  --->  

    [*] Staging drivers  --->  

        <*>   Support for small TFT LCD display modules  --->

              <*>   FB driver for the ST7789V LCD Controller 

图11 勾选ST7789V驱动模块编译进内核

保存退出,然后make -j4编译内核,然后将镜像拷贝到tf卡第一分区中,此时可以看到屏幕已经可以驱动起来了,并且/dev目录下有fb0设备。

注意:对于1.14寸液晶屏而言,其屏幕有偏移,这里需要修改fbtft-core.c文件中的fbtft_set_addr_win函数

代码块
clike
自动换行
复制代码
static void fbtft_set_addr_win(struct fbtft_par *par, int xs, int ys, int xe,
			       int ye)
{
	write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,(xs+40) >> 8, xs+40, ((xe+40) >> 8) & 0xFF, (xe+40) & 0xFF);

	write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,((ys+52) >> 8) & 0xFF, (ys+52) & 0xFF, ((ye+52) >> 8) & 0xFF, (ye+52) & 0xFF);

	write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
}
复制成功

图12 针对1.14寸修改屏幕显示偏移

对于SPI的CLK和SDA(MOSI)而言,最好接上拉电阻