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

Configure Feed

Select the types of activity you want to include in your feed.

platform/x86: bitland-mifs-wmi: Add new Bitland MIFS WMI driver

Add a new driver for Bitland laptops that utilize the MIFS (MiInterface)
WMI interface.

The driver implements several features through the WMI interface:

- Platform Profile: Supports "Quiet", "Balanced", "Performance", and
"Full Speed" modes. The "Full Speed" mode is intelligently restricted
based on the AC adapter type (requires DC power, not supported on
USB-C charging) as required by the hardware.
- Hwmon: Provides monitoring for CPU, GPU, and System fan speeds,
as well as CPU temperature sensors.
- Keyboard Backlight: Integrated with the LED class device for
brightness control and provides sysfs attributes for keyboard modes
(cyclic, fixed, etc.).
- GPU Mode: Allows switching between Hybrid, Discrete, and UMA
graphics modes via sysfs.
- Hotkeys: Handles WMI events for system hotkeys (Calculator, Browser,
App launch) using sparse keymaps and reports status changes for
Airplane mode, Touchpad, and CapsLock.
- Fan Boost: Provides a sysfs interface to force fans to maximum speed.

The driver registers two WMI GUIDs:
- B60BFB48-3E5B-49E4-A0E9-8CFFE1B3434B: Control methods
- 46C93E13-EE9B-4262-8488-563BCA757FEF: Event notifications

Reviewed-by: Armin Wolf <W_Armin@gmx.de>
Signed-off-by: Mingyou Chen <qby140326@gmail.com>
Link: https://patch.msgid.link/20260323132218.444383-1-qby140326@gmail.com
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Mingyou Chen and committed by
Ilpo Järvinen
dc1ec4fa a5877e92

+1071
+207
Documentation/wmi/devices/bitland-mifs-wmi.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0-or-later 2 + 3 + ======================================== 4 + Bitland MIFS driver (bitland-mifs-wmi) 5 + ======================================== 6 + 7 + Introduction 8 + ============ 9 + 10 + 11 + EC WMI interface description 12 + ============================ 13 + 14 + The EC WMI interface description can be decoded from the embedded binary MOF (bmof) 15 + data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility: 16 + 17 + :: 18 + 19 + class WMIEvent : __ExtrinsicEvent { 20 + }; 21 + 22 + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x40A"), Description("Root WMI HID_EVENT20"), guid("{46c93e13-ee9b-4262-8488-563bca757fef}")] 23 + class HID_EVENT20 : WmiEvent { 24 + [key, read] string InstanceName; 25 + [read] boolean Active; 26 + [WmiDataId(1), read, write, Description("Package Data")] uint8 EventDetail[8]; 27 + }; 28 + 29 + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x40A"), Description("Root WMI HID_EVENT21"), guid("{fa78e245-2c0f-4ca1-91cf-15f34e474850}")] 30 + class HID_EVENT21 : WmiEvent { 31 + [key, read] string InstanceName; 32 + [read] boolean Active; 33 + [WmiDataId(1), read, write, Description("Package Data")] uint8 EventDetail[8]; 34 + }; 35 + 36 + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x40A"), Description("Root WMI HID_EVENT22"), guid("{1dceaf0a-4d63-44bb-bd0c-0d6281bfddc5}")] 37 + class HID_EVENT22 : WmiEvent { 38 + [key, read] string InstanceName; 39 + [read] boolean Active; 40 + [WmiDataId(1), read, write, Description("Package Data")] uint8 EventDetail[8]; 41 + }; 42 + 43 + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x40A"), Description("Root WMI HID_EVENT23"), guid("{3f9e3c26-b077-4f86-91f5-37ff64d8c7ed}")] 44 + class HID_EVENT23 : WmiEvent { 45 + [key, read] string InstanceName; 46 + [read] boolean Active; 47 + [WmiDataId(1), read, write, Description("Package Data")] uint8 EventDetail[8]; 48 + }; 49 + 50 + [WMI, Dynamic, provider("WmiProv"), Locale("MS\\0x409"), Description("Class used to operate firmware interface"), guid("{b60bfb48-3e5b-49e4-a0e9-8cffe1b3434b}")] 51 + class MICommonInterface { 52 + [key, read] string InstanceName; 53 + [read] boolean Active; 54 + 55 + [WmiMethodId(1), Implemented, read, write, Description("Method used to support system functions.")] void MiInterface([in, Description("WMI Interface")] uint8 InData[32], [out] uint8 OutData[30], [out] uint16 Reserved); 56 + }; 57 + 58 + Reverse-Engineering the EC WMI interface 59 + ======================================== 60 + 61 + The OEM software can be download from `this link <https://iknow.lenovo.com.cn/detail/429447>`_ 62 + 63 + Nothing is obfuscated, In this case, `ILSpy <https://github.com/icsharpcode/ILSpy>`_ could be helpful. 64 + 65 + WMI Methods (MICommonInterface) 66 + ======================================== 67 + 68 + The ``MICommonInterface`` class (GUID: ``{b60bfb48-3e5b-49e4-a0e9-8cffe1b3434b}``) 69 + is the primary control interface. It uses a 32-byte buffer for both input 70 + (``InData``) and output (``OutData``). 71 + 72 + Method Structure 73 + ---------------- 74 + 75 + The data packet follows a standardized format: 76 + 77 + +----------+------------------------------------------------------------------+ 78 + | Byte | Description | 79 + +==========+==================================================================+ 80 + | 1 | Method Type: Get (0xFA / 250) or Set (0xFB / 251) | 81 + +----------+------------------------------------------------------------------+ 82 + | 3 | Command ID (Method Name) | 83 + +----------+------------------------------------------------------------------+ 84 + | 4 - 31 | Arguments (for Set) or Return Data (for Get) | 85 + +----------+------------------------------------------------------------------+ 86 + 87 + 88 + Command IDs 89 + ----------- 90 + 91 + The following Command IDs are used in the third byte of the buffer: 92 + 93 + +----------+-----------------------+------------------------------------------+ 94 + | ID | Name | Values / Description | 95 + +==========+=======================+==========================================+ 96 + | 8 | SystemPerMode | 0: Balance, 1: Performance, 2: Quiet, | 97 + | | | 3: Full-speed | 98 + +----------+-----------------------+------------------------------------------+ 99 + | 9 | GPUMode | 0: Hybrid, 1: Discrete, 2: UMA | 100 + +----------+-----------------------+------------------------------------------+ 101 + | 10 | KeyboardType | 0: White, 1: Single RGB, 2: Zone RGB | 102 + +----------+-----------------------+------------------------------------------+ 103 + | 11 | FnLock | 0: Off, 1: On | 104 + +----------+-----------------------+------------------------------------------+ 105 + | 12 | TPLock | 0: Unlock, 1: Lock (Touchpad) | 106 + +----------+-----------------------+------------------------------------------+ 107 + | 13 | CPUGPUSYSFanSpeed | Returns 12 bytes of fan data: | 108 + | | | Bytes 4-5: CPU Fan RPM (Little Endian) | 109 + | | | Bytes 6-7: GPU Fan RPM (Little Endian) | 110 + | | | Bytes 10-11: SYS Fan RPM (Little Endian) | 111 + +----------+-----------------------+------------------------------------------+ 112 + | 16 | RGBKeyboardMode | 0: Off, 1: Auto Cyclic, 2: Fixed, | 113 + | | | 3: Custom | 114 + +----------+-----------------------+------------------------------------------+ 115 + | 17 | RGBKeyboardColor | Bytes 4, 5, 6: Red, Green, Blue values | 116 + +----------+-----------------------+------------------------------------------+ 117 + | 18 | RGBKeyboardBrightness | 0-10: Brightness Levels, 128: Auto | 118 + +----------+-----------------------+------------------------------------------+ 119 + | 19 | SystemAcType | 1: Type-C, 2: Circular Hole (DC) | 120 + +----------+-----------------------+------------------------------------------+ 121 + | 20 | MaxFanSpeedSwitch | Byte 4: Fan Type (0: CPU/GPU, 1: SYS) | 122 + | | | Byte 5: State (0: Off, 1: On) | 123 + +----------+-----------------------+------------------------------------------+ 124 + | 21 | MaxFanSpeed | Sets manual fan speed duty cycle | 125 + +----------+-----------------------+------------------------------------------+ 126 + | 22 | CPUThermometer | Returns CPU Temperature | 127 + +----------+-----------------------+------------------------------------------+ 128 + 129 + WMI Events (HID_EVENT20) 130 + ======================== 131 + 132 + The driver listens for events from the ``HID_EVENT20`` class 133 + (GUID: ``{46c93e13-ee9b-4262-8488-563bca757fef}``). These events are triggered 134 + by hotkeys or system state changes (e.g., plugging in AC power). 135 + 136 + Event Structure 137 + --------------- 138 + 139 + The event data is provided in an 8-byte array (``EventDetail``): 140 + 141 + +----------+------------------------------------------------------------------+ 142 + | Byte | Description | 143 + +==========+==================================================================+ 144 + | 0 | Event Type (Always 0x01 for HotKey/Notification) | 145 + +----------+------------------------------------------------------------------+ 146 + | 1 | Event ID (Corresponds to the Command IDs above) | 147 + +----------+------------------------------------------------------------------+ 148 + | 2 | Value (The new state or value of the feature) | 149 + +----------+------------------------------------------------------------------+ 150 + 151 + Common Event IDs: 152 + ----------------- 153 + 154 + Note: reserved event ids are not listed there 155 + 156 + +----------+------------------------------------------------------------------+ 157 + | Event Id | Description | 158 + +==========+==================================================================+ 159 + | 4 | AirPlane mode change | 160 + +----------+------------------------------------------------------------------+ 161 + | 5 | Keyboard brightness change | 162 + +----------+------------------------------------------------------------------+ 163 + | 6 | Touchpad state (enabled/disabled) change | 164 + +----------+------------------------------------------------------------------+ 165 + | 7 | FnLock state (enabled/disabled) change | 166 + +----------+------------------------------------------------------------------+ 167 + | 8 | Keyboard mode change | 168 + +----------+------------------------------------------------------------------+ 169 + | 9 | CapsLock state change | 170 + +----------+------------------------------------------------------------------+ 171 + | 13 | NumLock state change | 172 + +----------+------------------------------------------------------------------+ 173 + | 14 | ScrollLock state change | 174 + +----------+------------------------------------------------------------------+ 175 + | 15 | Performance plan change | 176 + +----------+------------------------------------------------------------------+ 177 + | 25 | Display refresh rate change | 178 + +----------+------------------------------------------------------------------+ 179 + | 33 | Super key lock state (enabled/disabled) change | 180 + +----------+------------------------------------------------------------------+ 181 + | 35 | Open control center key | 182 + +----------+------------------------------------------------------------------+ 183 + 184 + Implementation Details 185 + ====================== 186 + 187 + Performance Modes 188 + ----------------- 189 + Changing the performance mode via Command ID 0x08 (SystemPerMode) affects the 190 + power limits (PL1/PL2) and fan curves managed by the Embedded Controller (EC). 191 + Note that the "Full-speed" and "Performance" mode (1, 3) is typically only 192 + available when the system is connected to a DC power source (not USB-C/PD). 193 + 194 + In the driver implementation, switch to performance/full-speed mode without 195 + DC power connected will throw the EOPNOTSUPP error. 196 + 197 + Graphics Switching 198 + ------------------ 199 + The ``GPUMode`` (0x09) allows switching between Hybrid (Muxless) and Discrete 200 + (Muxed) graphics. Changing this value usually requires a system reboot to 201 + take effect in the BIOS/Firmware. 202 + 203 + Fan Control 204 + ----------- 205 + The system supports both automatic EC control and manual overrides. Command ID 206 + 0x14 (``MaxFanSpeedSwitch``) is used to toggle manual control, while ID 0x15 207 + sets the actual PWM duty cycle.
+18
drivers/platform/x86/Kconfig
··· 113 113 To compile this driver as a module, choose M here: the module will 114 114 be called gigabyte-wmi. 115 115 116 + config BITLAND_MIFS_WMI 117 + tristate "Bitland MIFS (MiInterface) WMI driver" 118 + depends on ACPI_WMI 119 + depends on HWMON 120 + depends on INPUT 121 + depends on POWER_SUPPLY 122 + select ACPI_PLATFORM_PROFILE 123 + select INPUT_SPARSEKMAP 124 + help 125 + This is a driver for Bitland MiInterface based laptops. 126 + 127 + It provides the access to the temperature, fan speed, gpu 128 + control, keyboard backlight brightness and platform profile 129 + via hwmon and sysfs. 130 + 131 + To compile this driver as a module, choose M here: the module will 132 + be called bitland-mifs-wmi. 133 + 116 134 config ACERHDF 117 135 tristate "Acer Aspire One temperature and fan driver" 118 136 depends on ACPI_EC && THERMAL
+1
drivers/platform/x86/Makefile
··· 14 14 obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o 15 15 obj-$(CONFIG_REDMI_WMI) += redmi-wmi.o 16 16 obj-$(CONFIG_GIGABYTE_WMI) += gigabyte-wmi.o 17 + obj-$(CONFIG_BITLAND_MIFS_WMI) += bitland-mifs-wmi.o 17 18 18 19 # Acer 19 20 obj-$(CONFIG_ACERHDF) += acerhdf.o
+845
drivers/platform/x86/bitland-mifs-wmi.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * Linux driver for Bitland notebooks. 4 + * 5 + * Copyright (C) 2026 2 Mingyou Chen <qby140326@gmail.com> 6 + */ 7 + 8 + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 9 + 10 + #include <linux/acpi.h> 11 + #include <linux/array_size.h> 12 + #include <linux/bits.h> 13 + #include <linux/cleanup.h> 14 + #include <linux/container_of.h> 15 + #include <linux/dev_printk.h> 16 + #include <linux/device.h> 17 + #include <linux/device/devres.h> 18 + #include <linux/err.h> 19 + #include <linux/hwmon.h> 20 + #include <linux/init.h> 21 + #include <linux/input-event-codes.h> 22 + #include <linux/input.h> 23 + #include <linux/input/sparse-keymap.h> 24 + #include <linux/kernel.h> 25 + #include <linux/leds.h> 26 + #include <linux/module.h> 27 + #include <linux/notifier.h> 28 + #include <linux/platform_profile.h> 29 + #include <linux/pm.h> 30 + #include <linux/power_supply.h> 31 + #include <linux/stddef.h> 32 + #include <linux/string.h> 33 + #include <linux/sysfs.h> 34 + #include <linux/unaligned.h> 35 + #include <linux/units.h> 36 + #include <linux/wmi.h> 37 + 38 + #define DRV_NAME "bitland-mifs-wmi" 39 + #define BITLAND_MIFS_GUID "B60BFB48-3E5B-49E4-A0E9-8CFFE1B3434B" 40 + #define BITLAND_EVENT_GUID "46C93E13-EE9B-4262-8488-563BCA757FEF" 41 + 42 + enum bitland_mifs_operation { 43 + WMI_METHOD_GET = 250, 44 + WMI_METHOD_SET = 251, 45 + }; 46 + 47 + enum bitland_mifs_function { 48 + WMI_FN_SYSTEM_PER_MODE = 8, 49 + WMI_FN_GPU_MODE = 9, 50 + WMI_FN_KBD_TYPE = 10, 51 + WMI_FN_FN_LOCK = 11, 52 + WMI_FN_TP_LOCK = 12, 53 + WMI_FN_FAN_SPEEDS = 13, 54 + WMI_FN_RGB_KB_MODE = 16, 55 + WMI_FN_RGB_KB_COLOR = 17, 56 + WMI_FN_RGB_KB_BRIGHTNESS = 18, 57 + WMI_FN_SYSTEM_AC_TYPE = 19, 58 + WMI_FN_MAX_FAN_SWITCH = 20, 59 + WMI_FN_MAX_FAN_SPEED = 21, 60 + WMI_FN_CPU_THERMOMETER = 22, 61 + WMI_FN_CPU_POWER = 23, 62 + }; 63 + 64 + enum bitland_system_ac_mode { 65 + WMI_SYSTEM_AC_TYPEC = 1, 66 + /* Unknown type, this is unused in the original driver */ 67 + WMI_SYSTEM_AC_CIRCULARHOLE = 2, 68 + }; 69 + 70 + enum bitland_mifs_power_profile { 71 + WMI_PP_BALANCED = 0, 72 + WMI_PP_PERFORMANCE = 1, 73 + WMI_PP_QUIET = 2, 74 + WMI_PP_FULL_SPEED = 3, 75 + }; 76 + 77 + enum bitland_mifs_event_id { 78 + WMI_EVENT_RESERVED_1 = 1, 79 + WMI_EVENT_RESERVED_2 = 2, 80 + WMI_EVENT_RESERVED_3 = 3, 81 + WMI_EVENT_AIRPLANE_MODE = 4, 82 + WMI_EVENT_KBD_BRIGHTNESS = 5, 83 + WMI_EVENT_TOUCHPAD_STATE = 6, 84 + WMI_EVENT_FNLOCK_STATE = 7, 85 + WMI_EVENT_KBD_MODE = 8, 86 + WMI_EVENT_CAPSLOCK_STATE = 9, 87 + WMI_EVENT_CALCULATOR_START = 11, 88 + WMI_EVENT_BROWSER_START = 12, 89 + WMI_EVENT_NUMLOCK_STATE = 13, 90 + WMI_EVENT_SCROLLLOCK_STATE = 14, 91 + WMI_EVENT_PERFORMANCE_PLAN = 15, 92 + WMI_EVENT_FN_J = 16, 93 + WMI_EVENT_FN_F = 17, 94 + WMI_EVENT_FN_0 = 18, 95 + WMI_EVENT_FN_1 = 19, 96 + WMI_EVENT_FN_2 = 20, 97 + WMI_EVENT_FN_3 = 21, 98 + WMI_EVENT_FN_4 = 22, 99 + WMI_EVENT_FN_5 = 24, 100 + WMI_EVENT_REFRESH_RATE = 25, 101 + WMI_EVENT_CPU_FAN_SPEED = 26, 102 + WMI_EVENT_GPU_FAN_SPEED = 32, 103 + WMI_EVENT_WIN_KEY_LOCK = 33, 104 + WMI_EVENT_RESERVED_23 = 34, 105 + WMI_EVENT_OPEN_APP = 35, 106 + }; 107 + 108 + enum bitland_mifs_event_type { 109 + WMI_EVENT_TYPE_HOTKEY = 1, 110 + }; 111 + 112 + enum bitland_wmi_device_type { 113 + BITLAND_WMI_CONTROL = 0, 114 + BITLAND_WMI_EVENT = 1, 115 + }; 116 + 117 + struct bitland_mifs_input { 118 + u8 reserved1; 119 + u8 operation; 120 + u8 reserved2; 121 + u8 function; 122 + u8 payload[28]; 123 + } __packed; 124 + 125 + struct bitland_mifs_output { 126 + u8 reserved1; 127 + u8 operation; 128 + u8 reserved2; 129 + u8 function; 130 + u8 data[28]; 131 + } __packed; 132 + 133 + struct bitland_mifs_event { 134 + u8 event_type; 135 + u8 event_id; 136 + u8 value_low; /* For most events, this is the value */ 137 + u8 value_high; /* For fan speed events, combined with value_low */ 138 + u8 reserved[4]; 139 + } __packed; 140 + 141 + static BLOCKING_NOTIFIER_HEAD(bitland_notifier_list); 142 + 143 + enum bitland_notifier_actions { 144 + BITLAND_NOTIFY_KBD_BRIGHTNESS, 145 + BITLAND_NOTIFY_PLATFORM_PROFILE, 146 + BITLAND_NOTIFY_HWMON, 147 + }; 148 + 149 + struct bitland_fan_notify_data { 150 + int channel; /* 0 = CPU, 1 = GPU */ 151 + u16 speed; 152 + }; 153 + 154 + struct bitland_mifs_wmi_data { 155 + struct wmi_device *wdev; 156 + struct mutex lock; /* Protects WMI calls */ 157 + struct led_classdev kbd_led; 158 + struct notifier_block notifier; 159 + struct input_dev *input_dev; 160 + struct device *hwmon_dev; 161 + struct device *pp_dev; 162 + enum platform_profile_option saved_profile; 163 + }; 164 + 165 + static int bitland_mifs_wmi_call(struct bitland_mifs_wmi_data *data, 166 + const struct bitland_mifs_input *input, 167 + struct bitland_mifs_output *output) 168 + { 169 + struct wmi_buffer in_buf = { .length = sizeof(*input), .data = (void *)input }; 170 + struct wmi_buffer out_buf = { 0 }; 171 + int ret; 172 + 173 + guard(mutex)(&data->lock); 174 + 175 + ret = wmidev_invoke_method(data->wdev, 0, 1, &in_buf, output ? &out_buf : NULL); 176 + if (ret) 177 + return ret; 178 + 179 + if (output) { 180 + void *out_data __free(kfree) = out_buf.data; 181 + 182 + if (out_buf.length < sizeof(*output)) 183 + return -EIO; 184 + 185 + memcpy(output, out_data, sizeof(*output)); 186 + } 187 + 188 + return 0; 189 + } 190 + 191 + static int laptop_profile_get(struct device *dev, 192 + enum platform_profile_option *profile) 193 + { 194 + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 195 + struct bitland_mifs_input input = { 196 + .reserved1 = 0, 197 + .operation = WMI_METHOD_GET, 198 + .reserved2 = 0, 199 + .function = WMI_FN_SYSTEM_PER_MODE, 200 + }; 201 + struct bitland_mifs_output result; 202 + int ret; 203 + 204 + ret = bitland_mifs_wmi_call(data, &input, &result); 205 + if (ret) 206 + return ret; 207 + 208 + switch (result.data[0]) { 209 + case WMI_PP_BALANCED: 210 + *profile = PLATFORM_PROFILE_BALANCED; 211 + break; 212 + case WMI_PP_PERFORMANCE: 213 + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE; 214 + break; 215 + case WMI_PP_QUIET: 216 + *profile = PLATFORM_PROFILE_LOW_POWER; 217 + break; 218 + case WMI_PP_FULL_SPEED: 219 + *profile = PLATFORM_PROFILE_PERFORMANCE; 220 + break; 221 + default: 222 + return -EINVAL; 223 + } 224 + return 0; 225 + } 226 + 227 + static int bitland_check_performance_capability(struct bitland_mifs_wmi_data *data) 228 + { 229 + struct bitland_mifs_input input = { 230 + .operation = WMI_METHOD_GET, 231 + .function = WMI_FN_SYSTEM_AC_TYPE, 232 + }; 233 + struct bitland_mifs_output output; 234 + int ret; 235 + 236 + /* Full-speed/performance mode requires DC power (not USB-C) */ 237 + if (!power_supply_is_system_supplied()) 238 + return -EOPNOTSUPP; 239 + 240 + ret = bitland_mifs_wmi_call(data, &input, &output); 241 + if (ret) 242 + return ret; 243 + 244 + if (output.data[0] != WMI_SYSTEM_AC_CIRCULARHOLE) 245 + return -EOPNOTSUPP; 246 + 247 + return 0; 248 + } 249 + 250 + static int laptop_profile_set(struct device *dev, 251 + enum platform_profile_option profile) 252 + { 253 + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 254 + struct bitland_mifs_input input = { 255 + .reserved1 = 0, 256 + .operation = WMI_METHOD_SET, 257 + .reserved2 = 0, 258 + .function = WMI_FN_SYSTEM_PER_MODE, 259 + }; 260 + int ret; 261 + u8 val; 262 + 263 + switch (profile) { 264 + case PLATFORM_PROFILE_LOW_POWER: 265 + val = WMI_PP_QUIET; 266 + break; 267 + case PLATFORM_PROFILE_BALANCED: 268 + val = WMI_PP_BALANCED; 269 + break; 270 + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: 271 + ret = bitland_check_performance_capability(data); 272 + if (ret) 273 + return ret; 274 + val = WMI_PP_PERFORMANCE; 275 + break; 276 + case PLATFORM_PROFILE_PERFORMANCE: 277 + ret = bitland_check_performance_capability(data); 278 + if (ret) 279 + return ret; 280 + val = WMI_PP_FULL_SPEED; 281 + break; 282 + default: 283 + return -EOPNOTSUPP; 284 + } 285 + 286 + input.payload[0] = val; 287 + 288 + return bitland_mifs_wmi_call(data, &input, NULL); 289 + } 290 + 291 + static int platform_profile_probe(void *drvdata, unsigned long *choices) 292 + { 293 + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); 294 + set_bit(PLATFORM_PROFILE_BALANCED, choices); 295 + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices); 296 + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); 297 + 298 + return 0; 299 + } 300 + 301 + static int bitland_mifs_wmi_suspend(struct device *dev) 302 + { 303 + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 304 + enum platform_profile_option profile; 305 + int ret; 306 + 307 + ret = laptop_profile_get(data->pp_dev, &profile); 308 + if (ret == 0) 309 + data->saved_profile = profile; 310 + 311 + return ret; 312 + } 313 + 314 + static int bitland_mifs_wmi_resume(struct device *dev) 315 + { 316 + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 317 + 318 + dev_dbg(dev, "Resuming, restoring profile %d\n", data->saved_profile); 319 + return laptop_profile_set(dev, data->saved_profile); 320 + } 321 + 322 + static DEFINE_SIMPLE_DEV_PM_OPS(bitland_mifs_wmi_pm_ops, 323 + bitland_mifs_wmi_suspend, 324 + bitland_mifs_wmi_resume); 325 + 326 + static const struct platform_profile_ops laptop_profile_ops = { 327 + .probe = platform_profile_probe, 328 + .profile_get = laptop_profile_get, 329 + .profile_set = laptop_profile_set, 330 + }; 331 + 332 + static const char *const fan_labels[] = { 333 + "CPU", /* 0 */ 334 + "GPU", /* 1 */ 335 + "SYS", /* 2 */ 336 + }; 337 + 338 + static int laptop_hwmon_read(struct device *dev, enum hwmon_sensor_types type, 339 + u32 attr, int channel, long *val) 340 + { 341 + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 342 + struct bitland_mifs_input input = { 343 + .reserved1 = 0, 344 + .operation = WMI_METHOD_GET, 345 + .reserved2 = 0, 346 + }; 347 + struct bitland_mifs_output res; 348 + int ret; 349 + 350 + switch (type) { 351 + case hwmon_temp: 352 + input.function = WMI_FN_CPU_THERMOMETER; 353 + ret = bitland_mifs_wmi_call(data, &input, &res); 354 + if (!ret) 355 + *val = res.data[0] * MILLIDEGREE_PER_DEGREE; 356 + return ret; 357 + case hwmon_fan: 358 + input.function = WMI_FN_FAN_SPEEDS; 359 + ret = bitland_mifs_wmi_call(data, &input, &res); 360 + if (ret) 361 + return ret; 362 + 363 + switch (channel) { 364 + case 0: /* CPU */ 365 + *val = get_unaligned_le16(&res.data[0]); 366 + return 0; 367 + case 1: /* GPU */ 368 + *val = get_unaligned_le16(&res.data[2]); 369 + return 0; 370 + case 2: /* SYS */ 371 + *val = get_unaligned_le16(&res.data[6]); 372 + return 0; 373 + default: 374 + return -EINVAL; 375 + } 376 + default: 377 + return -EINVAL; 378 + } 379 + } 380 + 381 + static int laptop_hwmon_read_string(struct device *dev, 382 + enum hwmon_sensor_types type, u32 attr, 383 + int channel, const char **str) 384 + { 385 + if (type == hwmon_fan && attr == hwmon_fan_label) { 386 + if (channel >= 0 && channel < ARRAY_SIZE(fan_labels)) { 387 + *str = fan_labels[channel]; 388 + return 0; 389 + } 390 + } 391 + return -EINVAL; 392 + } 393 + 394 + static const struct hwmon_channel_info *laptop_hwmon_info[] = { 395 + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), 396 + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL, 397 + HWMON_F_INPUT | HWMON_F_LABEL, 398 + HWMON_F_INPUT | HWMON_F_LABEL), 399 + NULL 400 + }; 401 + 402 + static const struct hwmon_ops laptop_hwmon_ops = { 403 + .visible = 0444, 404 + .read = laptop_hwmon_read, 405 + .read_string = laptop_hwmon_read_string, 406 + }; 407 + 408 + static const struct hwmon_chip_info laptop_chip_info = { 409 + .ops = &laptop_hwmon_ops, 410 + .info = laptop_hwmon_info, 411 + }; 412 + 413 + static int laptop_kbd_led_set(struct led_classdev *led_cdev, 414 + enum led_brightness value) 415 + { 416 + struct bitland_mifs_wmi_data *data = 417 + container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led); 418 + struct bitland_mifs_input input = { 419 + .reserved1 = 0, 420 + .operation = WMI_METHOD_SET, 421 + .reserved2 = 0, 422 + .function = WMI_FN_RGB_KB_BRIGHTNESS, 423 + }; 424 + 425 + input.payload[0] = (u8)value; 426 + 427 + return bitland_mifs_wmi_call(data, &input, NULL); 428 + } 429 + 430 + static enum led_brightness laptop_kbd_led_get(struct led_classdev *led_cdev) 431 + { 432 + struct bitland_mifs_wmi_data *data = 433 + container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led); 434 + struct bitland_mifs_input input = { 435 + .reserved1 = 0, 436 + .operation = WMI_METHOD_GET, 437 + .reserved2 = 0, 438 + .function = WMI_FN_RGB_KB_BRIGHTNESS, 439 + }; 440 + struct bitland_mifs_output res; 441 + int ret; 442 + 443 + ret = bitland_mifs_wmi_call(data, &input, &res); 444 + if (ret) 445 + return ret; 446 + 447 + return res.data[0]; 448 + } 449 + 450 + static const char *const gpu_mode_strings[] = { 451 + "hybrid", 452 + "discrete", 453 + "uma", 454 + }; 455 + 456 + /* GPU Mode: 0:Hybrid, 1:Discrete, 2:UMA */ 457 + static ssize_t gpu_mode_show(struct device *dev, struct device_attribute *attr, 458 + char *buf) 459 + { 460 + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 461 + struct bitland_mifs_input input = { 462 + .reserved1 = 0, 463 + .operation = WMI_METHOD_GET, 464 + .reserved2 = 0, 465 + .function = WMI_FN_GPU_MODE, 466 + }; 467 + struct bitland_mifs_output res; 468 + u8 mode_val; 469 + int ret; 470 + 471 + ret = bitland_mifs_wmi_call(data, &input, &res); 472 + if (ret) 473 + return ret; 474 + 475 + mode_val = res.data[0]; 476 + if (mode_val >= ARRAY_SIZE(gpu_mode_strings)) 477 + return -EPROTO; 478 + 479 + return sysfs_emit(buf, "%s\n", gpu_mode_strings[mode_val]); 480 + } 481 + 482 + static ssize_t gpu_mode_store(struct device *dev, struct device_attribute *attr, 483 + const char *buf, size_t count) 484 + { 485 + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 486 + struct bitland_mifs_input input = { 487 + .reserved1 = 0, 488 + .operation = WMI_METHOD_SET, 489 + .reserved2 = 0, 490 + .function = WMI_FN_GPU_MODE, 491 + }; 492 + int val; 493 + int ret; 494 + 495 + val = sysfs_match_string(gpu_mode_strings, buf); 496 + if (val < 0) 497 + return -EINVAL; 498 + 499 + input.payload[0] = (u8)val; 500 + 501 + ret = bitland_mifs_wmi_call(data, &input, NULL); 502 + if (ret) 503 + return ret; 504 + 505 + return count; 506 + } 507 + 508 + static const char *const kb_mode_strings[] = { 509 + "off", /* 0 */ 510 + "cyclic", /* 1 */ 511 + "fixed", /* 2 */ 512 + "custom", /* 3 */ 513 + }; 514 + 515 + static ssize_t kb_mode_show(struct device *dev, struct device_attribute *attr, 516 + char *buf) 517 + { 518 + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 519 + struct bitland_mifs_input input = { 520 + .reserved1 = 0, 521 + .operation = WMI_METHOD_GET, 522 + .reserved2 = 0, 523 + .function = WMI_FN_RGB_KB_MODE, 524 + }; 525 + struct bitland_mifs_output res; 526 + u8 mode_val; 527 + int ret; 528 + 529 + ret = bitland_mifs_wmi_call(data, &input, &res); 530 + if (ret) 531 + return ret; 532 + 533 + mode_val = res.data[0]; 534 + if (mode_val >= ARRAY_SIZE(kb_mode_strings)) 535 + return -EPROTO; 536 + 537 + return sysfs_emit(buf, "%s\n", kb_mode_strings[mode_val]); 538 + } 539 + 540 + static ssize_t kb_mode_store(struct device *dev, struct device_attribute *attr, 541 + const char *buf, size_t count) 542 + { 543 + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 544 + struct bitland_mifs_input input = { 545 + .reserved1 = 0, 546 + .operation = WMI_METHOD_SET, 547 + .reserved2 = 0, 548 + .function = WMI_FN_RGB_KB_MODE, 549 + }; 550 + // the wmi value (0, 1, 2 or 3) 551 + int val; 552 + int ret; 553 + 554 + val = sysfs_match_string(kb_mode_strings, buf); 555 + if (val < 0) 556 + return -EINVAL; 557 + 558 + input.payload[0] = (u8)val; 559 + 560 + ret = bitland_mifs_wmi_call(data, &input, NULL); 561 + if (ret) 562 + return ret; 563 + 564 + return count; 565 + } 566 + 567 + /* Fan Boost: 0:Normal, 1:Max Speed */ 568 + static ssize_t fan_boost_store(struct device *dev, 569 + struct device_attribute *attr, const char *buf, 570 + size_t count) 571 + { 572 + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 573 + struct bitland_mifs_input input = { 574 + .reserved1 = 0, 575 + .operation = WMI_METHOD_SET, 576 + .reserved2 = 0, 577 + .function = WMI_FN_MAX_FAN_SWITCH, 578 + }; 579 + bool val; 580 + int ret; 581 + 582 + if (kstrtobool(buf, &val)) 583 + return -EINVAL; 584 + 585 + input.payload[0] = 0; /* CPU/GPU Fan */ 586 + input.payload[1] = val; 587 + 588 + ret = bitland_mifs_wmi_call(data, &input, NULL); 589 + if (ret) 590 + return ret; 591 + 592 + return count; 593 + } 594 + 595 + static const DEVICE_ATTR_RW(gpu_mode); 596 + static const DEVICE_ATTR_RW(kb_mode); 597 + static const DEVICE_ATTR_WO(fan_boost); 598 + 599 + static const struct attribute *const laptop_attrs[] = { 600 + &dev_attr_gpu_mode.attr, 601 + &dev_attr_kb_mode.attr, 602 + &dev_attr_fan_boost.attr, 603 + NULL, 604 + }; 605 + ATTRIBUTE_GROUPS(laptop); 606 + 607 + static const struct key_entry bitland_mifs_wmi_keymap[] = { 608 + { KE_KEY, WMI_EVENT_OPEN_APP, { KEY_PROG1 } }, 609 + { KE_KEY, WMI_EVENT_CALCULATOR_START, { KEY_CALC } }, 610 + { KE_KEY, WMI_EVENT_BROWSER_START, { KEY_WWW } }, 611 + { KE_IGNORE, WMI_EVENT_FN_J, { KEY_RESERVED } }, 612 + { KE_IGNORE, WMI_EVENT_FN_F, { KEY_RESERVED } }, 613 + { KE_IGNORE, WMI_EVENT_FN_0, { KEY_RESERVED } }, 614 + { KE_IGNORE, WMI_EVENT_FN_1, { KEY_RESERVED } }, 615 + { KE_IGNORE, WMI_EVENT_FN_2, { KEY_RESERVED } }, 616 + { KE_IGNORE, WMI_EVENT_FN_3, { KEY_RESERVED } }, 617 + { KE_IGNORE, WMI_EVENT_FN_4, { KEY_RESERVED } }, 618 + { KE_IGNORE, WMI_EVENT_FN_5, { KEY_RESERVED } }, 619 + { KE_END, 0 } 620 + }; 621 + 622 + static void bitland_notifier_unregister(void *data) 623 + { 624 + struct notifier_block *nb = data; 625 + 626 + blocking_notifier_chain_unregister(&bitland_notifier_list, nb); 627 + } 628 + 629 + static int bitland_notifier_callback(struct notifier_block *nb, 630 + unsigned long action, void *data) 631 + { 632 + struct bitland_mifs_wmi_data *data_ctx = 633 + container_of(nb, struct bitland_mifs_wmi_data, notifier); 634 + struct bitland_fan_notify_data *fan_info; 635 + u8 *brightness; 636 + 637 + switch (action) { 638 + case BITLAND_NOTIFY_KBD_BRIGHTNESS: 639 + brightness = data; 640 + led_classdev_notify_brightness_hw_changed(&data_ctx->kbd_led, 641 + *brightness); 642 + break; 643 + case BITLAND_NOTIFY_PLATFORM_PROFILE: 644 + platform_profile_notify(data_ctx->pp_dev); 645 + break; 646 + case BITLAND_NOTIFY_HWMON: 647 + fan_info = data; 648 + 649 + hwmon_notify_event(data_ctx->hwmon_dev, hwmon_fan, 650 + hwmon_fan_input, fan_info->channel); 651 + break; 652 + } 653 + 654 + return NOTIFY_OK; 655 + } 656 + 657 + static int bitland_mifs_wmi_probe(struct wmi_device *wdev, const void *context) 658 + { 659 + struct bitland_mifs_wmi_data *drv_data; 660 + enum bitland_wmi_device_type dev_type = 661 + (enum bitland_wmi_device_type)(unsigned long)context; 662 + struct led_init_data init_data = { 663 + .devicename = DRV_NAME, 664 + .default_label = ":" LED_FUNCTION_KBD_BACKLIGHT, 665 + .devname_mandatory = true, 666 + }; 667 + int ret; 668 + 669 + drv_data = devm_kzalloc(&wdev->dev, sizeof(*drv_data), GFP_KERNEL); 670 + if (!drv_data) 671 + return -ENOMEM; 672 + 673 + drv_data->wdev = wdev; 674 + 675 + ret = devm_mutex_init(&wdev->dev, &drv_data->lock); 676 + if (ret) 677 + return ret; 678 + 679 + dev_set_drvdata(&wdev->dev, drv_data); 680 + 681 + if (dev_type == BITLAND_WMI_EVENT) { 682 + /* Register input device for hotkeys */ 683 + drv_data->input_dev = devm_input_allocate_device(&wdev->dev); 684 + if (!drv_data->input_dev) 685 + return -ENOMEM; 686 + 687 + drv_data->input_dev->name = "Bitland MIFS WMI hotkeys"; 688 + drv_data->input_dev->phys = "wmi/input0"; 689 + drv_data->input_dev->id.bustype = BUS_HOST; 690 + drv_data->input_dev->dev.parent = &wdev->dev; 691 + 692 + ret = sparse_keymap_setup(drv_data->input_dev, 693 + bitland_mifs_wmi_keymap, NULL); 694 + if (ret) 695 + return ret; 696 + 697 + return input_register_device(drv_data->input_dev); 698 + } 699 + 700 + /* Register platform profile */ 701 + drv_data->pp_dev = devm_platform_profile_register(&wdev->dev, DRV_NAME, drv_data, 702 + &laptop_profile_ops); 703 + if (IS_ERR(drv_data->pp_dev)) 704 + return PTR_ERR(drv_data->pp_dev); 705 + 706 + /* Register hwmon */ 707 + drv_data->hwmon_dev = devm_hwmon_device_register_with_info(&wdev->dev, 708 + "bitland_mifs", 709 + drv_data, 710 + &laptop_chip_info, 711 + NULL); 712 + if (IS_ERR(drv_data->hwmon_dev)) 713 + return PTR_ERR(drv_data->hwmon_dev); 714 + 715 + /* Register keyboard LED */ 716 + drv_data->kbd_led.max_brightness = 3; 717 + drv_data->kbd_led.brightness_set_blocking = laptop_kbd_led_set; 718 + drv_data->kbd_led.brightness_get = laptop_kbd_led_get; 719 + drv_data->kbd_led.brightness = laptop_kbd_led_get(&drv_data->kbd_led); 720 + drv_data->kbd_led.flags = LED_CORE_SUSPENDRESUME | 721 + LED_BRIGHT_HW_CHANGED | 722 + LED_REJECT_NAME_CONFLICT; 723 + ret = devm_led_classdev_register_ext(&wdev->dev, &drv_data->kbd_led, &init_data); 724 + if (ret) 725 + return ret; 726 + 727 + drv_data->notifier.notifier_call = bitland_notifier_callback; 728 + ret = blocking_notifier_chain_register(&bitland_notifier_list, &drv_data->notifier); 729 + if (ret) 730 + return ret; 731 + 732 + return devm_add_action_or_reset(&wdev->dev, 733 + bitland_notifier_unregister, 734 + &drv_data->notifier); 735 + } 736 + 737 + static void bitland_mifs_wmi_notify(struct wmi_device *wdev, 738 + const struct wmi_buffer *buffer) 739 + { 740 + struct bitland_mifs_wmi_data *data = dev_get_drvdata(&wdev->dev); 741 + const struct bitland_mifs_event *event; 742 + struct bitland_fan_notify_data fan_data; 743 + u8 brightness; 744 + 745 + if (buffer->length < sizeof(*event)) 746 + return; 747 + 748 + event = buffer->data; 749 + 750 + /* Validate event type */ 751 + if (event->event_type != WMI_EVENT_TYPE_HOTKEY) 752 + return; 753 + 754 + dev_dbg(&wdev->dev, 755 + "WMI event: id=0x%02x value_low=0x%02x value_high=0x%02x\n", 756 + event->event_id, event->value_low, event->value_high); 757 + 758 + switch (event->event_id) { 759 + case WMI_EVENT_KBD_BRIGHTNESS: 760 + brightness = event->value_low; 761 + blocking_notifier_call_chain(&bitland_notifier_list, 762 + BITLAND_NOTIFY_KBD_BRIGHTNESS, 763 + &brightness); 764 + break; 765 + 766 + case WMI_EVENT_PERFORMANCE_PLAN: 767 + blocking_notifier_call_chain(&bitland_notifier_list, 768 + BITLAND_NOTIFY_PLATFORM_PROFILE, 769 + NULL); 770 + break; 771 + 772 + case WMI_EVENT_OPEN_APP: 773 + case WMI_EVENT_CALCULATOR_START: 774 + case WMI_EVENT_BROWSER_START: { 775 + guard(mutex)(&data->lock); 776 + if (!sparse_keymap_report_event(data->input_dev, 777 + event->event_id, 1, true)) 778 + dev_warn(&wdev->dev, "Unknown key pressed: 0x%02x\n", 779 + event->event_id); 780 + break; 781 + } 782 + 783 + /* 784 + * The device has 3 fans (CPU, GPU, SYS), 785 + * but there are only the CPU and GPU fan has events 786 + */ 787 + case WMI_EVENT_CPU_FAN_SPEED: 788 + case WMI_EVENT_GPU_FAN_SPEED: 789 + if (event->event_id == WMI_EVENT_CPU_FAN_SPEED) 790 + fan_data.channel = 0; 791 + else 792 + fan_data.channel = 1; 793 + 794 + /* Fan speed is 16-bit value (value_low is LSB, value_high is MSB) */ 795 + fan_data.speed = (event->value_high << 8) | event->value_low; 796 + blocking_notifier_call_chain(&bitland_notifier_list, 797 + BITLAND_NOTIFY_HWMON, 798 + &fan_data); 799 + break; 800 + 801 + case WMI_EVENT_AIRPLANE_MODE: 802 + case WMI_EVENT_TOUCHPAD_STATE: 803 + case WMI_EVENT_FNLOCK_STATE: 804 + case WMI_EVENT_KBD_MODE: 805 + case WMI_EVENT_CAPSLOCK_STATE: 806 + case WMI_EVENT_NUMLOCK_STATE: 807 + case WMI_EVENT_SCROLLLOCK_STATE: 808 + case WMI_EVENT_REFRESH_RATE: 809 + case WMI_EVENT_WIN_KEY_LOCK: 810 + /* These events are informational or handled by firmware */ 811 + dev_dbg(&wdev->dev, "State change event: id=%d value=%d\n", 812 + event->event_id, event->value_low); 813 + break; 814 + 815 + default: 816 + dev_dbg(&wdev->dev, "Unknown event: id=0x%02x value=0x%02x\n", 817 + event->event_id, event->value_low); 818 + break; 819 + } 820 + } 821 + 822 + static const struct wmi_device_id bitland_mifs_wmi_id_table[] = { 823 + { BITLAND_MIFS_GUID, (void *)BITLAND_WMI_CONTROL }, 824 + { BITLAND_EVENT_GUID, (void *)BITLAND_WMI_EVENT }, 825 + {} 826 + }; 827 + MODULE_DEVICE_TABLE(wmi, bitland_mifs_wmi_id_table); 828 + 829 + static struct wmi_driver bitland_mifs_wmi_driver = { 830 + .no_singleton = true, 831 + .driver = { 832 + .name = DRV_NAME, 833 + .dev_groups = laptop_groups, 834 + .pm = pm_sleep_ptr(&bitland_mifs_wmi_pm_ops), 835 + }, 836 + .id_table = bitland_mifs_wmi_id_table, 837 + .probe = bitland_mifs_wmi_probe, 838 + .notify_new = bitland_mifs_wmi_notify, 839 + }; 840 + 841 + module_wmi_driver(bitland_mifs_wmi_driver); 842 + 843 + MODULE_AUTHOR("Mingyou Chen <qby140326@gmail.com>"); 844 + MODULE_DESCRIPTION("Bitland MIFS (MiInterface) WMI driver"); 845 + MODULE_LICENSE("GPL");