增加 1.9inch LCD Module fbtft 驱动 for RK356x。
Signed-off-by: rick.chan <cy@haoan119.com>
This commit is contained in:
parent
c37dce6446
commit
604b4ee4f5
|
@ -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 的 Animation(AnimatedImage、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 到设备,显示屏会延迟点亮。如果需要上电立刻亮屏,可以将以上设备树配置固定到设备树文件中。
|
Loading…
Reference in New Issue