增加 1.9inch LCD Module fbtft 驱动 for RK356x。

Signed-off-by: rick.chan <cy@haoan119.com>
This commit is contained in:
rick.chan 2024-08-23 16:54:37 +08:00
parent c37dce6446
commit 604b4ee4f5
1 changed files with 409 additions and 0 deletions

View File

@ -0,0 +1,409 @@
# 1.9inch LCD Module fbtft 驱动 for RK356x
由于需要在 RK356x 平台下驱动 SPI 显示屏,因此做了一下技术调查。这种屏的驱动主要使用 fbtft这一种 framebuffer 驱动。另一说可以使用基于 DRM 技术的 [TinyDRM](https://github.com/notro/tinydrm) 驱动。
framebuffer 的缺点在于,使用非桌面环境的 Qt 图形程序时不支持视频播放。但是 Qt 的 AnimationAnimatedImage、PropertyAnimation 等) 是可以正常使用的。
我对 TinyDRM 的框架存有许多疑问,但时间有限,不能仔细阅读其框架代码,只好先使用 fbtft 来驱动。
我所使用的 SPI 显示屏是 微雪 的 1.9inch LCD 模块,其控制芯片为 ST7789V2对于这款芯片内核是有类似驱动的参考微雪官方示例代码对此文件和设备树进行修改即可。
Linux 内核版本为 4.9。
## 1. 显示屏信息
本驱动对应 微雪 1.9inch SPI 显示屏,该显示屏参数如下:
- 工作电压: 3.3V/5V
- 通信接口SPI
- 屏幕类型IPS
- 控制芯片ST7789V2
- 分辨率170(H)RGB × 320(V)
- 显示尺寸22.70 × 42.72mm
- 像素间距0.1335 × 0.1335mm
- 产品尺寸27.3 × 51.2mm
- 像素格式RGB444、RGB565、RGB666
官方资料及参考代码下载见:[微雪 1.9inch LCD Module 资料连接](https://www.waveshare.net/wiki/1.9inch_LCD_Module)
## 2. 接线方式
| LCD Module | RK356x |
|------------|--------|
| VCC | V3.3 |
| GND | GND |
| DIN | SPI3_MOSI_M1 |
| CLK | SPI3_CLK_M1 |
| CS | GPIO4_C4 |
| DC | GPIO3_A6 |
| RST | GPIO1_A4 |
| BL | GPIO3_A5 |
注意 SPI 通讯使用 Mode 0。
## 3. 内核驱动及设备树
Linux 在 /drivers/staging/fbtft 下有 fb_st7789v.c 文件,直接在此文件基础上进行修改,内容如下:
```cpp
// 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"
/**
* 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); // 0x11
// mdelay(120);
write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, 0x70); // 0x36, 0x00-HORIZONTAL, 0x70-VERTICAL
/* set pixel format to RGB-565 */
write_reg(par, MIPI_DCS_SET_PIXEL_FORMAT, 0x55); // 0x3A
// write_reg(par, MIPI_DCS_ENTER_INVERT_MODE); // 0x21
// write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x01, 0x3F); // 0x2A
// write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x00, 0xEF); // 0x2B
write_reg(par, PORCTRL, 0x0C, 0x0C, 0x00, 0x33, 0x33); // 0xB2
// VGH VGL
write_reg(par, GCTRL, 0x35); // 0xB7
// VCOM
write_reg(par, VCOMS, 0x13); // 0xBB
write_reg(par, 0xC0, 0x2C);
/*
* VDV and VRH register values come from command write
* (instead of NVM)
*/
write_reg(par, VDVVRHEN, 0x01); // 0xC2
// VAP VAN
write_reg(par, VRHS, 0x0B); // 0xC3
// VDV
write_reg(par, VDVS, 0x20); // 0xC4
write_reg(par, 0xC6, 0x0F);
// AVDD AVCL VDS
write_reg(par, PWCTRL1, 0xA4, 0xA1); // 0xD0
// write_reg(par, PVGAMCTRL, 0x00, 0x03, 0x07, 0x08, 0x07, 0x15, 0x2A, 0x44, 0x42, 0x0A, 0x17, 0x18, 0x25, 0x27); // 0xE0
// write_reg(par, NVGAMCTRL, 0x00, 0x03, 0x08, 0x07, 0x07, 0x23, 0x2A, 0x43, 0x42, 0x09, 0x18, 0x17, 0x25, 0x27); // 0xE1
write_reg(par, MIPI_DCS_ENTER_INVERT_MODE); // 0x21
write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE); // 0x11
/* VCOM offset = 0V */
// write_reg(par, VCMOFSET, 0x20);
mdelay(50);
write_reg(par, MIPI_DCS_SET_DISPLAY_ON); // 0x29
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)
{
switch (par->info->var.rotate) {
case 0:
write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, 0x00); // 0x36, 0x00-HORIZONTAL, 0x70-VERTICAL
break;
default:
write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, 0x70); // 0x36, 0x00-HORIZONTAL, 0x70-VERTICAL
break;
}
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 c;
for (i = 0; i < par->gamma.num_curves; i++) {
c = i * par->gamma.num_values;
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 += 0x23;
xe += 0x23;
ys += 0x00;
ye += 0x00;
break;
default:
xs += 0x00;
xe += 0x00;
ys += 0x23;
ye += 0x23;
break;
}
write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
(xs >> 8) & 0xFF, xs & 0xFF, (xe >> 8) & 0xFF, xe & 0xFF); // 0x2A
write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
(ys >> 8) & 0xFF, ys & 0xFF, (ye >> 8) & 0xFF, ye & 0xFF); // 0x2B
write_reg(par, MIPI_DCS_WRITE_MEMORY_START); // 0x2C
}
static struct fbtft_display display = {
.regwidth = 8,
.width = 170,
.height = 320,
.gamma_num = 2,
.gamma_len = 14,
.gamma = DEFAULT_GAMMA,
.fbtftops = {
.init_display = init_display,
.set_var = set_var,
.set_gamma = set_gamma,
.blank = blank,
.set_addr_win = set_addr_win
},
};
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");
```
主要的修改是参考示例编写 init_display() 函数中的初始化序列。
其次是 set_addr_win() 函数xs 等有个偏移,每个屏厂参数都不同。如果你的屏显示出现 **撕裂,错位倾斜** 或者 **偏移**可能就是这个参数有问题。微雪官方给的参考中xe 和 ye 进行了 减1 处理,但 Linux 驱动**不需要 减1**,否则显示的图像/文字就会因为减 1 的偏移导致错位倾斜。
这个驱动的显示方向可以由设备树中的 rotate 参数配置gamma 也可以通过设备树配置。
我所使用的 鲁班猫 1N设备树配置比较清晰很容易的在 arch/arm64/boot/dts/rockchip/overlay/rk356x-lubancat-spi3-m1-gpio-cs-overlay.dts 文件中找到了 SPI3 设备,注释掉其中的 spi_dev 并修改如下:
```dts
/dts-v1/;
/plugin/;
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/pinctrl/rockchip.h>
/ {
compatible = "rockchip,rk3568";
fragment@0 {
target = <&spi3>;
__overlay__ {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
// SPI gpio 模拟多个 cs 信号
pinctrl-names = "default", "high_speed";
pinctrl-0 = <&spi3_cs0 &spi3_cs1 &spi3m1_pins>;
pinctrl-1 = <&spi3_cs0 &spi3_cs1 &spi3m1_pins_hs>;
cs-gpios = <&gpio4 RK_PC4 GPIO_ACTIVE_LOW>;
// spi_dev@00 {
// compatible = "rockchip,spidev";
// reg = <0>; //chip select 0:cs0 1:cs1
// spi-max-frequency = <24000000>; //spi output clock
// };
// spi_dev@01 {
// compatible = "rockchip,spidev";
// reg = <1>;
// spi-max-frequency = <24000000>;
// };
st7789v@00 {
status = "okay";
compatible = "sitronix,st7789v";
reg = <0>;
spi-max-frequency = <24000000>;
rotate = <90>;
rgb;
fps = <30>;
buswidth = <8>;
gamma = "0x00 0x03 0x07 0x08 0x07 0x15 0x2A 0x44 0x42 0x0A 0x17 0x18 0x25 0x27\n0x00 0x03 0x08 0x07 0x07 0x23 0x2A 0x43 0x42 0x09 0x18 0x17 0x25 0x27";
reset-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_LOW>;
dc-gpios = <&gpio3 RK_PA6 GPIO_ACTIVE_LOW>;
led-gpios = <&gpio3 RK_PA5 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&st7789v_cmd_pin &st7789v_reset_pin &st7789v_light_pin>;
debug = <1>;
};
};
};
fragment@1 {
target = <&pinctrl>;
__overlay__ {
st7789v {
// 配置指定引脚的 复用/上下拉/驱动能力 等
st7789v_cmd_pin: st7789v_cmd_pin {
rockchip,pins = <3 RK_PA6 RK_FUNC_GPIO &pcfg_pull_up_drv_level_1>;
};
st7789v_reset_pin: st7789v_reset_pin {
rockchip,pins = <1 RK_PA4 RK_FUNC_GPIO &pcfg_pull_up_drv_level_1>;
};
st7789v_light_pin: st7789v_light_pin {
rockchip,pins = <3 RK_PA5 RK_FUNC_GPIO &pcfg_pull_up_drv_level_1>;
};
};
};
};
// 关 hdmi 等,否则默认显示设备不是 SPI 屏
fragment@2 {
target = <&route_hdmi>;
__overlay__ {
status = "disabled";
};
};
fragment@3 {
target = <&hdmi_in_vp0>;
__overlay__ {
status = "disabled";
};
};
fragment@4 {
target = <&hdmi_in_vp1>;
__overlay__ {
status = "disabled";
};
};
fragment@5 {
target = <&hdmi>;
__overlay__ {
status = "disabled";
};
};
};
```
需要注意的是,设备树 overlay 加载时间会比较晚,使用 overlay 驱动显示屏将导致无法在上电时立刻 probe 到设备,显示屏会延迟点亮。如果需要上电立刻亮屏,可以将以上设备树配置固定到设备树文件中。