File 0007-hid-add-asus-vivobook-s-15.patch of Package linux-qcom-laptops

From 7223262adbb7965eb119201d3aaf14dcb33ff768 Mon Sep 17 00:00:00 2001
From: binarycraft007 <elliot.huang.signed@gmail.com>
Date: Sun, 21 Sep 2025 18:34:18 +0800
Subject: [PATCH 7/9] hid: add asus vivobook s 15

---
 drivers/hid/Kconfig                 |  14 ++
 drivers/hid/Makefile                |   1 +
 drivers/hid/hid-asus-vivobook-s15.c | 360 ++++++++++++++++++++++++++++
 3 files changed, 375 insertions(+)
 create mode 100644 drivers/hid/hid-asus-vivobook-s15.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 04420a713..e7386acfe 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -202,6 +202,20 @@ config HID_ASUS
 	- GL553V series
 	- GL753V series
 
+config HID_ASUS_VIVOBOOK_S15
+	tristate "ASUS Vivobook S15 S5507 keyboard"
+	help
+	  Say Y or M here if you have the ASUS Vivobook S15 S5507 laptop.
+
+	  This driver adds support for the special function keys on the
+	  built-in I2C keyboard, including backlight control, Fn-Lock, and
+	  remapping of brightness keys.
+
+	  Without this driver, the special keys will not work.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called hid-asus-vivobook-s15.
+
 config HID_AUREAL
 	tristate "Aureal"
 	help
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 361a7daed..7f287d1d7 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_HID_APPLETB_BL)	+= hid-appletb-bl.o
 obj-$(CONFIG_HID_APPLETB_KBD)	+= hid-appletb-kbd.o
 obj-$(CONFIG_HID_CREATIVE_SB0540)	+= hid-creative-sb0540.o
 obj-$(CONFIG_HID_ASUS)		+= hid-asus.o
+obj-$(CONFIG_HID_ASUS_VIVOBOOK_S15)	+= hid-asus-vivobook-s15.o
 obj-$(CONFIG_HID_AUREAL)	+= hid-aureal.o
 obj-$(CONFIG_HID_BELKIN)	+= hid-belkin.o
 obj-$(CONFIG_HID_BETOP_FF)	+= hid-betopff.o
diff --git a/drivers/hid/hid-asus-vivobook-s15.c b/drivers/hid/hid-asus-vivobook-s15.c
new file mode 100644
index 000000000..cab1c38c2
--- /dev/null
+++ b/drivers/hid/hid-asus-vivobook-s15.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HID driver for ASUS Vivobook S15 S5507 Keyboard
+ *
+ * Copyright (c) 2024 Elliot Huang (keep.it.sns@gmail.com)
+ *
+ * This driver handles special function keys for keyboard backlight control,
+ * Fn-Lock, and re-maps certain vendor-specific keys to standard consumer
+ * control usages (e.g., display brightness). It also handles suspend and
+ * resume by turning off the backlight on suspend and restoring the full
+ * device state on resume.
+ */
+
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/atomic.h>
+#include <linux/leds.h>
+
+#define VID_ASUS 0x0B05
+#define PID_VIVOBOOK_S15_S5507_KEYBOARD 0x4543
+
+/*
+ * The device sends vendor-specific reports with ID 0x5A for special keys.
+ * We intercept these and either schedule work or remap them to standard
+ * reports.
+ */
+#define ASUS_VENDOR_REPORT_ID 0x5A
+
+/* Key codes from vendor report */
+#define VENDOR_KEY_FNLOCK 0x4E
+#define VENDOR_KEY_BACKLIGHT 0xC7
+#define VENDOR_KEY_BRIGHTNESS_DOWN 0x10
+#define VENDOR_KEY_BRIGHTNESS_UP 0x20
+#define VENDOR_KEY_RELEASE 0x00
+
+/*
+ * We remap brightness keys to a custom report with ID 0x37, which the
+ * device's report descriptor seems to handle as consumer control.
+ */
+#define CONSUMER_REPORT_ID 0x37
+#define CONSUMER_USAGE_BRIGHTNESS_DOWN 0x70
+#define CONSUMER_USAGE_BRIGHTNESS_UP 0x6F
+#define CONSUMER_USAGE_RELEASE 0x00
+
+enum work_type {
+	WORK_TYPE_INIT,
+	WORK_TYPE_BACKLIGHT,
+	WORK_TYPE_FNLOCK,
+};
+
+struct vivobook_s15_kbd {
+	struct hid_device *hdev;
+	struct work_struct work;
+
+	/* Bitmask of pending work items */
+	atomic_t pending_work;
+
+	/* Device state */
+	u8 backlight_brightness;
+	bool fn_lock_state;
+
+	/* State for remapping key-up events */
+	bool remap_next_keyup;
+
+	/* LED Class device for keyboard backlight */
+	struct led_classdev kbd_backlight;
+};
+
+/*
+ * The following functions send HID_FEATURE_REPORT commands to the device.
+ * They return 0 on success, or a negative error code on failure.
+ */
+static int set_init_unk_1(struct hid_device *hdev)
+{
+	u8 *buf;
+	int ret;
+
+	buf = kzalloc(64, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	buf[0] = 0x5A;
+	buf[1] = 0x05;
+	buf[2] = 0x20;
+	buf[3] = 0x31;
+	buf[4] = 0x00;
+	buf[5] = 0x08;
+
+	ret = hid_hw_raw_request(hdev, 0x5A, buf, 64, HID_FEATURE_REPORT,
+				 HID_REQ_SET_REPORT);
+	kfree(buf);
+	return ret < 0 ? ret : 0;
+}
+
+static int set_init_unk_2(struct hid_device *hdev)
+{
+	u8 *buf;
+	int ret;
+
+	buf = kzalloc(64, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	buf[0] = 0x5A;
+	buf[1] = 0xD0;
+	buf[2] = 0x8F;
+	buf[3] = 0x01;
+
+	ret = hid_hw_raw_request(hdev, 0x5A, buf, 64, HID_FEATURE_REPORT,
+				 HID_REQ_SET_REPORT);
+	kfree(buf);
+	return ret < 0 ? ret : 0;
+}
+
+static int set_brightness(struct hid_device *hdev, u8 brightness)
+{
+	u8 *buf;
+	int ret;
+
+	buf = kzalloc(64, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	buf[0] = 0x5A;
+	buf[1] = 0xBA;
+	buf[2] = 0xC5;
+	buf[3] = 0xC4;
+	buf[4] = brightness;
+
+	ret = hid_hw_raw_request(hdev, 0x5A, buf, 64, HID_FEATURE_REPORT,
+				 HID_REQ_SET_REPORT);
+	kfree(buf);
+	return ret < 0 ? ret : 0;
+}
+
+static int set_fn_lock(struct hid_device *hdev, bool fn_lock)
+{
+	u8 *buf;
+	int ret;
+
+	buf = kzalloc(64, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	buf[0] = 0x5A;
+	buf[1] = 0xD0;
+	buf[2] = 0x4E;
+	buf[3] = fn_lock;
+
+	ret = hid_hw_raw_request(hdev, 0x5A, buf, 64, HID_FEATURE_REPORT,
+				 HID_REQ_SET_REPORT);
+	kfree(buf);
+	return ret < 0 ? ret : 0;
+}
+
+static int vivobook_s15_kbd_bl_set(struct led_classdev *led_cdev,
+				   enum led_brightness brightness)
+{
+	struct vivobook_s15_kbd *priv =
+		container_of(led_cdev, struct vivobook_s15_kbd, kbd_backlight);
+	int ret;
+
+	ret = set_brightness(priv->hdev, brightness);
+	if (ret == 0)
+		priv->backlight_brightness = brightness;
+	return ret;
+}
+
+static enum led_brightness
+vivobook_s15_kbd_bl_get(struct led_classdev *led_cdev)
+{
+	struct vivobook_s15_kbd *priv =
+		container_of(led_cdev, struct vivobook_s15_kbd, kbd_backlight);
+	return priv->backlight_brightness;
+}
+
+static void vivobook_s15_work_handler(struct work_struct *work)
+{
+	struct vivobook_s15_kbd *priv =
+		container_of(work, struct vivobook_s15_kbd, work);
+	struct hid_device *hdev = priv->hdev;
+	int ret;
+
+	if (test_and_clear_bit(WORK_TYPE_BACKLIGHT,
+			       (void *)&priv->pending_work)) {
+		u8 new_brightness = (priv->backlight_brightness + 1);
+		if (new_brightness > 3)
+			new_brightness = 0;
+		led_set_brightness_sync(&priv->kbd_backlight, new_brightness);
+	}
+
+	if (test_and_clear_bit(WORK_TYPE_FNLOCK, (void *)&priv->pending_work)) {
+		bool new_fn_lock = !priv->fn_lock_state;
+
+		ret = set_fn_lock(hdev, new_fn_lock);
+		if (ret == 0)
+			priv->fn_lock_state = new_fn_lock;
+		else
+			dev_err(&hdev->dev, "Failed to set Fn-Lock: %d\n", ret);
+	}
+
+	if (test_and_clear_bit(WORK_TYPE_INIT, (void *)&priv->pending_work)) {
+		dev_info(&hdev->dev, "Initializing keyboard\n");
+		set_init_unk_1(hdev);
+		set_init_unk_2(hdev);
+		led_set_brightness_sync(&priv->kbd_backlight,
+					priv->backlight_brightness);
+		set_fn_lock(hdev, priv->fn_lock_state);
+	}
+}
+
+static void schedule_vivobook_work(struct vivobook_s15_kbd *priv,
+				   enum work_type type)
+{
+	set_bit(type, (void *)&priv->pending_work);
+	schedule_work(&priv->work);
+}
+
+static int remap_to_consumer_control(struct vivobook_s15_kbd *priv, u8 *data,
+				     u8 code)
+{
+	data[0] = CONSUMER_REPORT_ID;
+	data[1] = code;
+	data[2] = 0x00;
+
+	priv->remap_next_keyup = (code != CONSUMER_USAGE_RELEASE);
+	return 3; /* New report size is 3 bytes */
+}
+
+static int vivobook_s15_raw_event(struct hid_device *hdev,
+				  struct hid_report *report, u8 *data, int size)
+{
+	struct vivobook_s15_kbd *priv = hid_get_drvdata(hdev);
+
+	/* We are only interested in vendor-defined input reports */
+	if (report->type != HID_INPUT_REPORT ||
+	    data[0] != ASUS_VENDOR_REPORT_ID)
+		return 0;
+
+	/* The vendor report for special keys is 6 bytes long */
+	if (size != 6)
+		return 0;
+
+	switch (data[1]) {
+	case VENDOR_KEY_FNLOCK:
+		schedule_vivobook_work(priv, WORK_TYPE_FNLOCK);
+		break;
+	case VENDOR_KEY_BACKLIGHT:
+		schedule_vivobook_work(priv, WORK_TYPE_BACKLIGHT);
+		break;
+	case VENDOR_KEY_BRIGHTNESS_DOWN:
+		return remap_to_consumer_control(
+			priv, data, CONSUMER_USAGE_BRIGHTNESS_DOWN);
+	case VENDOR_KEY_BRIGHTNESS_UP:
+		return remap_to_consumer_control(priv, data,
+						 CONSUMER_USAGE_BRIGHTNESS_UP);
+	case VENDOR_KEY_RELEASE:
+		if (priv->remap_next_keyup)
+			return remap_to_consumer_control(
+				priv, data, CONSUMER_USAGE_RELEASE);
+		break;
+	default:
+		/* Let unhandled vendor keys pass through if needed, or drop them */
+		break;
+	}
+
+	/* Drop the original vendor report, as we have handled it. */
+	return 0;
+}
+
+static void vivobook_s15_stop_hw(void *data)
+{
+	struct hid_device *hdev = data;
+
+	hid_hw_stop(hdev);
+}
+
+static int vivobook_s15_probe(struct hid_device *hdev,
+			      const struct hid_device_id *id)
+{
+	struct vivobook_s15_kbd *priv;
+	int ret;
+
+	priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->hdev = hdev;
+	hid_set_drvdata(hdev, priv);
+
+	/* Set initial default state */
+	priv->backlight_brightness = 1;
+	priv->fn_lock_state = false;
+	priv->remap_next_keyup = false;
+
+	atomic_set(&priv->pending_work, 0);
+	INIT_WORK(&priv->work, vivobook_s15_work_handler);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		dev_err(&hdev->dev, "hid_parse failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		dev_err(&hdev->dev, "hid_hw_start failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_add_action_or_reset(&hdev->dev, vivobook_s15_stop_hw, hdev);
+	if (ret)
+		return ret;
+
+	priv->kbd_backlight.name = "asus::kbd_backlight";
+	priv->kbd_backlight.max_brightness = 3;
+	priv->kbd_backlight.brightness_set_blocking = vivobook_s15_kbd_bl_set;
+	priv->kbd_backlight.brightness_get = vivobook_s15_kbd_bl_get;
+	priv->kbd_backlight.flags = LED_BRIGHT_HW_CHANGED;
+
+	ret = devm_led_classdev_register(&hdev->dev, &priv->kbd_backlight);
+	if (ret) {
+		dev_err(&hdev->dev,
+			"Failed to register kbd_backlight LED: %d\n", ret);
+		return ret;
+	}
+
+	/* Schedule work to perform initial setup */
+	schedule_vivobook_work(priv, WORK_TYPE_INIT);
+
+	return 0;
+}
+
+static void vivobook_s15_remove(struct hid_device *hdev)
+{
+	struct vivobook_s15_kbd *priv = hid_get_drvdata(hdev);
+
+	cancel_work_sync(&priv->work);
+	/* hid_hw_stop() is handled by devm action */
+}
+
+static const struct hid_device_id vivobook_s15_kbd_id_table[] = {
+	{ HID_I2C_DEVICE(VID_ASUS, PID_VIVOBOOK_S15_S5507_KEYBOARD) },
+	{} /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(hid, vivobook_s15_kbd_id_table);
+
+static struct hid_driver vivobook_s15_kbd_driver = {
+	.name = "hid-asus-vivobook-s15",
+	.id_table = vivobook_s15_kbd_id_table,
+	.probe = vivobook_s15_probe,
+	.remove = vivobook_s15_remove,
+	.raw_event = vivobook_s15_raw_event,
+};
+module_hid_driver(vivobook_s15_kbd_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Elliot Huang <keep.it.sns@gmail.com>");
+MODULE_DESCRIPTION("HID driver for ASUS Vivobook S15 S5507 Keyboard");
-- 
2.53.0

openSUSE Build Service is sponsored by