1379 字
7 分钟
Linux下GT911驱动移植
Kernel版本:6.6.52
开始前首先确认所用屏幕背面IC的丝印,到底是ft5426还是gt911,二者驱动不通用
客制化驱动
若使用正点原子提供的gt9147.c驱动:
设备树
&i2c2 { gt911:gt911@14 { compatible = "goodix,gt911", "goodix,gt9xx"; reg = <0x14>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_gt911>; interrupt-parent = <&gpio1>; interrupts = <9 0>; reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; status = "okay"; };};
&iomuxc { pinctrl_gt911: gt911grp { fsl,pins = < MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x79 /* TSC_INT */ MX6UL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0 >; };};6.6.52版本的Kernel DT不需要把MX6UL_PAD_SNVS_TAMPER9__GPIO5_IO09单独移到iomuxc_snvs中
驱动
#include <linux/module.h>#include <linux/i2c.h>#include <linux/regmap.h>#include <linux/gpio/consumer.h>#include <linux/of_irq.h>#include <linux/interrupt.h>#include <linux/input.h>#include <linux/input/mt.h>#include <linux/debugfs.h>#include <linux/delay.h>#include <linux/slab.h>#include <linux/gpio.h>#include <linux/of_gpio.h>#include <linux/of.h>#include <linux/input/mt.h>#include <linux/input/touchscreen.h>#include <linux/i2c.h>#include <asm/unaligned.h>
#define GT_CTRL_REG 0X8040#define GT_MODSW_REG 0X804D#define GT_9xx_CFGS_REG 0X8047#define GT_1xx_CFGS_REG 0X8050#define GT_CHECK_REG 0X80FF#define GT_PID_REG 0X8140
#define GT_GSTID_REG 0X814E#define GT_TP1_REG 0X814F#define GT_TP2_REG 0X8157#define GT_TP3_REG 0X815F#define GT_TP4_REG 0X8167#define GT_TP5_REG 0X816F#define MAX_SUPPORT_POINTS 5
struct gt911_dev { int irq_pin,reset_pin; int irqnum; int irqtype; int max_x; int max_y; void *private_data; struct input_dev *input; struct i2c_client *client;
};struct gt911_dev gt911;
const u8 irq_table[] = {IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, IRQ_TYPE_LEVEL_LOW, IRQ_TYPE_LEVEL_HIGH}; /* 触发方式 */
/* reset gt911 */static int gt911_ts_reset(struct i2c_client *client, struct gt911_dev *dev){ int ret = 0;
/* request RESET gpio */ if (gpio_is_valid(dev->reset_pin)) { ret = devm_gpio_request_one(&client->dev, dev->reset_pin, GPIOF_OUT_INIT_HIGH, "gt911 reset"); if (ret) { return ret; } }
/* request INT gpio */ if (gpio_is_valid(dev->irq_pin)) { ret = devm_gpio_request_one(&client->dev, dev->irq_pin, GPIOF_OUT_INIT_HIGH, "gt911 int"); if (ret) { return ret; } }
/* initialize gt911 follow datasheet */ gpio_set_value(dev->reset_pin, 0); msleep(10); gpio_set_value(dev->reset_pin, 1); msleep(10); gpio_set_value(dev->irq_pin, 0); msleep(50); gpio_direction_input(dev->irq_pin);
return 0;}
static int gt911_read_regs(struct gt911_dev *dev, u16 reg, u8 *buf, int len){ int ret; u8 regdata[2]; struct i2c_msg msg[2]; struct i2c_client *client = (struct i2c_client *)dev->client;
regdata[0] = reg >> 8; regdata[1] = reg & 0xFF;
msg[0].addr = client->addr; msg[0].flags = !I2C_M_RD; msg[0].buf = ®data[0]; msg[0].len = 2;
msg[1].addr = client->addr; msg[1].flags = I2C_M_RD; msg[1].buf = buf; msg[1].len = len;
ret = i2c_transfer(client->adapter, msg, 2); if(ret == 2) { ret = 0; } else { ret = -EREMOTEIO; } return ret;}
static s32 gt911_write_regs(struct gt911_dev *dev, u16 reg, u8 *buf, u8 len){ u8 b[256]; struct i2c_msg msg; struct i2c_client *client = (struct i2c_client *)dev->client;
b[0] = reg >> 8; b[1] = reg & 0XFF; memcpy(&b[2],buf,len);
msg.addr = client->addr; msg.flags = 0;
msg.buf = b; msg.len = len + 2;
return i2c_transfer(client->adapter, &msg, 1);}
static irqreturn_t gt911_irq_handler(int irq, void *dev_id){ int touch_num = 0; int input_x, input_y; int id = 0; int ret = 0; u8 data; u8 touch_data[5]; struct gt911_dev *dev = dev_id;
ret = gt911_read_regs(dev, GT_GSTID_REG, &data, 1); if (data == 0x00) { /* no touch data */ goto fail; } else { /* calculate touch data */ touch_num = data & 0x0f; }
if(touch_num) { gt911_read_regs(dev, GT_TP1_REG, touch_data, 5); id = touch_data[0] & 0x0F; if(id == 0) { input_x = touch_data[1] | (touch_data[2] << 8); input_y = touch_data[3] | (touch_data[4] << 8);
input_mt_slot(dev->input, id); input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true); input_report_abs(dev->input, ABS_MT_POSITION_X, input_x); input_report_abs(dev->input, ABS_MT_POSITION_Y, input_y); } } else if(touch_num == 0){ input_mt_slot(dev->input, id); input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false); }
input_mt_report_pointer_emulation(dev->input, true); input_sync(dev->input);
data = 0x00; gt911_write_regs(dev, GT_GSTID_REG, &data, 1);
fail: return IRQ_HANDLED;}
static int gt911_ts_irq(struct i2c_client *client, struct gt911_dev *dev){ int ret = 0;
/* requeset IRQ */ ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, gt911_irq_handler, irq_table[dev->irqtype] | IRQF_ONESHOT, client->name, >911); if (ret) { dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); return ret; }
return 0;}
static int gt911_read_firmware(struct i2c_client *client, struct gt911_dev *dev){ int ret = 0, version = 0; u16 id = 0; u8 data[7]={0}; char id_str[5]; ret = gt911_read_regs(dev, GT_PID_REG, data, 6); if (ret) { dev_err(&client->dev, "Unable to read PID.\n"); return ret; } memcpy(id_str, data, 4); id_str[4] = 0; if (kstrtou16(id_str, 10, &id)) id = 0x1001; version = get_unaligned_le16(&data[4]); dev_info(&client->dev, "ID %d, version: %04x\n", id, version); switch (id) { case 1151: case 1158: case 5663: case 5688: ret = gt911_read_regs(dev, GT_1xx_CFGS_REG, data, 7); break; default: ret = gt911_read_regs(dev, GT_9xx_CFGS_REG, data, 7); break; } if (ret) { dev_err(&client->dev, "Unable to read Firmware.\n"); return ret; } dev->max_x = (data[2] << 8) + data[1]; dev->max_y = (data[4] << 8) + data[3]; dev->irqtype = data[6] & 0x3; printk("X_MAX: %d, Y_MAX: %d, TRIGGER: 0x%02x", dev->max_x, dev->max_y, dev->irqtype);
return 0;}
static int gt911_probe(struct i2c_client *client){ u8 data, ret; gt911.client = client;
gt911.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0); gt911.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);
ret = gt911_ts_reset(client, >911); if(ret < 0) { goto fail; }
data = 0x02; gt911_write_regs(>911, GT_CTRL_REG, &data, 1); mdelay(100); data = 0x0; gt911_write_regs(>911, GT_CTRL_REG, &data, 1); mdelay(100);
ret = gt911_read_firmware(client, >911); if(ret != 0) { printk("Fail !!! check !!\r\n"); goto fail; }
gt911.input = devm_input_allocate_device(&client->dev); if (!gt911.input) { ret = -ENOMEM; goto fail; } gt911.input->name = client->name; gt911.input->id.bustype = BUS_I2C; gt911.input->dev.parent = &client->dev;
__set_bit(EV_KEY, gt911.input->evbit); __set_bit(EV_ABS, gt911.input->evbit); __set_bit(BTN_TOUCH, gt911.input->keybit);
input_set_abs_params(gt911.input, ABS_X, 0, gt911.max_x, 0, 0); input_set_abs_params(gt911.input, ABS_Y, 0, gt911.max_y, 0, 0); input_set_abs_params(gt911.input, ABS_MT_POSITION_X,0, gt911.max_x, 0, 0); input_set_abs_params(gt911.input, ABS_MT_POSITION_Y,0, gt911.max_y, 0, 0); ret = input_mt_init_slots(gt911.input, MAX_SUPPORT_POINTS, 0); if (ret) { goto fail; }
ret = input_register_device(gt911.input); if (ret) goto fail;
ret = gt911_ts_irq(client, >911); if(ret < 0) { goto fail; }
dev_info(&client->dev, "GT911 Probe OK\r\n");
return 0;
fail: return ret;}
static void gt911_remove(struct i2c_client *client){ input_unregister_device(gt911.input);}
const struct of_device_id gt911_of_match_table[] = { {.compatible = "goodix,gt911" }, { }};
struct i2c_driver gt911_i2c_driver = { .driver = { .name = "gt911", .owner = THIS_MODULE, .of_match_table = gt911_of_match_table, }, .probe = gt911_probe, .remove = gt911_remove,};
module_i2c_driver(gt911_i2c_driver);
MODULE_LICENSE("GPL");MODULE_AUTHOR("aki");Kernel提供驱动
驱动文件为/drivers/input/touchscreen/goodix.c
设备树
按照binding要求
%YAML 1.2---$id: http://devicetree.org/schemas/input/touchscreen/goodix.yaml#$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Goodix GT9xx series touchscreen controller
maintainers: - Dmitry Torokhov <dmitry.torokhov@gmail.com>
allOf: - $ref: touchscreen.yaml#
properties: compatible: enum: - goodix,gt1151 - goodix,gt1158 - goodix,gt5663 - goodix,gt5688 - goodix,gt911 - goodix,gt9110 - goodix,gt912 - goodix,gt9147 - goodix,gt917s - goodix,gt927 - goodix,gt9271 - goodix,gt928 - goodix,gt9286 - goodix,gt967
reg: enum: [ 0x5d, 0x14 ]
interrupts: maxItems: 1
irq-gpios: description: GPIO pin used for IRQ. The driver uses the interrupt gpio pin as output to reset the device. maxItems: 1
reset-gpios: maxItems: 1
AVDD28-supply: description: Analog power supply regulator on AVDD28 pin
VDDIO-supply: description: GPIO power supply regulator on VDDIO pin
touchscreen-inverted-x: true touchscreen-inverted-y: true touchscreen-size-x: true touchscreen-size-y: true touchscreen-swapped-x-y: true
additionalProperties: false
required: - compatible - reg - interrupts
examples: - | i2c { #address-cells = <1>; #size-cells = <0>; gt928@5d { compatible = "goodix,gt928"; reg = <0x5d>; interrupt-parent = <&gpio>; interrupts = <0 0>; irq-gpios = <&gpio1 0 0>; reset-gpios = <&gpio1 1 0>; }; };compatible、reg和interrupts必须要有,其他的无所谓,修改设备树节点如下:
&i2c2 { gt911:gt911@14 { compatible = "goodix,gt911", "goodix,gt9xx"; reg = <0x14>; interrupt-parent = <&gpio1>; interrupts = <9 0>; reset-gpios = <&gpio5 9 GPIO_ACTIVE_HIGH>; irq-gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>; status = "okay"; };};注意:这里的reset-gpio和irq-gpio有效电平均为高电平,不能设置成低电平,虽然硬件设计时INT和RST为外部上拉低电平有效。原因未知。
驱动由于找不到本地的xxx_cfg.bin文件导致长时间等待(一般为64s)后再进行input注册的问题可以通过在goodix.c中Probe函数中修改判断条件解决:
/* 注释掉本地cfg检索,防止找不到cfg.bin时长时间等待 */ //if (ts->load_cfg_from_disk) { if (0){ /* update device config */ error = device_property_read_string(&client->dev, "goodix,config-name", &cfg_name); if (!error) snprintf(ts->cfg_name, sizeof(ts->cfg_name), "goodix/%s", cfg_name); else snprintf(ts->cfg_name, sizeof(ts->cfg_name), "goodix_%s_cfg.bin", ts->id);
error = request_firmware_nowait(THIS_MODULE, true, ts->cfg_name, &client->dev, GFP_KERNEL, ts, goodix_config_cb); if (error) { dev_err(&client->dev, "Failed to invoke firmware loader: %d\n", error); return error; }
return 0; } else { ...... } Linux下GT911驱动移植
https://arkmostima.pages.dev/posts/linux下gt911驱动移植/