[PATCH 2/2] input: touchscreen: goodix_berlin: Add stylus support
From: Pengyu Luo <mitltlatltl@gmail.com>
Date: 2025-06-05 05:49:21
Also in:
linux-devicetree, lkml
Subsystem:
goodix touchscreen, input (keyboard, mouse, joystick, touchscreen) drivers, the rest · Maintainers:
Hans de Goede, Dmitry Torokhov, Linus Torvalds
Add support for stylus events in the Goodix Berlin touchscreen driver. This patch introduces a new input device dedicated to stylus reporting, allowing precise handling of stylus-specific data such as pressure, tilt, and side buttons. The implementation distinguishes between touch and stylus events and ensures that the appropriate input device reports each event. Key changes include: - New event type tracking to differentiate between finger and stylus input. - A new `struct goodix_berlin_stylus` to represent stylus data layout. - Support for stylus pressure, tilt (X/Y), and button states (BTN_STYLUS, BTN_STYLUS2). - Switching between input devices when changing from touch to stylus events. - Internal handling to suppress the downstream's dropped first packet behavior. **Known issue:** Stylus key reporting follows the downstream implementation([1-2]). However, on the GXTS7986 device, when BTN_STYLUS2 is continuously held, the event stream occasionally includes 4 unexpected BTN_STYLUS presses. This leads to intermittent and incorrect toggling of the BTN_STYLUS, despite it not being physically pressed. [1]: https://github.com/goodix/goodix_ts_berlin/blob/master/goodix_berlin_driver/goodix_brl_hw.c#L1165 [2]: https://github.com/goodix/goodix_ts_berlin/blob/master/goodix_berlin_driver/goodix_ts_core.c#L1157 Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com> --- .../input/touchscreen/goodix_berlin_core.c | 248 ++++++++++++++++-- 1 file changed, 230 insertions(+), 18 deletions(-)
diff --git a/drivers/input/touchscreen/goodix_berlin_core.c b/drivers/input/touchscreen/goodix_berlin_core.c
index 02a1d9a46..9118071e3 100644
--- a/drivers/input/touchscreen/goodix_berlin_core.c
+++ b/drivers/input/touchscreen/goodix_berlin_core.c@@ -18,7 +18,6 @@ * - ESD Management * - Firmware update/flashing * - "Config" update/flashing - * - Stylus Events * - Gesture Events * - Support for revision B */
@@ -28,6 +27,7 @@ #include <linux/input.h> #include <linux/input/mt.h> #include <linux/input/touchscreen.h> +#include <linux/of.h> #include <linux/property.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h>
@@ -50,6 +50,8 @@ #define GOODIX_BERLIN_POINT_TYPE_STYLUS_HOVER 1 #define GOODIX_BERLIN_POINT_TYPE_STYLUS 3 +#define GOODIX_BERLIN_STYLUS_MAX_TILT 90 + #define GOODIX_BERLIN_TOUCH_ID_MASK GENMASK(7, 4) #define GOODIX_BERLIN_DEV_CONFIRM_VAL 0xAA
@@ -59,6 +61,11 @@ #define GOODIX_BERLIN_CHECKSUM_SIZE sizeof(u16) +/* BIT(3) is unused */ +#define GOODIX_BERLIN_STYLUS_BTN_MASK GENMASK(3, 1) +static unsigned int stylus_btn[] = {BTN_STYLUS, BTN_STYLUS2}; +#define GOODIX_BERLIN_MAX_STYLUS_BTN ARRAY_SIZE(stylus_btn) + struct goodix_berlin_fw_version { u8 rom_pid[6]; u8 rom_vid[3];
@@ -144,11 +151,24 @@ struct goodix_berlin_touch { }; #define GOODIX_BERLIN_TOUCH_SIZE sizeof(struct goodix_berlin_touch) +struct goodix_berlin_stylus { + u8 status; + u8 reserved; + __le16 x; + __le16 y; + __le16 p; + __le16 x_angle; + __le16 y_angle; + u8 reserved2[4]; +}; +#define GOODIX_BERLIN_STYLUS_SIZE sizeof(struct goodix_berlin_stylus) + struct goodix_berlin_header { u8 status; u8 reserved1; u8 request_type; - u8 reserved2[3]; + u8 stylus_btn; + u8 reserved2[2]; __le16 checksum; }; #define GOODIX_BERLIN_HEADER_SIZE sizeof(struct goodix_berlin_header)
@@ -160,6 +180,12 @@ struct goodix_berlin_event { GOODIX_BERLIN_CHECKSUM_SIZE]; }; +enum goodix_berlin_event_type { + EVENT_NONE, + EVENT_STYLUS, + EVENT_TOUCH +}; + struct goodix_berlin_core { struct device *dev; struct regmap *regmap;
@@ -169,6 +195,7 @@ struct goodix_berlin_core { struct touchscreen_properties props; struct goodix_berlin_fw_version fw_version; struct input_dev *input_dev; + struct input_dev *stylus_dev; int irq; /* Runtime parameters extracted from IC_INFO buffer */
@@ -177,6 +204,9 @@ struct goodix_berlin_core { const struct goodix_berlin_ic_data *ic_data; struct goodix_berlin_event event; + + enum goodix_berlin_event_type last_event; + enum goodix_berlin_event_type cur_event; }; static bool goodix_berlin_checksum_valid(const u8 *data, int size)
@@ -432,24 +462,53 @@ static int goodix_berlin_get_remaining_contacts(struct goodix_berlin_core *cd, return 0; } -static void goodix_berlin_report_state(struct goodix_berlin_core *cd, int n) +static void goodix_berlin_stylus_report(struct goodix_berlin_core *cd, + u8 btn_pressed) +{ + struct goodix_berlin_stylus *s = + (struct goodix_berlin_stylus *)cd->event.data; + + struct input_dev *dev = cd->stylus_dev; + s8 tilt_x, tilt_y; + int i; + + if (!dev) + return; + + tilt_x = (s8)(le16_to_cpu(s->x_angle) / 100); + tilt_y = (s8)(le16_to_cpu(s->y_angle) / 100); + + input_report_key(dev, BTN_TOUCH, 1); + input_report_key(dev, BTN_TOOL_PEN, 1); + input_report_abs(dev, ABS_X, le16_to_cpu(s->x)); + input_report_abs(dev, ABS_Y, le16_to_cpu(s->y)); + input_report_abs(dev, ABS_PRESSURE, le16_to_cpu(s->p)); + input_report_abs(dev, ABS_DISTANCE, !le16_to_cpu(s->p)); + input_report_abs(dev, ABS_TILT_X, tilt_x); + input_report_abs(dev, ABS_TILT_Y, tilt_y); + + dev_dbg(&dev->dev, "stylus: x: %d, y: %d, pressure: %d, tilt_x: %d tilt_y: %d, btn: %d", + le16_to_cpu(s->x), le16_to_cpu(s->y), le16_to_cpu(s->p), tilt_x, + tilt_y, btn_pressed); + + for (i = 0; i < GOODIX_BERLIN_MAX_STYLUS_BTN; i++) + input_report_key(dev, stylus_btn[i], + !!(btn_pressed & (1 << i))); + + input_sync(dev); +} + +static void goodix_berlin_mt_report(struct goodix_berlin_core *cd, int n) { struct goodix_berlin_touch *touch_data = (struct goodix_berlin_touch *)cd->event.data; struct goodix_berlin_touch *t; int i; - u8 type, id; + u8 id; for (i = 0; i < n; i++) { t = &touch_data[i]; - type = FIELD_GET(GOODIX_BERLIN_POINT_TYPE_MASK, t->status); - if (type == GOODIX_BERLIN_POINT_TYPE_STYLUS || - type == GOODIX_BERLIN_POINT_TYPE_STYLUS_HOVER) { - dev_warn_once(cd->dev, "Stylus event type not handled\n"); - continue; - } - id = FIELD_GET(GOODIX_BERLIN_TOUCH_ID_MASK, t->status); if (id >= GOODIX_BERLIN_MAX_TOUCH) { dev_warn_ratelimited(cd->dev, "invalid finger id %d\n", id);
@@ -470,10 +529,95 @@ static void goodix_berlin_report_state(struct goodix_berlin_core *cd, int n) input_sync(cd->input_dev); } +static void goodix_berlin_device_switch(struct goodix_berlin_core *cd) +{ + int i; + + switch (cd->last_event) { + case EVENT_STYLUS: + input_report_key(cd->stylus_dev, BTN_TOUCH, 0); + input_report_key(cd->stylus_dev, BTN_TOOL_PEN, 0); + input_sync(cd->stylus_dev); + break; + case EVENT_TOUCH: + for (i = 0; i < GOODIX_BERLIN_MAX_TOUCH; i++) { + input_mt_slot(cd->input_dev, i); + input_mt_report_slot_state(cd->input_dev, + MT_TOOL_FINGER, false); + } + input_report_key(cd->input_dev, BTN_TOUCH, 0); + input_sync(cd->input_dev); + break; + default: + dev_warn(cd->dev, "%s: unsupported event code %d\n", + __func__, cd->cur_event); + } +} + +static void goodix_berlin_report_state(struct goodix_berlin_core *cd, + u8 btn_pressed, int n) +{ + /* + * When switching devices, the downstream code drops the first event + * from the new device and instead reports a touch-up event. Retaining + * and handling that initial event appears to be harmless. + */ + if (cd->last_event != EVENT_NONE && cd->last_event != cd->cur_event) + goodix_berlin_device_switch(cd); + + switch (cd->cur_event) { + case EVENT_STYLUS: + goodix_berlin_stylus_report(cd, btn_pressed); + break; + case EVENT_TOUCH: + goodix_berlin_mt_report(cd, n); + break; + default: + dev_warn(cd->dev, "%s: unsupported event code %d\n", + __func__, cd->cur_event); + } +} + +static inline void goodix_berlin_event_update(struct goodix_berlin_core *cd) +{ + struct goodix_berlin_touch *touch_data = + (struct goodix_berlin_touch *)cd->event.data; + + u8 type; + + /* + * According to the downstream code, the type of the first contact + * point determines the type for the entire event sequence. In the + * stylus case, there is typically only one contact point. + */ + cd->last_event = cd->cur_event; + type = FIELD_GET(GOODIX_BERLIN_POINT_TYPE_MASK, touch_data->status); + if (type == GOODIX_BERLIN_POINT_TYPE_STYLUS || + type == GOODIX_BERLIN_POINT_TYPE_STYLUS_HOVER) + cd->cur_event = EVENT_STYLUS; + else + cd->cur_event = EVENT_TOUCH; +} + +static inline int goodix_berlin_event_len(struct goodix_berlin_core *cd, int n) +{ + switch (cd->cur_event) { + case EVENT_STYLUS: + return GOODIX_BERLIN_STYLUS_SIZE + GOODIX_BERLIN_CHECKSUM_SIZE; + case EVENT_TOUCH: + return n * GOODIX_BERLIN_TOUCH_SIZE + + GOODIX_BERLIN_CHECKSUM_SIZE; + default: + dev_warn(cd->dev, "%s: unsupported event code %d\n", + __func__, cd->cur_event); + return 0; + } +} + static void goodix_berlin_touch_handler(struct goodix_berlin_core *cd) { - u8 touch_num; - int error; + u8 touch_num, btn_pressed; + int error, len; touch_num = FIELD_GET(GOODIX_BERLIN_TOUCH_COUNT_MASK, cd->event.hdr.request_type);
@@ -490,8 +634,9 @@ static void goodix_berlin_touch_handler(struct goodix_berlin_core *cd) } if (touch_num) { - int len = touch_num * GOODIX_BERLIN_TOUCH_SIZE + - GOODIX_BERLIN_CHECKSUM_SIZE; + goodix_berlin_event_update(cd); + len = goodix_berlin_event_len(cd, touch_num); + if (!goodix_berlin_checksum_valid(cd->event.data, len)) { dev_err(cd->dev, "touch data checksum error: %*ph\n", len, cd->event.data);
@@ -499,7 +644,10 @@ static void goodix_berlin_touch_handler(struct goodix_berlin_core *cd) } } - goodix_berlin_report_state(cd, touch_num); + btn_pressed = FIELD_GET(GOODIX_BERLIN_STYLUS_BTN_MASK, + cd->event.hdr.stylus_btn); + + goodix_berlin_report_state(cd, btn_pressed, touch_num); } static int goodix_berlin_request_handle_reset(struct goodix_berlin_core *cd)
@@ -519,9 +667,9 @@ static irqreturn_t goodix_berlin_irq(int irq, void *data) int error; /* - * First, read buffer with space for 2 touch events: + * First, read buffer with space for 2 touch events / 1 stylus event: * - GOODIX_BERLIN_HEADER_SIZE = 8 bytes - * - GOODIX_BERLIN_TOUCH_SIZE * 2 = 16 bytes + * - GOODIX_BERLIN_TOUCH_SIZE * 2 = GOODIX_BERLIN_STYLUS_SIZE = 16 bytes * - GOODIX_BERLIN_CHECKLSUM_SIZE = 2 bytes * For a total of 26 bytes. *
@@ -532,6 +680,12 @@ static irqreturn_t goodix_berlin_irq(int irq, void *data) * - bytes 24-25: Checksum * - bytes 18-25: Unused 8 bytes * + * If only a stylus is reported + * - bytes 0-7: Header (GOODIX_BERLIN_HEADER_SIZE) + * - bytes 8-19: Stylus Data + * - bytes 20-23: Unused 4 bytes + * - bytes 24-25: Checksum + * * If 2 fingers are reported, we would have read the exact needed * amount of data and checksum would be at the end of the buffer: * - bytes 0-7: Header (GOODIX_BERLIN_HEADER_SIZE)
@@ -601,6 +755,58 @@ static irqreturn_t goodix_berlin_irq(int irq, void *data) return IRQ_HANDLED; } +static int goodix_berlin_stylus_dev_config(struct goodix_berlin_core *cd, + const struct input_id *id) +{ + struct device_node *np = cd->dev->of_node; + struct input_dev *stylus_dev; + int i, width, height, pressure; + + if (!of_property_read_bool(np, "goodix,stylus-enable")) + return 0; + + if (of_property_read_u32(np, "goodix,physical-x", &width)) + width = cd->props.max_x / 10; + + if (of_property_read_u32(np, "goodix,physical-y", &height)) + height = cd->props.max_y / 10; + + if (of_property_read_u32(np, "goodix,stylus-pressure-level", &pressure)) + pressure = 4096; + + stylus_dev = devm_input_allocate_device(cd->dev); + if (!stylus_dev) + return -ENOMEM; + + cd->stylus_dev = stylus_dev; + input_set_drvdata(stylus_dev, cd); + + stylus_dev->name = "Goodix Berlin Stylus"; + stylus_dev->phys = "input/stylus"; + stylus_dev->id = *id; + + input_set_capability(stylus_dev, EV_KEY, BTN_TOUCH); + input_set_capability(stylus_dev, EV_KEY, BTN_TOOL_PEN); + for (i = 0; i < GOODIX_BERLIN_MAX_STYLUS_BTN; i++) + input_set_capability(stylus_dev, EV_KEY, stylus_btn[i]); + __set_bit(INPUT_PROP_DIRECT, stylus_dev->propbit); + + input_set_abs_params(stylus_dev, ABS_X, 0, cd->props.max_x, 0, 0); + input_set_abs_params(stylus_dev, ABS_Y, 0, cd->props.max_y, 0, 0); + input_abs_set_res(stylus_dev, ABS_X, cd->props.max_x / width); + input_abs_set_res(stylus_dev, ABS_Y, cd->props.max_y / height); + input_set_abs_params(stylus_dev, ABS_PRESSURE, 0, pressure - 1, 0, 0); + input_set_abs_params(stylus_dev, ABS_DISTANCE, 0, 255, 0, 0); + input_set_abs_params(stylus_dev, ABS_TILT_X, + -GOODIX_BERLIN_STYLUS_MAX_TILT, + GOODIX_BERLIN_STYLUS_MAX_TILT, 0, 0); + input_set_abs_params(stylus_dev, ABS_TILT_Y, + -GOODIX_BERLIN_STYLUS_MAX_TILT, + GOODIX_BERLIN_STYLUS_MAX_TILT, 0, 0); + + return input_register_device(stylus_dev); +} + static int goodix_berlin_input_dev_config(struct goodix_berlin_core *cd, const struct input_id *id) {
@@ -780,6 +986,12 @@ int goodix_berlin_probe(struct device *dev, int irq, const struct input_id *id, return error; } + error = goodix_berlin_stylus_dev_config(cd, id); + if (error) { + dev_err(dev, "failed set stylus device"); + return error; + } + error = devm_request_threaded_irq(dev, cd->irq, NULL, goodix_berlin_irq, IRQF_ONESHOT, "goodix-berlin", cd); if (error) {
--
2.49.0