Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

platform/x86: Add Uniwill laptop driver

Add a new driver for Uniwill laptops. The driver uses a ACPI
interface to talk with the embedded controller, but relies on a
ACPI WMI interface for receiving event notifications.

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 <chumuzero@gmail.com>
Closes: https://github.com/lm-sensors/lm-sensors/issues/508
Closes: https://github.com/Wer-Wolf/uniwill-laptop/issues/3
Tested-by: Werner Sembach <wse@tuxedocomputers.com>
Signed-off-by: Armin Wolf <W_Armin@gmx.de>
Link: https://patch.msgid.link/20251102172942.17879-2-W_Armin@gmx.de
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Armin Wolf and committed by
Ilpo Järvinen
d0504796 90430ea9

+2081
+53
Documentation/ABI/testing/sysfs-driver-uniwill-laptop
··· 1 + What: /sys/bus/platform/devices/INOU0000:XX/fn_lock_toggle_enable 2 + Date: November 2025 3 + KernelVersion: 6.19 4 + Contact: Armin Wolf <W_Armin@gmx.de> 5 + Description: 6 + Allows userspace applications to enable/disable the FN lock feature 7 + of the integrated keyboard by writing "1"/"0" into this file. 8 + 9 + Reading this file returns the current enable status of the FN lock functionality. 10 + 11 + What: /sys/bus/platform/devices/INOU0000:XX/super_key_toggle_enable 12 + Date: November 2025 13 + KernelVersion: 6.19 14 + Contact: Armin Wolf <W_Armin@gmx.de> 15 + Description: 16 + Allows userspace applications to enable/disable the super key functionality 17 + of the integrated keyboard by writing "1"/"0" into this file. 18 + 19 + Reading this file returns the current enable status of the super key functionality. 20 + 21 + What: /sys/bus/platform/devices/INOU0000:XX/touchpad_toggle_enable 22 + Date: November 2025 23 + KernelVersion: 6.19 24 + Contact: Armin Wolf <W_Armin@gmx.de> 25 + Description: 26 + Allows userspace applications to enable/disable the touchpad toggle functionality 27 + of the integrated touchpad by writing "1"/"0" into this file. 28 + 29 + Reading this file returns the current enable status of the touchpad toggle 30 + functionality. 31 + 32 + What: /sys/bus/platform/devices/INOU0000:XX/rainbow_animation 33 + Date: November 2025 34 + KernelVersion: 6.19 35 + Contact: Armin Wolf <W_Armin@gmx.de> 36 + Description: 37 + Forces the integrated lightbar to display a rainbow animation when the machine 38 + is not suspended. Writing "1"/"0" into this file enables/disables this 39 + functionality. 40 + 41 + Reading this file returns the current status of the rainbow animation functionality. 42 + 43 + What: /sys/bus/platform/devices/INOU0000:XX/breathing_in_suspend 44 + Date: November 2025 45 + KernelVersion: 6.19 46 + Contact: Armin Wolf <W_Armin@gmx.de> 47 + Description: 48 + Causes the integrated lightbar to display a breathing animation when the machine 49 + has been suspended and is running on AC power. Writing "1"/"0" into this file 50 + enables/disables this functionality. 51 + 52 + Reading this file returns the current status of the breathing animation 53 + functionality.
+198
Documentation/wmi/devices/uniwill-laptop.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0-or-later 2 + 3 + ======================================== 4 + Uniwill Notebook driver (uniwill-laptop) 5 + ======================================== 6 + 7 + Introduction 8 + ============ 9 + 10 + Many notebooks manufactured by Uniwill (either directly or as ODM) provide a EC interface 11 + for controlling various platform settings like sensors and fan control. This interface is 12 + used by the ``uniwill-laptop`` driver to map those features onto standard kernel interfaces. 13 + 14 + EC WMI interface description 15 + ============================ 16 + 17 + The EC WMI interface description can be decoded from the embedded binary MOF (bmof) 18 + data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility: 19 + 20 + :: 21 + 22 + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), 23 + Description("Class used to operate methods on a ULong"), 24 + guid("{ABBC0F6F-8EA1-11d1-00A0-C90629100000}")] 25 + class AcpiTest_MULong { 26 + [key, read] string InstanceName; 27 + [read] boolean Active; 28 + 29 + [WmiMethodId(1), Implemented, read, write, Description("Return the contents of a ULong")] 30 + void GetULong([out, Description("Ulong Data")] uint32 Data); 31 + 32 + [WmiMethodId(2), Implemented, read, write, Description("Set the contents of a ULong")] 33 + void SetULong([in, Description("Ulong Data")] uint32 Data); 34 + 35 + [WmiMethodId(3), Implemented, read, write, 36 + Description("Generate an event containing ULong data")] 37 + void FireULong([in, Description("WMI requires a parameter")] uint32 Hack); 38 + 39 + [WmiMethodId(4), Implemented, read, write, Description("Get and Set the contents of a ULong")] 40 + void GetSetULong([in, Description("Ulong Data")] uint64 Data, 41 + [out, Description("Ulong Data")] uint32 Return); 42 + 43 + [WmiMethodId(5), Implemented, read, write, 44 + Description("Get and Set the contents of a ULong for Dollby button")] 45 + void GetButton([in, Description("Ulong Data")] uint64 Data, 46 + [out, Description("Ulong Data")] uint32 Return); 47 + }; 48 + 49 + Most of the WMI-related code was copied from the Windows driver samples, which unfortunately means 50 + that the WMI-GUID is not unique. This makes the WMI-GUID unusable for autoloading. 51 + 52 + WMI method GetULong() 53 + --------------------- 54 + 55 + This WMI method was copied from the Windows driver samples and has no function. 56 + 57 + WMI method SetULong() 58 + --------------------- 59 + 60 + This WMI method was copied from the Windows driver samples and has no function. 61 + 62 + WMI method FireULong() 63 + ---------------------- 64 + 65 + This WMI method allows to inject a WMI event with a 32-bit payload. Its primary purpose seems 66 + to be debugging. 67 + 68 + WMI method GetSetULong() 69 + ------------------------ 70 + 71 + This WMI method is used to communicate with the EC. The ``Data`` argument holds the following 72 + information (starting with the least significant byte): 73 + 74 + 1. 16-bit address 75 + 2. 16-bit data (set to ``0x0000`` when reading) 76 + 3. 16-bit operation (``0x0100`` for reading and ``0x0000`` for writing) 77 + 4. 16-bit reserved (set to ``0x0000``) 78 + 79 + The first 8 bits of the ``Return`` value contain the data returned by the EC when reading. 80 + The special value ``0xFEFEFEFE`` is used to indicate a communication failure with the EC. 81 + 82 + WMI method GetButton() 83 + ---------------------- 84 + 85 + This WMI method is not implemented on all machines and has an unknown purpose. 86 + 87 + Reverse-Engineering the EC WMI interface 88 + ======================================== 89 + 90 + .. warning:: Randomly poking the EC can potentially cause damage to the machine and other unwanted 91 + side effects, please be careful. 92 + 93 + The EC behind the ``GetSetULong`` method is used by the OEM software supplied by the manufacturer. 94 + Reverse-engineering of this software is difficult since it uses an obfuscator, however some parts 95 + are not obfuscated. In this case `dnSpy <https://github.com/dnSpy/dnSpy>`_ could also be helpful. 96 + 97 + The EC can be accessed under Windows using powershell (requires admin privileges): 98 + 99 + :: 100 + 101 + > $obj = Get-CimInstance -Namespace root/wmi -ClassName AcpiTest_MULong | Select-Object -First 1 102 + > Invoke-CimMethod -InputObject $obj -MethodName GetSetULong -Arguments @{Data = <input>} 103 + 104 + WMI event interface description 105 + =============================== 106 + 107 + The WMI interface description can also be decoded from the embedded binary MOF (bmof) 108 + data: 109 + 110 + :: 111 + 112 + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), 113 + Description("Class containing event generated ULong data"), 114 + guid("{ABBC0F72-8EA1-11d1-00A0-C90629100000}")] 115 + class AcpiTest_EventULong : WmiEvent { 116 + [key, read] string InstanceName; 117 + [read] boolean Active; 118 + 119 + [WmiDataId(1), read, write, Description("ULong Data")] uint32 ULong; 120 + }; 121 + 122 + Most of the WMI-related code was again copied from the Windows driver samples, causing this WMI 123 + interface to suffer from the same restrictions as the EC WMI interface described above. 124 + 125 + WMI event data 126 + -------------- 127 + 128 + The WMI event data contains a single 32-bit value which is used to indicate various platform events. 129 + 130 + Reverse-Engineering the Uniwill WMI event interface 131 + =================================================== 132 + 133 + The driver logs debug messages when receiving a WMI event. Thus enabling debug messages will be 134 + useful for finding unknown event codes. 135 + 136 + EC ACPI interface description 137 + ============================= 138 + 139 + The ``INOU0000`` ACPI device is a virtual device used to access various hardware registers 140 + available on notebooks manufactured by Uniwill. Reading and writing those registers happens 141 + by calling ACPI control methods. The ``uniwill-laptop`` driver uses this device to communicate 142 + with the EC because the ACPI control methods are faster than the WMI methods described above. 143 + 144 + ACPI control methods used for reading registers take a single ACPI integer containing the address 145 + of the register to read and return a ACPI integer containing the data inside said register. ACPI 146 + control methods used for writing registers however take two ACPI integers, with the additional 147 + ACPI integer containing the data to be written into the register. Such ACPI control methods return 148 + nothing. 149 + 150 + System memory 151 + ------------- 152 + 153 + System memory can be accessed with a granularity of either a single byte (``MMRB`` for reading and 154 + ``MMWB`` for writing) or four bytes (``MMRD`` for reading and ``MMWD`` for writing). Those ACPI 155 + control methods are unused because they provide no benefit when compared to the native memory 156 + access functions provided by the kernel. 157 + 158 + EC RAM 159 + ------ 160 + 161 + The internal RAM of the EC can be accessed with a granularity of a single byte using the ``ECRR`` 162 + (read) and ``ECRW`` (write) ACPI control methods, with the maximum register address being ``0xFFF``. 163 + The OEM software waits 6 ms after calling one of those ACPI control methods, likely to avoid 164 + overwhelming the EC when being connected over LPC. 165 + 166 + PCI config space 167 + ---------------- 168 + 169 + The PCI config space can be accessed with a granularity of four bytes using the ``PCRD`` (read) and 170 + ``PCWD`` (write) ACPI control methods. The exact address format is unknown, and poking random PCI 171 + devices might confuse the PCI subsystem. Because of this those ACPI control methods are not used. 172 + 173 + IO ports 174 + -------- 175 + 176 + IO ports can be accessed with a granularity of four bytes using the ``IORD`` (read) and ``IOWD`` 177 + (write) ACPI control methods. Those ACPI control methods are unused because they provide no benefit 178 + when compared to the native IO port access functions provided by the kernel. 179 + 180 + CMOS RAM 181 + -------- 182 + 183 + The CMOS RAM can be accessed with a granularity of a single byte using the ``RCMS`` (read) and 184 + ``WCMS`` ACPI control methods. Using those ACPI methods might interfere with the native CMOS RAM 185 + access functions provided by the kernel due to the usage of indexed IO, so they are unused. 186 + 187 + Indexed IO 188 + ---------- 189 + 190 + Indexed IO with IO ports with a granularity of a single byte can be performed using the ``RIOP`` 191 + (read) and ``WIOP`` (write) ACPI control methods. Those ACPI methods are unused because they 192 + provide no benifit when compared to the native IO port access functions provided by the kernel. 193 + 194 + Special thanks go to github user `pobrn` which developed the 195 + `qc71_laptop <https://github.com/pobrn/qc71_laptop>`_ driver on which this driver is partly based. 196 + The same is true for Tuxedo Computers, which developed the 197 + `tuxedo-drivers <https://gitlab.com/tuxedocomputers/development/packages/tuxedo-drivers>`_ package 198 + which also served as a foundation for this driver.
+10
MAINTAINERS
··· 26376 26376 S: Maintained 26377 26377 F: drivers/ufs/host/ufs-renesas.c 26378 26378 26379 + UNIWILL LAPTOP DRIVER 26380 + M: Armin Wolf <W_Armin@gmx.de> 26381 + L: platform-driver-x86@vger.kernel.org 26382 + S: Maintained 26383 + F: Documentation/ABI/testing/sysfs-driver-uniwill-laptop 26384 + F: Documentation/wmi/devices/uniwill-laptop.rst 26385 + F: drivers/platform/x86/uniwill/uniwill-acpi.c 26386 + F: drivers/platform/x86/uniwill/uniwill-wmi.c 26387 + F: drivers/platform/x86/uniwill/uniwill-wmi.h 26388 + 26379 26389 UNSORTED BLOCK IMAGES (UBI) 26380 26390 M: Richard Weinberger <richard@nod.at> 26381 26391 R: Zhihao Cheng <chengzhihao1@huawei.com>
+2
drivers/platform/x86/Kconfig
··· 74 74 To compile this driver as a module, choose M here: the module 75 75 will be called huawei-wmi. 76 76 77 + source "drivers/platform/x86/uniwill/Kconfig" 78 + 77 79 config UV_SYSFS 78 80 tristate "Sysfs structure for UV systems" 79 81 depends on X86_UV
+3
drivers/platform/x86/Makefile
··· 110 110 # before toshiba_acpi initializes 111 111 obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o 112 112 113 + # Uniwill 114 + obj-y += uniwill/ 115 + 113 116 # Inspur 114 117 obj-$(CONFIG_INSPUR_PLATFORM_PROFILE) += inspur_platform_profile.o 115 118
+38
drivers/platform/x86/uniwill/Kconfig
··· 1 + # SPDX-License-Identifier: GPL-2.0-or-later 2 + # 3 + # Uniwill X86 Platform Specific Drivers 4 + # 5 + 6 + menuconfig X86_PLATFORM_DRIVERS_UNIWILL 7 + bool "Uniwill X86 Platform Specific Device Drivers" 8 + depends on X86_PLATFORM_DEVICES 9 + help 10 + Say Y here to see options for device drivers for various 11 + Uniwill x86 platforms, including many OEM laptops originally 12 + manufactured by Uniwill. 13 + This option alone does not add any kernel code. 14 + 15 + If you say N, all options in this submenu will be skipped and disabled. 16 + 17 + if X86_PLATFORM_DRIVERS_UNIWILL 18 + 19 + config UNIWILL_LAPTOP 20 + tristate "Uniwill Laptop Extras" 21 + default m 22 + depends on ACPI 23 + depends on ACPI_WMI 24 + depends on ACPI_BATTERY 25 + depends on HWMON 26 + depends on INPUT 27 + depends on LEDS_CLASS_MULTICOLOR 28 + depends on DMI 29 + select REGMAP 30 + select INPUT_SPARSEKMAP 31 + help 32 + This driver adds support for various extra features found on Uniwill laptops, 33 + like the lightbar, hwmon sensors and hotkeys. It also supports many OEM laptops 34 + originally manufactured by Uniwill. 35 + 36 + If you have such a laptop, say Y or M here. 37 + 38 + endif
+8
drivers/platform/x86/uniwill/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0-or-later 2 + # 3 + # Makefile for linux/drivers/platform/x86/uniwill 4 + # Uniwill X86 Platform Specific Drivers 5 + # 6 + 7 + obj-$(CONFIG_UNIWILL_LAPTOP) += uniwill-laptop.o 8 + uniwill-laptop-y := uniwill-acpi.o uniwill-wmi.o
+1550
drivers/platform/x86/uniwill/uniwill-acpi.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * Linux driver for Uniwill notebooks. 4 + * 5 + * Special thanks go to Pőcze Barnabás, Christoffer Sandberg and Werner Sembach 6 + * for supporting the development of this driver either through prior work or 7 + * by answering questions regarding the underlying ACPI and WMI interfaces. 8 + * 9 + * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de> 10 + */ 11 + 12 + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 13 + 14 + #include <linux/acpi.h> 15 + #include <linux/array_size.h> 16 + #include <linux/bits.h> 17 + #include <linux/bitfield.h> 18 + #include <linux/cleanup.h> 19 + #include <linux/debugfs.h> 20 + #include <linux/delay.h> 21 + #include <linux/device.h> 22 + #include <linux/device/driver.h> 23 + #include <linux/dmi.h> 24 + #include <linux/errno.h> 25 + #include <linux/fixp-arith.h> 26 + #include <linux/hwmon.h> 27 + #include <linux/hwmon-sysfs.h> 28 + #include <linux/init.h> 29 + #include <linux/input.h> 30 + #include <linux/input/sparse-keymap.h> 31 + #include <linux/kernel.h> 32 + #include <linux/kstrtox.h> 33 + #include <linux/leds.h> 34 + #include <linux/led-class-multicolor.h> 35 + #include <linux/limits.h> 36 + #include <linux/list.h> 37 + #include <linux/minmax.h> 38 + #include <linux/module.h> 39 + #include <linux/mutex.h> 40 + #include <linux/notifier.h> 41 + #include <linux/platform_device.h> 42 + #include <linux/pm.h> 43 + #include <linux/printk.h> 44 + #include <linux/regmap.h> 45 + #include <linux/string.h> 46 + #include <linux/sysfs.h> 47 + #include <linux/types.h> 48 + #include <linux/units.h> 49 + 50 + #include <acpi/battery.h> 51 + 52 + #include "uniwill-wmi.h" 53 + 54 + #define EC_ADDR_BAT_POWER_UNIT_1 0x0400 55 + 56 + #define EC_ADDR_BAT_POWER_UNIT_2 0x0401 57 + 58 + #define EC_ADDR_BAT_DESIGN_CAPACITY_1 0x0402 59 + 60 + #define EC_ADDR_BAT_DESIGN_CAPACITY_2 0x0403 61 + 62 + #define EC_ADDR_BAT_FULL_CAPACITY_1 0x0404 63 + 64 + #define EC_ADDR_BAT_FULL_CAPACITY_2 0x0405 65 + 66 + #define EC_ADDR_BAT_DESIGN_VOLTAGE_1 0x0408 67 + 68 + #define EC_ADDR_BAT_DESIGN_VOLTAGE_2 0x0409 69 + 70 + #define EC_ADDR_BAT_STATUS_1 0x0432 71 + #define BAT_DISCHARGING BIT(0) 72 + 73 + #define EC_ADDR_BAT_STATUS_2 0x0433 74 + 75 + #define EC_ADDR_BAT_CURRENT_1 0x0434 76 + 77 + #define EC_ADDR_BAT_CURRENT_2 0x0435 78 + 79 + #define EC_ADDR_BAT_REMAIN_CAPACITY_1 0x0436 80 + 81 + #define EC_ADDR_BAT_REMAIN_CAPACITY_2 0x0437 82 + 83 + #define EC_ADDR_BAT_VOLTAGE_1 0x0438 84 + 85 + #define EC_ADDR_BAT_VOLTAGE_2 0x0439 86 + 87 + #define EC_ADDR_CPU_TEMP 0x043E 88 + 89 + #define EC_ADDR_GPU_TEMP 0x044F 90 + 91 + #define EC_ADDR_MAIN_FAN_RPM_1 0x0464 92 + 93 + #define EC_ADDR_MAIN_FAN_RPM_2 0x0465 94 + 95 + #define EC_ADDR_SECOND_FAN_RPM_1 0x046C 96 + 97 + #define EC_ADDR_SECOND_FAN_RPM_2 0x046D 98 + 99 + #define EC_ADDR_DEVICE_STATUS 0x047B 100 + #define WIFI_STATUS_ON BIT(7) 101 + /* BIT(5) is also unset depending on the rfkill state (bluetooth?) */ 102 + 103 + #define EC_ADDR_BAT_ALERT 0x0494 104 + 105 + #define EC_ADDR_BAT_CYCLE_COUNT_1 0x04A6 106 + 107 + #define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7 108 + 109 + #define EC_ADDR_PROJECT_ID 0x0740 110 + 111 + #define EC_ADDR_AP_OEM 0x0741 112 + #define ENABLE_MANUAL_CTRL BIT(0) 113 + #define ITE_KBD_EFFECT_REACTIVE BIT(3) 114 + #define FAN_ABNORMAL BIT(5) 115 + 116 + #define EC_ADDR_SUPPORT_5 0x0742 117 + #define FAN_TURBO_SUPPORTED BIT(4) 118 + #define FAN_SUPPORT BIT(5) 119 + 120 + #define EC_ADDR_CTGP_DB_CTRL 0x0743 121 + #define CTGP_DB_GENERAL_ENABLE BIT(0) 122 + #define CTGP_DB_DB_ENABLE BIT(1) 123 + #define CTGP_DB_CTGP_ENABLE BIT(2) 124 + 125 + #define EC_ADDR_CTGP_OFFSET 0x0744 126 + 127 + #define EC_ADDR_TPP_OFFSET 0x0745 128 + 129 + #define EC_ADDR_MAX_TGP 0x0746 130 + 131 + #define EC_ADDR_LIGHTBAR_AC_CTRL 0x0748 132 + #define LIGHTBAR_APP_EXISTS BIT(0) 133 + #define LIGHTBAR_POWER_SAVE BIT(1) 134 + #define LIGHTBAR_S0_OFF BIT(2) 135 + #define LIGHTBAR_S3_OFF BIT(3) // Breathing animation when suspended 136 + #define LIGHTBAR_WELCOME BIT(7) // Rainbow animation 137 + 138 + #define EC_ADDR_LIGHTBAR_AC_RED 0x0749 139 + 140 + #define EC_ADDR_LIGHTBAR_AC_GREEN 0x074A 141 + 142 + #define EC_ADDR_LIGHTBAR_AC_BLUE 0x074B 143 + 144 + #define EC_ADDR_BIOS_OEM 0x074E 145 + #define FN_LOCK_STATUS BIT(4) 146 + 147 + #define EC_ADDR_MANUAL_FAN_CTRL 0x0751 148 + #define FAN_LEVEL_MASK GENMASK(2, 0) 149 + #define FAN_MODE_TURBO BIT(4) 150 + #define FAN_MODE_HIGH BIT(5) 151 + #define FAN_MODE_BOOST BIT(6) 152 + #define FAN_MODE_USER BIT(7) 153 + 154 + #define EC_ADDR_PWM_1 0x075B 155 + 156 + #define EC_ADDR_PWM_2 0x075C 157 + 158 + /* Unreliable */ 159 + #define EC_ADDR_SUPPORT_1 0x0765 160 + #define AIRPLANE_MODE BIT(0) 161 + #define GPS_SWITCH BIT(1) 162 + #define OVERCLOCK BIT(2) 163 + #define MACRO_KEY BIT(3) 164 + #define SHORTCUT_KEY BIT(4) 165 + #define SUPER_KEY_LOCK BIT(5) 166 + #define LIGHTBAR BIT(6) 167 + #define FAN_BOOST BIT(7) 168 + 169 + #define EC_ADDR_SUPPORT_2 0x0766 170 + #define SILENT_MODE BIT(0) 171 + #define USB_CHARGING BIT(1) 172 + #define RGB_KEYBOARD BIT(2) 173 + #define CHINA_MODE BIT(5) 174 + #define MY_BATTERY BIT(6) 175 + 176 + #define EC_ADDR_TRIGGER 0x0767 177 + #define TRIGGER_SUPER_KEY_LOCK BIT(0) 178 + #define TRIGGER_LIGHTBAR BIT(1) 179 + #define TRIGGER_FAN_BOOST BIT(2) 180 + #define TRIGGER_SILENT_MODE BIT(3) 181 + #define TRIGGER_USB_CHARGING BIT(4) 182 + #define RGB_APPLY_COLOR BIT(5) 183 + #define RGB_LOGO_EFFECT BIT(6) 184 + #define RGB_RAINBOW_EFFECT BIT(7) 185 + 186 + #define EC_ADDR_SWITCH_STATUS 0x0768 187 + #define SUPER_KEY_LOCK_STATUS BIT(0) 188 + #define LIGHTBAR_STATUS BIT(1) 189 + #define FAN_BOOST_STATUS BIT(2) 190 + #define MACRO_KEY_STATUS BIT(3) 191 + #define MY_BAT_POWER_BAT_STATUS BIT(4) 192 + 193 + #define EC_ADDR_RGB_RED 0x0769 194 + 195 + #define EC_ADDR_RGB_GREEN 0x076A 196 + 197 + #define EC_ADDR_RGB_BLUE 0x076B 198 + 199 + #define EC_ADDR_ROMID_START 0x0770 200 + #define ROMID_LENGTH 14 201 + 202 + #define EC_ADDR_ROMID_EXTRA_1 0x077E 203 + 204 + #define EC_ADDR_ROMID_EXTRA_2 0x077F 205 + 206 + #define EC_ADDR_BIOS_OEM_2 0x0782 207 + #define FAN_V2_NEW BIT(0) 208 + #define FAN_QKEY BIT(1) 209 + #define FAN_TABLE_OFFICE_MODE BIT(2) 210 + #define FAN_V3 BIT(3) 211 + #define DEFAULT_MODE BIT(4) 212 + 213 + #define EC_ADDR_PL1_SETTING 0x0783 214 + 215 + #define EC_ADDR_PL2_SETTING 0x0784 216 + 217 + #define EC_ADDR_PL4_SETTING 0x0785 218 + 219 + #define EC_ADDR_FAN_DEFAULT 0x0786 220 + #define FAN_CURVE_LENGTH 5 221 + 222 + #define EC_ADDR_KBD_STATUS 0x078C 223 + #define KBD_WHITE_ONLY BIT(0) // ~single color 224 + #define KBD_SINGLE_COLOR_OFF BIT(1) 225 + #define KBD_TURBO_LEVEL_MASK GENMASK(3, 2) 226 + #define KBD_APPLY BIT(4) 227 + #define KBD_BRIGHTNESS GENMASK(7, 5) 228 + 229 + #define EC_ADDR_FAN_CTRL 0x078E 230 + #define FAN3P5 BIT(1) 231 + #define CHARGING_PROFILE BIT(3) 232 + #define UNIVERSAL_FAN_CTRL BIT(6) 233 + 234 + #define EC_ADDR_BIOS_OEM_3 0x07A3 235 + #define FAN_REDUCED_DURY_CYCLE BIT(5) 236 + #define FAN_ALWAYS_ON BIT(6) 237 + 238 + #define EC_ADDR_BIOS_BYTE 0x07A4 239 + #define FN_LOCK_SWITCH BIT(3) 240 + 241 + #define EC_ADDR_OEM_3 0x07A5 242 + #define POWER_LED_MASK GENMASK(1, 0) 243 + #define POWER_LED_LEFT 0x00 244 + #define POWER_LED_BOTH 0x01 245 + #define POWER_LED_NONE 0x02 246 + #define FAN_QUIET BIT(2) 247 + #define OVERBOOST BIT(4) 248 + #define HIGH_POWER BIT(7) 249 + 250 + #define EC_ADDR_OEM_4 0x07A6 251 + #define OVERBOOST_DYN_TEMP_OFF BIT(1) 252 + #define TOUCHPAD_TOGGLE_OFF BIT(6) 253 + 254 + #define EC_ADDR_CHARGE_CTRL 0x07B9 255 + #define CHARGE_CTRL_MASK GENMASK(6, 0) 256 + #define CHARGE_CTRL_REACHED BIT(7) 257 + 258 + #define EC_ADDR_UNIVERSAL_FAN_CTRL 0x07C5 259 + #define SPLIT_TABLES BIT(7) 260 + 261 + #define EC_ADDR_AP_OEM_6 0x07C6 262 + #define ENABLE_UNIVERSAL_FAN_CTRL BIT(2) 263 + #define BATTERY_CHARGE_FULL_OVER_24H BIT(3) 264 + #define BATTERY_ERM_STATUS_REACHED BIT(4) 265 + 266 + #define EC_ADDR_CHARGE_PRIO 0x07CC 267 + #define CHARGING_PERFORMANCE BIT(7) 268 + 269 + /* Same bits as EC_ADDR_LIGHTBAR_AC_CTRL except LIGHTBAR_S3_OFF */ 270 + #define EC_ADDR_LIGHTBAR_BAT_CTRL 0x07E2 271 + 272 + #define EC_ADDR_LIGHTBAR_BAT_RED 0x07E3 273 + 274 + #define EC_ADDR_LIGHTBAR_BAT_GREEN 0x07E4 275 + 276 + #define EC_ADDR_LIGHTBAR_BAT_BLUE 0x07E5 277 + 278 + #define EC_ADDR_CPU_TEMP_END_TABLE 0x0F00 279 + 280 + #define EC_ADDR_CPU_TEMP_START_TABLE 0x0F10 281 + 282 + #define EC_ADDR_CPU_FAN_SPEED_TABLE 0x0F20 283 + 284 + #define EC_ADDR_GPU_TEMP_END_TABLE 0x0F30 285 + 286 + #define EC_ADDR_GPU_TEMP_START_TABLE 0x0F40 287 + 288 + #define EC_ADDR_GPU_FAN_SPEED_TABLE 0x0F50 289 + 290 + /* 291 + * Those two registers technically allow for manual fan control, 292 + * but are unstable on some models and are likely not meant to 293 + * be used by applications as they are only accessible when using 294 + * the WMI interface. 295 + */ 296 + #define EC_ADDR_PWM_1_WRITEABLE 0x1804 297 + 298 + #define EC_ADDR_PWM_2_WRITEABLE 0x1809 299 + 300 + #define DRIVER_NAME "uniwill" 301 + 302 + /* 303 + * The OEM software always sleeps up to 6 ms after reading/writing EC 304 + * registers, so we emulate this behaviour for maximum compatibility. 305 + */ 306 + #define UNIWILL_EC_DELAY_US 6000 307 + 308 + #define PWM_MAX 200 309 + #define FAN_TABLE_LENGTH 16 310 + 311 + #define LED_CHANNELS 3 312 + #define LED_MAX_BRIGHTNESS 200 313 + 314 + #define UNIWILL_FEATURE_FN_LOCK_TOGGLE BIT(0) 315 + #define UNIWILL_FEATURE_SUPER_KEY_TOGGLE BIT(1) 316 + #define UNIWILL_FEATURE_TOUCHPAD_TOGGLE BIT(2) 317 + #define UNIWILL_FEATURE_LIGHTBAR BIT(3) 318 + #define UNIWILL_FEATURE_BATTERY BIT(4) 319 + #define UNIWILL_FEATURE_HWMON BIT(5) 320 + 321 + struct uniwill_data { 322 + struct device *dev; 323 + acpi_handle handle; 324 + struct regmap *regmap; 325 + struct acpi_battery_hook hook; 326 + unsigned int last_charge_ctrl; 327 + struct mutex battery_lock; /* Protects the list of currently registered batteries */ 328 + unsigned int last_switch_status; 329 + struct mutex super_key_lock; /* Protects the toggling of the super key lock state */ 330 + struct list_head batteries; 331 + struct mutex led_lock; /* Protects writes to the lightbar registers */ 332 + struct led_classdev_mc led_mc_cdev; 333 + struct mc_subled led_mc_subled_info[LED_CHANNELS]; 334 + struct mutex input_lock; /* Protects input sequence during notify */ 335 + struct input_dev *input_device; 336 + struct notifier_block nb; 337 + }; 338 + 339 + struct uniwill_battery_entry { 340 + struct list_head head; 341 + struct power_supply *battery; 342 + }; 343 + 344 + static bool force; 345 + module_param_unsafe(force, bool, 0); 346 + MODULE_PARM_DESC(force, "Force loading without checking for supported devices\n"); 347 + 348 + /* Feature bitmask since the associated registers are not reliable */ 349 + static unsigned int supported_features; 350 + 351 + static const char * const uniwill_temp_labels[] = { 352 + "CPU", 353 + "GPU", 354 + }; 355 + 356 + static const char * const uniwill_fan_labels[] = { 357 + "Main", 358 + "Secondary", 359 + }; 360 + 361 + static const struct key_entry uniwill_keymap[] = { 362 + /* Reported via keyboard controller */ 363 + { KE_IGNORE, UNIWILL_OSD_CAPSLOCK, { KEY_CAPSLOCK }}, 364 + { KE_IGNORE, UNIWILL_OSD_NUMLOCK, { KEY_NUMLOCK }}, 365 + 366 + /* Reported when the user locks/unlocks the super key */ 367 + { KE_IGNORE, UNIWILL_OSD_SUPER_KEY_LOCK_ENABLE, { KEY_UNKNOWN }}, 368 + { KE_IGNORE, UNIWILL_OSD_SUPER_KEY_LOCK_DISABLE, { KEY_UNKNOWN }}, 369 + /* Optional, might not be reported by all devices */ 370 + { KE_IGNORE, UNIWILL_OSD_SUPER_KEY_LOCK_CHANGED, { KEY_UNKNOWN }}, 371 + 372 + /* Reported in manual mode when toggling the airplane mode status */ 373 + { KE_KEY, UNIWILL_OSD_RFKILL, { KEY_RFKILL }}, 374 + 375 + /* Reported when user wants to cycle the platform profile */ 376 + { KE_IGNORE, UNIWILL_OSD_PERFORMANCE_MODE_TOGGLE, { KEY_UNKNOWN }}, 377 + 378 + /* Reported when the user wants to adjust the brightness of the keyboard */ 379 + { KE_KEY, UNIWILL_OSD_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }}, 380 + { KE_KEY, UNIWILL_OSD_KBDILLUMUP, { KEY_KBDILLUMUP }}, 381 + 382 + /* Reported when the user wants to toggle the microphone mute status */ 383 + { KE_KEY, UNIWILL_OSD_MIC_MUTE, { KEY_MICMUTE }}, 384 + 385 + /* Reported when the user locks/unlocks the Fn key */ 386 + { KE_IGNORE, UNIWILL_OSD_FN_LOCK, { KEY_FN_ESC }}, 387 + 388 + /* Reported when the user wants to toggle the brightness of the keyboard */ 389 + { KE_KEY, UNIWILL_OSD_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }}, 390 + 391 + /* FIXME: find out the exact meaning of those events */ 392 + { KE_IGNORE, UNIWILL_OSD_BAT_CHARGE_FULL_24_H, { KEY_UNKNOWN }}, 393 + { KE_IGNORE, UNIWILL_OSD_BAT_ERM_UPDATE, { KEY_UNKNOWN }}, 394 + 395 + /* Reported when the user wants to toggle the benchmark mode status */ 396 + { KE_IGNORE, UNIWILL_OSD_BENCHMARK_MODE_TOGGLE, { KEY_UNKNOWN }}, 397 + 398 + { KE_END } 399 + }; 400 + 401 + static int uniwill_ec_reg_write(void *context, unsigned int reg, unsigned int val) 402 + { 403 + union acpi_object params[2] = { 404 + { 405 + .integer = { 406 + .type = ACPI_TYPE_INTEGER, 407 + .value = reg, 408 + }, 409 + }, 410 + { 411 + .integer = { 412 + .type = ACPI_TYPE_INTEGER, 413 + .value = val, 414 + }, 415 + }, 416 + }; 417 + struct uniwill_data *data = context; 418 + struct acpi_object_list input = { 419 + .count = ARRAY_SIZE(params), 420 + .pointer = params, 421 + }; 422 + acpi_status status; 423 + 424 + status = acpi_evaluate_object(data->handle, "ECRW", &input, NULL); 425 + if (ACPI_FAILURE(status)) 426 + return -EIO; 427 + 428 + usleep_range(UNIWILL_EC_DELAY_US, UNIWILL_EC_DELAY_US * 2); 429 + 430 + return 0; 431 + } 432 + 433 + static int uniwill_ec_reg_read(void *context, unsigned int reg, unsigned int *val) 434 + { 435 + union acpi_object params[1] = { 436 + { 437 + .integer = { 438 + .type = ACPI_TYPE_INTEGER, 439 + .value = reg, 440 + }, 441 + }, 442 + }; 443 + struct uniwill_data *data = context; 444 + struct acpi_object_list input = { 445 + .count = ARRAY_SIZE(params), 446 + .pointer = params, 447 + }; 448 + unsigned long long output; 449 + acpi_status status; 450 + 451 + status = acpi_evaluate_integer(data->handle, "ECRR", &input, &output); 452 + if (ACPI_FAILURE(status)) 453 + return -EIO; 454 + 455 + if (output > U8_MAX) 456 + return -ENXIO; 457 + 458 + usleep_range(UNIWILL_EC_DELAY_US, UNIWILL_EC_DELAY_US * 2); 459 + 460 + *val = output; 461 + 462 + return 0; 463 + } 464 + 465 + static const struct regmap_bus uniwill_ec_bus = { 466 + .reg_write = uniwill_ec_reg_write, 467 + .reg_read = uniwill_ec_reg_read, 468 + .reg_format_endian_default = REGMAP_ENDIAN_LITTLE, 469 + .val_format_endian_default = REGMAP_ENDIAN_LITTLE, 470 + }; 471 + 472 + static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) 473 + { 474 + switch (reg) { 475 + case EC_ADDR_AP_OEM: 476 + case EC_ADDR_LIGHTBAR_AC_CTRL: 477 + case EC_ADDR_LIGHTBAR_AC_RED: 478 + case EC_ADDR_LIGHTBAR_AC_GREEN: 479 + case EC_ADDR_LIGHTBAR_AC_BLUE: 480 + case EC_ADDR_BIOS_OEM: 481 + case EC_ADDR_TRIGGER: 482 + case EC_ADDR_OEM_4: 483 + case EC_ADDR_CHARGE_CTRL: 484 + case EC_ADDR_LIGHTBAR_BAT_CTRL: 485 + case EC_ADDR_LIGHTBAR_BAT_RED: 486 + case EC_ADDR_LIGHTBAR_BAT_GREEN: 487 + case EC_ADDR_LIGHTBAR_BAT_BLUE: 488 + return true; 489 + default: 490 + return false; 491 + } 492 + } 493 + 494 + static bool uniwill_readable_reg(struct device *dev, unsigned int reg) 495 + { 496 + switch (reg) { 497 + case EC_ADDR_CPU_TEMP: 498 + case EC_ADDR_GPU_TEMP: 499 + case EC_ADDR_MAIN_FAN_RPM_1: 500 + case EC_ADDR_MAIN_FAN_RPM_2: 501 + case EC_ADDR_SECOND_FAN_RPM_1: 502 + case EC_ADDR_SECOND_FAN_RPM_2: 503 + case EC_ADDR_BAT_ALERT: 504 + case EC_ADDR_PROJECT_ID: 505 + case EC_ADDR_AP_OEM: 506 + case EC_ADDR_LIGHTBAR_AC_CTRL: 507 + case EC_ADDR_LIGHTBAR_AC_RED: 508 + case EC_ADDR_LIGHTBAR_AC_GREEN: 509 + case EC_ADDR_LIGHTBAR_AC_BLUE: 510 + case EC_ADDR_BIOS_OEM: 511 + case EC_ADDR_PWM_1: 512 + case EC_ADDR_PWM_2: 513 + case EC_ADDR_TRIGGER: 514 + case EC_ADDR_SWITCH_STATUS: 515 + case EC_ADDR_OEM_4: 516 + case EC_ADDR_CHARGE_CTRL: 517 + case EC_ADDR_LIGHTBAR_BAT_CTRL: 518 + case EC_ADDR_LIGHTBAR_BAT_RED: 519 + case EC_ADDR_LIGHTBAR_BAT_GREEN: 520 + case EC_ADDR_LIGHTBAR_BAT_BLUE: 521 + return true; 522 + default: 523 + return false; 524 + } 525 + } 526 + 527 + static bool uniwill_volatile_reg(struct device *dev, unsigned int reg) 528 + { 529 + switch (reg) { 530 + case EC_ADDR_CPU_TEMP: 531 + case EC_ADDR_GPU_TEMP: 532 + case EC_ADDR_MAIN_FAN_RPM_1: 533 + case EC_ADDR_MAIN_FAN_RPM_2: 534 + case EC_ADDR_SECOND_FAN_RPM_1: 535 + case EC_ADDR_SECOND_FAN_RPM_2: 536 + case EC_ADDR_BAT_ALERT: 537 + case EC_ADDR_PWM_1: 538 + case EC_ADDR_PWM_2: 539 + case EC_ADDR_TRIGGER: 540 + case EC_ADDR_SWITCH_STATUS: 541 + case EC_ADDR_CHARGE_CTRL: 542 + return true; 543 + default: 544 + return false; 545 + } 546 + } 547 + 548 + static const struct regmap_config uniwill_ec_config = { 549 + .reg_bits = 16, 550 + .val_bits = 8, 551 + .writeable_reg = uniwill_writeable_reg, 552 + .readable_reg = uniwill_readable_reg, 553 + .volatile_reg = uniwill_volatile_reg, 554 + .can_sleep = true, 555 + .max_register = 0xFFF, 556 + .cache_type = REGCACHE_MAPLE, 557 + .use_single_read = true, 558 + .use_single_write = true, 559 + }; 560 + 561 + static ssize_t fn_lock_toggle_enable_store(struct device *dev, struct device_attribute *attr, 562 + const char *buf, size_t count) 563 + { 564 + struct uniwill_data *data = dev_get_drvdata(dev); 565 + unsigned int value; 566 + bool enable; 567 + int ret; 568 + 569 + ret = kstrtobool(buf, &enable); 570 + if (ret < 0) 571 + return ret; 572 + 573 + if (enable) 574 + value = FN_LOCK_STATUS; 575 + else 576 + value = 0; 577 + 578 + ret = regmap_update_bits(data->regmap, EC_ADDR_BIOS_OEM, FN_LOCK_STATUS, value); 579 + if (ret < 0) 580 + return ret; 581 + 582 + return count; 583 + } 584 + 585 + static ssize_t fn_lock_toggle_enable_show(struct device *dev, struct device_attribute *attr, 586 + char *buf) 587 + { 588 + struct uniwill_data *data = dev_get_drvdata(dev); 589 + unsigned int value; 590 + int ret; 591 + 592 + ret = regmap_read(data->regmap, EC_ADDR_BIOS_OEM, &value); 593 + if (ret < 0) 594 + return ret; 595 + 596 + return sysfs_emit(buf, "%d\n", !!(value & FN_LOCK_STATUS)); 597 + } 598 + 599 + static DEVICE_ATTR_RW(fn_lock_toggle_enable); 600 + 601 + static ssize_t super_key_toggle_enable_store(struct device *dev, struct device_attribute *attr, 602 + const char *buf, size_t count) 603 + { 604 + struct uniwill_data *data = dev_get_drvdata(dev); 605 + unsigned int value; 606 + bool enable; 607 + int ret; 608 + 609 + ret = kstrtobool(buf, &enable); 610 + if (ret < 0) 611 + return ret; 612 + 613 + guard(mutex)(&data->super_key_lock); 614 + 615 + ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value); 616 + if (ret < 0) 617 + return ret; 618 + 619 + /* 620 + * We can only toggle the super key lock, so we return early if the setting 621 + * is already in the correct state. 622 + */ 623 + if (enable == !(value & SUPER_KEY_LOCK_STATUS)) 624 + return count; 625 + 626 + ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_SUPER_KEY_LOCK, 627 + TRIGGER_SUPER_KEY_LOCK); 628 + if (ret < 0) 629 + return ret; 630 + 631 + return count; 632 + } 633 + 634 + static ssize_t super_key_toggle_enable_show(struct device *dev, struct device_attribute *attr, 635 + char *buf) 636 + { 637 + struct uniwill_data *data = dev_get_drvdata(dev); 638 + unsigned int value; 639 + int ret; 640 + 641 + ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value); 642 + if (ret < 0) 643 + return ret; 644 + 645 + return sysfs_emit(buf, "%d\n", !(value & SUPER_KEY_LOCK_STATUS)); 646 + } 647 + 648 + static DEVICE_ATTR_RW(super_key_toggle_enable); 649 + 650 + static ssize_t touchpad_toggle_enable_store(struct device *dev, struct device_attribute *attr, 651 + const char *buf, size_t count) 652 + { 653 + struct uniwill_data *data = dev_get_drvdata(dev); 654 + unsigned int value; 655 + bool enable; 656 + int ret; 657 + 658 + ret = kstrtobool(buf, &enable); 659 + if (ret < 0) 660 + return ret; 661 + 662 + if (enable) 663 + value = 0; 664 + else 665 + value = TOUCHPAD_TOGGLE_OFF; 666 + 667 + ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_4, TOUCHPAD_TOGGLE_OFF, value); 668 + if (ret < 0) 669 + return ret; 670 + 671 + return count; 672 + } 673 + 674 + static ssize_t touchpad_toggle_enable_show(struct device *dev, struct device_attribute *attr, 675 + char *buf) 676 + { 677 + struct uniwill_data *data = dev_get_drvdata(dev); 678 + unsigned int value; 679 + int ret; 680 + 681 + ret = regmap_read(data->regmap, EC_ADDR_OEM_4, &value); 682 + if (ret < 0) 683 + return ret; 684 + 685 + return sysfs_emit(buf, "%d\n", !(value & TOUCHPAD_TOGGLE_OFF)); 686 + } 687 + 688 + static DEVICE_ATTR_RW(touchpad_toggle_enable); 689 + 690 + static ssize_t rainbow_animation_store(struct device *dev, struct device_attribute *attr, 691 + const char *buf, size_t count) 692 + { 693 + struct uniwill_data *data = dev_get_drvdata(dev); 694 + unsigned int value; 695 + bool enable; 696 + int ret; 697 + 698 + ret = kstrtobool(buf, &enable); 699 + if (ret < 0) 700 + return ret; 701 + 702 + if (enable) 703 + value = LIGHTBAR_WELCOME; 704 + else 705 + value = 0; 706 + 707 + guard(mutex)(&data->led_lock); 708 + 709 + ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_WELCOME, value); 710 + if (ret < 0) 711 + return ret; 712 + 713 + ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_WELCOME, value); 714 + if (ret < 0) 715 + return ret; 716 + 717 + return count; 718 + } 719 + 720 + static ssize_t rainbow_animation_show(struct device *dev, struct device_attribute *attr, char *buf) 721 + { 722 + struct uniwill_data *data = dev_get_drvdata(dev); 723 + unsigned int value; 724 + int ret; 725 + 726 + ret = regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value); 727 + if (ret < 0) 728 + return ret; 729 + 730 + return sysfs_emit(buf, "%d\n", !!(value & LIGHTBAR_WELCOME)); 731 + } 732 + 733 + static DEVICE_ATTR_RW(rainbow_animation); 734 + 735 + static ssize_t breathing_in_suspend_store(struct device *dev, struct device_attribute *attr, 736 + const char *buf, size_t count) 737 + { 738 + struct uniwill_data *data = dev_get_drvdata(dev); 739 + unsigned int value; 740 + bool enable; 741 + int ret; 742 + 743 + ret = kstrtobool(buf, &enable); 744 + if (ret < 0) 745 + return ret; 746 + 747 + if (enable) 748 + value = 0; 749 + else 750 + value = LIGHTBAR_S3_OFF; 751 + 752 + /* We only access a single register here, so we do not need to use data->led_lock */ 753 + ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_S3_OFF, value); 754 + if (ret < 0) 755 + return ret; 756 + 757 + return count; 758 + } 759 + 760 + static ssize_t breathing_in_suspend_show(struct device *dev, struct device_attribute *attr, 761 + char *buf) 762 + { 763 + struct uniwill_data *data = dev_get_drvdata(dev); 764 + unsigned int value; 765 + int ret; 766 + 767 + ret = regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value); 768 + if (ret < 0) 769 + return ret; 770 + 771 + return sysfs_emit(buf, "%d\n", !(value & LIGHTBAR_S3_OFF)); 772 + } 773 + 774 + static DEVICE_ATTR_RW(breathing_in_suspend); 775 + 776 + static struct attribute *uniwill_attrs[] = { 777 + /* Keyboard-related */ 778 + &dev_attr_fn_lock_toggle_enable.attr, 779 + &dev_attr_super_key_toggle_enable.attr, 780 + &dev_attr_touchpad_toggle_enable.attr, 781 + /* Lightbar-related */ 782 + &dev_attr_rainbow_animation.attr, 783 + &dev_attr_breathing_in_suspend.attr, 784 + NULL 785 + }; 786 + 787 + static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) 788 + { 789 + if (attr == &dev_attr_fn_lock_toggle_enable.attr) { 790 + if (supported_features & UNIWILL_FEATURE_FN_LOCK_TOGGLE) 791 + return attr->mode; 792 + } 793 + 794 + if (attr == &dev_attr_super_key_toggle_enable.attr) { 795 + if (supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE) 796 + return attr->mode; 797 + } 798 + 799 + if (attr == &dev_attr_touchpad_toggle_enable.attr) { 800 + if (supported_features & UNIWILL_FEATURE_TOUCHPAD_TOGGLE) 801 + return attr->mode; 802 + } 803 + 804 + if (attr == &dev_attr_rainbow_animation.attr || 805 + attr == &dev_attr_breathing_in_suspend.attr) { 806 + if (supported_features & UNIWILL_FEATURE_LIGHTBAR) 807 + return attr->mode; 808 + } 809 + 810 + return 0; 811 + } 812 + 813 + static const struct attribute_group uniwill_group = { 814 + .is_visible = uniwill_attr_is_visible, 815 + .attrs = uniwill_attrs, 816 + }; 817 + 818 + static const struct attribute_group *uniwill_groups[] = { 819 + &uniwill_group, 820 + NULL 821 + }; 822 + 823 + static int uniwill_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, 824 + long *val) 825 + { 826 + struct uniwill_data *data = dev_get_drvdata(dev); 827 + unsigned int value; 828 + __be16 rpm; 829 + int ret; 830 + 831 + switch (type) { 832 + case hwmon_temp: 833 + switch (channel) { 834 + case 0: 835 + ret = regmap_read(data->regmap, EC_ADDR_CPU_TEMP, &value); 836 + break; 837 + case 1: 838 + ret = regmap_read(data->regmap, EC_ADDR_GPU_TEMP, &value); 839 + break; 840 + default: 841 + return -EOPNOTSUPP; 842 + } 843 + 844 + if (ret < 0) 845 + return ret; 846 + 847 + *val = value * MILLIDEGREE_PER_DEGREE; 848 + return 0; 849 + case hwmon_fan: 850 + switch (channel) { 851 + case 0: 852 + ret = regmap_bulk_read(data->regmap, EC_ADDR_MAIN_FAN_RPM_1, &rpm, 853 + sizeof(rpm)); 854 + break; 855 + case 1: 856 + ret = regmap_bulk_read(data->regmap, EC_ADDR_SECOND_FAN_RPM_1, &rpm, 857 + sizeof(rpm)); 858 + break; 859 + default: 860 + return -EOPNOTSUPP; 861 + } 862 + 863 + if (ret < 0) 864 + return ret; 865 + 866 + *val = be16_to_cpu(rpm); 867 + return 0; 868 + case hwmon_pwm: 869 + switch (channel) { 870 + case 0: 871 + ret = regmap_read(data->regmap, EC_ADDR_PWM_1, &value); 872 + break; 873 + case 1: 874 + ret = regmap_read(data->regmap, EC_ADDR_PWM_2, &value); 875 + break; 876 + default: 877 + return -EOPNOTSUPP; 878 + } 879 + 880 + if (ret < 0) 881 + return ret; 882 + 883 + *val = fixp_linear_interpolate(0, 0, PWM_MAX, U8_MAX, value); 884 + return 0; 885 + default: 886 + return -EOPNOTSUPP; 887 + } 888 + } 889 + 890 + static int uniwill_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, 891 + int channel, const char **str) 892 + { 893 + switch (type) { 894 + case hwmon_temp: 895 + *str = uniwill_temp_labels[channel]; 896 + return 0; 897 + case hwmon_fan: 898 + *str = uniwill_fan_labels[channel]; 899 + return 0; 900 + default: 901 + return -EOPNOTSUPP; 902 + } 903 + } 904 + 905 + static const struct hwmon_ops uniwill_ops = { 906 + .visible = 0444, 907 + .read = uniwill_read, 908 + .read_string = uniwill_read_string, 909 + }; 910 + 911 + static const struct hwmon_channel_info * const uniwill_info[] = { 912 + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), 913 + HWMON_CHANNEL_INFO(temp, 914 + HWMON_T_INPUT | HWMON_T_LABEL, 915 + HWMON_T_INPUT | HWMON_T_LABEL), 916 + HWMON_CHANNEL_INFO(fan, 917 + HWMON_F_INPUT | HWMON_F_LABEL, 918 + HWMON_F_INPUT | HWMON_F_LABEL), 919 + HWMON_CHANNEL_INFO(pwm, 920 + HWMON_PWM_INPUT, 921 + HWMON_PWM_INPUT), 922 + NULL 923 + }; 924 + 925 + static const struct hwmon_chip_info uniwill_chip_info = { 926 + .ops = &uniwill_ops, 927 + .info = uniwill_info, 928 + }; 929 + 930 + static int uniwill_hwmon_init(struct uniwill_data *data) 931 + { 932 + struct device *hdev; 933 + 934 + if (!(supported_features & UNIWILL_FEATURE_HWMON)) 935 + return 0; 936 + 937 + hdev = devm_hwmon_device_register_with_info(data->dev, "uniwill", data, 938 + &uniwill_chip_info, NULL); 939 + 940 + return PTR_ERR_OR_ZERO(hdev); 941 + } 942 + 943 + static const unsigned int uniwill_led_channel_to_bat_reg[LED_CHANNELS] = { 944 + EC_ADDR_LIGHTBAR_BAT_RED, 945 + EC_ADDR_LIGHTBAR_BAT_GREEN, 946 + EC_ADDR_LIGHTBAR_BAT_BLUE, 947 + }; 948 + 949 + static const unsigned int uniwill_led_channel_to_ac_reg[LED_CHANNELS] = { 950 + EC_ADDR_LIGHTBAR_AC_RED, 951 + EC_ADDR_LIGHTBAR_AC_GREEN, 952 + EC_ADDR_LIGHTBAR_AC_BLUE, 953 + }; 954 + 955 + static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) 956 + { 957 + struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); 958 + struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, led_mc_cdev); 959 + unsigned int value; 960 + int ret; 961 + 962 + ret = led_mc_calc_color_components(led_mc_cdev, brightness); 963 + if (ret < 0) 964 + return ret; 965 + 966 + guard(mutex)(&data->led_lock); 967 + 968 + for (int i = 0; i < LED_CHANNELS; i++) { 969 + /* Prevent the brightness values from overflowing */ 970 + value = min(LED_MAX_BRIGHTNESS, data->led_mc_subled_info[i].brightness); 971 + ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); 972 + if (ret < 0) 973 + return ret; 974 + 975 + ret = regmap_write(data->regmap, uniwill_led_channel_to_bat_reg[i], value); 976 + if (ret < 0) 977 + return ret; 978 + } 979 + 980 + if (brightness) 981 + value = 0; 982 + else 983 + value = LIGHTBAR_S0_OFF; 984 + 985 + ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_S0_OFF, value); 986 + if (ret < 0) 987 + return ret; 988 + 989 + return regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_S0_OFF, value); 990 + } 991 + 992 + #define LIGHTBAR_MASK (LIGHTBAR_APP_EXISTS | LIGHTBAR_S0_OFF | LIGHTBAR_S3_OFF | LIGHTBAR_WELCOME) 993 + 994 + static int uniwill_led_init(struct uniwill_data *data) 995 + { 996 + struct led_init_data init_data = { 997 + .devicename = DRIVER_NAME, 998 + .default_label = "multicolor:" LED_FUNCTION_STATUS, 999 + .devname_mandatory = true, 1000 + }; 1001 + unsigned int color_indices[3] = { 1002 + LED_COLOR_ID_RED, 1003 + LED_COLOR_ID_GREEN, 1004 + LED_COLOR_ID_BLUE, 1005 + }; 1006 + unsigned int value; 1007 + int ret; 1008 + 1009 + if (!(supported_features & UNIWILL_FEATURE_LIGHTBAR)) 1010 + return 0; 1011 + 1012 + ret = devm_mutex_init(data->dev, &data->led_lock); 1013 + if (ret < 0) 1014 + return ret; 1015 + 1016 + /* 1017 + * The EC has separate lightbar settings for AC and battery mode, 1018 + * so we have to ensure that both settings are the same. 1019 + */ 1020 + ret = regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value); 1021 + if (ret < 0) 1022 + return ret; 1023 + 1024 + value |= LIGHTBAR_APP_EXISTS; 1025 + ret = regmap_write(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, value); 1026 + if (ret < 0) 1027 + return ret; 1028 + 1029 + /* 1030 + * The breathing animation during suspend is not supported when 1031 + * running on battery power. 1032 + */ 1033 + value |= LIGHTBAR_S3_OFF; 1034 + ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_MASK, value); 1035 + if (ret < 0) 1036 + return ret; 1037 + 1038 + data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; 1039 + data->led_mc_cdev.led_cdev.max_brightness = LED_MAX_BRIGHTNESS; 1040 + data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT; 1041 + data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set; 1042 + 1043 + if (value & LIGHTBAR_S0_OFF) 1044 + data->led_mc_cdev.led_cdev.brightness = 0; 1045 + else 1046 + data->led_mc_cdev.led_cdev.brightness = LED_MAX_BRIGHTNESS; 1047 + 1048 + for (int i = 0; i < LED_CHANNELS; i++) { 1049 + data->led_mc_subled_info[i].color_index = color_indices[i]; 1050 + 1051 + ret = regmap_read(data->regmap, uniwill_led_channel_to_ac_reg[i], &value); 1052 + if (ret < 0) 1053 + return ret; 1054 + 1055 + /* 1056 + * Make sure that the initial intensity value is not greater than 1057 + * the maximum brightness. 1058 + */ 1059 + value = min(LED_MAX_BRIGHTNESS, value); 1060 + ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); 1061 + if (ret < 0) 1062 + return ret; 1063 + 1064 + ret = regmap_write(data->regmap, uniwill_led_channel_to_bat_reg[i], value); 1065 + if (ret < 0) 1066 + return ret; 1067 + 1068 + data->led_mc_subled_info[i].intensity = value; 1069 + data->led_mc_subled_info[i].channel = i; 1070 + } 1071 + 1072 + data->led_mc_cdev.subled_info = data->led_mc_subled_info; 1073 + data->led_mc_cdev.num_colors = LED_CHANNELS; 1074 + 1075 + return devm_led_classdev_multicolor_register_ext(data->dev, &data->led_mc_cdev, 1076 + &init_data); 1077 + } 1078 + 1079 + static int uniwill_get_property(struct power_supply *psy, const struct power_supply_ext *ext, 1080 + void *drvdata, enum power_supply_property psp, 1081 + union power_supply_propval *val) 1082 + { 1083 + struct uniwill_data *data = drvdata; 1084 + union power_supply_propval prop; 1085 + unsigned int regval; 1086 + int ret; 1087 + 1088 + switch (psp) { 1089 + case POWER_SUPPLY_PROP_HEALTH: 1090 + ret = power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_PRESENT, &prop); 1091 + if (ret < 0) 1092 + return ret; 1093 + 1094 + if (!prop.intval) { 1095 + val->intval = POWER_SUPPLY_HEALTH_NO_BATTERY; 1096 + return 0; 1097 + } 1098 + 1099 + ret = power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_STATUS, &prop); 1100 + if (ret < 0) 1101 + return ret; 1102 + 1103 + if (prop.intval == POWER_SUPPLY_STATUS_UNKNOWN) { 1104 + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; 1105 + return 0; 1106 + } 1107 + 1108 + ret = regmap_read(data->regmap, EC_ADDR_BAT_ALERT, &regval); 1109 + if (ret < 0) 1110 + return ret; 1111 + 1112 + if (regval) { 1113 + /* Charging issue */ 1114 + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; 1115 + return 0; 1116 + } 1117 + 1118 + val->intval = POWER_SUPPLY_HEALTH_GOOD; 1119 + return 0; 1120 + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: 1121 + ret = regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &regval); 1122 + if (ret < 0) 1123 + return ret; 1124 + 1125 + val->intval = clamp_val(FIELD_GET(CHARGE_CTRL_MASK, regval), 0, 100); 1126 + return 0; 1127 + default: 1128 + return -EINVAL; 1129 + } 1130 + } 1131 + 1132 + static int uniwill_set_property(struct power_supply *psy, const struct power_supply_ext *ext, 1133 + void *drvdata, enum power_supply_property psp, 1134 + const union power_supply_propval *val) 1135 + { 1136 + struct uniwill_data *data = drvdata; 1137 + 1138 + switch (psp) { 1139 + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: 1140 + if (val->intval < 1 || val->intval > 100) 1141 + return -EINVAL; 1142 + 1143 + return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK, 1144 + val->intval); 1145 + default: 1146 + return -EINVAL; 1147 + } 1148 + } 1149 + 1150 + static int uniwill_property_is_writeable(struct power_supply *psy, 1151 + const struct power_supply_ext *ext, void *drvdata, 1152 + enum power_supply_property psp) 1153 + { 1154 + if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) 1155 + return true; 1156 + 1157 + return false; 1158 + } 1159 + 1160 + static const enum power_supply_property uniwill_properties[] = { 1161 + POWER_SUPPLY_PROP_HEALTH, 1162 + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, 1163 + }; 1164 + 1165 + static const struct power_supply_ext uniwill_extension = { 1166 + .name = DRIVER_NAME, 1167 + .properties = uniwill_properties, 1168 + .num_properties = ARRAY_SIZE(uniwill_properties), 1169 + .get_property = uniwill_get_property, 1170 + .set_property = uniwill_set_property, 1171 + .property_is_writeable = uniwill_property_is_writeable, 1172 + }; 1173 + 1174 + static int uniwill_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook) 1175 + { 1176 + struct uniwill_data *data = container_of(hook, struct uniwill_data, hook); 1177 + struct uniwill_battery_entry *entry; 1178 + int ret; 1179 + 1180 + entry = kzalloc(sizeof(*entry), GFP_KERNEL); 1181 + if (!entry) 1182 + return -ENOMEM; 1183 + 1184 + ret = power_supply_register_extension(battery, &uniwill_extension, data->dev, data); 1185 + if (ret < 0) { 1186 + kfree(entry); 1187 + return ret; 1188 + } 1189 + 1190 + guard(mutex)(&data->battery_lock); 1191 + 1192 + entry->battery = battery; 1193 + list_add(&entry->head, &data->batteries); 1194 + 1195 + return 0; 1196 + } 1197 + 1198 + static int uniwill_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook) 1199 + { 1200 + struct uniwill_data *data = container_of(hook, struct uniwill_data, hook); 1201 + struct uniwill_battery_entry *entry, *tmp; 1202 + 1203 + scoped_guard(mutex, &data->battery_lock) { 1204 + list_for_each_entry_safe(entry, tmp, &data->batteries, head) { 1205 + if (entry->battery == battery) { 1206 + list_del(&entry->head); 1207 + kfree(entry); 1208 + break; 1209 + } 1210 + } 1211 + } 1212 + 1213 + power_supply_unregister_extension(battery, &uniwill_extension); 1214 + 1215 + return 0; 1216 + } 1217 + 1218 + static int uniwill_battery_init(struct uniwill_data *data) 1219 + { 1220 + int ret; 1221 + 1222 + if (!(supported_features & UNIWILL_FEATURE_BATTERY)) 1223 + return 0; 1224 + 1225 + ret = devm_mutex_init(data->dev, &data->battery_lock); 1226 + if (ret < 0) 1227 + return ret; 1228 + 1229 + INIT_LIST_HEAD(&data->batteries); 1230 + data->hook.name = "Uniwill Battery Extension"; 1231 + data->hook.add_battery = uniwill_add_battery; 1232 + data->hook.remove_battery = uniwill_remove_battery; 1233 + 1234 + return devm_battery_hook_register(data->dev, &data->hook); 1235 + } 1236 + 1237 + static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action, void *dummy) 1238 + { 1239 + struct uniwill_data *data = container_of(nb, struct uniwill_data, nb); 1240 + struct uniwill_battery_entry *entry; 1241 + 1242 + switch (action) { 1243 + case UNIWILL_OSD_BATTERY_ALERT: 1244 + mutex_lock(&data->battery_lock); 1245 + list_for_each_entry(entry, &data->batteries, head) { 1246 + power_supply_changed(entry->battery); 1247 + } 1248 + mutex_unlock(&data->battery_lock); 1249 + 1250 + return NOTIFY_OK; 1251 + default: 1252 + mutex_lock(&data->input_lock); 1253 + sparse_keymap_report_event(data->input_device, action, 1, true); 1254 + mutex_unlock(&data->input_lock); 1255 + 1256 + return NOTIFY_OK; 1257 + } 1258 + } 1259 + 1260 + static int uniwill_input_init(struct uniwill_data *data) 1261 + { 1262 + int ret; 1263 + 1264 + ret = devm_mutex_init(data->dev, &data->input_lock); 1265 + if (ret < 0) 1266 + return ret; 1267 + 1268 + data->input_device = devm_input_allocate_device(data->dev); 1269 + if (!data->input_device) 1270 + return -ENOMEM; 1271 + 1272 + ret = sparse_keymap_setup(data->input_device, uniwill_keymap, NULL); 1273 + if (ret < 0) 1274 + return ret; 1275 + 1276 + data->input_device->name = "Uniwill WMI hotkeys"; 1277 + data->input_device->phys = "wmi/input0"; 1278 + data->input_device->id.bustype = BUS_HOST; 1279 + ret = input_register_device(data->input_device); 1280 + if (ret < 0) 1281 + return ret; 1282 + 1283 + data->nb.notifier_call = uniwill_notifier_call; 1284 + 1285 + return devm_uniwill_wmi_register_notifier(data->dev, &data->nb); 1286 + } 1287 + 1288 + static void uniwill_disable_manual_control(void *context) 1289 + { 1290 + struct uniwill_data *data = context; 1291 + 1292 + regmap_clear_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); 1293 + } 1294 + 1295 + static int uniwill_ec_init(struct uniwill_data *data) 1296 + { 1297 + unsigned int value; 1298 + int ret; 1299 + 1300 + ret = regmap_read(data->regmap, EC_ADDR_PROJECT_ID, &value); 1301 + if (ret < 0) 1302 + return ret; 1303 + 1304 + dev_dbg(data->dev, "Project ID: %u\n", value); 1305 + 1306 + ret = regmap_set_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); 1307 + if (ret < 0) 1308 + return ret; 1309 + 1310 + return devm_add_action_or_reset(data->dev, uniwill_disable_manual_control, data); 1311 + } 1312 + 1313 + static int uniwill_probe(struct platform_device *pdev) 1314 + { 1315 + struct uniwill_data *data; 1316 + struct regmap *regmap; 1317 + acpi_handle handle; 1318 + int ret; 1319 + 1320 + handle = ACPI_HANDLE(&pdev->dev); 1321 + if (!handle) 1322 + return -ENODEV; 1323 + 1324 + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 1325 + if (!data) 1326 + return -ENOMEM; 1327 + 1328 + data->dev = &pdev->dev; 1329 + data->handle = handle; 1330 + platform_set_drvdata(pdev, data); 1331 + 1332 + regmap = devm_regmap_init(&pdev->dev, &uniwill_ec_bus, data, &uniwill_ec_config); 1333 + if (IS_ERR(regmap)) 1334 + return PTR_ERR(regmap); 1335 + 1336 + data->regmap = regmap; 1337 + ret = devm_mutex_init(&pdev->dev, &data->super_key_lock); 1338 + if (ret < 0) 1339 + return ret; 1340 + 1341 + ret = uniwill_ec_init(data); 1342 + if (ret < 0) 1343 + return ret; 1344 + 1345 + ret = uniwill_battery_init(data); 1346 + if (ret < 0) 1347 + return ret; 1348 + 1349 + ret = uniwill_led_init(data); 1350 + if (ret < 0) 1351 + return ret; 1352 + 1353 + ret = uniwill_hwmon_init(data); 1354 + if (ret < 0) 1355 + return ret; 1356 + 1357 + return uniwill_input_init(data); 1358 + } 1359 + 1360 + static void uniwill_shutdown(struct platform_device *pdev) 1361 + { 1362 + struct uniwill_data *data = platform_get_drvdata(pdev); 1363 + 1364 + regmap_clear_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); 1365 + } 1366 + 1367 + static int uniwill_suspend_keyboard(struct uniwill_data *data) 1368 + { 1369 + if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE)) 1370 + return 0; 1371 + 1372 + /* 1373 + * The EC_ADDR_SWITCH_STATUS is marked as volatile, so we have to restore it 1374 + * ourselves. 1375 + */ 1376 + return regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &data->last_switch_status); 1377 + } 1378 + 1379 + static int uniwill_suspend_battery(struct uniwill_data *data) 1380 + { 1381 + if (!(supported_features & UNIWILL_FEATURE_BATTERY)) 1382 + return 0; 1383 + 1384 + /* 1385 + * Save the current charge limit in order to restore it during resume. 1386 + * We cannot use the regmap code for that since this register needs to 1387 + * be declared as volatile due to CHARGE_CTRL_REACHED. 1388 + */ 1389 + return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl); 1390 + } 1391 + 1392 + static int uniwill_suspend(struct device *dev) 1393 + { 1394 + struct uniwill_data *data = dev_get_drvdata(dev); 1395 + int ret; 1396 + 1397 + ret = uniwill_suspend_keyboard(data); 1398 + if (ret < 0) 1399 + return ret; 1400 + 1401 + ret = uniwill_suspend_battery(data); 1402 + if (ret < 0) 1403 + return ret; 1404 + 1405 + regcache_cache_only(data->regmap, true); 1406 + regcache_mark_dirty(data->regmap); 1407 + 1408 + return 0; 1409 + } 1410 + 1411 + static int uniwill_resume_keyboard(struct uniwill_data *data) 1412 + { 1413 + unsigned int value; 1414 + int ret; 1415 + 1416 + if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE)) 1417 + return 0; 1418 + 1419 + ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value); 1420 + if (ret < 0) 1421 + return ret; 1422 + 1423 + if ((data->last_switch_status & SUPER_KEY_LOCK_STATUS) == (value & SUPER_KEY_LOCK_STATUS)) 1424 + return 0; 1425 + 1426 + return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_SUPER_KEY_LOCK, 1427 + TRIGGER_SUPER_KEY_LOCK); 1428 + } 1429 + 1430 + static int uniwill_resume_battery(struct uniwill_data *data) 1431 + { 1432 + if (!(supported_features & UNIWILL_FEATURE_BATTERY)) 1433 + return 0; 1434 + 1435 + return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK, 1436 + data->last_charge_ctrl); 1437 + } 1438 + 1439 + static int uniwill_resume(struct device *dev) 1440 + { 1441 + struct uniwill_data *data = dev_get_drvdata(dev); 1442 + int ret; 1443 + 1444 + regcache_cache_only(data->regmap, false); 1445 + 1446 + ret = regcache_sync(data->regmap); 1447 + if (ret < 0) 1448 + return ret; 1449 + 1450 + ret = uniwill_resume_keyboard(data); 1451 + if (ret < 0) 1452 + return ret; 1453 + 1454 + return uniwill_resume_battery(data); 1455 + } 1456 + 1457 + static DEFINE_SIMPLE_DEV_PM_OPS(uniwill_pm_ops, uniwill_suspend, uniwill_resume); 1458 + 1459 + /* 1460 + * We only use the DMI table for auoloading because the ACPI device itself 1461 + * does not guarantee that the underlying EC implementation is supported. 1462 + */ 1463 + static const struct acpi_device_id uniwill_id_table[] = { 1464 + { "INOU0000" }, 1465 + { }, 1466 + }; 1467 + 1468 + static struct platform_driver uniwill_driver = { 1469 + .driver = { 1470 + .name = DRIVER_NAME, 1471 + .dev_groups = uniwill_groups, 1472 + .probe_type = PROBE_PREFER_ASYNCHRONOUS, 1473 + .acpi_match_table = uniwill_id_table, 1474 + .pm = pm_sleep_ptr(&uniwill_pm_ops), 1475 + }, 1476 + .probe = uniwill_probe, 1477 + .shutdown = uniwill_shutdown, 1478 + }; 1479 + 1480 + static const struct dmi_system_id uniwill_dmi_table[] __initconst = { 1481 + { 1482 + .ident = "Intel NUC x15", 1483 + .matches = { 1484 + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), 1485 + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPAC71H"), 1486 + }, 1487 + .driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE | 1488 + UNIWILL_FEATURE_SUPER_KEY_TOGGLE | 1489 + UNIWILL_FEATURE_TOUCHPAD_TOGGLE | 1490 + UNIWILL_FEATURE_BATTERY | 1491 + UNIWILL_FEATURE_HWMON), 1492 + }, 1493 + { 1494 + .ident = "Intel NUC x15", 1495 + .matches = { 1496 + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), 1497 + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71F"), 1498 + }, 1499 + .driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE | 1500 + UNIWILL_FEATURE_SUPER_KEY_TOGGLE | 1501 + UNIWILL_FEATURE_TOUCHPAD_TOGGLE | 1502 + UNIWILL_FEATURE_LIGHTBAR | 1503 + UNIWILL_FEATURE_BATTERY | 1504 + UNIWILL_FEATURE_HWMON), 1505 + }, 1506 + { } 1507 + }; 1508 + MODULE_DEVICE_TABLE(dmi, uniwill_dmi_table); 1509 + 1510 + static int __init uniwill_init(void) 1511 + { 1512 + const struct dmi_system_id *id; 1513 + int ret; 1514 + 1515 + id = dmi_first_match(uniwill_dmi_table); 1516 + if (!id) { 1517 + if (!force) 1518 + return -ENODEV; 1519 + 1520 + /* Assume that the device supports all features */ 1521 + supported_features = UINT_MAX; 1522 + pr_warn("Loading on a potentially unsupported device\n"); 1523 + } else { 1524 + supported_features = (uintptr_t)id->driver_data; 1525 + } 1526 + 1527 + ret = platform_driver_register(&uniwill_driver); 1528 + if (ret < 0) 1529 + return ret; 1530 + 1531 + ret = uniwill_wmi_register_driver(); 1532 + if (ret < 0) { 1533 + platform_driver_unregister(&uniwill_driver); 1534 + return ret; 1535 + } 1536 + 1537 + return 0; 1538 + } 1539 + module_init(uniwill_init); 1540 + 1541 + static void __exit uniwill_exit(void) 1542 + { 1543 + uniwill_wmi_unregister_driver(); 1544 + platform_driver_unregister(&uniwill_driver); 1545 + } 1546 + module_exit(uniwill_exit); 1547 + 1548 + MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>"); 1549 + MODULE_DESCRIPTION("Uniwill notebook driver"); 1550 + MODULE_LICENSE("GPL");
+92
drivers/platform/x86/uniwill/uniwill-wmi.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * Linux hotkey driver for Uniwill notebooks. 4 + * 5 + * Special thanks go to Pőcze Barnabás, Christoffer Sandberg and Werner Sembach 6 + * for supporting the development of this driver either through prior work or 7 + * by answering questions regarding the underlying WMI interface. 8 + * 9 + * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de> 10 + */ 11 + 12 + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 13 + 14 + #include <linux/acpi.h> 15 + #include <linux/device.h> 16 + #include <linux/init.h> 17 + #include <linux/mod_devicetable.h> 18 + #include <linux/notifier.h> 19 + #include <linux/printk.h> 20 + #include <linux/types.h> 21 + #include <linux/wmi.h> 22 + 23 + #include "uniwill-wmi.h" 24 + 25 + #define DRIVER_NAME "uniwill-wmi" 26 + #define UNIWILL_EVENT_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000" 27 + 28 + static BLOCKING_NOTIFIER_HEAD(uniwill_wmi_chain_head); 29 + 30 + static void devm_uniwill_wmi_unregister_notifier(void *data) 31 + { 32 + struct notifier_block *nb = data; 33 + 34 + blocking_notifier_chain_unregister(&uniwill_wmi_chain_head, nb); 35 + } 36 + 37 + int devm_uniwill_wmi_register_notifier(struct device *dev, struct notifier_block *nb) 38 + { 39 + int ret; 40 + 41 + ret = blocking_notifier_chain_register(&uniwill_wmi_chain_head, nb); 42 + if (ret < 0) 43 + return ret; 44 + 45 + return devm_add_action_or_reset(dev, devm_uniwill_wmi_unregister_notifier, nb); 46 + } 47 + 48 + static void uniwill_wmi_notify(struct wmi_device *wdev, union acpi_object *obj) 49 + { 50 + u32 value; 51 + 52 + if (obj->type != ACPI_TYPE_INTEGER) 53 + return; 54 + 55 + value = obj->integer.value; 56 + 57 + dev_dbg(&wdev->dev, "Received WMI event %u\n", value); 58 + 59 + blocking_notifier_call_chain(&uniwill_wmi_chain_head, value, NULL); 60 + } 61 + 62 + /* 63 + * We cannot fully trust this GUID since Uniwill just copied the WMI GUID 64 + * from the Windows driver example, and others probably did the same. 65 + * 66 + * Because of this we cannot use this WMI GUID for autoloading. Instead the 67 + * associated driver will be registered manually after matching a DMI table. 68 + */ 69 + static const struct wmi_device_id uniwill_wmi_id_table[] = { 70 + { UNIWILL_EVENT_GUID, NULL }, 71 + { } 72 + }; 73 + 74 + static struct wmi_driver uniwill_wmi_driver = { 75 + .driver = { 76 + .name = DRIVER_NAME, 77 + .probe_type = PROBE_PREFER_ASYNCHRONOUS, 78 + }, 79 + .id_table = uniwill_wmi_id_table, 80 + .notify = uniwill_wmi_notify, 81 + .no_singleton = true, 82 + }; 83 + 84 + int __init uniwill_wmi_register_driver(void) 85 + { 86 + return wmi_driver_register(&uniwill_wmi_driver); 87 + } 88 + 89 + void __exit uniwill_wmi_unregister_driver(void) 90 + { 91 + wmi_driver_unregister(&uniwill_wmi_driver); 92 + }
+127
drivers/platform/x86/uniwill/uniwill-wmi.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-or-later */ 2 + /* 3 + * Linux hotkey driver for Uniwill notebooks. 4 + * 5 + * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de> 6 + */ 7 + 8 + #ifndef UNIWILL_WMI_H 9 + #define UNIWILL_WMI_H 10 + 11 + #include <linux/init.h> 12 + 13 + #define UNIWILL_OSD_CAPSLOCK 0x01 14 + #define UNIWILL_OSD_NUMLOCK 0x02 15 + #define UNIWILL_OSD_SCROLLLOCK 0x03 16 + 17 + #define UNIWILL_OSD_TOUCHPAD_ON 0x04 18 + #define UNIWILL_OSD_TOUCHPAD_OFF 0x05 19 + 20 + #define UNIWILL_OSD_SILENT_MODE_ON 0x06 21 + #define UNIWILL_OSD_SILENT_MODE_OFF 0x07 22 + 23 + #define UNIWILL_OSD_WLAN_ON 0x08 24 + #define UNIWILL_OSD_WLAN_OFF 0x09 25 + 26 + #define UNIWILL_OSD_WIMAX_ON 0x0A 27 + #define UNIWILL_OSD_WIMAX_OFF 0x0B 28 + 29 + #define UNIWILL_OSD_BLUETOOTH_ON 0x0C 30 + #define UNIWILL_OSD_BLUETOOTH_OFF 0x0D 31 + 32 + #define UNIWILL_OSD_RF_ON 0x0E 33 + #define UNIWILL_OSD_RF_OFF 0x0F 34 + 35 + #define UNIWILL_OSD_3G_ON 0x10 36 + #define UNIWILL_OSD_3G_OFF 0x11 37 + 38 + #define UNIWILL_OSD_WEBCAM_ON 0x12 39 + #define UNIWILL_OSD_WEBCAM_OFF 0x13 40 + 41 + #define UNIWILL_OSD_BRIGHTNESSUP 0x14 42 + #define UNIWILL_OSD_BRIGHTNESSDOWN 0x15 43 + 44 + #define UNIWILL_OSD_RADIOON 0x1A 45 + #define UNIWILL_OSD_RADIOOFF 0x1B 46 + 47 + #define UNIWILL_OSD_POWERSAVE_ON 0x31 48 + #define UNIWILL_OSD_POWERSAVE_OFF 0x32 49 + 50 + #define UNIWILL_OSD_MENU 0x34 51 + 52 + #define UNIWILL_OSD_MUTE 0x35 53 + #define UNIWILL_OSD_VOLUMEDOWN 0x36 54 + #define UNIWILL_OSD_VOLUMEUP 0x37 55 + 56 + #define UNIWILL_OSD_MENU_2 0x38 57 + 58 + #define UNIWILL_OSD_LIGHTBAR_ON 0x39 59 + #define UNIWILL_OSD_LIGHTBAR_OFF 0x3A 60 + 61 + #define UNIWILL_OSD_KB_LED_LEVEL0 0x3B 62 + #define UNIWILL_OSD_KB_LED_LEVEL1 0x3C 63 + #define UNIWILL_OSD_KB_LED_LEVEL2 0x3D 64 + #define UNIWILL_OSD_KB_LED_LEVEL3 0x3E 65 + #define UNIWILL_OSD_KB_LED_LEVEL4 0x3F 66 + 67 + #define UNIWILL_OSD_SUPER_KEY_LOCK_ENABLE 0x40 68 + #define UNIWILL_OSD_SUPER_KEY_LOCK_DISABLE 0x41 69 + 70 + #define UNIWILL_OSD_MENU_JP 0x42 71 + 72 + #define UNIWILL_OSD_CAMERA_ON 0x90 73 + #define UNIWILL_OSD_CAMERA_OFF 0x91 74 + 75 + #define UNIWILL_OSD_RFKILL 0xA4 76 + 77 + #define UNIWILL_OSD_SUPER_KEY_LOCK_CHANGED 0xA5 78 + 79 + #define UNIWILL_OSD_LIGHTBAR_STATE_CHANGED 0xA6 80 + 81 + #define UNIWILL_OSD_FAN_BOOST_STATE_CHANGED 0xA7 82 + 83 + #define UNIWILL_OSD_LCD_SW 0xA9 84 + 85 + #define UNIWILL_OSD_FAN_OVERTEMP 0xAA 86 + 87 + #define UNIWILL_OSD_DC_ADAPTER_CHANGED 0xAB 88 + 89 + #define UNIWILL_OSD_BAT_HP_OFF 0xAC 90 + 91 + #define UNIWILL_OSD_FAN_DOWN_TEMP 0xAD 92 + 93 + #define UNIWILL_OSD_BATTERY_ALERT 0xAE 94 + 95 + #define UNIWILL_OSD_TIMAP_HAIERLB_SW 0xAF 96 + 97 + #define UNIWILL_OSD_PERFORMANCE_MODE_TOGGLE 0xB0 98 + 99 + #define UNIWILL_OSD_KBDILLUMDOWN 0xB1 100 + #define UNIWILL_OSD_KBDILLUMUP 0xB2 101 + 102 + #define UNIWILL_OSD_BACKLIGHT_LEVEL_CHANGE 0xB3 103 + #define UNIWILL_OSD_BACKLIGHT_POWER_CHANGE 0xB4 104 + 105 + #define UNIWILL_OSD_MIC_MUTE 0xB7 106 + 107 + #define UNIWILL_OSD_FN_LOCK 0xB8 108 + #define UNIWILL_OSD_KBDILLUMTOGGLE 0xB9 109 + 110 + #define UNIWILL_OSD_BAT_CHARGE_FULL_24_H 0xBE 111 + 112 + #define UNIWILL_OSD_BAT_ERM_UPDATE 0xBF 113 + 114 + #define UNIWILL_OSD_BENCHMARK_MODE_TOGGLE 0xC0 115 + 116 + #define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0 117 + 118 + struct device; 119 + struct notifier_block; 120 + 121 + int devm_uniwill_wmi_register_notifier(struct device *dev, struct notifier_block *nb); 122 + 123 + int __init uniwill_wmi_register_driver(void); 124 + 125 + void __exit uniwill_wmi_unregister_driver(void); 126 + 127 + #endif /* UNIWILL_WMI_H */