您尚未登录。

楼主 # 2021-08-13 22:07:45

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

效果
w9.jpg
我使用的是Linux-5.10内核
当时驱动不了屏幕,后来发现驱动程序有点问题,就照着STM32的程序改了一下
路径为linux-5.10/drivers/staging/fbtft/
改好的fb_st7789v程序如下
fb_st7789v.zip

内核配置

Device Drivers  --->
     【*】 Staging drivers  --->
              <*>   Support for small TFT LCD display modules  --->
                        <*>   FB driver for the ST7789V LCD Controller

设备树
路径为linux-5.10/arch/arm/boot/dts/

sun8i-v3s-licheepi-zero-dock.dts

chosen {
            /delete-node/ framebuffer@0;
    };

&spi0{
    status = "okay";
    st7789v: st7789v@0{
        compatible = "sitronix,st7789v";
        reg = <0>;
        status = "okay";
        spi-max-frequency = <96000000>;
        spi-cpol;
        spi-cpha;
        rotate = <270>;
        fps = <60>;
        buswidth = <8>;
        dc-gpios = <&pio 1 4 GPIO_ACTIVE_HIGH>;  // PB4 PB0
        reset-gpios = <&pio 1 5 GPIO_ACTIVE_HIGH>; // PB5
        //led-gpios = <&pio 0 0 GPIO_ACTIVE_LOW>;  // PA0
        debug = <0x0>;
        };
};

最近编辑记录 资本家大善人 (2021-08-13 22:26:39)

离线

楼主 #1 2021-08-13 22:37:08

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

顺便问一下,那个字体大小怎么改,找不大地方:)

离线

#2 2021-09-22 21:35:52

thanksgivingday
会员
注册时间: 2021-09-22
已发帖子: 2
积分: 1

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

你好啊,我看了了你的驱动代码,请问
    // mdelay(50);
    // write_reg(par,0x36,0x00);
    // write_reg(par,0x3A,0x05);
    // write_reg(par,0xB2,0x0C,0x0C,0x00,0x33,0x33);
    // write_reg(par,0xB7,0x35);
    // write_reg(par,0xBB,0x19);
    // write_reg(par,0xC0,0x2C);
    // write_reg(par,0xC2,0x01);
    // write_reg(par,0xC3,0x12);
    // write_reg(par,0xC4,0x20);
    // write_reg(par,0xC6,0x0F);
    // write_reg(par,0xD0,0xA4,0xA1);
    // write_reg(par,0xE0,0xD0,0x04,0x0D,0x11,0x13,0x2B,0x3F,0x54,0x4C,0x18,0x0D,0x0B,0x1F,0x23);
    // write_reg(par,0xE1,0xD0,0x04,0x0C,0x11,0x13,0x2C,0x3F,0x44,0x51,0x2F,0x1F,0x1F,0x20,0x23);
    // write_reg(par,0x21);
    // write_reg(par,0x11);
    // mdelay(50);
    // write_reg(par,0x29);
    // mdelay(200);
    // return 0;
用这串代码初始化 你那边是遇到的什么问题呢,我是用这串代码初始化 结果黑屏了。
之前我用ILI9341走FB驱动 ,字体网上下一个字体库就行了。我有现成的

离线

楼主 #3 2021-09-23 03:57:55

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

@thanksgivingday
我用原驱动代码,屏幕亮不了(320*240 2寸st7789SPI屏幕)
与STM32的程序对照,发现两边初始化代码不一样,改成STM32的就成功点亮了

离线

#4 2021-09-23 09:54:23

thanksgivingday
会员
注册时间: 2021-09-22
已发帖子: 2
积分: 1

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

资本家大善人 说:

@thanksgivingday
我用原驱动代码,屏幕亮不了(320*240 2寸st7789SPI屏幕)
与STM32的程序对照,发现两边初始化代码不一样,改成STM32的就成功点亮了

我也用了STM32的初始化代码还是不行 - -
你贴出来的代码是你最后成功的代码吗

离线

楼主 #5 2021-09-23 15:08:37

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

@thanksgivingday

// SPDX-License-Identifier: GPL-2.0+
/*
 * FB driver for the ST7789V LCD Controller
 *
 * Copyright (C) 2015 Dennis Menschel
 */

#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <video/mipi_display.h>

#include "fbtft.h"

#define DRVNAME "fb_st7789v"

#define DEFAULT_GAMMA \
	"70 2C 2E 15 10 09 48 33 53 0B 19 18 20 25\n" \
	"70 2C 2E 15 10 09 48 33 53 0B 19 18 20 25"

#define HSD20_IPS_GAMMA \
	"D0 05 0A 09 08 05 2E 44 45 0F 17 16 2B 33\n" \
	"D0 05 0A 09 08 05 2E 43 45 0F 16 16 2B 33"

#define HSD20_IPS 1

/**
 * enum st7789v_command - ST7789V display controller commands
 *
 * @PORCTRL: porch setting
 * @GCTRL: gate control
 * @VCOMS: VCOM setting
 * @VDVVRHEN: VDV and VRH command enable
 * @VRHS: VRH set
 * @VDVS: VDV set
 * @VCMOFSET: VCOM offset set
 * @PWCTRL1: power control 1
 * @PVGAMCTRL: positive voltage gamma control
 * @NVGAMCTRL: negative voltage gamma control
 *
 * The command names are the same as those found in the datasheet to ease
 * looking up their semantics and usage.
 *
 * Note that the ST7789V display controller offers quite a few more commands
 * which have been omitted from this list as they are not used at the moment.
 * Furthermore, commands that are compliant with the MIPI DCS have been left
 * out as well to avoid duplicate entries.
 */
enum st7789v_command {
	PORCTRL = 0xB2,
	GCTRL = 0xB7,
	VCOMS = 0xBB,
	VDVVRHEN = 0xC2,
	VRHS = 0xC3,
	VDVS = 0xC4,
	VCMOFSET = 0xC5,
	PWCTRL1 = 0xD0,
	PVGAMCTRL = 0xE0,
	NVGAMCTRL = 0xE1,
};

#define MADCTL_BGR BIT(3) /* bitmask for RGB/BGR order */
#define MADCTL_MV BIT(5) /* bitmask for page/column order */
#define MADCTL_MX BIT(6) /* bitmask for column address order */
#define MADCTL_MY BIT(7) /* bitmask for page address order */

/**
 * init_display() - initialize the display controller
 *
 * @par: FBTFT parameter object
 *
 * Most of the commands in this init function set their parameters to the
 * same default values which are already in place after the display has been
 * powered up. (The main exception to this rule is the pixel format which
 * would default to 18 instead of 16 bit per pixel.)
 * Nonetheless, this sequence can be used as a template for concrete
 * displays which usually need some adjustments.
 *
 * Return: 0 on success, < 0 if error occurred.
 */
static int init_display(struct fbtft_par *par)
{
	par->fbtftops.reset(par);
	/* turn off sleep mode */
	write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE);
	mdelay(120);

	/* set pixel format to RGB-565 */
	write_reg(par, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_16BIT);
	if (HSD20_IPS)
		write_reg(par, PORCTRL, 0x05, 0x05, 0x00, 0x33, 0x33);
	else
		write_reg(par, PORCTRL, 0x08, 0x08, 0x00, 0x22, 0x22);

	/*
	 * VGH = 13.26V
	 * VGL = -10.43V
	 */
	if (HSD20_IPS)
		write_reg(par, GCTRL, 0x75);
	else
		write_reg(par, GCTRL, 0x35);

	/*
	 * VDV and VRH register values come from command write
	 * (instead of NVM)
	 */
	write_reg(par, VDVVRHEN, 0x01, 0xFF);

	/*
	 * VAP =  4.1V + (VCOM + VCOM offset + 0.5 * VDV)
	 * VAN = -4.1V + (VCOM + VCOM offset + 0.5 * VDV)
	 */
	if (HSD20_IPS)
		write_reg(par, VRHS, 0x13);
	else
		write_reg(par, VRHS, 0x0B);

	/* VDV = 0V */
	write_reg(par, VDVS, 0x20);

	/* VCOM = 0.9V */
	if (HSD20_IPS)
		write_reg(par, VCOMS, 0x22);
	else
		write_reg(par, VCOMS, 0x20);

	/* VCOM offset = 0V */
	write_reg(par, VCMOFSET, 0x20);

	/*
	 * AVDD = 6.8V
	 * AVCL = -4.8V
	 * VDS = 2.3V
	 */
	write_reg(par, PWCTRL1, 0xA4, 0xA1);

	write_reg(par, MIPI_DCS_SET_DISPLAY_ON);

	if (HSD20_IPS)
		write_reg(par, MIPI_DCS_ENTER_INVERT_MODE);

	return 0;
	//  par->fbtftops.reset(par);
    // mdelay(50);
    // write_reg(par,0x36,0x00);
    // write_reg(par,0x3A,0x05);
    // write_reg(par,0xB2,0x0C,0x0C,0x00,0x33,0x33);
    // write_reg(par,0xB7,0x35);
    // write_reg(par,0xBB,0x19);
    // write_reg(par,0xC0,0x2C);
    // write_reg(par,0xC2,0x01);
    // write_reg(par,0xC3,0x12);
    // write_reg(par,0xC4,0x20);
    // write_reg(par,0xC6,0x0F);
    // write_reg(par,0xD0,0xA4,0xA1);
    // write_reg(par,0xE0,0xD0,0x04,0x0D,0x11,0x13,0x2B,0x3F,0x54,0x4C,0x18,0x0D,0x0B,0x1F,0x23);
    // write_reg(par,0xE1,0xD0,0x04,0x0C,0x11,0x13,0x2C,0x3F,0x44,0x51,0x2F,0x1F,0x1F,0x20,0x23);
    // write_reg(par,0x21);
    // write_reg(par,0x11);
    // mdelay(50);
    // write_reg(par,0x29);
    // mdelay(200);
    // return 0;
}

/**
 * set_var() - apply LCD properties like rotation and BGR mode
 *
 * @par: FBTFT parameter object
 *
 * Return: 0 on success, < 0 if error occurred.
 */
static int set_var(struct fbtft_par *par)
{
	u8 madctl_par = 0;

	if (par->bgr)
		madctl_par |= MADCTL_BGR;
	switch (par->info->var.rotate) {
	case 0:
		break;
	case 90:
		madctl_par |= (MADCTL_MV | MADCTL_MY);
		break;
	case 180:
		madctl_par |= (MADCTL_MX | MADCTL_MY);
		break;
	case 270:
		madctl_par |= (MADCTL_MV | MADCTL_MX);
		break;
	default:
		return -EINVAL;
	}
	write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, madctl_par);
	return 0;
}

/**
 * set_gamma() - set gamma curves
 *
 * @par: FBTFT parameter object
 * @curves: gamma curves
 *
 * Before the gamma curves are applied, they are preprocessed with a bitmask
 * to ensure syntactically correct input for the display controller.
 * This implies that the curves input parameter might be changed by this
 * function and that illegal gamma values are auto-corrected and not
 * reported as errors.
 *
 * Return: 0 on success, < 0 if error occurred.
 */
static int set_gamma(struct fbtft_par *par, u32 *curves)
{
	int i;
	int j;
	int c; /* curve index offset */

	/*
	 * Bitmasks for gamma curve command parameters.
	 * The masks are the same for both positive and negative voltage
	 * gamma curves.
	 */
	static const u8 gamma_par_mask[] = {
		0xFF, /* V63[3:0], V0[3:0]*/
		0x3F, /* V1[5:0] */
		0x3F, /* V2[5:0] */
		0x1F, /* V4[4:0] */
		0x1F, /* V6[4:0] */
		0x3F, /* J0[1:0], V13[3:0] */
		0x7F, /* V20[6:0] */
		0x77, /* V36[2:0], V27[2:0] */
		0x7F, /* V43[6:0] */
		0x3F, /* J1[1:0], V50[3:0] */
		0x1F, /* V57[4:0] */
		0x1F, /* V59[4:0] */
		0x3F, /* V61[5:0] */
		0x3F, /* V62[5:0] */
	};

	for (i = 0; i < par->gamma.num_curves; i++) {
		c = i * par->gamma.num_values;
		for (j = 0; j < par->gamma.num_values; j++)
			curves[c + j] &= gamma_par_mask[j];
		write_reg(par, PVGAMCTRL + i,
			  curves[c + 0],  curves[c + 1],  curves[c + 2],
			  curves[c + 3],  curves[c + 4],  curves[c + 5],
			  curves[c + 6],  curves[c + 7],  curves[c + 8],
			  curves[c + 9],  curves[c + 10], curves[c + 11],
			  curves[c + 12], curves[c + 13]);
	}
	return 0;
}

/**
 * blank() - blank the display
 *
 * @par: FBTFT parameter object
 * @on: whether to enable or disable blanking the display
 *
 * Return: 0 on success, < 0 if error occurred.
 */
static int blank(struct fbtft_par *par, bool on)
{
	if (on)
		write_reg(par, MIPI_DCS_SET_DISPLAY_OFF);
	else
		write_reg(par, MIPI_DCS_SET_DISPLAY_ON);
	return 0;
}
static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
{
	// switch(par->info->var.rotate)
	// {
	// 	case   0: xs+=53;xe+=53;ys+=40;ye+=40;
	// 			 break;
	// 	// case  90: xs+=40;xe+=40;ys+=53;ye+=53;
	// 	case  90: xs+=80;xe+=80;
	// 			 break;
	// 	case 180: xs+=53;xe+=53;ys+=40;ye+=40;
	// 			 break;
	// 	case 270: xs+=40;xe+=40;ys+=53;ye+=53;
	// 			 break;
	// 	default :
	// 			 break;
	// }
	write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
		  xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF);

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

	write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
}

static struct fbtft_display display = {
	.regwidth = 8,
	.width = 240,
	.height = 320,
	.gamma_num = 2,
	.gamma_len = 14,
	.gamma = HSD20_IPS_GAMMA,
	.fbtftops = {
		
		.init_display = init_display,
		.set_addr_win = set_addr_win,
		.set_var = set_var,
		.set_gamma = set_gamma,
		.blank = blank,
	},
};

FBTFT_REGISTER_DRIVER(DRVNAME, "sitronix,st7789v", &display);

MODULE_ALIAS("spi:" DRVNAME);
MODULE_ALIAS("platform:" DRVNAME);
MODULE_ALIAS("spi:st7789v");
MODULE_ALIAS("platform:st7789v");

MODULE_DESCRIPTION("FB driver for the ST7789V LCD Controller");
MODULE_AUTHOR("Dennis Menschel");
MODULE_LICENSE("GPL");

离线

#6 2021-10-14 23:15:14

zerg
会员
注册时间: 2021-02-07
已发帖子: 11
积分: 10.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

楼主,串口和 USB键盘输入 多终端显示,是怎样 配置成同步的啊

离线

楼主 #7 2021-10-17 19:42:30

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

zerg 说:

楼主,串口和 USB键盘输入 多终端显示,是怎样 配置成同步的啊

vi /etc/inittab

将原有的console注释掉,改为ttyS0和tty0,ttyS0是USB键盘输入,tty0就是正常输出输入

#console::respawn:-/bin/sh
ttyS0::respawn:-/bin/sh
tty0::respawn:-/bin/sh

离线

#8 2021-11-02 10:50:08

HongSang
会员
注册时间: 2021-08-12
已发帖子: 42
积分: 16.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

请问怎么初始化lcd的?

离线

#9 2021-11-02 22:47:05

树莓学LINUX
会员
注册时间: 2021-10-28
已发帖子: 91
积分: 101
个人网站

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

请问io硬件连接是怎样的,连接到v3s的spi吗

离线

楼主 #10 2021-11-02 23:45:35

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

树莓学LINUX 说:

请问io硬件连接是怎样的,连接到v3s的spi吗

是的

离线

楼主 #11 2021-11-02 23:45:57

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

HongSang 说:

请问怎么初始化lcd的?

改内核改设备树

离线

#12 2021-11-03 11:22:40

树莓学LINUX
会员
注册时间: 2021-10-28
已发帖子: 91
积分: 101
个人网站

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

资本家大善人 说:
树莓学LINUX 说:

请问io硬件连接是怎样的,连接到v3s的spi吗

是的

请问我可不可以用荔枝派zero连接外部的tf屏幕做测试

离线

楼主 #13 2021-11-04 01:32:47

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

树莓学LINUX 说:
资本家大善人 说:
树莓学LINUX 说:

请问io硬件连接是怎样的,连接到v3s的spi吗

是的

请问我可不可以用荔枝派zero连接外部的tf屏幕做测试

可以

离线

#14 2021-11-18 22:45:52

树莓学LINUX
会员
注册时间: 2021-10-28
已发帖子: 91
积分: 101
个人网站

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

请问已经按照您的教程操作了,可是用官方教程的buildroot-2017.08.01编译出来的根文件系统,开机以后就是白屏,我又使用别人的已经编译成功的Debian根文件系统,开机之后可以正常显示,请问这是什么原因?难道在根文件系统中也要进行设置吗?

离线

楼主 #15 2021-11-19 01:15:44

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

@树莓学LINUX
印象里好像不用,只设置了个控制台,7楼
这个很详细
https://blog.csdn.net/qq_28877125/article/details/120007416

最近编辑记录 资本家大善人 (2021-11-19 01:18:54)

离线

#16 2021-11-19 11:51:25

树莓学LINUX
会员
注册时间: 2021-10-28
已发帖子: 91
积分: 101
个人网站

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

资本家大善人 说:

@树莓学LINUX
印象里好像不用,只设置了个控制台,7楼
这个很详细
https://blog.csdn.net/qq_28877125/article/details/120007416

尝试了很多次,使用自己编译的根文件系统不能显示,直接白屏,使用别人的根文件系统就可以正常显示,我哭了

离线

#17 2021-11-19 12:16:43

树莓学LINUX
会员
注册时间: 2021-10-28
已发帖子: 91
积分: 101
个人网站

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

资本家大善人 说:

@树莓学LINUX
印象里好像不用,只设置了个控制台,7楼
这个很详细
https://blog.csdn.net/qq_28877125/article/details/120007416

开启双端显示后  屏幕能正常显示了   难道在使用串口的时候  屏幕默认不显示吗

离线

楼主 #18 2021-11-20 10:42:25

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

树莓学LINUX 说:
资本家大善人 说:

@树莓学LINUX
印象里好像不用,只设置了个控制台,7楼
这个很详细
https://blog.csdn.net/qq_28877125/article/details/120007416

开启双端显示后  屏幕能正常显示了   难道在使用串口的时候  屏幕默认不显示吗

能的,双端显示,双端控制

离线

#19 2021-11-20 13:19:29

树莓学LINUX
会员
注册时间: 2021-10-28
已发帖子: 91
积分: 101
个人网站

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

楼主你的内核配置config文件可以看看吗?我们的屏幕应该是同一个,但是我的屏幕显示是反色

离线

#20 2021-11-30 15:54:35

inner
会员
注册时间: 2021-11-09
已发帖子: 21
积分: 6

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

请问初始化LCD到底应该在u-boot还是linux kernel里修改,具体在哪修改

离线

#21 2021-11-30 23:03:18

树莓学LINUX
会员
注册时间: 2021-10-28
已发帖子: 91
积分: 101
个人网站

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

inner 说:

请问初始化LCD到底应该在u-boot还是linux kernel里修改,具体在哪修改

在内核里面

离线

#22 2021-12-01 09:51:52

inner
会员
注册时间: 2021-11-09
已发帖子: 21
积分: 6

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

谢谢,请问除了linux/drivers/staging/fbtft/里的需要修改,还有哪需要修改呢

离线

#23 2022-03-17 17:33:23

jojo
会员
注册时间: 2022-01-18
已发帖子: 1
积分: 1

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

设备树节点SPI0和flash启动的spi0的节点是不是重合的啊

离线

#24 2022-04-12 08:21:42

stutian
会员
注册时间: 2022-03-30
已发帖子: 7
积分: 5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

@资本家大善人

vi /etc/inittab

将原有的console注释掉,改为ttyS0和tty0,ttyS0是USB键盘输入,tty0就是正常输出输入

#console::respawn:-/bin/sh
ttyS0::respawn:-/bin/sh
tty0::respawn:-/bin/sh

如下配置屏幕显示了内核启动的log但是,控制串口终端的时候为什么屏幕就不同步了,就一直显示log,重启也只是显示log?
还是说这个只是单独的两个,那这么操作可以让电脑键盘输入,然后在控制台和屏幕同时显示呢?
poh.jpgcom.jpg

# vi /etc/inittab
# /etc/inittab
#
# Copyright (C) 2001 Erik Andersen <andersen@codepoet.org>
#
# Note: BusyBox init doesn't support runlevels.  The runlevels field is
# completely ignored by BusyBox init. If you want runlevels, use
# sysvinit.
#
# Format for each entry: <id>:<runlevels>:<action>:<process>
#
# id        == tty to run on, or empty for /dev/console
# runlevels == ignored
# action    == one of sysinit, respawn, askfirst, wait, and once
# process   == program to run

# Startup the system
::sysinit:/bin/mount -t proc proc /proc
::sysinit:/bin/mount -o remount,rw /
::sysinit:/bin/mkdir -p /dev/pts
::sysinit:/bin/mkdir -p /dev/shm
::sysinit:/bin/mount -a
::sysinit:/bin/hostname -F /etc/hostname
# now run any rc scripts
::sysinit:/etc/init.d/rcS

# Put a getty on the serial port
#console::respawn:/sbin/getty -L  console 0 vt100 # GENERIC_SERIAL
ttyS0::respawn:-/bin/sh
tty0::respawn:-/bin/sh

# Stuff to do for the 3-finger salute
#::ctrlaltdel:/sbin/reboot

# Stuff to do before rebooting
::shutdown:/etc/init.d/rcK
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r

最近编辑记录 stutian (2022-04-12 08:28:03)

离线

#25 2022-04-22 21:44:01

stutian
会员
注册时间: 2022-03-30
已发帖子: 7
积分: 5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

资本家大善人 说:

顺便问一下,那个字体大小怎么改,找不大地方:)

fbtft上终端的字体大小怎么改???

离线

楼主 #26 2022-06-11 11:09:42

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

fb_st7789v.c

// SPDX-License-Identifier: GPL-2.0+
/*
 * FB driver for the ST7789V LCD Controller
 *
 * Copyright (C) 2015 Dennis Menschel
 */

#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <video/mipi_display.h>

#include "fbtft.h"

#define DRVNAME "fb_st7789v"

#define DEFAULT_GAMMA \
	"70 2C 2E 15 10 09 48 33 53 0B 19 18 20 25\n" \
	"70 2C 2E 15 10 09 48 33 53 0B 19 18 20 25"

#define HSD20_IPS_GAMMA \
	"D0 05 0A 09 08 05 2E 44 45 0F 17 16 2B 33\n" \
	"D0 05 0A 09 08 05 2E 43 45 0F 16 16 2B 33"

#define HSD20_IPS 1

/**
 * enum st7789v_command - ST7789V display controller commands
 *
 * @PORCTRL: porch setting
 * @GCTRL: gate control
 * @VCOMS: VCOM setting
 * @VDVVRHEN: VDV and VRH command enable
 * @VRHS: VRH set
 * @VDVS: VDV set
 * @VCMOFSET: VCOM offset set
 * @PWCTRL1: power control 1
 * @PVGAMCTRL: positive voltage gamma control
 * @NVGAMCTRL: negative voltage gamma control
 *
 * The command names are the same as those found in the datasheet to ease
 * looking up their semantics and usage.
 *
 * Note that the ST7789V display controller offers quite a few more commands
 * which have been omitted from this list as they are not used at the moment.
 * Furthermore, commands that are compliant with the MIPI DCS have been left
 * out as well to avoid duplicate entries.
 */
enum st7789v_command {
	PORCTRL = 0xB2,
	GCTRL = 0xB7,
	VCOMS = 0xBB,
	VDVVRHEN = 0xC2,
	VRHS = 0xC3,
	VDVS = 0xC4,
	VCMOFSET = 0xC5,
	PWCTRL1 = 0xD0,
	PVGAMCTRL = 0xE0,
	NVGAMCTRL = 0xE1,
};

#define MADCTL_BGR BIT(3) /* bitmask for RGB/BGR order */
#define MADCTL_MV BIT(5) /* bitmask for page/column order */
#define MADCTL_MX BIT(6) /* bitmask for column address order */
#define MADCTL_MY BIT(7) /* bitmask for page address order */

/**
 * init_display() - initialize the display controller
 *
 * @par: FBTFT parameter object
 *
 * Most of the commands in this init function set their parameters to the
 * same default values which are already in place after the display has been
 * powered up. (The main exception to this rule is the pixel format which
 * would default to 18 instead of 16 bit per pixel.)
 * Nonetheless, this sequence can be used as a template for concrete
 * displays which usually need some adjustments.
 *
 * Return: 0 on success, < 0 if error occurred.
 */
static int init_display(struct fbtft_par *par)
{
	par->fbtftops.reset(par);
	/* turn off sleep mode */
	write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE);
	mdelay(120);

	/* set pixel format to RGB-565 */
	write_reg(par, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_16BIT);
	if (HSD20_IPS)
		write_reg(par, PORCTRL, 0x05, 0x05, 0x00, 0x33, 0x33);
	else
		write_reg(par, PORCTRL, 0x08, 0x08, 0x00, 0x22, 0x22);

	/*
	 * VGH = 13.26V
	 * VGL = -10.43V
	 */
	if (HSD20_IPS)
		write_reg(par, GCTRL, 0x75);
	else
		write_reg(par, GCTRL, 0x35);

	/*
	 * VDV and VRH register values come from command write
	 * (instead of NVM)
	 */
	write_reg(par, VDVVRHEN, 0x01, 0xFF);

	/*
	 * VAP =  4.1V + (VCOM + VCOM offset + 0.5 * VDV)
	 * VAN = -4.1V + (VCOM + VCOM offset + 0.5 * VDV)
	 */
	if (HSD20_IPS)
		write_reg(par, VRHS, 0x13);
	else
		write_reg(par, VRHS, 0x0B);

	/* VDV = 0V */
	write_reg(par, VDVS, 0x20);

	/* VCOM = 0.9V */
	if (HSD20_IPS)
		write_reg(par, VCOMS, 0x22);
	else
		write_reg(par, VCOMS, 0x20);

	/* VCOM offset = 0V */
	write_reg(par, VCMOFSET, 0x20);

	/*
	 * AVDD = 6.8V
	 * AVCL = -4.8V
	 * VDS = 2.3V
	 */
	write_reg(par, PWCTRL1, 0xA4, 0xA1);

	write_reg(par, MIPI_DCS_SET_DISPLAY_ON);

	if (HSD20_IPS)
		write_reg(par, MIPI_DCS_ENTER_INVERT_MODE);

	return 0;
	//  par->fbtftops.reset(par);
    // mdelay(50);
    // write_reg(par,0x36,0x00);
    // write_reg(par,0x3A,0x05);
    // write_reg(par,0xB2,0x0C,0x0C,0x00,0x33,0x33);
    // write_reg(par,0xB7,0x35);
    // write_reg(par,0xBB,0x19);
    // write_reg(par,0xC0,0x2C);
    // write_reg(par,0xC2,0x01);
    // write_reg(par,0xC3,0x12);
    // write_reg(par,0xC4,0x20);
    // write_reg(par,0xC6,0x0F);
    // write_reg(par,0xD0,0xA4,0xA1);
    // write_reg(par,0xE0,0xD0,0x04,0x0D,0x11,0x13,0x2B,0x3F,0x54,0x4C,0x18,0x0D,0x0B,0x1F,0x23);
    // write_reg(par,0xE1,0xD0,0x04,0x0C,0x11,0x13,0x2C,0x3F,0x44,0x51,0x2F,0x1F,0x1F,0x20,0x23);
    // write_reg(par,0x21);
    // write_reg(par,0x11);
    // mdelay(50);
    // write_reg(par,0x29);
    // mdelay(200);
    // return 0;
}

/**
 * set_var() - apply LCD properties like rotation and BGR mode
 *
 * @par: FBTFT parameter object
 *
 * Return: 0 on success, < 0 if error occurred.
 */
static int set_var(struct fbtft_par *par)
{
	u8 madctl_par = 0;

	if (par->bgr)
		madctl_par |= MADCTL_BGR;
	switch (par->info->var.rotate) {
	case 0:
		break;
	case 90:
		madctl_par |= (MADCTL_MV | MADCTL_MY);
		break;
	case 180:
		madctl_par |= (MADCTL_MX | MADCTL_MY);
		break;
	case 270:
		madctl_par |= (MADCTL_MV | MADCTL_MX);
		break;
	default:
		return -EINVAL;
	}
	write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, madctl_par);
	return 0;
}

/**
 * set_gamma() - set gamma curves
 *
 * @par: FBTFT parameter object
 * @curves: gamma curves
 *
 * Before the gamma curves are applied, they are preprocessed with a bitmask
 * to ensure syntactically correct input for the display controller.
 * This implies that the curves input parameter might be changed by this
 * function and that illegal gamma values are auto-corrected and not
 * reported as errors.
 *
 * Return: 0 on success, < 0 if error occurred.
 */
static int set_gamma(struct fbtft_par *par, u32 *curves)
{
	int i;
	int j;
	int c; /* curve index offset */

	/*
	 * Bitmasks for gamma curve command parameters.
	 * The masks are the same for both positive and negative voltage
	 * gamma curves.
	 */
	static const u8 gamma_par_mask[] = {
		0xFF, /* V63[3:0], V0[3:0]*/
		0x3F, /* V1[5:0] */
		0x3F, /* V2[5:0] */
		0x1F, /* V4[4:0] */
		0x1F, /* V6[4:0] */
		0x3F, /* J0[1:0], V13[3:0] */
		0x7F, /* V20[6:0] */
		0x77, /* V36[2:0], V27[2:0] */
		0x7F, /* V43[6:0] */
		0x3F, /* J1[1:0], V50[3:0] */
		0x1F, /* V57[4:0] */
		0x1F, /* V59[4:0] */
		0x3F, /* V61[5:0] */
		0x3F, /* V62[5:0] */
	};

	for (i = 0; i < par->gamma.num_curves; i++) {
		c = i * par->gamma.num_values;
		for (j = 0; j < par->gamma.num_values; j++)
			curves[c + j] &= gamma_par_mask[j];
		write_reg(par, PVGAMCTRL + i,
			  curves[c + 0],  curves[c + 1],  curves[c + 2],
			  curves[c + 3],  curves[c + 4],  curves[c + 5],
			  curves[c + 6],  curves[c + 7],  curves[c + 8],
			  curves[c + 9],  curves[c + 10], curves[c + 11],
			  curves[c + 12], curves[c + 13]);
	}
	return 0;
}

/**
 * blank() - blank the display
 *
 * @par: FBTFT parameter object
 * @on: whether to enable or disable blanking the display
 *
 * Return: 0 on success, < 0 if error occurred.
 */
static int blank(struct fbtft_par *par, bool on)
{
	if (on)
		write_reg(par, MIPI_DCS_SET_DISPLAY_OFF);
	else
		write_reg(par, MIPI_DCS_SET_DISPLAY_ON);
	return 0;
}
static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
{
	// switch(par->info->var.rotate)
	// {
	// 	case   0: xs+=53;xe+=53;ys+=40;ye+=40;
	// 			 break;
	// 	// case  90: xs+=40;xe+=40;ys+=53;ye+=53;
	// 	case  90: xs+=80;xe+=80;
	// 			 break;
	// 	case 180: xs+=53;xe+=53;ys+=40;ye+=40;
	// 			 break;
	// 	case 270: xs+=40;xe+=40;ys+=53;ye+=53;
	// 			 break;
	// 	default :
	// 			 break;
	// }
	write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
		  xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF);

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

	write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
}

static struct fbtft_display display = {
	.regwidth = 8,
	.width = 240,
	.height = 240,
	.gamma_num = 2,
	.gamma_len = 14,
	.gamma = HSD20_IPS_GAMMA,
	.fbtftops = {
		
		.init_display = init_display,
		.set_addr_win = set_addr_win,
		.set_var = set_var,
		.set_gamma = set_gamma,
		.blank = blank,
	},
};

FBTFT_REGISTER_DRIVER(DRVNAME, "sitronix,st7789v", &display);

MODULE_ALIAS("spi:" DRVNAME);
MODULE_ALIAS("platform:" DRVNAME);
MODULE_ALIAS("spi:st7789v");
MODULE_ALIAS("platform:st7789v");

MODULE_DESCRIPTION("FB driver for the ST7789V LCD Controller");
MODULE_AUTHOR("Dennis Menschel");
MODULE_LICENSE("GPL");

离线

楼主 #27 2022-06-11 11:10:24

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

fbtft-core.c

// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2013 Noralf Tronnes
 *
 * This driver is inspired by:
 *   st7735fb.c, Copyright (C) 2011, Matt Porter
 *   broadsheetfb.c, Copyright (C) 2008, Jaya Kumar
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fb.h>
#include <linux/gpio/consumer.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/backlight.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/spinlock.h>

#include <video/mipi_display.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include "fbtft.h"
#include "internal.h"

static unsigned long debug;
module_param(debug, ulong, 0000);
MODULE_PARM_DESC(debug, "override device debug level");

int fbtft_write_buf_dc(struct fbtft_par *par, void *buf, size_t len, int dc)
{
	int ret;

	if (par->gpio.dc)
		gpiod_set_value(par->gpio.dc, dc);

	ret = par->fbtftops.write(par, buf, len);
	if (ret < 0)
		dev_err(par->info->device,
			"write() failed and returned %d\n", ret);
	return ret;
}
EXPORT_SYMBOL(fbtft_write_buf_dc);

void fbtft_dbg_hex(const struct device *dev, int groupsize,
		   void *buf, size_t len, const char *fmt, ...)
{
	va_list args;
	static char textbuf[512];
	char *text = textbuf;
	size_t text_len;

	va_start(args, fmt);
	text_len = vscnprintf(text, sizeof(textbuf), fmt, args);
	va_end(args);

	hex_dump_to_buffer(buf, len, 32, groupsize, text + text_len,
			   512 - text_len, false);

	if (len > 32)
		dev_info(dev, "%s ...\n", text);
	else
		dev_info(dev, "%s\n", text);
}
EXPORT_SYMBOL(fbtft_dbg_hex);

// 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;
// 	int ret = 0;

// 	*gpiop = devm_gpiod_get_index_optional(dev, name, index,
// 					       GPIOD_OUT_HIGH);
// 	if (IS_ERR(*gpiop)) {
// 		ret = PTR_ERR(*gpiop);
// 		dev_err(dev,
// 			"Failed to request %s GPIO: %d\n", name, ret);
// 		return ret;
// 	}
// 	fbtft_par_dbg(DEBUG_REQUEST_GPIOS, par, "%s: '%s' GPIO\n",
// 		      __func__, name);

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

	return 0;
}

#ifdef CONFIG_FB_BACKLIGHT
static int fbtft_backlight_update_status(struct backlight_device *bd)
{
	struct fbtft_par *par = bl_get_data(bd);
	bool polarity = par->polarity;

	fbtft_par_dbg(DEBUG_BACKLIGHT, par,
		      "%s: polarity=%d, power=%d, fb_blank=%d\n",
		      __func__, polarity, bd->props.power, bd->props.fb_blank);

	if ((bd->props.power == FB_BLANK_UNBLANK) &&
	    (bd->props.fb_blank == FB_BLANK_UNBLANK))
		gpiod_set_value(par->gpio.led[0], polarity);
	else
		gpiod_set_value(par->gpio.led[0], !polarity);

	return 0;
}

static int fbtft_backlight_get_brightness(struct backlight_device *bd)
{
	return bd->props.brightness;
}

void fbtft_unregister_backlight(struct fbtft_par *par)
{
	if (par->info->bl_dev) {
		par->info->bl_dev->props.power = FB_BLANK_POWERDOWN;
		backlight_update_status(par->info->bl_dev);
		backlight_device_unregister(par->info->bl_dev);
		par->info->bl_dev = NULL;
	}
}

static const struct backlight_ops fbtft_bl_ops = {
	.get_brightness	= fbtft_backlight_get_brightness,
	.update_status	= fbtft_backlight_update_status,
};

void fbtft_register_backlight(struct fbtft_par *par)
{
	struct backlight_device *bd;
	struct backlight_properties bl_props = { 0, };

	if (!par->gpio.led[0]) {
		fbtft_par_dbg(DEBUG_BACKLIGHT, par,
			      "%s(): led pin not set, exiting.\n", __func__);
		return;
	}

	bl_props.type = BACKLIGHT_RAW;
	/* Assume backlight is off, get polarity from current state of pin */
	bl_props.power = FB_BLANK_POWERDOWN;
	if (!gpiod_get_value(par->gpio.led[0]))
		par->polarity = true;

	bd = backlight_device_register(dev_driver_string(par->info->device),
				       par->info->device, par,
				       &fbtft_bl_ops, &bl_props);
	if (IS_ERR(bd)) {
		dev_err(par->info->device,
			"cannot register backlight device (%ld)\n",
			PTR_ERR(bd));
		return;
	}
	par->info->bl_dev = bd;

	if (!par->fbtftops.unregister_backlight)
		par->fbtftops.unregister_backlight = fbtft_unregister_backlight;
}
#else
void fbtft_register_backlight(struct fbtft_par *par) { };
void fbtft_unregister_backlight(struct fbtft_par *par) { };
#endif
EXPORT_SYMBOL(fbtft_register_backlight);
EXPORT_SYMBOL(fbtft_unregister_backlight);

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 >> 8) & 0xFF, xs & 0xFF, (xe >> 8) & 0xFF, xe & 0xFF);

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

	write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
}

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);
	usleep_range(20, 40);
	gpiod_set_value_cansleep(par->gpio.reset, 0);
	msleep(120);
	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);
}

static void fbtft_update_display(struct fbtft_par *par, unsigned int start_line,
				 unsigned int end_line)
{
	size_t offset, len;
	ktime_t ts_start, ts_end;
	long fps, throughput;
	bool timeit = false;
	int ret = 0;

	if (unlikely(par->debug & (DEBUG_TIME_FIRST_UPDATE |
			DEBUG_TIME_EACH_UPDATE))) {
		if ((par->debug & DEBUG_TIME_EACH_UPDATE) ||
		    ((par->debug & DEBUG_TIME_FIRST_UPDATE) &&
		    !par->first_update_done)) {
			ts_start = ktime_get();
			timeit = true;
		}
	}

	/* Sanity checks */
	if (start_line > end_line) {
		dev_warn(par->info->device,
			 "%s: start_line=%u is larger than end_line=%u. Shouldn't happen, will do full display update\n",
			 __func__, start_line, end_line);
		start_line = 0;
		end_line = par->info->var.yres - 1;
	}
	if (start_line > par->info->var.yres - 1 ||
	    end_line > par->info->var.yres - 1) {
		dev_warn(par->info->device,
			 "%s: start_line=%u or end_line=%u is larger than max=%d. Shouldn't happen, will do full display update\n",
			 __func__, start_line,
			 end_line, par->info->var.yres - 1);
		start_line = 0;
		end_line = par->info->var.yres - 1;
	}

	fbtft_par_dbg(DEBUG_UPDATE_DISPLAY, par, "%s(start_line=%u, end_line=%u)\n",
		      __func__, start_line, end_line);

	if (par->fbtftops.set_addr_win)
		par->fbtftops.set_addr_win(par, 0, start_line,
				par->info->var.xres - 1, end_line);

	offset = start_line * par->info->fix.line_length;
	len = (end_line - start_line + 1) * par->info->fix.line_length;
	ret = par->fbtftops.write_vmem(par, offset, len);
	if (ret < 0)
		dev_err(par->info->device,
			"%s: write_vmem failed to update display buffer\n",
			__func__);

	if (unlikely(timeit)) {
		ts_end = ktime_get();
		if (!ktime_to_ns(par->update_time))
			par->update_time = ts_start;

		fps = ktime_us_delta(ts_start, par->update_time);
		par->update_time = ts_start;
		fps = fps ? 1000000 / fps : 0;

		throughput = ktime_us_delta(ts_end, ts_start);
		throughput = throughput ? (len * 1000) / throughput : 0;
		throughput = throughput * 1000 / 1024;

		dev_info(par->info->device,
			 "Display update: %ld kB/s, fps=%ld\n",
			 throughput, fps);
		par->first_update_done = true;
	}
}

static void fbtft_mkdirty(struct fb_info *info, int y, int height)
{
	struct fbtft_par *par = info->par;
	struct fb_deferred_io *fbdefio = info->fbdefio;

	/* special case, needed ? */
	if (y == -1) {
		y = 0;
		height = info->var.yres;
	}

	/* Mark display lines/area as dirty */
	spin_lock(&par->dirty_lock);
	if (y < par->dirty_lines_start)
		par->dirty_lines_start = y;
	if (y + height - 1 > par->dirty_lines_end)
		par->dirty_lines_end = y + height - 1;
	spin_unlock(&par->dirty_lock);

	/* Schedule deferred_io to update display (no-op if already on queue)*/
	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
}

static void fbtft_deferred_io(struct fb_info *info, struct list_head *pagelist)
{
	struct fbtft_par *par = info->par;
	unsigned int dirty_lines_start, dirty_lines_end;
	struct page *page;
	unsigned long index;
	unsigned int y_low = 0, y_high = 0;
	int count = 0;

	spin_lock(&par->dirty_lock);
	dirty_lines_start = par->dirty_lines_start;
	dirty_lines_end = par->dirty_lines_end;
	/* set display line markers as clean */
	par->dirty_lines_start = par->info->var.yres - 1;
	par->dirty_lines_end = 0;
	spin_unlock(&par->dirty_lock);

	/* Mark display lines as dirty */
	list_for_each_entry(page, pagelist, lru) {
		count++;
		index = page->index << PAGE_SHIFT;
		y_low = index / info->fix.line_length;
		y_high = (index + PAGE_SIZE - 1) / info->fix.line_length;
		dev_dbg(info->device,
			"page->index=%lu y_low=%d y_high=%d\n",
			page->index, y_low, y_high);
		if (y_high > info->var.yres - 1)
			y_high = info->var.yres - 1;
		if (y_low < dirty_lines_start)
			dirty_lines_start = y_low;
		if (y_high > dirty_lines_end)
			dirty_lines_end = y_high;
	}

	par->fbtftops.update_display(info->par,
					dirty_lines_start, dirty_lines_end);
}

static void fbtft_fb_fillrect(struct fb_info *info,
			      const struct fb_fillrect *rect)
{
	struct fbtft_par *par = info->par;

	dev_dbg(info->dev,
		"%s: dx=%d, dy=%d, width=%d, height=%d\n",
		__func__, rect->dx, rect->dy, rect->width, rect->height);
	sys_fillrect(info, rect);

	par->fbtftops.mkdirty(info, rect->dy, rect->height);
}

static void fbtft_fb_copyarea(struct fb_info *info,
			      const struct fb_copyarea *area)
{
	struct fbtft_par *par = info->par;

	dev_dbg(info->dev,
		"%s: dx=%d, dy=%d, width=%d, height=%d\n",
		__func__,  area->dx, area->dy, area->width, area->height);
	sys_copyarea(info, area);

	par->fbtftops.mkdirty(info, area->dy, area->height);
}

static void fbtft_fb_imageblit(struct fb_info *info,
			       const struct fb_image *image)
{
	struct fbtft_par *par = info->par;

	dev_dbg(info->dev,
		"%s: dx=%d, dy=%d, width=%d, height=%d\n",
		__func__,  image->dx, image->dy, image->width, image->height);
	sys_imageblit(info, image);

	par->fbtftops.mkdirty(info, image->dy, image->height);
}

static ssize_t fbtft_fb_write(struct fb_info *info, const char __user *buf,
			      size_t count, loff_t *ppos)
{
	struct fbtft_par *par = info->par;
	ssize_t res;

	dev_dbg(info->dev,
		"%s: count=%zd, ppos=%llu\n", __func__,  count, *ppos);
	res = fb_sys_write(info, buf, count, ppos);

	/* TODO: only mark changed area update all for now */
	par->fbtftops.mkdirty(info, -1, 0);

	return res;
}

/* from pxafb.c */
static unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length;
	return chan << bf->offset;
}

static int fbtft_fb_setcolreg(unsigned int regno, unsigned int red,
			      unsigned int green, unsigned int blue,
			      unsigned int transp, struct fb_info *info)
{
	unsigned int val;
	int ret = 1;

	dev_dbg(info->dev,
		"%s(regno=%u, red=0x%X, green=0x%X, blue=0x%X, trans=0x%X)\n",
		__func__, regno, red, green, blue, transp);

	switch (info->fix.visual) {
	case FB_VISUAL_TRUECOLOR:
		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;
			ret = 0;
		}
		break;
	}
	return ret;
}

static int fbtft_fb_blank(int blank, struct fb_info *info)
{
	struct fbtft_par *par = info->par;
	int ret = -EINVAL;

	dev_dbg(info->dev, "%s(blank=%d)\n",
		__func__, blank);

	if (!par->fbtftops.blank)
		return ret;

	switch (blank) {
	case FB_BLANK_POWERDOWN:
	case FB_BLANK_VSYNC_SUSPEND:
	case FB_BLANK_HSYNC_SUSPEND:
	case FB_BLANK_NORMAL:
		ret = par->fbtftops.blank(par, true);
		break;
	case FB_BLANK_UNBLANK:
		ret = par->fbtftops.blank(par, false);
		break;
	}
	return ret;
}

static void fbtft_merge_fbtftops(struct fbtft_ops *dst, struct fbtft_ops *src)
{
	if (src->write)
		dst->write = src->write;
	if (src->read)
		dst->read = src->read;
	if (src->write_vmem)
		dst->write_vmem = src->write_vmem;
	if (src->write_register)
		dst->write_register = src->write_register;
	if (src->set_addr_win)
		dst->set_addr_win = src->set_addr_win;
	if (src->reset)
		dst->reset = src->reset;
	if (src->mkdirty)
		dst->mkdirty = src->mkdirty;
	if (src->update_display)
		dst->update_display = src->update_display;
	if (src->init_display)
		dst->init_display = src->init_display;
	if (src->blank)
		dst->blank = src->blank;
	if (src->request_gpios_match)
		dst->request_gpios_match = src->request_gpios_match;
	if (src->request_gpios)
		dst->request_gpios = src->request_gpios;
	if (src->verify_gpios)
		dst->verify_gpios = src->verify_gpios;
	if (src->register_backlight)
		dst->register_backlight = src->register_backlight;
	if (src->unregister_backlight)
		dst->unregister_backlight = src->unregister_backlight;
	if (src->set_var)
		dst->set_var = src->set_var;
	if (src->set_gamma)
		dst->set_gamma = src->set_gamma;
}

/**
 * fbtft_framebuffer_alloc - creates a new frame buffer info structure
 *
 * @display: pointer to structure describing the display
 * @dev: pointer to the device for this fb, this can be NULL
 * @pdata: platform data for the display in use
 *
 * Creates a new frame buffer info structure.
 *
 * Also creates and populates the following structures:
 *   info->fbops
 *   info->fbdefio
 *   info->pseudo_palette
 *   par->fbtftops
 *   par->txbuf
 *
 * Returns the new structure, or NULL if an error occurred.
 *
 */
struct fb_info *fbtft_framebuffer_alloc(struct fbtft_display *display,
					struct device *dev,
					struct fbtft_platform_data *pdata)
{
	struct fb_info *info;
	struct fbtft_par *par;
	struct fb_ops *fbops = NULL;
	struct fb_deferred_io *fbdefio = NULL;
	u8 *vmem = NULL;
	void *txbuf = NULL;
	void *buf = NULL;
	unsigned int width;
	unsigned int height;
	int txbuflen = display->txbuflen;
	unsigned int bpp = display->bpp;
	unsigned int fps = display->fps;
	int vmem_size;
	const s16 *init_sequence = display->init_sequence;
	char *gamma = display->gamma;
	u32 *gamma_curves = NULL;

	/* sanity check */
	if (display->gamma_num * display->gamma_len >
			FBTFT_GAMMA_MAX_VALUES_TOTAL) {
		dev_err(dev, "FBTFT_GAMMA_MAX_VALUES_TOTAL=%d is exceeded\n",
			FBTFT_GAMMA_MAX_VALUES_TOTAL);
		return NULL;
	}

	/* defaults */
	if (!fps)
		fps = 20;
	if (!bpp)
		bpp = 16;

	if (!pdata) {
		dev_err(dev, "platform data is missing\n");
		return NULL;
	}

	/* override driver values? */
	if (pdata->fps)
		fps = pdata->fps;
	if (pdata->txbuflen)
		txbuflen = pdata->txbuflen;
	if (pdata->display.init_sequence)
		init_sequence = pdata->display.init_sequence;
	if (pdata->gamma)
		gamma = pdata->gamma;
	if (pdata->display.debug)
		display->debug = pdata->display.debug;
	if (pdata->display.backlight)
		display->backlight = pdata->display.backlight;
	if (pdata->display.width)
		display->width = pdata->display.width;
	if (pdata->display.height)
		display->height = pdata->display.height;
	if (pdata->display.buswidth)
		display->buswidth = pdata->display.buswidth;
	if (pdata->display.regwidth)
		display->regwidth = pdata->display.regwidth;

	display->debug |= debug;
	fbtft_expand_debug_value(&display->debug);

	switch (pdata->rotate) {
	case 90:
	case 270:
		width =  display->height;
		height = display->width;
		break;
	default:
		width =  display->width;
		height = display->height;
	}

	vmem_size = display->width * display->height * bpp / 8;
	vmem = vzalloc(vmem_size);
	if (!vmem)
		goto alloc_fail;

	fbops = devm_kzalloc(dev, sizeof(struct fb_ops), GFP_KERNEL);
	if (!fbops)
		goto alloc_fail;

	fbdefio = devm_kzalloc(dev, sizeof(struct fb_deferred_io), GFP_KERNEL);
	if (!fbdefio)
		goto alloc_fail;

	buf = devm_kzalloc(dev, 128, GFP_KERNEL);
	if (!buf)
		goto alloc_fail;

	if (display->gamma_num && display->gamma_len) {
		gamma_curves = devm_kcalloc(dev,
					    display->gamma_num *
					    display->gamma_len,
					    sizeof(gamma_curves[0]),
					    GFP_KERNEL);
		if (!gamma_curves)
			goto alloc_fail;
	}

	info = framebuffer_alloc(sizeof(struct fbtft_par), dev);
	if (!info)
		goto alloc_fail;

	info->screen_buffer = vmem;
	info->fbops = fbops;
	info->fbdefio = fbdefio;

	fbops->owner        =      dev->driver->owner;
	fbops->fb_read      =      fb_sys_read;
	fbops->fb_write     =      fbtft_fb_write;
	fbops->fb_fillrect  =      fbtft_fb_fillrect;
	fbops->fb_copyarea  =      fbtft_fb_copyarea;
	fbops->fb_imageblit =      fbtft_fb_imageblit;
	fbops->fb_setcolreg =      fbtft_fb_setcolreg;
	fbops->fb_blank     =      fbtft_fb_blank;

	fbdefio->delay =           HZ / fps;
	fbdefio->deferred_io =     fbtft_deferred_io;
	fb_deferred_io_init(info);

	snprintf(info->fix.id, sizeof(info->fix.id), "%s", dev->driver->name);
	info->fix.type =           FB_TYPE_PACKED_PIXELS;
	info->fix.visual =         FB_VISUAL_TRUECOLOR;
	info->fix.xpanstep =	   0;
	info->fix.ypanstep =	   0;
	info->fix.ywrapstep =	   0;
	info->fix.line_length =    width * bpp / 8;
	info->fix.accel =          FB_ACCEL_NONE;
	info->fix.smem_len =       vmem_size;

	info->var.rotate =         pdata->rotate;
	info->var.xres =           width;
	info->var.yres =           height;
	info->var.xres_virtual =   info->var.xres;
	info->var.yres_virtual =   info->var.yres;
	info->var.bits_per_pixel = bpp;
	info->var.nonstd =         1;

	/* RGB565 */
	info->var.red.offset =     11;
	info->var.red.length =     5;
	info->var.green.offset =   5;
	info->var.green.length =   6;
	info->var.blue.offset =    0;
	info->var.blue.length =    5;
	info->var.transp.offset =  0;
	info->var.transp.length =  0;

	info->flags =              FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB;

	par = info->par;
	par->info = info;
	par->pdata = pdata;
	par->debug = display->debug;
	par->buf = buf;
	spin_lock_init(&par->dirty_lock);
	par->bgr = pdata->bgr;
	par->startbyte = pdata->startbyte;
	par->init_sequence = init_sequence;
	par->gamma.curves = gamma_curves;
	par->gamma.num_curves = display->gamma_num;
	par->gamma.num_values = display->gamma_len;
	mutex_init(&par->gamma.lock);
	info->pseudo_palette = par->pseudo_palette;

	if (par->gamma.curves && gamma) {
		if (fbtft_gamma_parse_str(par, par->gamma.curves, gamma,
					  strlen(gamma)))
			goto release_framebuf;
	}

	/* Transmit buffer */
	if (txbuflen == -1)
		txbuflen = vmem_size + 2; /* add in case startbyte is used */
	if (txbuflen >= vmem_size + 2)
		txbuflen = 0;

#ifdef __LITTLE_ENDIAN
	if ((!txbuflen) && (bpp > 8))
		txbuflen = PAGE_SIZE; /* need buffer for byteswapping */
#endif

	if (txbuflen > 0) {
		txbuf = devm_kzalloc(par->info->device, txbuflen, GFP_KERNEL);
		if (!txbuf)
			goto release_framebuf;
		par->txbuf.buf = txbuf;
		par->txbuf.len = txbuflen;
	}

	/* default fbtft operations */
	par->fbtftops.write = fbtft_write_spi;
	par->fbtftops.read = fbtft_read_spi;
	par->fbtftops.write_vmem = fbtft_write_vmem16_bus8;
	par->fbtftops.write_register = fbtft_write_reg8_bus8;
	par->fbtftops.set_addr_win = fbtft_set_addr_win;
	par->fbtftops.reset = fbtft_reset;
	par->fbtftops.mkdirty = fbtft_mkdirty;
	par->fbtftops.update_display = fbtft_update_display;
	if (display->backlight)
		par->fbtftops.register_backlight = fbtft_register_backlight;

	/* use driver provided functions */
	fbtft_merge_fbtftops(&par->fbtftops, &display->fbtftops);

	return info;

release_framebuf:
	framebuffer_release(info);

alloc_fail:
	vfree(vmem);

	return NULL;
}
EXPORT_SYMBOL(fbtft_framebuffer_alloc);

/**
 * fbtft_framebuffer_release - frees up all memory used by the framebuffer
 *
 * @info: frame buffer info structure
 *
 */
void fbtft_framebuffer_release(struct fb_info *info)
{
	fb_deferred_io_cleanup(info);
	vfree(info->screen_buffer);
	framebuffer_release(info);
}
EXPORT_SYMBOL(fbtft_framebuffer_release);

/**
 *	fbtft_register_framebuffer - registers a tft frame buffer device
 *	@fb_info: frame buffer info structure
 *
 *  Sets SPI driverdata if needed
 *  Requests needed gpios.
 *  Initializes display
 *  Updates display.
 *	Registers a frame buffer device @fb_info.
 *
 *	Returns negative errno on error, or zero for success.
 *
 */
int fbtft_register_framebuffer(struct fb_info *fb_info)
{
	int ret;
	char text1[50] = "";
	char text2[50] = "";
	struct fbtft_par *par = fb_info->par;
	struct spi_device *spi = par->spi;

	/* sanity checks */
	if (!par->fbtftops.init_display) {
		dev_err(fb_info->device, "missing fbtftops.init_display()\n");
		return -EINVAL;
	}

	if (spi)
		spi_set_drvdata(spi, fb_info);
	if (par->pdev)
		platform_set_drvdata(par->pdev, fb_info);

	ret = par->fbtftops.request_gpios(par);
	if (ret < 0)
		goto reg_fail;

	if (par->fbtftops.verify_gpios) {
		ret = par->fbtftops.verify_gpios(par);
		if (ret < 0)
			goto reg_fail;
	}

	ret = par->fbtftops.init_display(par);
	if (ret < 0)
		goto reg_fail;
	if (par->fbtftops.set_var) {
		ret = par->fbtftops.set_var(par);
		if (ret < 0)
			goto reg_fail;
	}

	/* update the entire display */
	par->fbtftops.update_display(par, 0, par->info->var.yres - 1);

	if (par->fbtftops.set_gamma && par->gamma.curves) {
		ret = par->fbtftops.set_gamma(par, par->gamma.curves);
		if (ret)
			goto reg_fail;
	}

	if (par->fbtftops.register_backlight)
		par->fbtftops.register_backlight(par);

	ret = register_framebuffer(fb_info);
	if (ret < 0)
		goto reg_fail;

	fbtft_sysfs_init(par);

	if (par->txbuf.buf && par->txbuf.len >= 1024)
		sprintf(text1, ", %zu KiB buffer memory", par->txbuf.len >> 10);
	if (spi)
		sprintf(text2, ", spi%d.%d at %d MHz", spi->master->bus_num,
			spi->chip_select, spi->max_speed_hz / 1000000);
	dev_info(fb_info->dev,
		 "%s frame buffer, %dx%d, %d KiB video memory%s, fps=%lu%s\n",
		 fb_info->fix.id, fb_info->var.xres, fb_info->var.yres,
		 fb_info->fix.smem_len >> 10, text1,
		 HZ / fb_info->fbdefio->delay, text2);

#ifdef CONFIG_FB_BACKLIGHT
	/* Turn on backlight if available */
	if (fb_info->bl_dev) {
		fb_info->bl_dev->props.power = FB_BLANK_UNBLANK;
		fb_info->bl_dev->ops->update_status(fb_info->bl_dev);
	}
#endif

	return 0;

reg_fail:
	if (par->fbtftops.unregister_backlight)
		par->fbtftops.unregister_backlight(par);

	return ret;
}
EXPORT_SYMBOL(fbtft_register_framebuffer);

/**
 *	fbtft_unregister_framebuffer - releases a tft frame buffer device
 *	@fb_info: frame buffer info structure
 *
 *  Frees SPI driverdata if needed
 *  Frees gpios.
 *	Unregisters frame buffer device.
 *
 */
int fbtft_unregister_framebuffer(struct fb_info *fb_info)
{
	struct fbtft_par *par = fb_info->par;

	if (par->fbtftops.unregister_backlight)
		par->fbtftops.unregister_backlight(par);
	fbtft_sysfs_exit(par);
	unregister_framebuffer(fb_info);

	return 0;
}
EXPORT_SYMBOL(fbtft_unregister_framebuffer);

/**
 * fbtft_init_display_from_property() - Device Tree init_display() function
 * @par: Driver data
 *
 * Return: 0 if successful, negative if error
 */
static int fbtft_init_display_from_property(struct fbtft_par *par)
{
	struct device *dev = par->info->device;
	int buf[64], count, index, i, j, ret;
	u32 *values;
	u32 val;

	count = device_property_count_u32(dev, "init");
	if (count < 0)
		return count;
	if (count == 0)
		return -EINVAL;

	values = kmalloc_array(count + 1, sizeof(*values), GFP_KERNEL);
	if (!values)
		return -ENOMEM;

	ret = device_property_read_u32_array(dev, "init", values, count);
	if (ret)
		goto out_free;

	par->fbtftops.reset(par);
	if (par->gpio.cs)
		gpiod_set_value(par->gpio.cs, 0);  /* Activate chip */

	index = -1;
	val = values[++index];

	while (index < count) {
		if (val & FBTFT_OF_INIT_CMD) {
			val &= 0xFFFF;
			i = 0;
			while ((index < count) && !(val & 0xFFFF0000)) {
				if (i > 63) {
					dev_err(dev,
						"%s: Maximum register values exceeded\n",
						__func__);
					ret = -EINVAL;
					goto out_free;
				}
				buf[i++] = val;
				val = values[++index];
			}
			/* make debug message */
			fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
				      "init: write_register:\n");
			for (j = 0; j < i; j++)
				fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
					      "buf[%d] = %02X\n", j, buf[j]);

			par->fbtftops.write_register(par, i,
				buf[0], buf[1], buf[2], buf[3],
				buf[4], buf[5], buf[6], buf[7],
				buf[8], buf[9], buf[10], buf[11],
				buf[12], buf[13], buf[14], buf[15],
				buf[16], buf[17], buf[18], buf[19],
				buf[20], buf[21], buf[22], buf[23],
				buf[24], buf[25], buf[26], buf[27],
				buf[28], buf[29], buf[30], buf[31],
				buf[32], buf[33], buf[34], buf[35],
				buf[36], buf[37], buf[38], buf[39],
				buf[40], buf[41], buf[42], buf[43],
				buf[44], buf[45], buf[46], buf[47],
				buf[48], buf[49], buf[50], buf[51],
				buf[52], buf[53], buf[54], buf[55],
				buf[56], buf[57], buf[58], buf[59],
				buf[60], buf[61], buf[62], buf[63]);
		} else if (val & FBTFT_OF_INIT_DELAY) {
			fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
				      "init: msleep(%u)\n", val & 0xFFFF);
			msleep(val & 0xFFFF);
			val = values[++index];
		} else {
			dev_err(dev, "illegal init value 0x%X\n", val);
			ret = -EINVAL;
			goto out_free;
		}
	}

out_free:
	kfree(values);
	return ret;
}

/**
 * fbtft_init_display() - Generic init_display() function
 * @par: Driver data
 *
 * Uses par->init_sequence to do the initialization
 *
 * Return: 0 if successful, negative if error
 */
int fbtft_init_display(struct fbtft_par *par)
{
	int buf[64];
	char msg[128];
	char str[16];
	int i = 0;
	int j;

	/* sanity check */
	if (!par->init_sequence) {
		dev_err(par->info->device,
			"error: init_sequence is not set\n");
		return -EINVAL;
	}

	/* make sure stop marker exists */
	for (i = 0; i < FBTFT_MAX_INIT_SEQUENCE; i++)
		if (par->init_sequence[i] == -3)
			break;
	if (i == FBTFT_MAX_INIT_SEQUENCE) {
		dev_err(par->info->device,
			"missing stop marker at end of init sequence\n");
		return -EINVAL;
	}

	par->fbtftops.reset(par);
	if (par->gpio.cs)
		gpiod_set_value(par->gpio.cs, 0);  /* Activate chip */

	i = 0;
	while (i < FBTFT_MAX_INIT_SEQUENCE) {
		if (par->init_sequence[i] == -3) {
			/* done */
			return 0;
		}
		if (par->init_sequence[i] >= 0) {
			dev_err(par->info->device,
				"missing delimiter at position %d\n", i);
			return -EINVAL;
		}
		if (par->init_sequence[i + 1] < 0) {
			dev_err(par->info->device,
				"missing value after delimiter %d at position %d\n",
				par->init_sequence[i], i);
			return -EINVAL;
		}
		switch (par->init_sequence[i]) {
		case -1:
			i++;
			/* make debug message */
			strcpy(msg, "");
			j = i + 1;
			while (par->init_sequence[j] >= 0) {
				sprintf(str, "0x%02X ", par->init_sequence[j]);
				strcat(msg, str);
				j++;
			}
			fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
				      "init: write(0x%02X) %s\n",
				      par->init_sequence[i], msg);

			/* Write */
			j = 0;
			while (par->init_sequence[i] >= 0) {
				if (j > 63) {
					dev_err(par->info->device,
						"%s: Maximum register values exceeded\n",
						__func__);
					return -EINVAL;
				}
				buf[j++] = par->init_sequence[i++];
			}
			par->fbtftops.write_register(par, j,
				buf[0], buf[1], buf[2], buf[3],
				buf[4], buf[5], buf[6], buf[7],
				buf[8], buf[9], buf[10], buf[11],
				buf[12], buf[13], buf[14], buf[15],
				buf[16], buf[17], buf[18], buf[19],
				buf[20], buf[21], buf[22], buf[23],
				buf[24], buf[25], buf[26], buf[27],
				buf[28], buf[29], buf[30], buf[31],
				buf[32], buf[33], buf[34], buf[35],
				buf[36], buf[37], buf[38], buf[39],
				buf[40], buf[41], buf[42], buf[43],
				buf[44], buf[45], buf[46], buf[47],
				buf[48], buf[49], buf[50], buf[51],
				buf[52], buf[53], buf[54], buf[55],
				buf[56], buf[57], buf[58], buf[59],
				buf[60], buf[61], buf[62], buf[63]);
			break;
		case -2:
			i++;
			fbtft_par_dbg(DEBUG_INIT_DISPLAY, par,
				      "init: mdelay(%d)\n",
				      par->init_sequence[i]);
			mdelay(par->init_sequence[i++]);
			break;
		default:
			dev_err(par->info->device,
				"unknown delimiter %d at position %d\n",
				par->init_sequence[i], i);
			return -EINVAL;
		}
	}

	dev_err(par->info->device,
		"%s: something is wrong. Shouldn't get here.\n", __func__);
	return -EINVAL;
}
EXPORT_SYMBOL(fbtft_init_display);

/**
 * fbtft_verify_gpios() - Generic verify_gpios() function
 * @par: Driver data
 *
 * Uses @spi, @pdev and @buswidth to determine which GPIOs is needed
 *
 * Return: 0 if successful, negative if error
 */
static int fbtft_verify_gpios(struct fbtft_par *par)
{
	struct fbtft_platform_data *pdata = par->pdata;
	int i;

	fbtft_par_dbg(DEBUG_VERIFY_GPIOS, par, "%s()\n", __func__);

	if (pdata->display.buswidth != 9 &&  par->startbyte == 0 &&
	    !par->gpio.dc) {
		dev_err(par->info->device,
			"Missing info about 'dc' gpio. Aborting.\n");
		return -EINVAL;
	}

	if (!par->pdev)
		return 0;

	if (!par->gpio.wr) {
		dev_err(par->info->device, "Missing 'wr' gpio. Aborting.\n");
		return -EINVAL;
	}
	for (i = 0; i < pdata->display.buswidth; i++) {
		if (!par->gpio.db[i]) {
			dev_err(par->info->device,
				"Missing 'db%02d' gpio. Aborting.\n", i);
			return -EINVAL;
		}
	}

	return 0;
}

/* returns 0 if the property is not present */
static u32 fbtft_property_value(struct device *dev, const char *propname)
{
	int ret;
	u32 val = 0;

	ret = device_property_read_u32(dev, propname, &val);
	if (ret == 0)
		dev_info(dev, "%s: %s = %u\n", __func__, propname, val);

	return val;
}

static struct fbtft_platform_data *fbtft_properties_read(struct device *dev)
{
	struct fbtft_platform_data *pdata;

	if (!dev_fwnode(dev)) {
		dev_err(dev, "Missing platform data or properties\n");
		return ERR_PTR(-EINVAL);
	}

	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
	if (!pdata)
		return ERR_PTR(-ENOMEM);

	pdata->display.width = fbtft_property_value(dev, "width");
	pdata->display.height = fbtft_property_value(dev, "height");
	pdata->display.regwidth = fbtft_property_value(dev, "regwidth");
	pdata->display.buswidth = fbtft_property_value(dev, "buswidth");
	pdata->display.backlight = fbtft_property_value(dev, "backlight");
	pdata->display.bpp = fbtft_property_value(dev, "bpp");
	pdata->display.debug = fbtft_property_value(dev, "debug");
	pdata->rotate = fbtft_property_value(dev, "rotate");
	pdata->bgr = device_property_read_bool(dev, "bgr");
	pdata->fps = fbtft_property_value(dev, "fps");
	pdata->txbuflen = fbtft_property_value(dev, "txbuflen");
	pdata->startbyte = fbtft_property_value(dev, "startbyte");
	device_property_read_string(dev, "gamma", (const char **)&pdata->gamma);

	if (device_property_present(dev, "led-gpios"))
		pdata->display.backlight = 1;
	if (device_property_present(dev, "init"))
		pdata->display.fbtftops.init_display =
			fbtft_init_display_from_property;

	pdata->display.fbtftops.request_gpios = fbtft_request_gpios;

	return pdata;
}

/**
 * fbtft_probe_common() - Generic device probe() helper function
 * @display: Display properties
 * @sdev: SPI device
 * @pdev: Platform device
 *
 * Allocates, initializes and registers a framebuffer
 *
 * Either @sdev or @pdev should be NULL
 *
 * Return: 0 if successful, negative if error
 */
int fbtft_probe_common(struct fbtft_display *display,
		       struct spi_device *sdev,
		       struct platform_device *pdev)
{
	struct device *dev;
	struct fb_info *info;
	struct fbtft_par *par;
	struct fbtft_platform_data *pdata;
	int ret;

	if (sdev)
		dev = &sdev->dev;
	else
		dev = &pdev->dev;

	if (unlikely(display->debug & DEBUG_DRIVER_INIT_FUNCTIONS))
		dev_info(dev, "%s()\n", __func__);

	pdata = dev->platform_data;
	if (!pdata) {
		pdata = fbtft_properties_read(dev);
		if (IS_ERR(pdata))
			return PTR_ERR(pdata);
	}

	info = fbtft_framebuffer_alloc(display, dev, pdata);
	if (!info)
		return -ENOMEM;

	par = info->par;
	par->spi = sdev;
	par->pdev = pdev;

	if (display->buswidth == 0) {
		dev_err(dev, "buswidth is not set\n");
		return -EINVAL;
	}

	/* write register functions */
	if (display->regwidth == 8 && display->buswidth == 8)
		par->fbtftops.write_register = fbtft_write_reg8_bus8;
	else if (display->regwidth == 8 && display->buswidth == 9 && par->spi)
		par->fbtftops.write_register = fbtft_write_reg8_bus9;
	else if (display->regwidth == 16 && display->buswidth == 8)
		par->fbtftops.write_register = fbtft_write_reg16_bus8;
	else if (display->regwidth == 16 && display->buswidth == 16)
		par->fbtftops.write_register = fbtft_write_reg16_bus16;
	else
		dev_warn(dev,
			 "no default functions for regwidth=%d and buswidth=%d\n",
			 display->regwidth, display->buswidth);

	/* write_vmem() functions */
	if (display->buswidth == 8)
		par->fbtftops.write_vmem = fbtft_write_vmem16_bus8;
	else if (display->buswidth == 9)
		par->fbtftops.write_vmem = fbtft_write_vmem16_bus9;
	else if (display->buswidth == 16)
		par->fbtftops.write_vmem = fbtft_write_vmem16_bus16;

	/* GPIO write() functions */
	if (par->pdev) {
		if (display->buswidth == 8)
			par->fbtftops.write = fbtft_write_gpio8_wr;
		else if (display->buswidth == 16)
			par->fbtftops.write = fbtft_write_gpio16_wr;
	}

	/* 9-bit SPI setup */
	if (par->spi && display->buswidth == 9) {
		if (par->spi->master->bits_per_word_mask & SPI_BPW_MASK(9)) {
			par->spi->bits_per_word = 9;
		} else {
			dev_warn(&par->spi->dev,
				 "9-bit SPI not available, emulating using 8-bit.\n");
			/* allocate buffer with room for dc bits */
			par->extra = devm_kzalloc(par->info->device,
						  par->txbuf.len +
						  (par->txbuf.len / 8) + 8,
						  GFP_KERNEL);
			if (!par->extra) {
				ret = -ENOMEM;
				goto out_release;
			}
			par->fbtftops.write = fbtft_write_spi_emulate_9;
		}
	}

	if (!par->fbtftops.verify_gpios)
		par->fbtftops.verify_gpios = fbtft_verify_gpios;

	/* make sure we still use the driver provided functions */
	fbtft_merge_fbtftops(&par->fbtftops, &display->fbtftops);

	/* use init_sequence if provided */
	if (par->init_sequence)
		par->fbtftops.init_display = fbtft_init_display;

	/* use platform_data provided functions above all */
	fbtft_merge_fbtftops(&par->fbtftops, &pdata->display.fbtftops);

	ret = fbtft_register_framebuffer(info);
	if (ret < 0)
		goto out_release;

	return 0;

out_release:
	fbtft_framebuffer_release(info);

	return ret;
}
EXPORT_SYMBOL(fbtft_probe_common);

/**
 * fbtft_remove_common() - Generic device remove() helper function
 * @dev: Device
 * @info: Framebuffer
 *
 * Unregisters and releases the framebuffer
 *
 * Return: 0 if successful, negative if error
 */
int fbtft_remove_common(struct device *dev, struct fb_info *info)
{
	struct fbtft_par *par;

	if (!info)
		return -EINVAL;
	par = info->par;
	if (par)
		fbtft_par_dbg(DEBUG_DRIVER_INIT_FUNCTIONS, par,
			      "%s()\n", __func__);
	fbtft_unregister_framebuffer(info);
	fbtft_framebuffer_release(info);

	return 0;
}
EXPORT_SYMBOL(fbtft_remove_common);

MODULE_LICENSE("GPL");

离线

楼主 #28 2022-06-11 11:11:32

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

fbtft-io.c

// SPDX-License-Identifier: GPL-2.0
#include <linux/export.h>
#include <linux/errno.h>
#include <linux/gpio/consumer.h>
#include <linux/spi/spi.h>
#include "fbtft.h"

int fbtft_write_spi(struct fbtft_par *par, void *buf, size_t len)
{
	struct spi_transfer t = {
		.tx_buf = buf,
		.len = len,
	};
	struct spi_message m;

	fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len,
			  "%s(len=%zu): ", __func__, len);

	if (!par->spi) {
		dev_err(par->info->device,
			"%s: par->spi is unexpectedly NULL\n", __func__);
		return -1;
	}

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spi_sync(par->spi, &m);
}
EXPORT_SYMBOL(fbtft_write_spi);

/**
 * fbtft_write_spi_emulate_9() - write SPI emulating 9-bit
 * @par: Driver data
 * @buf: Buffer to write
 * @len: Length of buffer (must be divisible by 8)
 *
 * When 9-bit SPI is not available, this function can be used to emulate that.
 * par->extra must hold a transformation buffer used for transfer.
 */
int fbtft_write_spi_emulate_9(struct fbtft_par *par, void *buf, size_t len)
{
	u16 *src = buf;
	u8 *dst = par->extra;
	size_t size = len / 2;
	size_t added = 0;
	int bits, i, j;
	u64 val, dc, tmp;

	fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len,
			  "%s(len=%zu): ", __func__, len);

	if (!par->extra) {
		dev_err(par->info->device, "%s: error: par->extra is NULL\n",
			__func__);
		return -EINVAL;
	}
	if ((len % 8) != 0) {
		dev_err(par->info->device,
			"error: len=%zu must be divisible by 8\n", len);
		return -EINVAL;
	}

	for (i = 0; i < size; i += 8) {
		tmp = 0;
		bits = 63;
		for (j = 0; j < 7; j++) {
			dc = (*src & 0x0100) ? 1 : 0;
			val = *src & 0x00FF;
			tmp |= dc << bits;
			bits -= 8;
			tmp |= val << bits--;
			src++;
		}
		tmp |= ((*src & 0x0100) ? 1 : 0);
		*(__be64 *)dst = cpu_to_be64(tmp);
		dst += 8;
		*dst++ = (u8)(*src++ & 0x00FF);
		added++;
	}

	return spi_write(par->spi, par->extra, size + added);
}
EXPORT_SYMBOL(fbtft_write_spi_emulate_9);

int fbtft_read_spi(struct fbtft_par *par, void *buf, size_t len)
{
	int ret;
	u8 txbuf[32] = { 0, };
	struct spi_transfer	t = {
			.speed_hz = 2000000,
			.rx_buf		= buf,
			.len		= len,
		};
	struct spi_message	m;

	if (!par->spi) {
		dev_err(par->info->device,
			"%s: par->spi is unexpectedly NULL\n", __func__);
		return -ENODEV;
	}

	if (par->startbyte) {
		if (len > 32) {
			dev_err(par->info->device,
				"len=%zu can't be larger than 32 when using 'startbyte'\n",
				len);
			return -EINVAL;
		}
		txbuf[0] = par->startbyte | 0x3;
		t.tx_buf = txbuf;
		fbtft_par_dbg_hex(DEBUG_READ, par, par->info->device, u8,
				  txbuf, len, "%s(len=%zu) txbuf => ",
				  __func__, len);
	}

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	ret = spi_sync(par->spi, &m);
	fbtft_par_dbg_hex(DEBUG_READ, par, par->info->device, u8, buf, len,
			  "%s(len=%zu) buf <= ", __func__, len);

	return ret;
}
EXPORT_SYMBOL(fbtft_read_spi);

/*
 * Optimized use of gpiolib is twice as fast as no optimization
 * only one driver can use the optimized version at a time
 */
int fbtft_write_gpio8_wr(struct fbtft_par *par, void *buf, size_t len)
{
	u8 data;
	int i;
#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO
	static u8 prev_data;
#endif

	fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len,
			  "%s(len=%zu): ", __func__, len);

	while (len--) {
		data = *(u8 *)buf;

		/* Start writing by pulling down /WR */
		gpiod_set_value(par->gpio.wr, 0);

		/* Set data */
#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO
		if (data == prev_data) {
			gpiod_set_value(par->gpio.wr, 0); /* used as delay */
		} else {
			for (i = 0; i < 8; i++) {
				if ((data & 1) != (prev_data & 1))
					gpiod_set_value(par->gpio.db[i],
							data & 1);
				data >>= 1;
				prev_data >>= 1;
			}
		}
#else
		for (i = 0; i < 8; i++) {
			gpiod_set_value(par->gpio.db[i], data & 1);
			data >>= 1;
		}
#endif

		/* Pullup /WR */
		gpiod_set_value(par->gpio.wr, 1);

#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO
		prev_data = *(u8 *)buf;
#endif
		buf++;
	}

	return 0;
}
EXPORT_SYMBOL(fbtft_write_gpio8_wr);

int fbtft_write_gpio16_wr(struct fbtft_par *par, void *buf, size_t len)
{
	u16 data;
	int i;
#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO
	static u16 prev_data;
#endif

	fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len,
			  "%s(len=%zu): ", __func__, len);

	while (len) {
		data = *(u16 *)buf;

		/* Start writing by pulling down /WR */
		gpiod_set_value(par->gpio.wr, 0);

		/* Set data */
#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO
		if (data == prev_data) {
			gpiod_set_value(par->gpio.wr, 0); /* used as delay */
		} else {
			for (i = 0; i < 16; i++) {
				if ((data & 1) != (prev_data & 1))
					gpiod_set_value(par->gpio.db[i],
							data & 1);
				data >>= 1;
				prev_data >>= 1;
			}
		}
#else
		for (i = 0; i < 16; i++) {
			gpiod_set_value(par->gpio.db[i], data & 1);
			data >>= 1;
		}
#endif

		/* Pullup /WR */
		gpiod_set_value(par->gpio.wr, 1);

#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO
		prev_data = *(u16 *)buf;
#endif
		buf += 2;
		len -= 2;
	}

	return 0;
}
EXPORT_SYMBOL(fbtft_write_gpio16_wr);

int fbtft_write_gpio16_wr_latched(struct fbtft_par *par, void *buf, size_t len)
{
	dev_err(par->info->device, "%s: function not implemented\n", __func__);
	return -1;
}
EXPORT_SYMBOL(fbtft_write_gpio16_wr_latched);

离线

楼主 #29 2022-06-11 11:13:08

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

&spi1{
    status = "okay";
    st7789v: st7789v@0{
        compatible = "sitronix,st7789v";
        reg = <0>;
        status = "okay";
        spi-max-frequency = <24000000>;//点不亮适量降
        spi-cpol;
        spi-cpha;
        rotate = <270>;
        fps = <60>;
        buswidth = <8>;
        dc-gpios = <&pio 4 4 GPIO_ACTIVE_HIGH>;  // PE4 PB0
        reset-gpios = <&pio 4 5 GPIO_ACTIVE_HIGH>; // PE5
        //led-gpios = <&pio 0 0 GPIO_ACTIVE_LOW>;  // PA0
        debug = <0x0>;
        };
    };

离线

楼主 #30 2022-06-11 11:20:02

资本家大善人
会员
注册时间: 2021-03-26
已发帖子: 188
积分: 143.5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

QQ截图20220611111909.png

离线

#31 2022-06-12 10:35:06

forever_rainy
会员
注册时间: 2022-04-09
已发帖子: 21
积分: 40

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

@资本家大善人
请问你这是改了哪些东西吗,我下载的5.10.1的内核,menuconfig里配置了st7789v驱动就可以使用了。

离线

#32 2022-09-15 14:16:49

liutian
会员
注册时间: 2022-09-15
已发帖子: 5
积分: 0

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

楼主,这个硬件连接只有spi吗?就是说显示数据也是走的spi吗?屏幕直接和hdmi那种一样挂到系统的display下由系统控制输出显示画面的吗?

离线

#33 2022-09-19 14:01:52

liutian
会员
注册时间: 2022-09-15
已发帖子: 5
积分: 0

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

有人在安卓上用过这个屏吗?

离线

#34 2023-03-03 17:28:17

云槎外史
会员
注册时间: 2022-10-11
已发帖子: 10
积分: 5

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

@stutian
请问大佬解决了吗?我也是和你这个一样,弄了好久也没解决

离线

#37 2023-10-13 14:35:56

哦噶
会员
注册时间: 2023-10-13
已发帖子: 3
积分: 13

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

楼主使用小尺寸的spi lcd的时候uboot中CONFIG_VIDEO_LCD_MODE这一项怎么配置啊

离线

#38 2023-10-18 18:09:35

wcq6699
会员
注册时间: 2023-10-17
已发帖子: 15
积分: 0

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

请问BOOT可以放到FLASH里面进行启动吗,还是要配置到V3S里面才可以呢

离线

#39 2024-02-02 10:44:02

richie
会员
注册时间: 2024-02-02
已发帖子: 3
积分: 3

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

@stutian
朋友问题解决了吗?
我现在也遇到了同样的问题,调试了一段时间发现只要注册tty1,默认的console就变成lcd了
目前有看到资料说关闭 fiq_debugger,然后开启 uart2 可以解决,还在尝试

离线

#40 2024-02-03 00:35:32

richie
会员
注册时间: 2024-02-02
已发帖子: 3
积分: 3

Re: 荔枝派zero全志v3s驱动SPILCD屏幕(st7789)

@stutian
问题已解决,只有被注册console的设备才能开启终端
今天查阅资料发现,内核代码,在设备的dts中有个 chosen 的节点,里面指定了bootargs,uboot中的sys_bootargs会重新覆盖相同的变量
所有保持uboot中的参数不变,在dts中指定console即可

    chosen {
        bootargs = "earlycon=uart8250,mmio32,0xff4c0000 console=tty1 console=ttyFIQ0 root=/dev/mmcblk1p7 rootwait snd_soc_core.prealloc_buffer_size_kbytes=16 coherent_pool=0";
    };

console=tty1 是新增的,要位于串口之前,console只接收最后的设备作为输入

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn