diff mbox series

[3/3,hid] Emit digitizer serial number through power_supply

Message ID 20210519143836.3.I9d559632e582daaecdafd995ce7dfd9c89e64838@changeid
State New
Headers show
Series 64-bit Digitizer Serial Numbers | expand

Commit Message

Kenneth Albanowski May 20, 2021, 12:22 a.m. UTC
HID devices that expose a battery strength can have
associated power_supply nodes. This fills in the
SERIAL_NUMBER power_supply field if the same HID device
also has a Digitizer.Transducer Serial Number usage,
effectively allowing that particular stylus to be
identified.

If the field is present and non-zero, the serial number
will be 'DG-ABCD' where 'ABCD' is up to sixteen hex
digits -- field lengths of up to 64-bits are supported,
the largest currently known about.

Devices are expected to emit zero if the transducer
does not have a serial number, or the serial number
has not yet been acquired; zeros will be ignored.

Note that logical min/max (and other HID item
parameters) will be ignored for this field.

Signed-off-by: Kenneth Albanowski <kenalba@google.com>
---

 drivers/hid/hid-input.c | 100 +++++++++++++++++++++++++++++++++++++---
 include/linux/hid.h     |   5 ++
 2 files changed, 99 insertions(+), 6 deletions(-)
diff mbox series

Patch

diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index ee9e8d31a45ba..c5767ceb4a61c 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -286,6 +286,7 @@  static enum power_supply_property hidinput_battery_props[] = {
 	POWER_SUPPLY_PROP_ONLINE,
 	POWER_SUPPLY_PROP_CAPACITY,
 	POWER_SUPPLY_PROP_MODEL_NAME,
+	POWER_SUPPLY_PROP_SERIAL_NUMBER,
 	POWER_SUPPLY_PROP_STATUS,
 	POWER_SUPPLY_PROP_SCOPE,
 };
@@ -402,6 +403,26 @@  static int hidinput_get_battery_property(struct power_supply *psy,
 		val->strval = dev->name;
 		break;
 
+	case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+		/* Serial number does not have an active HID query
+		 * mechanism like hidinput_query_battery_capacity, as the
+		 * only devices expected to have serial numbers are digitizers,
+		 * which are unlikely to be able to pull the serial number from
+		 * an untethered pen on demand.
+		 */
+		if (dev->battery_serial_number == 0) {
+			/* Make no claims about S/N format if we haven't actually seen a value yet. */
+			strcpy(dev->battery_serial_number_str, "");
+		} else {
+			snprintf(dev->battery_serial_number_str,
+				 sizeof(dev->battery_serial_number_str),
+				 "DG-%0*llX",
+				 DIV_ROUND_UP(dev->battery_serial_number_bits, 4),
+				 dev->battery_serial_number);
+		}
+		val->strval = dev->battery_serial_number_str;
+		break;
+
 	case POWER_SUPPLY_PROP_STATUS:
 		if (dev->battery_status != HID_BATTERY_REPORTED &&
 		    !dev->battery_avoid_query) {
@@ -485,6 +506,8 @@  static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
 	dev->battery_max = max;
 	dev->battery_report_type = report_type;
 	dev->battery_report_id = field->report->id;
+	dev->battery_changed = false;
+	dev->battery_reported = false;
 
 	/*
 	 * Stylus is normally not connected to the device and thus we
@@ -526,7 +549,8 @@  static void hidinput_cleanup_battery(struct hid_device *dev)
 	dev->battery = NULL;
 }
 
-static void hidinput_update_battery(struct hid_device *dev, int value)
+static void hidinput_update_battery_capacity(struct hid_device *dev,
+					     __s32 value)
 {
 	int capacity;
 
@@ -538,11 +562,57 @@  static void hidinput_update_battery(struct hid_device *dev, int value)
 
 	capacity = hidinput_scale_battery_capacity(dev, value);
 
+	if (capacity != dev->battery_capacity) {
+		dev->battery_capacity = capacity;
+		dev->battery_changed = true;
+	}
+	dev->battery_reported = true;
+}
+
+static void hidinput_update_battery_serial(struct hid_device *dev,
+					   const __s32 *values, int bits)
+{
+	__u64 value;
+
+	if (!dev->battery)
+		return;
+
+	if (bits > 64)
+		bits = 64;
+
+	value = (__u64)(__u32)values[0];
+	if (bits > 32)
+		value |= (__u64)values[1] << 32;
+
+	if (value == 0)
+		return;
+
+	if (value != dev->battery_serial_number) {
+		dev->battery_serial_number = value;
+		dev->battery_serial_number_bits = bits;
+		dev->battery_changed = true;
+	}
+	dev->battery_reported = true;
+}
+
+static void hidinput_flush_battery(struct hid_device *dev)
+{
+	if (!dev->battery)
+		return;
+
+	/* Only consider pushing a battery change if there is a
+	 * battery field in this report.
+	 */
+	if (!dev->battery_reported)
+		return;
+
+	dev->battery_reported = false;
+
 	if (dev->battery_status != HID_BATTERY_REPORTED ||
-	    capacity != dev->battery_capacity ||
+	    dev->battery_changed ||
 	    ktime_after(ktime_get_coarse(), dev->battery_ratelimit_time)) {
-		dev->battery_capacity = capacity;
 		dev->battery_status = HID_BATTERY_REPORTED;
+		dev->battery_changed = false;
 		dev->battery_ratelimit_time =
 			ktime_add_ms(ktime_get_coarse(), 30 * 1000);
 		power_supply_changed(dev->battery);
@@ -559,7 +629,17 @@  static void hidinput_cleanup_battery(struct hid_device *dev)
 {
 }
 
-static void hidinput_update_battery(struct hid_device *dev, int value)
+static void hidinput_update_battery_capacity(struct hid_device *dev,
+					     __s32 value)
+{
+}
+
+static void hidinput_update_battery_serial(struct hid_device *dev,
+					   const __s32 *values, int bits)
+{
+}
+
+static void hidinput_flush_battery(struct hid_device *dev)
 {
 }
 #endif	/* CONFIG_HID_BATTERY_STRENGTH */
@@ -1273,7 +1353,9 @@  static void hidinput_handle_scroll(struct hid_usage *usage,
 	input_event(input, EV_REL, usage->code, hi_res);
 }
 
-void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, const __s32 *values, unsigned value_count)
+void hidinput_hid_event(struct hid_device *hid, struct hid_field *field,
+			struct hid_usage *usage, const __s32 *values,
+			unsigned value_count)
 {
 	struct input_dev *input;
 	unsigned *quirks = &hid->quirks;
@@ -1290,9 +1372,13 @@  void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
 		return;
 
 	if (usage->type == EV_PWR) {
-		hidinput_update_battery(hid, value);
+		hidinput_update_battery_capacity(hid, value);
 		return;
 	}
+	if (usage->type == EV_MSC && usage->code == MSC_SERIAL) {
+		hidinput_update_battery_serial(hid, values, field->report_size);
+		/* fall through to normal standard MSC_SERIAL processing */
+	}
 
 	if (!field->hidinput)
 		return;
@@ -1423,6 +1509,8 @@  void hidinput_report_event(struct hid_device *hid, struct hid_report *report)
 {
 	struct hid_input *hidinput;
 
+	hidinput_flush_battery(hid);
+
 	if (hid->quirks & HID_QUIRK_NO_INPUT_SYNC)
 		return;
 
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 8494b1995b10b..d5585a99b5ad9 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -587,8 +587,13 @@  struct hid_device {							/* device report descriptor */
 	__s32 battery_max;
 	__s32 battery_report_type;
 	__s32 battery_report_id;
+	__u64 battery_serial_number;
+	int battery_serial_number_bits;					/* Actual number of bits in SN */
+	char battery_serial_number_str[20];				/* Space for "DG-" + max 16 hex digits */
 	enum hid_battery_status battery_status;
 	bool battery_avoid_query;
+	bool battery_changed;
+	bool battery_reported;
 	ktime_t battery_ratelimit_time;
 #endif