lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250710115733.226670-3-mitltlatltl@gmail.com>
Date: Thu, 10 Jul 2025 19:57:33 +0800
From: Pengyu Luo <mitltlatltl@...il.com>
To: Dmitry Torokhov <dmitry.torokhov@...il.com>,
	Rob Herring <robh@...nel.org>,
	Krzysztof Kozlowski <krzk+dt@...nel.org>,
	Conor Dooley <conor+dt@...nel.org>,
	Hans de Goede <hansg@...nel.org>,
	Thomas Weißschuh <thomas.weissschuh@...utronix.de>,
	Len Brown <len.brown@...el.com>,
	Ingo Molnar <mingo@...nel.org>,
	Ilpo Järvinen <ilpo.jarvinen@...ux.intel.com>,
	Jason Gunthorpe <jgg@...pe.ca>,
	Luke Jones <luke@...nes.dev>,
	"Gustavo A. R. Silva" <gustavoars@...nel.org>,
	Al Viro <viro@...iv.linux.org.uk>,
	Eric Biggers <ebiggers@...nel.org>,
	Neil Armstrong <neil.armstrong@...aro.org>
Cc: Charles Wang <charles.goodix@...il.com>,
	linux-input@...r.kernel.org,
	devicetree@...r.kernel.org,
	linux-kernel@...r.kernel.org,
	Pengyu Luo <mitltlatltl@...il.com>
Subject: [PATCH v2 2/2] input: touchscreen: goodix_berlin: Add stylus support

This patch introduces a new input device dedicated to stylus reporting,
allowing 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.

**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@...il.com>
---
 .../input/touchscreen/goodix_berlin_core.c    | 240 ++++++++++++++++--
 1 file changed, 222 insertions(+), 18 deletions(-)

diff --git a/drivers/input/touchscreen/goodix_berlin_core.c b/drivers/input/touchscreen/goodix_berlin_core.c
index c78d512d9..74cd31878 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,17 @@ 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_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 +493,118 @@ static void goodix_berlin_report_state(struct goodix_berlin_core *cd, int n)
 	input_sync(cd->input_dev);
 }
 
+static void goodix_berlin_stylus_report(struct goodix_berlin_core *cd,
+					u8 btn_pressed, bool down)
+{
+	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;
+
+	input_report_key(dev, BTN_TOUCH, down);
+	input_report_key(dev, BTN_TOOL_PEN, down);
+
+	if (!down)
+		goto key_report;
+
+	tilt_x = (s8)(le16_to_cpu(s->x_angle) / 100);
+	tilt_y = (s8)(le16_to_cpu(s->y_angle) / 100);
+
+	touchscreen_report_pos(dev, &cd->props, le16_to_cpu(s->x),
+			       le16_to_cpu(s->y), false);
+
+	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);
+
+key_report:
+	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 inline void goodix_berlin_device_switch(struct goodix_berlin_core *cd)
+{
+	switch (cd->last_event) {
+	case EVENT_STYLUS:
+		goodix_berlin_stylus_report(cd, 0, false);
+		break;
+	case EVENT_TOUCH:
+		goodix_berlin_mt_report(cd, 0);
+		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)
+{
+	if (likely(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, true);
+		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,
+					      int touch_num)
+{
+	struct goodix_berlin_touch *touch_data =
+			(struct goodix_berlin_touch *)cd->event.data;
+
+	u8 type;
+
+	cd->last_event = cd->cur_event;
+	type = FIELD_GET(GOODIX_BERLIN_POINT_TYPE_MASK, touch_data->status);
+	if (touch_num && (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);
@@ -489,9 +620,11 @@ static void goodix_berlin_touch_handler(struct goodix_berlin_core *cd)
 			return;
 	}
 
+	goodix_berlin_event_update(cd, touch_num);
+
 	if (touch_num) {
-		int len = touch_num * GOODIX_BERLIN_TOUCH_SIZE +
-			  GOODIX_BERLIN_CHECKSUM_SIZE;
+		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 +632,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 +655,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 +668,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 +743,62 @@ 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, p_lvl;
+
+	if (!of_property_read_bool(np, "goodix,stylus-enable"))
+		return 0;
+
+	/*
+	 * If no value is provided, then setting the default to 10 units/mm
+	 * just like drivers/input/touchscreen/goodix.c
+	 */
+	if (of_property_read_u32(np, "touchscreen-x-mm", &width))
+		width = cd->props.max_x / 10;
+
+	if (of_property_read_u32(np, "touchscreen-y-mm", &height))
+		height = cd->props.max_y / 10;
+
+	if (of_property_read_u32(np, "goodix,stylus-pressure-level", &p_lvl))
+		p_lvl = 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, p_lvl - 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 +978,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.50.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ