25. Linux 触摸屏驱动
Linux 触摸屏驱动开发需要理解 Linux 内核中的输入子系统(Input Subsystem)及其与用户空间的交互机制。此章节讨论 LVDS 接口的触摸屏,详细描述从设备树(DTS)的配置,到驱动注册、事件报告,再到用户空间的传递流程。
25.1 LVDS 接口简介
LVDS(Low Voltage Differential Signaling)是一种用于传输视频数据的接口,广泛用于笔记本电脑和平板显示器中。虽然 LVDS 本身用于视频信号传输,但触摸屏部分通常通过 I2C 或 SPI 或 USB 接口与主机通信。开发 Linux 触摸屏驱动时,需分别处理视频显示和触摸输入两个部分。
在Neardi RK3588设备上面, 是由mipi转LVDS接口。这里的触摸屏型号是EV101WXM-N10, DTS设置如下:
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2020 Rockchip Electronics Co., Ltd.
*
*/
/ {
backlight: backlight {
status="okay";
compatible = "pwm-backlight";
pwms = <&pwm7 0 25000 0>;
brightness-levels = <
0 20 20 21 21 22 22 23
23 24 24 25 25 26 26 27
27 28 28 29 29 30 30 31
31 32 32 33 33 34 34 35
35 36 36 37 37 38 38 39
40 41 42 43 44 45 46 47
48 49 50 51 52 53 54 55
56 57 58 59 60 61 62 63
64 65 66 67 68 69 70 71
72 73 74 75 76 77 78 79
80 81 82 83 84 85 86 87
88 89 90 91 92 93 94 95
96 97 98 99 100 101 102 103
104 105 106 107 108 109 110 111
112 113 114 115 116 117 118 119
120 121 122 123 124 125 126 127
128 129 130 131 132 133 134 135
136 137 138 139 140 141 142 143
144 145 146 147 148 149 150 151
152 153 154 155 156 157 158 159
160 161 162 163 164 165 166 167
168 169 170 171 172 173 174 175
176 177 178 179 180 181 182 183
184 185 186 187 188 189 190 191
192 193 194 195 196 197 198 199
200 201 202 203 204 205 206 207
208 209 210 211 212 213 214 215
216 217 218 219 220 221 222 223
224 225 226 227 228 229 230 231
232 233 234 235 236 237 238 239
240 241 242 243 244 245 246 247
248 249 250 251 252 253 254 255
>;
default-brightness-level = <200>;
enable-gpios = <&gpio2 RK_PC1 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&lcd_en>;
};
};
&pwm7{
status = "okay";
pinctrl-names = "active";
pinctrl-0 = <&pwm7m3_pins>;
};
/*
* mipi_dcphy0 needs to be enabled
* when dsi0 is enabled
*/
&mipi_dcphy0 {
status = "okay";
};
&dsi0_in_vp2 {
status = "disabled";
};
&dsi0_in_vp3 {
status = "okay";
};
&route_dsi0 {
status = "disabled";
connect = <&vp3_out_dsi0>;
};
&dsi0 {
status = "okay";
//rockchip,lane-rate = <854>;
enable-gpios = <&gpio1 RK_PD6 GPIO_ACTIVE_HIGH>; // lvds_pwren
reset-gpios = <&gpio1 RK_PD7 GPIO_ACTIVE_HIGH>; // lvds_rst
dsi0_panel: panel@0 {
status = "okay";
compatible = "simple-panel-dsi";
reg = <0>;
backlight = <&backlight>;
reset-delay-ms = <60>;
enable-delay-ms = <60>;
prepare-delay-ms = <60>;
unprepare-delay-ms = <60>;
disable-delay-ms = <60>;
dsi,flags = <(MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
MIPI_DSI_MODE_VIDEO_HBP | MIPI_DSI_MODE_LPM )>;
dsi,format = <MIPI_DSI_FMT_RGB888>;
dsi,lanes = <4>;
panel-init-sequence = [
23 02 02 27 AA
23 02 02 48 02
23 02 02 B6 20
23 02 02 01 00
23 02 02 02 20
23 02 02 03 35
23 02 02 04 30
23 02 02 05 01
23 02 02 06 0A
23 02 02 07 00
23 02 02 08 03
23 02 02 09 05
23 02 02 0A 06
23 02 02 0B 82
23 02 02 0C 22
23 02 02 0D 01
23 02 02 0E 80
23 02 02 0F 20
23 02 02 10 20
23 02 02 11 03
23 02 02 12 1B
23 02 02 13 03
23 02 02 14 01
23 02 02 15 23
23 02 02 16 40
23 02 02 17 00
23 02 02 18 01
23 02 02 19 23
23 02 02 1A 40
23 02 02 1B 00
23 02 02 1E 46
23 02 02 51 30
23 02 02 1F 10
23 02 02 2A 01
];
disp_timings0: display-timings {
native-mode = <&dsi0_timing0>;
dsi0_timing0: timing0 {
clock-frequency = <110000000>;
hactive = <1280>;
vactive = <800>;
hback-porch = <10>;
hfront-porch = <48>;
hsync-len = <1>;
vback-porch = <6>;
vfront-porch = <3>;
vsync-len = <5>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <0>;
pixelclk-active = <1>;
};
};
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
panel_in_dsi: endpoint {
remote-endpoint = <&dsi_out_panel>;
};
};
};
};
ports {
#address-cells = <1>;
#size-cells = <0>;
port@1 {
reg = <1>;
dsi_out_panel: endpoint {
remote-endpoint = <&panel_in_dsi>;
};
};
};
};
&pinctrl {
backlight {
/* for edp and dsi2lvds */
lcd_en: lcd-en {
rockchip,pins = <2 RK_PC1 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
};
25.1.1 背光控制
backlight: backlight {
status="okay";
compatible = "pwm-backlight";
pwms = <&pwm7 0 25000 0>;
// other settings...
25.1.2 PWM控制器配置
&pwm7 {
status = "okay";
pinctrl-names = "active";
pinctrl-0 = <&pwm7m3_pins>;
};
25.1.3 MIPI DSI PHY配置
&mipi_dcphy0 {
status = "okay";
};
- 表示MIPI DC PHY的状态为可用,这是MIPI DSI接口所需的物理层配置。
25.1.4 MIPI DSI配置
&dsi0 {
status = "okay";
enable-gpios = <&gpio1 RK_PD6 GPIO_ACTIVE_HIGH>; // lvds_pwren
reset-gpios = <&gpio1 RK_PD7 GPIO_ACTIVE_HIGH>; // lvds_rst
dsi0_panel: panel@0 {
status = "okay";
compatible = "simple-panel-dsi";
reg = <0>;
backlight = <&backlight>;
// other settings ...
};
...
};
status = "okay";: DSI接口可用。
enable-gpios 和 reset-gpios: 控制LVDS的使能和复位引脚。
dsi0_panel: panel@0: DSI面板节点,定义该面板的具体参数。
25.1.5 面板初始化
panel-init-sequence = [
23 02 02 27 AA
// other settings...
];
- 该序列定义了面板初始化的指令,具体数据应根据面板的规格书进行设置。
25.1.6 显示时序配置
disp_timings0: display-timings {
native-mode = <&dsi0_timing0>;
dsi0_timing0: timing0 {
clock-frequency = <110000000>;
hactive = <1280>;
vactive = <800>;
// other settings ...
};
};
25.1.7 端口配置
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
panel_in_dsi: endpoint {
remote-endpoint = <&dsi_out_panel>;
};
};
// other settings ...
};
- 定义端口和相应的端点,panel_in_dsi和dsi_out_panel用于描述数据流的连接。
port和endpoint的设置用于定义设备间的连接,特别是在处理多媒体(如显示和摄像头)时。
port:
port节点表示设备的一个物理或逻辑连接点。每个port可以有多个endpoint,每个endpoint负责与其他设备的连接。 每个port都有一个唯一的reg属性,用于标识其地址。
endpoint:
endpoint节点表示数据流的实际传输点。它定义了如何在设备之间传输数据,以及所需的连接信息。remote-endpoint属性用于指向另一个设备的endpoint,表示两个设备之间的数据流方向。两个endpoint通过它们的“remote-endpoint”phandle 互相指向对方,形成包含端口之间的链接。
25.2 MIPI 驱动代码
Rockchip平台mipi驱动代码如下:
MIPI DSI-2 host controller:
drivers/gpu/drm/rockchip/dw-mipi-dsi2-rockchip.c
负责处理 Rockchip 平台上 DSI(Display Serial Interface)接口的控制, 实现了对 Rockchip SoC(如 RK35系列芯片)中嵌入的 DesignWare MIPI DSI v2 控制器 的支持和管理。
MIPI DCPHY:
drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
phy-rockchip-samsung-dcphy.c 是内核中的mipi dcphy驱动代码文件,负责支持 Rockchip 芯片和三星设计的 DCPHY(Display Controller PHY)模块。它的主要功能是处理物理层(PHY)硬件的初始化和控制,尤其是与显示子系统(如 MIPI DSI 接口或其他显示接口)相关的部分。PHY 是显示信号传输过程中非常重要的一部分,负责从数字信号到电信号的转换,以便在显示设备上呈现。
具体的代码设计及其框架, 在此不作讨论。
25.3 触摸屏驱动
此款触摸屏是USB接口, 驱动代码在kernel/drivers/input/touchscreen/usbtouchscreen.c, 驱动程序从触摸屏硬件接收触摸事件数据,并将这些数据转换为 Linux 内核的标准输入事件格式。这个过程包括读取 USB 数据包、解析触摸事件、并通过 input_report_key() 等函数将这些事件报告给操作系统。步骤如下:
25.3.1 input_register_device()
这个函数用于注册输入设备,使系统可以识别并使用该设备。代码片段如下:
err = input_register_device(usbtouch->input);
if (err) {
dev_dbg(&intf->dev,
"%s - input_register_device failed, err: %d\n",
__func__, err);
goto out_do_exit;
}
25.3.2 input_alloc_device()
用于分配并初始化一个新的 input_dev 结构体,这个结构体表示一个输入设备。如下:
usbtouch = kzalloc(sizeof(struct usbtouch_usb), GFP_KERNEL);
input_dev = input_allocate_device();
if (!usbtouch || !input_dev)
goto out_free;
mutex_init(&usbtouch->pm_mutex);
25.3.3 input_set_abs_params()
设置绝对坐标轴的参数(如触摸屏的 X 和 Y 轴范围)。触摸屏通常使用绝对坐标系来报告触摸位置。驱动程序调用 input_set_abs_params() 函数,指定触摸屏的 X 轴和 Y 轴的最小值、最大值、分辨率等参数。
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(input_dev, ABS_X, type->min_xc, type->max_xc, 0, 0);
input_set_abs_params(input_dev, ABS_Y, type->min_yc, type->max_yc, 0, 0);
25.3.4 input_report_abs()
向系统报告绝对位置的输入事件,如触摸屏的 X、Y 坐标。
/*****************************************************************************
* Generic Part
*/
static void usbtouch_process_pkt(struct usbtouch_usb *usbtouch,
unsigned char *pkt, int len)
{
struct usbtouch_device_info *type = usbtouch->type;
if (!type->read_data(usbtouch, pkt))
return;
input_report_key(usbtouch->input, BTN_TOUCH, usbtouch->touch);
if (swap_xy) {
input_report_abs(usbtouch->input, ABS_X, usbtouch->y);
input_report_abs(usbtouch->input, ABS_Y, usbtouch->x);
} else {
input_report_abs(usbtouch->input, ABS_X, usbtouch->x);
input_report_abs(usbtouch->input, ABS_Y, usbtouch->y);
}
if (type->max_press)
input_report_abs(usbtouch->input, ABS_PRESSURE, usbtouch->press);
input_sync(usbtouch->input);
}
这段代码, 处理触摸屏按键input_report_key, 报告绝对坐标input_report_abs, 以及通知内核输入事件处理已经完成input_sync。
25.4 加载USB触摸屏驱动
首先, 系统识别 USB 设备类型的过程主要依赖于设备的描述符,具体步骤如下:
设备枚举:当 USB 设备插入时,内核会通过 USB 总线进行设备枚举,读取设备描述符。
读取描述符:设备描述符包含重要信息,如供应商 ID(Vendor ID)、产品 ID(Product ID)、设备类(Device Class)等。每种设备都有特定的类代码,USB 触摸屏通常会有与人机接口设备(HID)相关的类代码。
设备类匹配:内核根据设备的类代码(如 HID 设备通常为 0x03)来识别设备类型。针对 HID 类设备,内核会进一步读取 HID 描述符,获取关于输入功能的信息。
驱动匹配:根据匹配的类和设备 ID,内核查找对应的驱动程序(如 usbtouchscreen 驱动),并调用其 probe 函数进行初始化。
通过这一系列步骤,系统能够准确识别 USB 触摸屏设备与其他 USB 设备。每种设备的类代码和描述符内容帮助系统区分设备类型。
USB设备ID定义如下:
static const struct usb_device_id usbtouch_devices[] = {
#ifdef CONFIG_TOUCHSCREEN_USB_EGALAX
/* ignore the HID capable devices, handled by usbhid */
{USB_DEVICE_HID_CLASS(0x0eef, 0x0001), .driver_info = DEVTYPE_IGNORE},
{USB_DEVICE_HID_CLASS(0x0eef, 0x0002), .driver_info = DEVTYPE_IGNORE},
/* normal device IDs */
{USB_DEVICE(0x3823, 0x0001), .driver_info = DEVTYPE_EGALAX},
{USB_DEVICE(0x3823, 0x0002), .driver_info = DEVTYPE_EGALAX},
{USB_DEVICE(0x0123, 0x0001), .driver_info = DEVTYPE_EGALAX},
{USB_DEVICE(0x0eef, 0x0001), .driver_info = DEVTYPE_EGALAX},
{USB_DEVICE(0x0eef, 0x0002), .driver_info = DEVTYPE_EGALAX},
{USB_DEVICE(0x1234, 0x0001), .driver_info = DEVTYPE_EGALAX},
{USB_DEVICE(0x1234, 0x0002), .driver_info = DEVTYPE_EGALAX},
#endif
// others USB vendor ID....
25.5 总结
触摸屏驱动包括2部分, 显示及触摸。 显示部分需要设置DTS等, 触摸部分需要了解Linux input system。