From nobody Tue Oct 7 07:06:10 2025 Received: from mout.gmx.net (mout.gmx.net [212.227.15.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D1B4027585D; Sat, 12 Jul 2025 11:23:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=212.227.15.19 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752319436; cv=none; b=MO5CvLLallwg8Xynt4fMiTsOrCAFwGSfVIUqUwCq+SR+gis6U4ghvUkIS+dpUsiC9MNfib6mnUEwVlVNtOmnxzNoXCiskkAZvVPwUpIWbPuP0CmxsC3UwptOrGxh3dZgS/40AmFB2a8C861eTxs95OvtFuahVjR3opsnCPSCtDE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752319436; c=relaxed/simple; bh=H7MmkYzTY9A75Z8n4KYCE4QBd+q/VGejpgGJtTUUnhc=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=JQuuCqpBLTxVVUpzwmV9oJ1BkrvV6vn0EkJA/yJcfySAtAHFHrV57AwejWGiR9FJz/g6dB7Mgjncw7BHJoyS3Ow+czbToirXvjkckZD8JpAbI3GWxpJzbM/WGYB988VXJayphn3xKoc3WNqX4j64PG41NIw4ku3u1TB+nBHzmz8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=gmx.de; spf=pass smtp.mailfrom=gmx.de; dkim=pass (2048-bit key) header.d=gmx.de header.i=w_armin@gmx.de header.b=G13Ue76D; arc=none smtp.client-ip=212.227.15.19 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=gmx.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmx.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmx.de header.i=w_armin@gmx.de header.b="G13Ue76D" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmx.de; s=s31663417; t=1752319412; x=1752924212; i=w_armin@gmx.de; bh=MtmYz03uy7tTpkaH0HAdObXnsYy5CPmZ83lSU10NRrI=; h=X-UI-Sender-Class:From:To:Cc:Subject:Date:Message-Id:In-Reply-To: References:MIME-Version:Content-Type:Content-Transfer-Encoding:cc: content-transfer-encoding:content-type:date:from:message-id: mime-version:reply-to:subject:to; b=G13Ue76Dm3psl2M85r/lk/zLYt96xtsz1KgLIKlf3K2xtRxmdliy06hc/oyR0bCD xRO1JS1IbEX8BiDhfwtmHZS37rcdhN8Oh01v9APTikMS19jkYha4jkP3JuznCdf2C wMWht5WsZoOWKoiO4ikGs7Xt9utJ1lmZQHE4qHABH8W5s+rXj7zKLaiGRrDkt1feR 7be9T6SVB6XjTpLl65byP6Aw9tZiOiYfnto1G/vhPpCJAEjM1+ATt5J2lAFRVwyUI 0xN7fW2p3k6ZWDYOxnLQa/2DQBlew6iAc69NY3O9KZyesCwIdOIOx3pUx0Q2uGevY j1RSBmLm6NtwSMaGuQ== X-UI-Sender-Class: 724b4f7f-cbec-4199-ad4e-598c01a50d3a Received: from mx-amd-b650.fritz.box ([87.177.78.219]) by mail.gmx.net (mrgmx005 [212.227.17.190]) with ESMTPSA (Nemesis) id 1M3DJv-1ue8AJ097t-00238l; Sat, 12 Jul 2025 13:23:30 +0200 From: Armin Wolf To: ilpo.jarvinen@linux.intel.com, hdegoede@redhat.com, chumuzero@gmail.com, corbet@lwn.net, cs@tuxedo.de, wse@tuxedocomputers.com, ggo@tuxedocomputers.com Cc: linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, platform-driver-x86@vger.kernel.org, rdunlap@infradead.org, alok.a.tiwari@oracle.com, linux-leds@vger.kernel.org, lee@kernel.org, pobrn@protonmail.com Subject: [PATCH 1/3] platform/x86: Add Uniwill WMI driver Date: Sat, 12 Jul 2025 13:23:08 +0200 Message-Id: <20250712112310.19964-2-W_Armin@gmx.de> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250712112310.19964-1-W_Armin@gmx.de> References: <20250712112310.19964-1-W_Armin@gmx.de> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Provags-ID: V03:K1:BsFTzSVQ/fn36Ax1Eq6dAKDgW5qVfg94v9xA8ScNXXrVo5e9fo8 BI6ITwr07dHaltQqc6ywQPoPp+vO2aAYL56TpeG8BF1z3wTuT0k+JGPD4hk8bvphzkc+bJv gvWqKwQnwRu6vurevxeasqJ4sldOsK8fgLRHCtDqNTK7atih6jJgHr0i3UswXSrSGV6GRc2 452arKz8+w6uUy2iAYbKg== X-Spam-Flag: NO UI-OutboundReport: notjunk:1;M01:P0:frjYrxVwh9Q=;qfiZfnxTpJFbkaDc6RA/z31zaWx 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 wvwmo0IobvuMNw= Content-Type: text/plain; charset="utf-8" Add a new driver for handling WMI events on Uniwill laptops. The driver sadly cannot use the WMI GUID for autoloading since Uniwill just copied it from the Windows driver example. The driver is reverse-engineered based on the following information: - https://github.com/pobrn/qc71_laptop - https://gitlab.com/tuxedocomputers/development/packages/tuxedo-drivers - various OEM software Reported-by: cyear Closes: https://github.com/lm-sensors/lm-sensors/issues/508 Closes: https://github.com/Wer-Wolf/uniwill-laptop/issues/3 Signed-off-by: Armin Wolf --- Documentation/wmi/devices/uniwill-wmi.rst | 52 ++++++ MAINTAINERS | 8 + drivers/platform/x86/Kconfig | 2 + drivers/platform/x86/Makefile | 3 + drivers/platform/x86/uniwill/Kconfig | 32 ++++ drivers/platform/x86/uniwill/Makefile | 7 + drivers/platform/x86/uniwill/uniwill-wmi.c | 185 +++++++++++++++++++++ drivers/platform/x86/uniwill/uniwill-wmi.h | 122 ++++++++++++++ 8 files changed, 411 insertions(+) create mode 100644 Documentation/wmi/devices/uniwill-wmi.rst create mode 100644 drivers/platform/x86/uniwill/Kconfig create mode 100644 drivers/platform/x86/uniwill/Makefile create mode 100644 drivers/platform/x86/uniwill/uniwill-wmi.c create mode 100644 drivers/platform/x86/uniwill/uniwill-wmi.h diff --git a/Documentation/wmi/devices/uniwill-wmi.rst b/Documentation/wmi/= devices/uniwill-wmi.rst new file mode 100644 index 000000000000..8de86c910bb9 --- /dev/null +++ b/Documentation/wmi/devices/uniwill-wmi.rst @@ -0,0 +1,52 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Uniwill WMI event driver (uniwill-wmi) +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Introduction +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Many notebooks manufactured by Uniwill (either directly or as ODM) provide= a WMI-based +event interface for various platform events like hotkeys. This interface i= s used by the +``uniwill-wmi`` driver to react to hotkey presses. + +WMI interface description +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D + +The WMI interface description can be decoded from the embedded binary MOF = (bmof) +data using the `bmfdec `_ utility: + +:: + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), + Description("Class containing event generated ULong data"), + guid("{ABBC0F72-8EA1-11d1-00A0-C90629100000}")] + class AcpiTest_EventULong : WmiEvent { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, write, Description("ULong Data")] uint32 ULong; + }; + +Most of the WMI-related code was copied from the Windows driver samples, w= hich unfortunately means +that the WMI-GUID is not unique. This makes the WMI-GUID unusable for auto= loading. + +WMI event data +-------------- + +The WMI event data contains a single 32-bit value which is used to indicat= e various platform events. +Many non-hotkey events are not directly consumed by the driver itself, but= are instead handled by +the ``uniwill-laptop`` driver. + +Reverse-Engineering the Uniwill WMI event interface +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D + +The driver logs debug messages when receiving a WMI event. Thus enabling d= ebug messages will be +useful for finding unknown event codes. + +Special thanks go to github user `pobrn` which developed the +`qc71_laptop `_ driver on which this= driver is partly based. +The same is true for Tuxedo Computers, which developed the +`tuxedo-drivers `_ package +which also served as a foundation for this driver. diff --git a/MAINTAINERS b/MAINTAINERS index e8f3dc93a569..6c5879bd2ba2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25490,6 +25490,14 @@ L: linux-scsi@vger.kernel.org S: Maintained F: drivers/ufs/host/ufs-renesas.c =20 +UNIWILL WMI DRIVER +M: Armin Wolf +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: Documentation/wmi/devices/uniwill-wmi.rst +F: drivers/platform/x86/uniwill/uniwill-wmi.c +F: drivers/platform/x86/uniwill/uniwill-wmi.h + UNSORTED BLOCK IMAGES (UBI) M: Richard Weinberger R: Zhihao Cheng diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index e5cbd58a99f3..59ce7247dae6 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -65,6 +65,8 @@ config HUAWEI_WMI To compile this driver as a module, choose M here: the module will be called huawei-wmi. =20 +source "drivers/platform/x86/uniwill/Kconfig" + config UV_SYSFS tristate "Sysfs structure for UV systems" depends on X86_UV diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index abbc2644ff6d..d0aac46bebf9 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -112,6 +112,9 @@ obj-$(CONFIG_TOSHIBA_WMI) +=3D toshiba-wmi.o # before toshiba_acpi initializes obj-$(CONFIG_ACPI_TOSHIBA) +=3D toshiba_acpi.o =20 +# Uniwill +obj-y +=3D uniwill/ + # Inspur obj-$(CONFIG_INSPUR_PLATFORM_PROFILE) +=3D inspur_platform_profile.o =20 diff --git a/drivers/platform/x86/uniwill/Kconfig b/drivers/platform/x86/un= iwill/Kconfig new file mode 100644 index 000000000000..7571b30edb11 --- /dev/null +++ b/drivers/platform/x86/uniwill/Kconfig @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Uniwill X86 Platform Specific Drivers +# + +menuconfig X86_PLATFORM_DRIVERS_UNIWILL + bool "Uniwill X86 Platform Specific Device Drivers" + depends on X86_PLATFORM_DEVICES + help + Say Y here to see options for device drivers for various + Uniwill x86 platforms, including many OEM laptops originally + manufactured by Uniwill. + This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if X86_PLATFORM_DRIVERS_UNIWILL + +config UNIWILL_WMI + tristate "Uniwill WMI Event Driver" + default m + depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + help + This driver adds support for various hotkey events on Uniwill laptops, + like rfkill and other special buttons. It also supports many OEM laptops + originally manufactured by Uniwill. + + If you have such a laptop, say Y or M here. + +endif diff --git a/drivers/platform/x86/uniwill/Makefile b/drivers/platform/x86/u= niwill/Makefile new file mode 100644 index 000000000000..a5a300be63f3 --- /dev/null +++ b/drivers/platform/x86/uniwill/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Makefile for linux/drivers/platform/x86/uniwill +# Uniwill X86 Platform Specific Drivers +# + +obj-$(CONFIG_UNIWILL_WMI) +=3D uniwill-wmi.o diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.c b/drivers/platform/= x86/uniwill/uniwill-wmi.c new file mode 100644 index 000000000000..f38e4bbcd8b6 --- /dev/null +++ b/drivers/platform/x86/uniwill/uniwill-wmi.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Linux hotkey driver for Uniwill notebooks. + * + * Special thanks go to P=C5=91cze Barnab=C3=A1s, Christoffer Sandberg and= Werner Sembach + * for supporting the development of this driver either through prior work= or + * by answering questions regarding the underlying WMI interface. + * + * Copyright (C) 2025 Armin Wolf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uniwill-wmi.h" + +#define DRIVER_NAME "uniwill-wmi" +#define UNIWILL_EVENT_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000" + +struct uniwill_wmi_data { + struct mutex input_lock; /* Protects input sequence during notify */ + struct input_dev *input_device; +}; + +static BLOCKING_NOTIFIER_HEAD(uniwill_wmi_chain_head); + +static const struct key_entry uniwill_wmi_keymap[] =3D { + /* Reported via keyboard controller */ + { KE_IGNORE, UNIWILL_OSD_CAPSLOCK, { KEY_CAPSLOCK }}, + { KE_IGNORE, UNIWILL_OSD_NUMLOCK, { KEY_NUMLOCK }}, + + /* Reported when the user locks/unlocks the super key */ + { KE_IGNORE, UNIWILL_OSD_SUPER_KEY_LOCK_ENABLE, { KEY_UNKNOWN }}, + { KE_IGNORE, UNIWILL_OSD_SUPER_KEY_LOCK_DISABLE, { KEY_UNKNOWN }}, + + /* Reported in manual mode when toggling the airplane mode status */ + { KE_KEY, UNIWILL_OSD_RFKILL, { KEY_RFKILL }}, + + /* Reported when user wants to cycle the platform profile */ + { KE_IGNORE, UNIWILL_OSD_PERFORMANCE_MODE_TOGGLE, { KEY_UNKNOWN }}, + + /* Reported when the user wants to adjust the brightness of the keyboard = */ + { KE_KEY, UNIWILL_OSD_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }}, + { KE_KEY, UNIWILL_OSD_KBDILLUMUP, { KEY_KBDILLUMUP }}, + + /* Reported when the user wants to toggle the microphone mute status */ + { KE_KEY, UNIWILL_OSD_MIC_MUTE, { KEY_MICMUTE }}, + + /* Reported when the user locks/unlocks the Fn key */ + { KE_IGNORE, UNIWILL_OSD_FN_LOCK, { KEY_FN_ESC }}, + + /* Reported when the user wants to toggle the brightness of the keyboard = */ + { KE_KEY, UNIWILL_OSD_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }}, + + /* FIXME: find out the exact meaning of those events */ + { KE_IGNORE, UNIWILL_OSD_BAT_CHARGE_FULL_24_H, { KEY_UNKNOWN }}, + { KE_IGNORE, UNIWILL_OSD_BAT_ERM_UPDATE, { KEY_UNKNOWN }}, + + /* Reported when the user wants to toggle the benchmark mode status */ + { KE_IGNORE, UNIWILL_OSD_BENCHMARK_MODE_TOGGLE, { KEY_UNKNOWN }}, + + { KE_END } +}; + +int uniwill_wmi_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&uniwill_wmi_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(uniwill_wmi_register_notifier, "UNIWILL"); + +int uniwill_wmi_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&uniwill_wmi_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(uniwill_wmi_unregister_notifier, "UNIWILL"); + +static void devm_uniwill_wmi_unregister_notifier(void *data) +{ + struct notifier_block *nb =3D data; + + uniwill_wmi_unregister_notifier(nb); +} + +int devm_uniwill_wmi_register_notifier(struct device *dev, struct notifier= _block *nb) +{ + int ret; + + ret =3D uniwill_wmi_register_notifier(nb); + if (ret < 0) + return ret; + + return devm_add_action_or_reset(dev, devm_uniwill_wmi_unregister_notifier= , nb); +} +EXPORT_SYMBOL_NS_GPL(devm_uniwill_wmi_register_notifier, "UNIWILL"); + +static void uniwill_wmi_notify(struct wmi_device *wdev, union acpi_object = *obj) +{ + struct uniwill_wmi_data *data =3D dev_get_drvdata(&wdev->dev); + u32 value; + int ret; + + if (obj->type !=3D ACPI_TYPE_INTEGER) + return; + + value =3D obj->integer.value; + + dev_dbg(&wdev->dev, "Received WMI event %u\n", value); + + ret =3D blocking_notifier_call_chain(&uniwill_wmi_chain_head, value, NULL= ); + if (ret =3D=3D NOTIFY_BAD) + return; + + mutex_lock(&data->input_lock); + sparse_keymap_report_event(data->input_device, value, 1, true); + mutex_unlock(&data->input_lock); +} + +static int uniwill_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct uniwill_wmi_data *data; + int ret; + + data =3D devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret =3D devm_mutex_init(&wdev->dev, &data->input_lock); + if (ret < 0) + return ret; + + dev_set_drvdata(&wdev->dev, data); + + data->input_device =3D devm_input_allocate_device(&wdev->dev); + if (!data->input_device) + return -ENOMEM; + + ret =3D sparse_keymap_setup(data->input_device, uniwill_wmi_keymap, NULL); + if (ret < 0) + return ret; + + data->input_device->name =3D "Uniwill WMI hotkeys"; + data->input_device->phys =3D "wmi/input0"; + data->input_device->id.bustype =3D BUS_HOST; + + return input_register_device(data->input_device); +} + +/* + * We cannot fully trust this GUID since Uniwill just copied the WMI GUID + * from the Windows driver example, and others probably did the same. + * + * Because of this we cannot use this WMI GUID for autoloading. + */ +static const struct wmi_device_id uniwill_wmi_id_table[] =3D { + { UNIWILL_EVENT_GUID, NULL }, + { } +}; + +static struct wmi_driver uniwill_wmi_driver =3D { + .driver =3D { + .name =3D DRIVER_NAME, + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table =3D uniwill_wmi_id_table, + .probe =3D uniwill_wmi_probe, + .notify =3D uniwill_wmi_notify, + .no_singleton =3D true, +}; +module_wmi_driver(uniwill_wmi_driver); + +MODULE_AUTHOR("Armin Wolf "); +MODULE_DESCRIPTION("Uniwill notebook hotkey driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/= x86/uniwill/uniwill-wmi.h new file mode 100644 index 000000000000..f6dd610d49eb --- /dev/null +++ b/drivers/platform/x86/uniwill/uniwill-wmi.h @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Linux hotkey driver for Uniwill notebooks. + * + * Copyright (C) 2025 Armin Wolf + */ + +#ifndef UNIWILL_WMI_H +#define UNIWILL_WMI_H + +#define UNIWILL_OSD_CAPSLOCK 0x01 +#define UNIWILL_OSD_NUMLOCK 0x02 +#define UNIWILL_OSD_SCROLLLOCK 0x03 + +#define UNIWILL_OSD_TOUCHPAD_ON 0x04 +#define UNIWILL_OSD_TOUCHPAD_OFF 0x05 + +#define UNIWILL_OSD_SILENT_MODE_ON 0x06 +#define UNIWILL_OSD_SILENT_MODE_OFF 0x07 + +#define UNIWILL_OSD_WLAN_ON 0x08 +#define UNIWILL_OSD_WLAN_OFF 0x09 + +#define UNIWILL_OSD_WIMAX_ON 0x0A +#define UNIWILL_OSD_WIMAX_OFF 0x0B + +#define UNIWILL_OSD_BLUETOOTH_ON 0x0C +#define UNIWILL_OSD_BLUETOOTH_OFF 0x0D + +#define UNIWILL_OSD_RF_ON 0x0E +#define UNIWILL_OSD_RF_OFF 0x0F + +#define UNIWILL_OSD_3G_ON 0x10 +#define UNIWILL_OSD_3G_OFF 0x11 + +#define UNIWILL_OSD_WEBCAM_ON 0x12 +#define UNIWILL_OSD_WEBCAM_OFF 0x13 + +#define UNIWILL_OSD_BRIGHTNESSUP 0x14 +#define UNIWILL_OSD_BRIGHTNESSDOWN 0x15 + +#define UNIWILL_OSD_RADIOON 0x1A +#define UNIWILL_OSD_RADIOOFF 0x1B + +#define UNIWILL_OSD_POWERSAVE_ON 0x31 +#define UNIWILL_OSD_POWERSAVE_OFF 0x32 + +#define UNIWILL_OSD_MENU 0x34 + +#define UNIWILL_OSD_MUTE 0x35 +#define UNIWILL_OSD_VOLUMEDOWN 0x36 +#define UNIWILL_OSD_VOLUMEUP 0x37 + +#define UNIWILL_OSD_MENU_2 0x38 + +#define UNIWILL_OSD_LIGHTBAR_ON 0x39 +#define UNIWILL_OSD_LIGHTBAR_OFF 0x3A + +#define UNIWILL_OSD_KB_LED_LEVEL0 0x3B +#define UNIWILL_OSD_KB_LED_LEVEL1 0x3C +#define UNIWILL_OSD_KB_LED_LEVEL2 0x3D +#define UNIWILL_OSD_KB_LED_LEVEL3 0x3E +#define UNIWILL_OSD_KB_LED_LEVEL4 0x3F + +#define UNIWILL_OSD_SUPER_KEY_LOCK_ENABLE 0x40 +#define UNIWILL_OSD_SUPER_KEY_LOCK_DISABLE 0x41 + +#define UNIWILL_OSD_MENU_JP 0x42 + +#define UNIWILL_OSD_CAMERA_ON 0x90 +#define UNIWILL_OSD_CAMERA_OFF 0x91 + +#define UNIWILL_OSD_RFKILL 0xA4 + +#define UNIWILL_OSD_SUPER_KEY_LOCK_CHANGED 0xA5 + +#define UNIWILL_OSD_LIGHTBAR_STATE_CHANGED 0xA6 + +#define UNIWILL_OSD_FAN_BOOST_STATE_CHANGED 0xA7 + +#define UNIWILL_OSD_LCD_SW 0xA9 + +#define UNIWILL_OSD_FAN_OVERTEMP 0xAA + +#define UNIWILL_OSD_DC_ADAPTER_CHANGED 0xAB + +#define UNIWILL_OSD_BAT_HP_OFF 0xAC + +#define UNIWILL_OSD_FAN_DOWN_TEMP 0xAD + +#define UNIWILL_OSD_BATTERY_ALERT 0xAE + +#define UNIWILL_OSD_TIMAP_HAIERLB_SW 0xAF + +#define UNIWILL_OSD_PERFORMANCE_MODE_TOGGLE 0xB0 + +#define UNIWILL_OSD_KBDILLUMDOWN 0xB1 +#define UNIWILL_OSD_KBDILLUMUP 0xB2 + +#define UNIWILL_OSD_BACKLIGHT_LEVEL_CHANGE 0xB3 +#define UNIWILL_OSD_BACKLIGHT_POWER_CHANGE 0xB4 + +#define UNIWILL_OSD_MIC_MUTE 0xB7 + +#define UNIWILL_OSD_FN_LOCK 0xB8 +#define UNIWILL_OSD_KBDILLUMTOGGLE 0xB9 + +#define UNIWILL_OSD_BAT_CHARGE_FULL_24_H 0xBE + +#define UNIWILL_OSD_BAT_ERM_UPDATE 0xBF + +#define UNIWILL_OSD_BENCHMARK_MODE_TOGGLE 0xC0 + +#define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0 + +struct notifier_block; + +int uniwill_wmi_register_notifier(struct notifier_block *nb); +int uniwill_wmi_unregister_notifier(struct notifier_block *nb); +int devm_uniwill_wmi_register_notifier(struct device *dev, struct notifier= _block *nb); + +#endif /* UNIWILL_WMI_H */ --=20 2.39.5 From nobody Tue Oct 7 07:06:10 2025 Received: from mout.gmx.net (mout.gmx.net [212.227.15.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 637201FBEB9; Sat, 12 Jul 2025 11:23:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=212.227.15.19 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752319425; cv=none; b=th81zTFZGr7xstcontWAAjtBKSRk9Q/g0K3tvRgGrIReWvGjU8cO9/XiKc3Vfl7WvKnWVonz7z8qPWADBP60Z8/zQmOptmzByz37drVbdw71sQd2Jkv2XObOOPSQNUqCUf1c6UcrQkz2yAfSLM3T9PtkmZe3y7Tgafz1dQbR7a4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752319425; c=relaxed/simple; bh=Y144qpSOyT1yG04T0ZyCMqBnyv1+lVwlZpKVj0EgkuQ=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=saPHUltlDBEoTsDxmN66FwNlGJUFTPZ82A9Z5udOaUvTJjTGRkAZDsLmPEkpEb0aKKYcs4s2CMumHXL4t4wNmky309aGM5jtmOMGRVjq7//1kz1+9+c0A7/W1XNqmBJ0JY7JWNl+G/TUBOQV9ip7HhVkJlC6nPVj+Qtc8kc/Ihk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=gmx.de; spf=pass smtp.mailfrom=gmx.de; dkim=pass (2048-bit key) header.d=gmx.de header.i=w_armin@gmx.de header.b=LjSOSYW0; arc=none smtp.client-ip=212.227.15.19 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=gmx.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmx.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmx.de header.i=w_armin@gmx.de header.b="LjSOSYW0" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmx.de; s=s31663417; t=1752319415; x=1752924215; i=w_armin@gmx.de; bh=i0foMDDyV76rhJ+TmJS86HnyAp9jPRpn1aNWF6XMmjk=; h=X-UI-Sender-Class:From:To:Cc:Subject:Date:Message-Id:In-Reply-To: References:MIME-Version:Content-Type:Content-Transfer-Encoding:cc: content-transfer-encoding:content-type:date:from:message-id: mime-version:reply-to:subject:to; b=LjSOSYW0iUoD9CIRQxcrloXRsYD+Rh8bPw4AAovH73yhi1fX5l1XxvBn/BrxQ98d oazC8/7KZSAQd2fhdDln+dydxuEV02ivaU2J8Dym3LJFrGzgds3DKkPcc9Gbdk5LD UGdm8ASzqabN6L1kV7ubbmN/XaoFzx23mXmNsREs8IPGVkjnF5UNqXQ9GOj7sZfij d5qyZa46EXSNdRqVeO11PuWakPMsNF5iJxlbD886WpD/LNPYxTJN8TI83bzehg67v AuKLm0ng8zMZJWlMk24XGPnxiWs3hgPcdSgz32I34CBvWyGo4dmBa3dTC8/I4n9Kp WoAGkQ/3ygqiV1Djvg== X-UI-Sender-Class: 724b4f7f-cbec-4199-ad4e-598c01a50d3a Received: from mx-amd-b650.fritz.box ([87.177.78.219]) by mail.gmx.net (mrgmx005 [212.227.17.190]) with ESMTPSA (Nemesis) id 1MKKUv-1uLup93m6p-00J1wh; Sat, 12 Jul 2025 13:23:33 +0200 From: Armin Wolf To: ilpo.jarvinen@linux.intel.com, hdegoede@redhat.com, chumuzero@gmail.com, corbet@lwn.net, cs@tuxedo.de, wse@tuxedocomputers.com, ggo@tuxedocomputers.com Cc: linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, platform-driver-x86@vger.kernel.org, rdunlap@infradead.org, alok.a.tiwari@oracle.com, linux-leds@vger.kernel.org, lee@kernel.org, pobrn@protonmail.com Subject: [PATCH 2/3] platform/x86: Add Uniwill laptop driver Date: Sat, 12 Jul 2025 13:23:09 +0200 Message-Id: <20250712112310.19964-3-W_Armin@gmx.de> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250712112310.19964-1-W_Armin@gmx.de> References: <20250712112310.19964-1-W_Armin@gmx.de> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Provags-ID: V03:K1:YKpiHo1BaBETa7ejQcqL/ImwldR489SdoCDpcFU08UEww/wjTs/ GscqUErIiSwBbxj+WxaH5zezJPnB2MuKXaF8vG2YoYsSRpOK8WEGYPuyLgkQopPhNPqj+n7 SjeltC+YRoZ5Ypmr7VJCIabaBTLBaYlTwK1nDJBvOyf6cn1STq7ppnUid+87asTTfZGoHvB MVaO5LWC2XY+8yHXntptA== X-Spam-Flag: NO UI-OutboundReport: notjunk:1;M01:P0:VeO8Y2ofTE0=;ehBoroUHnZuRLNHZNy5I/P4w46v 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 Content-Type: text/plain; charset="utf-8" Add a new driver for Uniwill laptops. The driver uses a ACPI WMI interface to talk with the embedded controller, but relies on a DMI whitelist for autoloading since Uniwill just copied the WMI GUID from the Windows driver example. The driver is reverse-engineered based on the following information: - OEM software from intel - https://github.com/pobrn/qc71_laptop - https://gitlab.com/tuxedocomputers/development/packages/tuxedo-drivers - https://github.com/tuxedocomputers/tuxedo-control-center The underlying EC supports various features, including hwmon sensors, battery charge limiting, a RGB lightbar and keyboard-related controls. Reported-by: cyear Closes: https://github.com/lm-sensors/lm-sensors/issues/508 Closes: https://github.com/Wer-Wolf/uniwill-laptop/issues/3 Signed-off-by: Armin Wolf --- .../ABI/testing/sysfs-driver-uniwill-laptop | 53 + Documentation/wmi/devices/uniwill-laptop.rst | 118 ++ MAINTAINERS | 8 + drivers/platform/x86/uniwill/Kconfig | 17 + drivers/platform/x86/uniwill/Makefile | 1 + drivers/platform/x86/uniwill/uniwill-laptop.c | 1481 +++++++++++++++++ drivers/platform/x86/uniwill/uniwill-wmi.c | 3 +- 7 files changed, 1680 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-driver-uniwill-laptop create mode 100644 Documentation/wmi/devices/uniwill-laptop.rst create mode 100644 drivers/platform/x86/uniwill/uniwill-laptop.c diff --git a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop b/Docume= ntation/ABI/testing/sysfs-driver-uniwill-laptop new file mode 100644 index 000000000000..7a540a7b9f24 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop @@ -0,0 +1,53 @@ +What: /sys/bus/wmi/devices/ABBC0F6F-8EA1-11D1-00A0-C90629100000[-X]/fn_lo= ck +Date: June 2025 +KernelVersion: 6.17 +Contact: Armin Wolf +Description: + Allows userspace applications to enable/disable the FN lock feature + of the integrated keyboard by writing "enable"/"disable" into this file. + + Reading this file returns the current enable status of the FN lock funct= ionality. + +What: /sys/bus/wmi/devices/ABBC0F6F-8EA1-11D1-00A0-C90629100000[-X]/super= _key_lock +Date: June 2025 +KernelVersion: 6.17 +Contact: Armin Wolf +Description: + Allows userspace applications to enable/disable the super = key functionality + of the integrated keyboard by writing "enable"/"disable" i= nto this file. + + Reading this file returns the current enable status of the super key fun= ctionality. + +What: /sys/bus/wmi/devices/ABBC0F6F-8EA1-11D1-00A0-C90629100000[-X]/touch= pad_toggle +Date: June 2025 +KernelVersion: 6.17 +Contact: Armin Wolf +Description: + Allows userspace applications to enable/disable the touchpad toggle func= tionality + of the integrated touchpad by writing "enable"/"disable" into this file. + + Reading this file returns the current enable status of the touchpad togg= le + functionality. + +What: /sys/bus/wmi/devices/ABBC0F6F-8EA1-11D1-00A0-C90629100000[-X]/rainb= ow_animation +Date: June 2025 +KernelVersion: 6.17 +Contact: Armin Wolf +Description: + Forces the integrated lightbar to display a rainbow animation when the m= achine + is not suspended. Writing "enable"/"disable" into this file enables/disa= bles + this functionality. + + Reading this file returns the current status of the rainbow animation fu= nctionality. + +What: /sys/bus/wmi/devices/ABBC0F6F-8EA1-11D1-00A0-C90629100000[-X]/breat= hing_in_suspend +Date: June 2025 +KernelVersion: 6.17 +Contact: Armin Wolf +Description: + Causes the integrated lightbar to display a breathing animation when the= machine + has been suspended and is running on AC power. Writing "enable"/"disable= " into + this file enables/disables this functionality. + + Reading this file returns the current status of the breathing animation + functionality. diff --git a/Documentation/wmi/devices/uniwill-laptop.rst b/Documentation/w= mi/devices/uniwill-laptop.rst new file mode 100644 index 000000000000..77a544b91f9e --- /dev/null +++ b/Documentation/wmi/devices/uniwill-laptop.rst @@ -0,0 +1,118 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Uniwill WMI Notebook driver (uniwill-laptop) +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Introduction +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Many notebooks manufactured by Uniwill (either directly or as ODM) provide= a WMI-based +EC interface for controlling various platform settings like sensors and fa= n control. +This interface is used by the ``uniwill-laptop`` driver to map those featu= res onto standard +kernel interfaces. + +WMI interface description +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D + +The WMI interface description can be decoded from the embedded binary MOF = (bmof) +data using the `bmfdec `_ utility: + +:: + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), + Description("Class used to operate methods on a ULong"), + guid("{ABBC0F6F-8EA1-11d1-00A0-C90629100000}")] + class AcpiTest_MULong { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiMethodId(1), Implemented, read, write, Description("Return the con= tents of a ULong")] + void GetULong([out, Description("Ulong Data")] uint32 Data); + + [WmiMethodId(2), Implemented, read, write, Description("Set the conten= ts of a ULong")] + void SetULong([in, Description("Ulong Data")] uint32 Data); + + [WmiMethodId(3), Implemented, read, write, + Description("Generate an event containing ULong data")] + void FireULong([in, Description("WMI requires a parameter")] uint32 Ha= ck); + + [WmiMethodId(4), Implemented, read, write, Description("Get and Set th= e contents of a ULong")] + void GetSetULong([in, Description("Ulong Data")] uint64 Data, + [out, Description("Ulong Data")] uint32 Return); + + [WmiMethodId(5), Implemented, read, write, + Description("Get and Set the contents of a ULong for Dollby button")] + void GetButton([in, Description("Ulong Data")] uint64 Data, + [out, Description("Ulong Data")] uint32 Return); + }; + +Most of the WMI-related code was copied from the Windows driver samples, w= hich unfortunately means +that the WMI-GUID is not unique. This makes the WMI-GUID unusable for auto= loading. + +WMI method GetULong() +--------------------- + +This WMI method was copied from the Windows driver samples and has no func= tion. + +WMI method SetULong() +--------------------- + +This WMI method was copied from the Windows driver samples and has no func= tion. + +WMI method FireULong() +---------------------- + +This WMI method allows to inject a WMI event with a 32-bit payload. Its pr= imary purpose seems +to be debugging. + +WMI method GetSetULong() +------------------------ + +This WMI method is used to communicate with the EC. The ``Data`` argument = hold the following +information (starting with the least significant byte): + +1. 16-bit address +2. 16-bit data (set to ``0x0000`` when reading) +3. 16-bit operation (``0x0100`` for reading and ``0x0000`` for writing) +4. 16-bit reserved (set to ``0x0000``) + +The first 8 bits of the ``Return`` value contain the data returned by the = EC when reading. +The special value ``0xFEFEFEFE`` is used to indicate a communication failu= re with the EC. + +WMI method GetButton() +---------------------- + +This WMI method is not implemented on all machines and has an unknown purp= ose. + +Relation with the ``INOU0000`` ACPI device +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +It seems that many of the embedded controller registers can also be access= ed by using the ``ECRR`` +and ``ECRW`` ACPI control methods under the ``INOU0000`` ACPI device. This= sidesteps the overhead +of the WMI interface but does not work for the registers in the range betw= een ``0x1800`` and +``0x18FF``. More research is needed to determine whether this interface im= poses addtional +restrictions. + +Reverse-Engineering the Uniwill WMI interface +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +.. warning:: Randomly poking the EC can potentially cause damage to the ma= chine and other unwanted + side effects, please be careful. + +The EC behind the ``GetSetULong`` method is used by the OEM software suppl= ied by the manufacturer. +Reverse-engineering of this software is difficult since it uses an obfusca= tor, however some parts +are not obfuscated. In this case `dnSpy `_= could also be helpful. + +The EC can be accessed under Windows using powershell (requires admin priv= ileges): + +:: + + > $obj =3D Get-CimInstance -Namespace root/wmi -ClassName AcpiTest_MULon= g | Select-Object -First 1 + > Invoke-CimMethod -InputObject $obj -MethodName GetSetULong -Arguments = @{Data =3D } + +Special thanks go to github user `pobrn` which developed the +`qc71_laptop `_ driver on which this= driver is partly based. +The same is true for Tuxedo Computers, which developed the +`tuxedo-drivers `_ package +which also served as a foundation for this driver. diff --git a/MAINTAINERS b/MAINTAINERS index 6c5879bd2ba2..3efec7a99262 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25490,6 +25490,14 @@ L: linux-scsi@vger.kernel.org S: Maintained F: drivers/ufs/host/ufs-renesas.c =20 +UNIWILL LAPTOP DRIVER +M: Armin Wolf +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: Documentation/ABI/testing/sysfs-driver-uniwill-laptop +F: Documentation/wmi/devices/uniwill-laptop.rst +F: drivers/platform/x86/uniwill/uniwill-laptop.c + UNIWILL WMI DRIVER M: Armin Wolf L: platform-driver-x86@vger.kernel.org diff --git a/drivers/platform/x86/uniwill/Kconfig b/drivers/platform/x86/un= iwill/Kconfig index 7571b30edb11..46d9af52b3f2 100644 --- a/drivers/platform/x86/uniwill/Kconfig +++ b/drivers/platform/x86/uniwill/Kconfig @@ -16,6 +16,23 @@ menuconfig X86_PLATFORM_DRIVERS_UNIWILL =20 if X86_PLATFORM_DRIVERS_UNIWILL =20 +config UNIWILL_LAPTOP + tristate "Uniwill Laptop Extras" + default m + depends on ACPI_WMI + depends on ACPI_BATTERY + depends on UNIWILL_WMI + depends on HWMON + depends on LEDS_CLASS_MULTICOLOR + depends on DMI + select REGMAP + help + This driver adds support for various extra features found on Uniwill la= ptops, + like the lightbar and hwmon sensors. It also supports many OEM laptops + originally manufactured by Uniwill. + + If you have such a laptop, say Y or M here. + config UNIWILL_WMI tristate "Uniwill WMI Event Driver" default m diff --git a/drivers/platform/x86/uniwill/Makefile b/drivers/platform/x86/u= niwill/Makefile index a5a300be63f3..b55169a49e1e 100644 --- a/drivers/platform/x86/uniwill/Makefile +++ b/drivers/platform/x86/uniwill/Makefile @@ -4,4 +4,5 @@ # Uniwill X86 Platform Specific Drivers # =20 +obj-$(CONFIG_UNIWILL_LAPTOP) +=3D uniwill-laptop.o obj-$(CONFIG_UNIWILL_WMI) +=3D uniwill-wmi.o diff --git a/drivers/platform/x86/uniwill/uniwill-laptop.c b/drivers/platfo= rm/x86/uniwill/uniwill-laptop.c new file mode 100644 index 000000000000..141c58673525 --- /dev/null +++ b/drivers/platform/x86/uniwill/uniwill-laptop.c @@ -0,0 +1,1481 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Linux driver for Uniwill notebooks. + * + * Special thanks go to P=C5=91cze Barnab=C3=A1s, Christoffer Sandberg and= Werner Sembach + * for supporting the development of this driver either through prior work= or + * by answering questions regarding the underlying WMI interface. + * + * Copyright (C) 2025 Armin Wolf + */ + +#define pr_format(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "uniwill-wmi.h" + +#define EC_ADDR_BAT_POWER_UNIT_1 0x0400 + +#define EC_ADDR_BAT_POWER_UNIT_2 0x0401 + +#define EC_ADDR_BAT_DESIGN_CAPACITY_1 0x0402 + +#define EC_ADDR_BAT_DESIGN_CAPACITY_2 0x0403 + +#define EC_ADDR_BAT_FULL_CAPACITY_1 0x0404 + +#define EC_ADDR_BAT_FULL_CAPACITY_2 0x0405 + +#define EC_ADDR_BAT_DESIGN_VOLTAGE_1 0x0408 + +#define EC_ADDR_BAT_DESIGN_VOLTAGE_2 0x0409 + +#define EC_ADDR_BAT_STATUS_1 0x0432 +#define BAT_DISCHARGING BIT(0) + +#define EC_ADDR_BAT_STATUS_2 0x0433 + +#define EC_ADDR_BAT_CURRENT_1 0x0434 + +#define EC_ADDR_BAT_CURRENT_2 0x0435 + +#define EC_ADDR_BAT_REMAIN_CAPACITY_1 0x0436 + +#define EC_ADDR_BAT_REMAIN_CAPACITY_2 0x0437 + +#define EC_ADDR_BAT_VOLTAGE_1 0x0438 + +#define EC_ADDR_BAT_VOLTAGE_2 0x0439 + +#define EC_ADDR_CPU_TEMP 0x043E + +#define EC_ADDR_GPU_TEMP 0x044F + +#define EC_ADDR_MAIN_FAN_RPM_1 0x0464 + +#define EC_ADDR_MAIN_FAN_RPM_2 0x0465 + +#define EC_ADDR_SECOND_FAN_RPM_1 0x046C + +#define EC_ADDR_SECOND_FAN_RPM_2 0x046D + +#define EC_ADDR_DEVICE_STATUS 0x047B +#define WIFI_STATUS_ON BIT(7) +/* BIT(5) is also unset depending on the rfkill state (bluetooth?) */ + +#define EC_ADDR_BAT_ALERT 0x0494 + +#define EC_ADDR_BAT_CYCLE_COUNT_1 0x04A6 + +#define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7 + +#define EC_ADDR_PROJECT_ID 0x0740 + +#define EC_ADDR_AP_OEM 0x0741 +#define ENABLE_MANUAL_CTRL BIT(0) +#define ITE_KBD_EFFECT_REACTIVE BIT(3) +#define FAN_ABNORMAL BIT(5) + +#define EC_ADDR_SUPPORT_5 0x0742 +#define FAN_TURBO_SUPPORTED BIT(4) +#define FAN_SUPPORT BIT(5) + +#define EC_ADDR_CTGP_DB_CTRL 0x0743 +#define CTGP_DB_GENERAL_ENABLE BIT(0) +#define CTGP_DB_DB_ENABLE BIT(1) +#define CTGP_DB_CTGP_ENABLE BIT(2) + +#define EC_ADDR_CTGP_OFFSET 0x0744 + +#define EC_ADDR_TPP_OFFSET 0x0745 + +#define EC_ADDR_MAX_TGP 0x0746 + +#define EC_ADDR_LIGHTBAR_AC_CTRL 0x0748 +#define LIGHTBAR_APP_EXISTS BIT(0) +#define LIGHTBAR_POWER_SAVE BIT(1) +#define LIGHTBAR_S0_OFF BIT(2) +#define LIGHTBAR_S3_OFF BIT(3) // Breathing animation when suspended +#define LIGHTBAR_WELCOME BIT(7) // Rainbow animation + +#define EC_ADDR_LIGHTBAR_AC_RED 0x0749 + +#define EC_ADDR_LIGHTBAR_AC_GREEN 0x074A + +#define EC_ADDR_LIGHTBAR_AC_BLUE 0x074B + +#define EC_ADDR_BIOS_OEM 0x074E +#define FN_LOCK_STATUS BIT(4) + +#define EC_ADDR_MANUAL_FAN_CTRL 0x0751 +#define FAN_LEVEL_MASK GENMASK(2, 0) +#define FAN_MODE_TURBO BIT(4) +#define FAN_MODE_HIGH BIT(5) +#define FAN_MODE_BOOST BIT(6) +#define FAN_MODE_USER BIT(7) + +#define EC_ADDR_PWM_1 0x075B + +#define EC_ADDR_PWM_2 0x075C + +/* Unreliable */ +#define EC_ADDR_SUPPORT_1 0x0765 +#define AIRPLANE_MODE BIT(0) +#define GPS_SWITCH BIT(1) +#define OVERCLOCK BIT(2) +#define MACRO_KEY BIT(3) +#define SHORTCUT_KEY BIT(4) +#define SUPER_KEY_LOCK BIT(5) +#define LIGHTBAR BIT(6) +#define FAN_BOOST BIT(7) + +#define EC_ADDR_SUPPORT_2 0x0766 +#define SILENT_MODE BIT(0) +#define USB_CHARGING BIT(1) +#define RGB_KEYBOARD BIT(2) +#define CHINA_MODE BIT(5) +#define MY_BATTERY BIT(6) + +#define EC_ADDR_TRIGGER 0x0767 +#define TRIGGER_SUPER_KEY_LOCK BIT(0) +#define TRIGGER_LIGHTBAR BIT(1) +#define TRIGGER_FAN_BOOST BIT(2) +#define TRIGGER_SILENT_MODE BIT(3) +#define TRIGGER_USB_CHARGING BIT(4) +#define RGB_APPLY_COLOR BIT(5) +#define RGB_LOGO_EFFECT BIT(6) +#define RGB_RAINBOW_EFFECT BIT(7) + +#define EC_ADDR_SWITCH_STATUS 0x0768 +#define SUPER_KEY_LOCK_STATUS BIT(0) +#define LIGHTBAR_STATUS BIT(1) +#define FAN_BOOST_STATUS BIT(2) +#define MACRO_KEY_STATUS BIT(3) +#define MY_BAT_POWER_BAT_STATUS BIT(4) + +#define EC_ADDR_RGB_RED 0x0769 + +#define EC_ADDR_RGB_GREEN 0x076A + +#define EC_ADDR_RGB_BLUE 0x076B + +#define EC_ADDR_ROMID_START 0x0770 +#define ROMID_LENGTH 14 + +#define EC_ADDR_ROMID_EXTRA_1 0x077E + +#define EC_ADDR_ROMID_EXTRA_2 0x077F + +#define EC_ADDR_BIOS_OEM_2 0x0782 +#define FAN_V2_NEW BIT(0) +#define FAN_QKEY BIT(1) +#define FAN_TABLE_OFFICE_MODE BIT(2) +#define FAN_V3 BIT(3) +#define DEFAULT_MODE BIT(4) + +#define EC_ADDR_PL1_SETTING 0x0783 + +#define EC_ADDR_PL2_SETTING 0x0784 + +#define EC_ADDR_PL4_SETTING 0x0785 + +#define EC_ADDR_FAN_DEFAULT 0x0786 +#define FAN_CURVE_LENGTH 5 + +#define EC_ADDR_KBD_STATUS 0x078C +#define KBD_WHITE_ONLY BIT(0) // ~single color +#define KBD_SINGLE_COLOR_OFF BIT(1) +#define KBD_TURBO_LEVEL_MASK GENMASK(3, 2) +#define KBD_APPLY BIT(4) +#define KBD_BRIGHTNESS GENMASK(7, 5) + +#define EC_ADDR_FAN_CTRL 0x078E +#define FAN3P5 BIT(1) +#define CHARGING_PROFILE BIT(3) +#define UNIVERSAL_FAN_CTRL BIT(6) + +#define EC_ADDR_BIOS_OEM_3 0x07A3 +#define FAN_REDUCED_DURY_CYCLE BIT(5) +#define FAN_ALWAYS_ON BIT(6) + +#define EC_ADDR_BIOS_BYTE 0x07A4 +#define FN_LOCK_SWITCH BIT(3) + +#define EC_ADDR_OEM_3 0x07A5 +#define POWER_LED_MASK GENMASK(1, 0) +#define POWER_LED_LEFT 0x00 +#define POWER_LED_BOTH 0x01 +#define POWER_LED_NONE 0x02 +#define FAN_QUIET BIT(2) +#define OVERBOOST BIT(4) +#define HIGH_POWER BIT(7) + +#define EC_ADDR_OEM_4 0x07A6 +#define OVERBOOST_DYN_TEMP_OFF BIT(1) +#define TOUCHPAD_TOGGLE_OFF BIT(6) + +#define EC_ADDR_CHARGE_CTRL 0x07B9 +#define CHARGE_CTRL_MASK GENMASK(6, 0) +#define CHARGE_CTRL_REACHED BIT(7) + +#define EC_ADDR_UNIVERSAL_FAN_CTRL 0x07C5 +#define SPLIT_TABLES BIT(7) + +#define EC_ADDR_AP_OEM_6 0x07C6 +#define ENABLE_UNIVERSAL_FAN_CTRL BIT(2) +#define BATTERY_CHARGE_FULL_OVER_24H BIT(3) +#define BATTERY_ERM_STATUS_REACHED BIT(4) + +#define EC_ADDR_CHARGE_PRIO 0x07CC +#define CHARGING_PERFORMANCE BIT(7) + +/* Same bits as EC_ADDR_LIGHTBAR_AC_CTRL except LIGHTBAR_S3_OFF */ +#define EC_ADDR_LIGHTBAR_BAT_CTRL 0x07E2 + +#define EC_ADDR_LIGHTBAR_BAT_RED 0x07E3 + +#define EC_ADDR_LIGHTBAR_BAT_GREEN 0x07E4 + +#define EC_ADDR_LIGHTBAR_BAT_BLUE 0x07E5 + +#define EC_ADDR_CPU_TEMP_END_TABLE 0x0F00 + +#define EC_ADDR_CPU_TEMP_START_TABLE 0x0F10 + +#define EC_ADDR_CPU_FAN_SPEED_TABLE 0x0F20 + +#define EC_ADDR_GPU_TEMP_END_TABLE 0x0F30 + +#define EC_ADDR_GPU_TEMP_START_TABLE 0x0F40 + +#define EC_ADDR_GPU_FAN_SPEED_TABLE 0x0F50 + +/* + * Those two registers technically allow for manual fan control, + * but are unstable on some models and are likely not meant to + * be used by applications. + */ +#define EC_ADDR_PWM_1_WRITEABLE 0x1804 + +#define EC_ADDR_PWM_2_WRITEABLE 0x1809 + +#define DRIVER_NAME "uniwill" +#define UNIWILL_GUID "ABBC0F6F-8EA1-11D1-00A0-C90629100000" + +#define PWM_MAX 200 +#define FAN_TABLE_LENGTH 16 + +#define LED_CHANNELS 3 +#define LED_MAX_BRIGHTNESS 200 + +#define UNIWILL_FEATURE_FN_LOCK BIT(0) +#define UNIWILL_FEATURE_SUPER_KEY_LOCK BIT(1) +#define UNIWILL_FEATURE_TOUCHPAD_TOGGLE BIT(2) +#define UNIWILL_FEATURE_LIGHTBAR BIT(3) +#define UNIWILL_FEATURE_BATTERY BIT(4) +#define UNIWILL_FEATURE_HWMON BIT(5) + +enum uniwill_method { + UNIWILL_GET_ULONG =3D 0x01, + UNIWILL_SET_ULONG =3D 0x02, + UNIWILL_FIRE_ULONG =3D 0x03, + UNIWILL_GET_SET_ULONG =3D 0x04, + UNIWILL_GET_BUTTON =3D 0x05, +}; + +struct uniwill_method_buffer { + __le16 address; + __le16 data; + __le16 operation; + __le16 reserved; +} __packed; + +struct uniwill_data { + struct wmi_device *wdev; + struct regmap *regmap; + struct acpi_battery_hook hook; + unsigned int last_charge_ctrl; + struct mutex battery_lock; /* Protects the list of currently registered b= atteries */ + unsigned int last_switch_status; + struct mutex super_key_lock; /* Protects the toggling of the super key lo= ck state */ + struct list_head batteries; + struct led_classdev_mc led_mc_cdev; + struct mc_subled led_mc_subled_info[LED_CHANNELS]; + struct notifier_block nb; +}; + +struct uniwill_battery_entry { + struct list_head head; + struct power_supply *battery; +}; + +static bool force; +module_param_unsafe(force, bool, 0); +MODULE_PARM_DESC(force, "Force loading without checking for supported devi= ces\n"); + +/* Feature bitmask since the associated registers are not reliable */ +static uintptr_t supported_features; + +/* + * "disable" is placed on index 0 so that the return value of sysfs_match_= string() + * directly translates into a boolean value. + */ +static const char * const uniwill_enable_disable_strings[] =3D { + [0] =3D "disable", + [1] =3D "enable", +}; + +static const char * const uniwill_temp_labels[] =3D { + "CPU", + "GPU", +}; + +static const char * const uniwill_fan_labels[] =3D { + "Main", + "Secondary", +}; + +static int uniwill_get_set_ulong(struct wmi_device *wdev, struct uniwill_m= ethod_buffer *input, + u32 *output) +{ + struct acpi_buffer out =3D { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer in =3D { + .length =3D sizeof(*input), + .pointer =3D input, + }; + union acpi_object *obj; + acpi_status status; + int ret =3D 0; + + status =3D wmidev_evaluate_method(wdev, 0x0, UNIWILL_GET_SET_ULONG, &in, = &out); + if (ACPI_FAILURE(status)) + return -EIO; + + obj =3D out.pointer; + if (!obj) + return -ENODATA; + + if (obj->type !=3D ACPI_TYPE_BUFFER) { + ret =3D -ENOMSG; + goto free_obj; + } + + if (obj->buffer.length < sizeof(*output)) { + ret =3D -EPROTO; + goto free_obj; + } + + *output =3D get_unaligned_le32(obj->buffer.pointer); + +free_obj: + kfree(obj); + + return ret; +} + +static int uniwill_ec_reg_write(void *context, unsigned int reg, unsigned = int val) +{ + struct uniwill_method_buffer input =3D { + .address =3D cpu_to_le16(reg), + .data =3D cpu_to_le16(val & U8_MAX), + .operation =3D 0x0000, + }; + struct uniwill_data *data =3D context; + u32 output; + int ret; + + ret =3D uniwill_get_set_ulong(data->wdev, &input, &output); + if (ret < 0) + return ret; + + if (output =3D=3D 0xFEFEFEFE) + return -ENXIO; + + return 0; +} + +static int uniwill_ec_reg_read(void *context, unsigned int reg, unsigned i= nt *val) +{ + struct uniwill_method_buffer input =3D { + .address =3D cpu_to_le16(reg), + .data =3D 0x0000, + .operation =3D cpu_to_le16(0x0100), + }; + struct uniwill_data *data =3D context; + u32 output; + int ret; + + ret =3D uniwill_get_set_ulong(data->wdev, &input, &output); + if (ret < 0) + return ret; + + if (output =3D=3D 0xFEFEFEFE) + return -ENXIO; + + *val =3D (u8)output; + + return 0; +} + +static const struct regmap_bus uniwill_ec_bus =3D { + .reg_write =3D uniwill_ec_reg_write, + .reg_read =3D uniwill_ec_reg_read, + .reg_format_endian_default =3D REGMAP_ENDIAN_LITTLE, + .val_format_endian_default =3D REGMAP_ENDIAN_LITTLE, +}; + +static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case EC_ADDR_AP_OEM: + case EC_ADDR_LIGHTBAR_AC_CTRL: + case EC_ADDR_LIGHTBAR_AC_RED: + case EC_ADDR_LIGHTBAR_AC_GREEN: + case EC_ADDR_LIGHTBAR_AC_BLUE: + case EC_ADDR_BIOS_OEM: + case EC_ADDR_TRIGGER: + case EC_ADDR_OEM_4: + case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_LIGHTBAR_BAT_CTRL: + case EC_ADDR_LIGHTBAR_BAT_RED: + case EC_ADDR_LIGHTBAR_BAT_GREEN: + case EC_ADDR_LIGHTBAR_BAT_BLUE: + return true; + default: + return false; + } +} + +static bool uniwill_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case EC_ADDR_CPU_TEMP: + case EC_ADDR_GPU_TEMP: + case EC_ADDR_MAIN_FAN_RPM_1: + case EC_ADDR_MAIN_FAN_RPM_2: + case EC_ADDR_SECOND_FAN_RPM_1: + case EC_ADDR_SECOND_FAN_RPM_2: + case EC_ADDR_BAT_ALERT: + case EC_ADDR_PROJECT_ID: + case EC_ADDR_AP_OEM: + case EC_ADDR_LIGHTBAR_AC_CTRL: + case EC_ADDR_LIGHTBAR_AC_RED: + case EC_ADDR_LIGHTBAR_AC_GREEN: + case EC_ADDR_LIGHTBAR_AC_BLUE: + case EC_ADDR_BIOS_OEM: + case EC_ADDR_PWM_1: + case EC_ADDR_PWM_2: + case EC_ADDR_TRIGGER: + case EC_ADDR_SWITCH_STATUS: + case EC_ADDR_OEM_4: + case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_LIGHTBAR_BAT_CTRL: + case EC_ADDR_LIGHTBAR_BAT_RED: + case EC_ADDR_LIGHTBAR_BAT_GREEN: + case EC_ADDR_LIGHTBAR_BAT_BLUE: + return true; + default: + return false; + } +} + +static bool uniwill_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case EC_ADDR_CPU_TEMP: + case EC_ADDR_GPU_TEMP: + case EC_ADDR_MAIN_FAN_RPM_1: + case EC_ADDR_MAIN_FAN_RPM_2: + case EC_ADDR_SECOND_FAN_RPM_1: + case EC_ADDR_SECOND_FAN_RPM_2: + case EC_ADDR_BAT_ALERT: + case EC_ADDR_PWM_1: + case EC_ADDR_PWM_2: + case EC_ADDR_TRIGGER: + case EC_ADDR_SWITCH_STATUS: + case EC_ADDR_CHARGE_CTRL: + return true; + default: + return false; + } +} + +static const struct regmap_config uniwill_ec_config =3D { + .reg_bits =3D 16, + .val_bits =3D 8, + .writeable_reg =3D uniwill_writeable_reg, + .readable_reg =3D uniwill_readable_reg, + .volatile_reg =3D uniwill_volatile_reg, + .can_sleep =3D true, + .max_register =3D 0xFFFF, + .cache_type =3D REGCACHE_MAPLE, + .use_single_read =3D true, + .use_single_write =3D true, +}; + +static ssize_t fn_lock_store(struct device *dev, struct device_attribute *= attr, const char *buf, + size_t count) +{ + struct uniwill_data *data =3D dev_get_drvdata(dev); + unsigned int value; + int ret; + + ret =3D sysfs_match_string(uniwill_enable_disable_strings, buf); + if (ret < 0) + return ret; + + if (ret) + value =3D FN_LOCK_STATUS; + else + value =3D 0; + + ret =3D regmap_update_bits(data->regmap, EC_ADDR_BIOS_OEM, FN_LOCK_STATUS= , value); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t fn_lock_show(struct device *dev, struct device_attribute *a= ttr, char *buf) +{ + struct uniwill_data *data =3D dev_get_drvdata(dev); + unsigned int value; + int ret; + + ret =3D regmap_read(data->regmap, EC_ADDR_BIOS_OEM, &value); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%s\n", str_enable_disable(value & FN_LOCK_STATUS)= ); +} + +static DEVICE_ATTR_RW(fn_lock); + +static ssize_t super_key_lock_store(struct device *dev, struct device_attr= ibute *attr, + const char *buf, size_t count) +{ + struct uniwill_data *data =3D dev_get_drvdata(dev); + unsigned int value; + int ret; + + ret =3D sysfs_match_string(uniwill_enable_disable_strings, buf); + if (ret < 0) + return ret; + + guard(mutex)(&data->super_key_lock); + + ret =3D regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value); + if (ret < 0) + return ret; + + /* + * We can only toggle the super key lock, so we return early if the setti= ng + * is already in the correct state. + */ + if (ret =3D=3D !(value & SUPER_KEY_LOCK_STATUS)) + return count; + + ret =3D regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_SUPER_KE= Y_LOCK, + TRIGGER_SUPER_KEY_LOCK); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t super_key_lock_show(struct device *dev, struct device_attri= bute *attr, char *buf) +{ + struct uniwill_data *data =3D dev_get_drvdata(dev); + unsigned int value; + int ret; + + ret =3D regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%s\n", str_enable_disable(!(value & SUPER_KEY_LOC= K_STATUS))); +} + +static DEVICE_ATTR_RW(super_key_lock); + +static ssize_t touchpad_toggle_store(struct device *dev, struct device_att= ribute *attr, + const char *buf, size_t count) +{ + struct uniwill_data *data =3D dev_get_drvdata(dev); + unsigned int value; + int ret; + + ret =3D sysfs_match_string(uniwill_enable_disable_strings, buf); + if (ret < 0) + return ret; + + if (ret) + value =3D 0; + else + value =3D TOUCHPAD_TOGGLE_OFF; + + ret =3D regmap_update_bits(data->regmap, EC_ADDR_OEM_4, TOUCHPAD_TOGGLE_O= FF, value); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t touchpad_toggle_show(struct device *dev, struct device_attr= ibute *attr, char *buf) +{ + struct uniwill_data *data =3D dev_get_drvdata(dev); + unsigned int value; + int ret; + + ret =3D regmap_read(data->regmap, EC_ADDR_OEM_4, &value); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%s\n", str_enable_disable(!(value & TOUCHPAD_TOGG= LE_OFF))); +} + +static DEVICE_ATTR_RW(touchpad_toggle); + +static ssize_t rainbow_animation_store(struct device *dev, struct device_a= ttribute *attr, + const char *buf, size_t count) +{ + struct uniwill_data *data =3D dev_get_drvdata(dev); + unsigned int value; + int ret; + + ret =3D sysfs_match_string(uniwill_enable_disable_strings, buf); + if (ret < 0) + return ret; + + if (ret) + value =3D LIGHTBAR_WELCOME; + else + value =3D 0; + + ret =3D regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTB= AR_WELCOME, value); + if (ret < 0) + return ret; + + ret =3D regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHT= BAR_WELCOME, value); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t rainbow_animation_show(struct device *dev, struct device_at= tribute *attr, char *buf) +{ + struct uniwill_data *data =3D dev_get_drvdata(dev); + unsigned int value; + int ret; + + ret =3D regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%s\n", str_enable_disable(value & LIGHTBAR_WELCOM= E)); +} + +static DEVICE_ATTR_RW(rainbow_animation); + +static ssize_t breathing_in_suspend_store(struct device *dev, struct devic= e_attribute *attr, + const char *buf, size_t count) +{ + struct uniwill_data *data =3D dev_get_drvdata(dev); + unsigned int value; + int ret; + + ret =3D sysfs_match_string(uniwill_enable_disable_strings, buf); + if (ret < 0) + return ret; + + if (ret) + value =3D 0; + else + value =3D LIGHTBAR_S3_OFF; + + ret =3D regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTB= AR_S3_OFF, value); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t breathing_in_suspend_show(struct device *dev, struct device= _attribute *attr, + char *buf) +{ + struct uniwill_data *data =3D dev_get_drvdata(dev); + unsigned int value; + int ret; + + ret =3D regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%s\n", str_enable_disable(!(value & LIGHTBAR_S3_O= FF))); +} + +static DEVICE_ATTR_RW(breathing_in_suspend); + +static struct attribute *uniwill_attrs[] =3D { + /* Keyboard-related */ + &dev_attr_fn_lock.attr, + &dev_attr_super_key_lock.attr, + &dev_attr_touchpad_toggle.attr, + /* Lightbar-related */ + &dev_attr_rainbow_animation.attr, + &dev_attr_breathing_in_suspend.attr, + NULL +}; + +static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attrib= ute *attr, int n) +{ + if (attr =3D=3D &dev_attr_fn_lock.attr) { + if (supported_features & UNIWILL_FEATURE_FN_LOCK) + return attr->mode; + } + + if (attr =3D=3D &dev_attr_super_key_lock.attr) { + if (supported_features & UNIWILL_FEATURE_SUPER_KEY_LOCK) + return attr->mode; + } + + if (attr =3D=3D &dev_attr_touchpad_toggle.attr) { + if (supported_features & UNIWILL_FEATURE_TOUCHPAD_TOGGLE) + return attr->mode; + } + + if (attr =3D=3D &dev_attr_rainbow_animation.attr || + attr =3D=3D &dev_attr_breathing_in_suspend.attr) { + if (supported_features & UNIWILL_FEATURE_LIGHTBAR) + return attr->mode; + } + + return 0; +} + +static const struct attribute_group uniwill_group =3D { + .is_visible =3D uniwill_attr_is_visible, + .attrs =3D uniwill_attrs, +}; + +static const struct attribute_group *uniwill_groups[] =3D { + &uniwill_group, + NULL +}; + +static int uniwill_read(struct device *dev, enum hwmon_sensor_types type, = u32 attr, int channel, + long *val) +{ + struct uniwill_data *data =3D dev_get_drvdata(dev); + unsigned int value; + __be16 rpm; + int ret; + + switch (type) { + case hwmon_temp: + switch (channel) { + case 0: + ret =3D regmap_read(data->regmap, EC_ADDR_CPU_TEMP, &value); + break; + case 1: + ret =3D regmap_read(data->regmap, EC_ADDR_GPU_TEMP, &value); + break; + default: + return -EOPNOTSUPP; + } + + if (ret < 0) + return ret; + + *val =3D value * MILLIDEGREE_PER_DEGREE; + return 0; + case hwmon_fan: + switch (channel) { + case 0: + ret =3D regmap_bulk_read(data->regmap, EC_ADDR_MAIN_FAN_RPM_1, &rpm, + sizeof(rpm)); + break; + case 1: + ret =3D regmap_bulk_read(data->regmap, EC_ADDR_SECOND_FAN_RPM_1, &rpm, + sizeof(rpm)); + break; + default: + return -EOPNOTSUPP; + } + + if (ret < 0) + return ret; + + *val =3D be16_to_cpu(rpm); + return 0; + case hwmon_pwm: + switch (channel) { + case 0: + ret =3D regmap_read(data->regmap, EC_ADDR_PWM_1, &value); + break; + case 1: + ret =3D regmap_read(data->regmap, EC_ADDR_PWM_2, &value); + break; + default: + return -EOPNOTSUPP; + } + + *val =3D fixp_linear_interpolate(0, 0, PWM_MAX, U8_MAX, value); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int uniwill_read_string(struct device *dev, enum hwmon_sensor_types= type, u32 attr, + int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + *str =3D uniwill_temp_labels[channel]; + return 0; + case hwmon_fan: + *str =3D uniwill_fan_labels[channel]; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_ops uniwill_ops =3D { + .visible =3D 0444, + .read =3D uniwill_read, + .read_string =3D uniwill_read_string, +}; + +static const struct hwmon_channel_info * const uniwill_info[] =3D { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT), + NULL +}; + +static const struct hwmon_chip_info uniwill_chip_info =3D { + .ops =3D &uniwill_ops, + .info =3D uniwill_info, +}; + +static int uniwill_hwmon_init(struct uniwill_data *data) +{ + struct device *hdev; + + if (!(supported_features & UNIWILL_FEATURE_HWMON)) + return 0; + + hdev =3D devm_hwmon_device_register_with_info(&data->wdev->dev, "uniwill"= , data, + &uniwill_chip_info, NULL); + + return PTR_ERR_OR_ZERO(hdev); +} + +static const unsigned int uniwill_led_channel_to_bat_reg[LED_CHANNELS] =3D= { + EC_ADDR_LIGHTBAR_BAT_RED, + EC_ADDR_LIGHTBAR_BAT_GREEN, + EC_ADDR_LIGHTBAR_BAT_BLUE, +}; + +static const unsigned int uniwill_led_channel_to_ac_reg[LED_CHANNELS] =3D { + EC_ADDR_LIGHTBAR_AC_RED, + EC_ADDR_LIGHTBAR_AC_GREEN, + EC_ADDR_LIGHTBAR_AC_BLUE, +}; + +static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum = led_brightness brightness) +{ + struct led_classdev_mc *led_mc_cdev =3D lcdev_to_mccdev(led_cdev); + struct uniwill_data *data =3D container_of(led_mc_cdev, struct uniwill_da= ta, led_mc_cdev); + unsigned int value; + int ret; + + ret =3D led_mc_calc_color_components(led_mc_cdev, brightness); + if (ret < 0) + return ret; + + for (int i =3D 0; i < LED_CHANNELS; i++) { + /* Prevent the brightness values from overflowing */ + value =3D min(LED_MAX_BRIGHTNESS, data->led_mc_subled_info[i].brightness= ); + ret =3D regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], val= ue); + if (ret < 0) + return ret; + + ret =3D regmap_write(data->regmap, uniwill_led_channel_to_bat_reg[i], va= lue); + if (ret < 0) + return ret; + } + + if (brightness) + value =3D 0; + else + value =3D LIGHTBAR_S0_OFF; + + ret =3D regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTB= AR_S0_OFF, value); + if (ret < 0) + return ret; + + return regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTB= AR_S0_OFF, value); +} + +#define LIGHTBAR_MASK (LIGHTBAR_APP_EXISTS | LIGHTBAR_S0_OFF | LIGHTBAR_S3= _OFF | LIGHTBAR_WELCOME) + +static int uniwill_led_init(struct uniwill_data *data) +{ + struct led_init_data init_data =3D { + .devicename =3D DRIVER_NAME, + .default_label =3D "multicolor:" LED_FUNCTION_STATUS, + .devname_mandatory =3D true, + }; + unsigned int color_indices[3] =3D { + LED_COLOR_ID_RED, + LED_COLOR_ID_GREEN, + LED_COLOR_ID_BLUE, + }; + unsigned int value; + int ret; + + if (!(supported_features & UNIWILL_FEATURE_LIGHTBAR)) + return 0; + + /* + * The EC has separate lightbar settings for AC and battery mode, + * so we have to ensure that both settings are the same. + */ + ret =3D regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value); + if (ret < 0) + return ret; + + value |=3D LIGHTBAR_APP_EXISTS; + ret =3D regmap_write(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, value); + if (ret < 0) + return ret; + + /* + * The breathing animation during suspend is not supported when + * running on battery power. + */ + value |=3D LIGHTBAR_S3_OFF; + ret =3D regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHT= BAR_MASK, value); + if (ret < 0) + return ret; + + data->led_mc_cdev.led_cdev.color =3D LED_COLOR_ID_MULTI; + data->led_mc_cdev.led_cdev.max_brightness =3D LED_MAX_BRIGHTNESS; + data->led_mc_cdev.led_cdev.flags =3D LED_REJECT_NAME_CONFLICT; + data->led_mc_cdev.led_cdev.brightness_set_blocking =3D uniwill_led_bright= ness_set; + + if (value & LIGHTBAR_S0_OFF) + data->led_mc_cdev.led_cdev.brightness =3D 0; + else + data->led_mc_cdev.led_cdev.brightness =3D LED_MAX_BRIGHTNESS; + + for (int i =3D 0; i < LED_CHANNELS; i++) { + data->led_mc_subled_info[i].color_index =3D color_indices[i]; + + ret =3D regmap_read(data->regmap, uniwill_led_channel_to_ac_reg[i], &val= ue); + if (ret < 0) + return ret; + + /* + * Make sure that the initial intensity value is not greater than + * the maximum brightness. + */ + value =3D min(LED_MAX_BRIGHTNESS, value); + ret =3D regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], val= ue); + if (ret < 0) + return ret; + + ret =3D regmap_write(data->regmap, uniwill_led_channel_to_bat_reg[i], va= lue); + if (ret < 0) + return ret; + + data->led_mc_subled_info[i].intensity =3D value; + data->led_mc_subled_info[i].channel =3D i; + } + + data->led_mc_cdev.subled_info =3D data->led_mc_subled_info; + data->led_mc_cdev.num_colors =3D LED_CHANNELS; + + return devm_led_classdev_multicolor_register_ext(&data->wdev->dev, &data-= >led_mc_cdev, + &init_data); +} + +static int uniwill_get_property(struct power_supply *psy, const struct pow= er_supply_ext *ext, + void *drvdata, enum power_supply_property psp, + union power_supply_propval *val) +{ + struct uniwill_data *data =3D drvdata; + union power_supply_propval prop; + unsigned int regval; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + ret =3D power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_PRESENT,= &prop); + if (ret < 0) + return ret; + + if (!prop.intval) { + val->intval =3D POWER_SUPPLY_HEALTH_NO_BATTERY; + return 0; + } + + ret =3D power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_STATUS, = &prop); + if (ret < 0) + return ret; + + if (prop.intval =3D=3D POWER_SUPPLY_STATUS_UNKNOWN) { + val->intval =3D POWER_SUPPLY_HEALTH_UNKNOWN; + return 0; + } + + ret =3D regmap_read(data->regmap, EC_ADDR_BAT_ALERT, ®val); + if (ret < 0) + return ret; + + if (regval) { + /* Charging issue */ + val->intval =3D POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + + val->intval =3D POWER_SUPPLY_HEALTH_GOOD; + return 0; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + ret =3D regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, ®val); + if (ret < 0) + return ret; + + val->intval =3D clamp_val(FIELD_GET(CHARGE_CTRL_MASK, regval), 0, 100); + return 0; + default: + return -EINVAL; + } +} + +static int uniwill_set_property(struct power_supply *psy, const struct pow= er_supply_ext *ext, + void *drvdata, enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct uniwill_data *data =3D drvdata; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + if (val->intval < 1 || val->intval > 100) + return -EINVAL; + + return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL= _MASK, + val->intval); + default: + return -EINVAL; + } +} + +static int uniwill_property_is_writeable(struct power_supply *psy, + const struct power_supply_ext *ext, void *drvdata, + enum power_supply_property psp) +{ + if (psp =3D=3D POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) + return true; + + return false; +} + +static const enum power_supply_property uniwill_properties[] =3D { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, +}; + +static const struct power_supply_ext uniwill_extension =3D { + .name =3D DRIVER_NAME, + .properties =3D uniwill_properties, + .num_properties =3D ARRAY_SIZE(uniwill_properties), + .get_property =3D uniwill_get_property, + .set_property =3D uniwill_set_property, + .property_is_writeable =3D uniwill_property_is_writeable, +}; + +static int uniwill_add_battery(struct power_supply *battery, struct acpi_b= attery_hook *hook) +{ + struct uniwill_data *data =3D container_of(hook, struct uniwill_data, hoo= k); + struct uniwill_battery_entry *entry; + int ret; + + entry =3D kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + ret =3D power_supply_register_extension(battery, &uniwill_extension, &dat= a->wdev->dev, data); + if (ret < 0) { + kfree(entry); + return ret; + } + + scoped_guard(mutex, &data->battery_lock) { + entry->battery =3D battery; + list_add(&entry->head, &data->batteries); + } + + return 0; +} + +static int uniwill_remove_battery(struct power_supply *battery, struct acp= i_battery_hook *hook) +{ + struct uniwill_data *data =3D container_of(hook, struct uniwill_data, hoo= k); + struct uniwill_battery_entry *entry, *tmp; + + scoped_guard(mutex, &data->battery_lock) { + list_for_each_entry_safe(entry, tmp, &data->batteries, head) { + if (entry->battery =3D=3D battery) { + list_del(&entry->head); + kfree(entry); + break; + } + } + } + + power_supply_unregister_extension(battery, &uniwill_extension); + + return 0; +} + +static int uniwill_battery_init(struct uniwill_data *data) +{ + int ret; + + if (!(supported_features & UNIWILL_FEATURE_BATTERY)) + return 0; + + ret =3D devm_mutex_init(&data->wdev->dev, &data->battery_lock); + if (ret < 0) + return ret; + + INIT_LIST_HEAD(&data->batteries); + data->hook.name =3D "Uniwill Battery Extension"; + data->hook.add_battery =3D uniwill_add_battery; + data->hook.remove_battery =3D uniwill_remove_battery; + + return devm_battery_hook_register(&data->wdev->dev, &data->hook); +} + +static int uniwill_notifier_call(struct notifier_block *nb, unsigned long = action, void *dummy) +{ + struct uniwill_data *data =3D container_of(nb, struct uniwill_data, nb); + struct uniwill_battery_entry *entry; + + switch (action) { + case UNIWILL_OSD_BATTERY_ALERT: + scoped_guard(mutex, &data->battery_lock) { + list_for_each_entry(entry, &data->batteries, head) { + power_supply_changed(entry->battery); + } + } + + return NOTIFY_OK; + default: + return NOTIFY_DONE; + } +} + +static int uniwill_notifier_init(struct uniwill_data *data) +{ + data->nb.notifier_call =3D uniwill_notifier_call; + + return devm_uniwill_wmi_register_notifier(&data->wdev->dev, &data->nb); +} + +static void uniwill_disable_manual_control(void *context) +{ + struct uniwill_data *data =3D context; + + regmap_clear_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); +} + +static int uniwill_ec_init(struct uniwill_data *data) +{ + unsigned int value; + int ret; + + ret =3D regmap_read(data->regmap, EC_ADDR_PROJECT_ID, &value); + if (ret < 0) + return ret; + + dev_dbg(&data->wdev->dev, "Project ID: %u\n", value); + + ret =3D regmap_set_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); + if (ret < 0) + return ret; + + return devm_add_action_or_reset(&data->wdev->dev, uniwill_disable_manual_= control, data); +} + +static int uniwill_probe(struct wmi_device *wdev, const void *context) +{ + struct uniwill_data *data; + struct regmap *regmap; + int ret; + + data =3D devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->wdev =3D wdev; + dev_set_drvdata(&wdev->dev, data); + + regmap =3D devm_regmap_init(&wdev->dev, &uniwill_ec_bus, data, &uniwill_e= c_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + data->regmap =3D regmap; + ret =3D devm_mutex_init(&wdev->dev, &data->super_key_lock); + if (ret < 0) + return ret; + + ret =3D uniwill_ec_init(data); + if (ret < 0) + return ret; + + ret =3D uniwill_battery_init(data); + if (ret < 0) + return ret; + + ret =3D uniwill_led_init(data); + if (ret < 0) + return ret; + + ret =3D uniwill_hwmon_init(data); + if (ret < 0) + return ret; + + return uniwill_notifier_init(data); +} + +static void uniwill_shutdown(struct wmi_device *wdev) +{ + struct uniwill_data *data =3D dev_get_drvdata(&wdev->dev); + + regmap_clear_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); +} + +static int uniwill_suspend_keyboard(struct uniwill_data *data) +{ + if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_LOCK)) + return 0; + + /* + * The EC_ADDR_SWITCH_STATUS is maked as volatile, so we have to restore = it + * ourself. + */ + return regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &data->last_switc= h_status); +} + +static int uniwill_suspend_battery(struct uniwill_data *data) +{ + if (!(supported_features & UNIWILL_FEATURE_BATTERY)) + return 0; + + /* + * Save the current charge limit in order to restore it during resume. + * We cannot use the regmap code for that since this register needs to + * be declared as volatile due to CHARGE_CTRL_REACHED. + */ + return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_= ctrl); +} + +static int uniwill_suspend(struct device *dev) +{ + struct uniwill_data *data =3D dev_get_drvdata(dev); + int ret; + + ret =3D uniwill_suspend_keyboard(data); + if (ret < 0) + return ret; + + ret =3D uniwill_suspend_battery(data); + if (ret < 0) + return ret; + + regcache_cache_only(data->regmap, true); + regcache_mark_dirty(data->regmap); + + return 0; +} + +static int uniwill_resume_keyboard(struct uniwill_data *data) +{ + unsigned int value; + int ret; + + if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_LOCK)) + return 0; + + ret =3D regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value); + if (ret < 0) + return ret; + + if ((data->last_switch_status & SUPER_KEY_LOCK_STATUS) =3D=3D (value & SU= PER_KEY_LOCK_STATUS)) + return 0; + + return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_SUPER_KEY= _LOCK, + TRIGGER_SUPER_KEY_LOCK); +} + +static int uniwill_resume_battery(struct uniwill_data *data) +{ + if (!(supported_features & UNIWILL_FEATURE_BATTERY)) + return 0; + + return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_= MASK, + data->last_charge_ctrl); +} + +static int uniwill_resume(struct device *dev) +{ + struct uniwill_data *data =3D dev_get_drvdata(dev); + int ret; + + regcache_cache_only(data->regmap, false); + + ret =3D regcache_sync(data->regmap); + if (ret < 0) + return ret; + + ret =3D uniwill_resume_keyboard(data); + if (ret < 0) + return ret; + + return uniwill_resume_battery(data); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(uniwill_pm_ops, uniwill_suspend, uniwill_r= esume); + +/* + * We cannot fully trust this GUID since Uniwill just copied the WMI GUID + * from the Windows driver example, and others probably did the same. + * + * Because of this we cannot use this WMI GUID for autoloading. + */ +static const struct wmi_device_id uniwill_id_table[] =3D { + { UNIWILL_GUID, NULL }, + { } +}; + +static struct wmi_driver uniwill_driver =3D { + .driver =3D { + .name =3D DRIVER_NAME, + .dev_groups =3D uniwill_groups, + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + .pm =3D pm_sleep_ptr(&uniwill_pm_ops), + }, + .id_table =3D uniwill_id_table, + .probe =3D uniwill_probe, + .shutdown =3D uniwill_shutdown, + .no_singleton =3D true, +}; + +static const struct dmi_system_id uniwill_dmi_table[] __initconst =3D { + { + .ident =3D "Intel NUC x15", + .matches =3D { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPAC71H"), + }, + .driver_data =3D (void *)(UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY_LOCK | + UNIWILL_FEATURE_TOUCHPAD_TOGGLE | + UNIWILL_FEATURE_BATTERY | + UNIWILL_FEATURE_HWMON), + }, + { + .ident =3D "Intel NUC x15", + .matches =3D { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71F"), + }, + .driver_data =3D (void *)(UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY_LOCK | + UNIWILL_FEATURE_TOUCHPAD_TOGGLE | + UNIWILL_FEATURE_LIGHTBAR | + UNIWILL_FEATURE_BATTERY | + UNIWILL_FEATURE_HWMON), + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, uniwill_dmi_table); + +static int __init uniwill_init(void) +{ + const struct dmi_system_id *id; + + id =3D dmi_first_match(uniwill_dmi_table); + if (!id) { + if (!force) + return -ENODEV; + + /* Assume that the device supports all features */ + supported_features =3D UINTPTR_MAX; + pr_warn("Loading on a potentially unsupported device\n"); + } else { + supported_features =3D (uintptr_t)id->driver_data; + } + + return wmi_driver_register(&uniwill_driver); +} +module_init(uniwill_init); + +static void __exit uniwill_exit(void) +{ + wmi_driver_unregister(&uniwill_driver); +} +module_exit(uniwill_exit); + +MODULE_AUTHOR("Armin Wolf "); +MODULE_DESCRIPTION("Uniwill notebook driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("UNIWILL"); diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.c b/drivers/platform/= x86/uniwill/uniwill-wmi.c index f38e4bbcd8b6..65700121ef52 100644 --- a/drivers/platform/x86/uniwill/uniwill-wmi.c +++ b/drivers/platform/x86/uniwill/uniwill-wmi.c @@ -161,7 +161,8 @@ static int uniwill_wmi_probe(struct wmi_device *wdev, c= onst void *context) * We cannot fully trust this GUID since Uniwill just copied the WMI GUID * from the Windows driver example, and others probably did the same. * - * Because of this we cannot use this WMI GUID for autoloading. + * Because of this we cannot use this WMI GUID for autoloading. The uniwil= l-laptop + * driver will instead load this module as a dependency. */ static const struct wmi_device_id uniwill_wmi_id_table[] =3D { { UNIWILL_EVENT_GUID, NULL }, --=20 2.39.5 From nobody Tue Oct 7 07:06:10 2025 Received: from mout.gmx.net (mout.gmx.net [212.227.15.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0054F27585D; Sat, 12 Jul 2025 11:24:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=212.227.15.15 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752319445; cv=none; b=Yjfuq5FHLYMijgxumJpLjjK4Gzqbd7fSROR8zlus1G5HyK1q3FDs/qkqpHhMAZmKNlFe5DslQeikCzPxCXyoligMkmg/QoNHX82+rccRcUVD4MN0bg1zr2rb7o0lE1BBpeWtQ/nIxjrPvjdDZiS0lrC6pywAYewj3UJHww7GC5c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752319445; c=relaxed/simple; bh=gTM4aMv6hJrPmPAHzUP/+2qQUoZgH+/FZgEWAaPEqOo=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=YkN4aWqpJ5cy8xnEpkxqhD/Nbc18uiKGt/g+DiKgVnmcemQ+Z3IMmD5sadppYD9uegQsQON/DXGg0kjvqoJBCumDL7Jy/diwgILsMLgqQpq8VuXiA+r794Hr/QVmJvQllIk7mTTC3VweMfgBJpJ+eOn3J5/C1UldHSu9EI2e7cQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=gmx.de; spf=pass smtp.mailfrom=gmx.de; dkim=pass (2048-bit key) header.d=gmx.de header.i=w_armin@gmx.de header.b=giJhRCi4; arc=none smtp.client-ip=212.227.15.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=gmx.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmx.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmx.de header.i=w_armin@gmx.de header.b="giJhRCi4" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmx.de; s=s31663417; t=1752319419; x=1752924219; i=w_armin@gmx.de; bh=XSx+6LzVSbNbIWznWvt7Yu+EJxbTFfYcUJ56Et6KTkk=; h=X-UI-Sender-Class:From:To:Cc:Subject:Date:Message-Id:In-Reply-To: References:MIME-Version:Content-Transfer-Encoding:cc: content-transfer-encoding:content-type:date:from:message-id: mime-version:reply-to:subject:to; b=giJhRCi43fdbP5sFDVkxlOWX12bgXh6D7O9NT0XItTOa9n/KCwimWqxY+iSOZ9Vt o1M2QvYMsqKIU9MNWoj7P74o75qX7sPCrQvYjqNPnemldlrWVAci4beUSposyqAx/ PzWdwSaUlHDgW4Zj3a/a7p9hfrVOZVbM3OkLsEysOCLbjdxzgrOQMLMmX8E/4FcJx u1KTtyQpoGYRmQlYUBIFhfTagRJx7ccmaO83c/0C/CiwbmqEfCySoZk5XZS0WIEob +s4VnkOu5306ENegn94/FoPa2gB5l4jtqoD50+iXgeCiCsI6oa8FGLnLCkowuJ6+Q iNN2SrNbHlq6Qo3Bhw== X-UI-Sender-Class: 724b4f7f-cbec-4199-ad4e-598c01a50d3a Received: from mx-amd-b650.fritz.box ([87.177.78.219]) by mail.gmx.net (mrgmx004 [212.227.17.190]) with ESMTPSA (Nemesis) id 1N1Obh-1ulOCZ3KRf-00zjlH; Sat, 12 Jul 2025 13:23:36 +0200 From: Armin Wolf To: ilpo.jarvinen@linux.intel.com, hdegoede@redhat.com, chumuzero@gmail.com, corbet@lwn.net, cs@tuxedo.de, wse@tuxedocomputers.com, ggo@tuxedocomputers.com Cc: linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, platform-driver-x86@vger.kernel.org, rdunlap@infradead.org, alok.a.tiwari@oracle.com, linux-leds@vger.kernel.org, lee@kernel.org, pobrn@protonmail.com Subject: [PATCH 3/3] Documentation: laptops: Add documentation for uniwill laptops Date: Sat, 12 Jul 2025 13:23:10 +0200 Message-Id: <20250712112310.19964-4-W_Armin@gmx.de> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250712112310.19964-1-W_Armin@gmx.de> References: <20250712112310.19964-1-W_Armin@gmx.de> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Provags-ID: V03:K1:5pqmTXX29idlzgbf473NXC34zm0Er27AkaiKFOGkJ/pPnoLjIkP pK6yvVilWIlUgNZ5VYmitagv1RKQLwsTZ6dvlfNFH4wRTOUQygY5b1y2VbknnslcwmKnGy1 J2YDgHK0eM/wJpoiIRscXngaNLRcmAyFqXMCuqI7QoDdQ+8RtlRsyfaTizoJ4DoWbRmigi3 iLREUQwUZHFmuXrFFVc9g== X-Spam-Flag: NO UI-OutboundReport: notjunk:1;M01:P0:KKFk0MVHGE4=;rlBCSsAay9fORttMRypBIgE3aY4 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 C1DcdW9Gcdf77HB/v+We1ntaUfGSDh+fNj1 Content-Type: text/plain; charset="utf-8" Add documentation for admins regarding Uniwill laptops. This should help users to setup the uniwill-laptop and uniwill-wmi drivers, which sadly cannot be loaded automatically. Reported-by: cyear Closes: https://github.com/lm-sensors/lm-sensors/issues/508 Closes: https://github.com/Wer-Wolf/uniwill-laptop/issues/3 Signed-off-by: Armin Wolf --- Documentation/admin-guide/laptops/index.rst | 1 + .../admin-guide/laptops/uniwill-laptop.rst | 68 +++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 70 insertions(+) create mode 100644 Documentation/admin-guide/laptops/uniwill-laptop.rst diff --git a/Documentation/admin-guide/laptops/index.rst b/Documentation/ad= min-guide/laptops/index.rst index db842b629303..6432c251dc95 100644 --- a/Documentation/admin-guide/laptops/index.rst +++ b/Documentation/admin-guide/laptops/index.rst @@ -17,3 +17,4 @@ Laptop Drivers sonypi thinkpad-acpi toshiba_haps + uniwill-laptop diff --git a/Documentation/admin-guide/laptops/uniwill-laptop.rst b/Documen= tation/admin-guide/laptops/uniwill-laptop.rst new file mode 100644 index 000000000000..29f6ee88063b --- /dev/null +++ b/Documentation/admin-guide/laptops/uniwill-laptop.rst @@ -0,0 +1,68 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +Uniwill laptop extra features +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D + +On laptops manufactured by Uniwill (either directly or as ODM), the ``uniw= ill-laptop`` and +``uniwill-wmi`` driver both handle various platform-specific features. +However due to a design flaw in the underlying firmware interface, both dr= ivers might need +to be loaded manually on some devices. + +.. warning:: Not all devices supporting the firmware interface will necess= arily support those + drivers, please be careful. + +Module Loading +-------------- + +The ``uniwill-laptop`` driver relies on a DMI table to automatically load = on supported devices. +When using the ``force`` module parameter, this DMI check will be omitted,= allowing the driver +to be loaded on unsupported devices for testing purposes. + +The ``uniwill-wmi`` driver always needs to be loaded manually. However the= ``uniwill-laptop`` +driver will automatically load it as a dependency. + +Hotkeys +------- + +Usually the FN keys work without a special driver. However as soon as the = ``uniwill-laptop`` driver +is loaded, the FN keys need to be handled manually. This is done by the ``= uniwill-wmi`` driver. + +Keyboard settings +----------------- + +The ``uniwill-laptop`` driver allows the user to enable/disable: + + - the FN and super key lock functionality of the integrated keyboard + - the touchpad toggle functionality of the integrated touchpad + +See Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details. + +Hwmon interface +--------------- + +The ``uniwill-laptop`` driver supports reading of the CPU and GPU temperat= ure and supports up to +two fans. Userspace applications can access sensor readings over the hwmon= sysfs interface. + +Platform profile +---------------- + +Support for changing the platform performance mode is currently not implem= ented. + +Battery Charging Control +------------------------ + +The ``uniwill-laptop`` driver supports controlling the battery charge limi= t. This happens over +the standard ``charge_control_end_threshold`` power supply sysfs attribute= . All values +between 1 and 100 percent are supported. + +Additionally the driver signals the presence of battery charging issues th= ru the standard ``health`` +power supply sysfs attribute. + +Lightbar +-------- + +The ``uniwill-laptop`` driver exposes the lightbar found on some models as= a standard multicolor +LED class device. The default name of this LED class device is ``uniwill:m= ulticolor:status``. + +See Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details on h= ow to control the various +animation modes of the lightbar. diff --git a/MAINTAINERS b/MAINTAINERS index 3efec7a99262..fe302a610fe6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25495,6 +25495,7 @@ M: Armin Wolf L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/ABI/testing/sysfs-driver-uniwill-laptop +F: Documentation/admin-guide/laptops/uniwill-laptop.rst F: Documentation/wmi/devices/uniwill-laptop.rst F: drivers/platform/x86/uniwill/uniwill-laptop.c =20 --=20 2.39.5