1dc1ec4faSMingyou Chen // SPDX-License-Identifier: GPL-2.0-or-later 2dc1ec4faSMingyou Chen /* 3dc1ec4faSMingyou Chen * Linux driver for Bitland notebooks. 4dc1ec4faSMingyou Chen * 5dc1ec4faSMingyou Chen * Copyright (C) 2026 2 Mingyou Chen <qby140326@gmail.com> 6dc1ec4faSMingyou Chen */ 7dc1ec4faSMingyou Chen 8dc1ec4faSMingyou Chen #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 9dc1ec4faSMingyou Chen 10dc1ec4faSMingyou Chen #include <linux/acpi.h> 11dc1ec4faSMingyou Chen #include <linux/array_size.h> 12dc1ec4faSMingyou Chen #include <linux/bits.h> 13dc1ec4faSMingyou Chen #include <linux/container_of.h> 14dc1ec4faSMingyou Chen #include <linux/dev_printk.h> 15dc1ec4faSMingyou Chen #include <linux/device.h> 16dc1ec4faSMingyou Chen #include <linux/device/devres.h> 17dc1ec4faSMingyou Chen #include <linux/err.h> 18dc1ec4faSMingyou Chen #include <linux/hwmon.h> 19dc1ec4faSMingyou Chen #include <linux/init.h> 20dc1ec4faSMingyou Chen #include <linux/input-event-codes.h> 21dc1ec4faSMingyou Chen #include <linux/input.h> 22dc1ec4faSMingyou Chen #include <linux/input/sparse-keymap.h> 23dc1ec4faSMingyou Chen #include <linux/kernel.h> 24dc1ec4faSMingyou Chen #include <linux/leds.h> 25dc1ec4faSMingyou Chen #include <linux/module.h> 26dc1ec4faSMingyou Chen #include <linux/notifier.h> 27dc1ec4faSMingyou Chen #include <linux/platform_profile.h> 28dc1ec4faSMingyou Chen #include <linux/pm.h> 29dc1ec4faSMingyou Chen #include <linux/power_supply.h> 30dc1ec4faSMingyou Chen #include <linux/stddef.h> 31dc1ec4faSMingyou Chen #include <linux/string.h> 32dc1ec4faSMingyou Chen #include <linux/sysfs.h> 33dc1ec4faSMingyou Chen #include <linux/unaligned.h> 34dc1ec4faSMingyou Chen #include <linux/units.h> 35dc1ec4faSMingyou Chen #include <linux/wmi.h> 36dc1ec4faSMingyou Chen 37dc1ec4faSMingyou Chen #define DRV_NAME "bitland-mifs-wmi" 38dc1ec4faSMingyou Chen #define BITLAND_MIFS_GUID "B60BFB48-3E5B-49E4-A0E9-8CFFE1B3434B" 39dc1ec4faSMingyou Chen #define BITLAND_EVENT_GUID "46C93E13-EE9B-4262-8488-563BCA757FEF" 40dc1ec4faSMingyou Chen 41dc1ec4faSMingyou Chen enum bitland_mifs_operation { 42dc1ec4faSMingyou Chen WMI_METHOD_GET = 250, 43dc1ec4faSMingyou Chen WMI_METHOD_SET = 251, 44dc1ec4faSMingyou Chen }; 45dc1ec4faSMingyou Chen 46dc1ec4faSMingyou Chen enum bitland_mifs_function { 47dc1ec4faSMingyou Chen WMI_FN_SYSTEM_PER_MODE = 8, 48dc1ec4faSMingyou Chen WMI_FN_GPU_MODE = 9, 49dc1ec4faSMingyou Chen WMI_FN_KBD_TYPE = 10, 50dc1ec4faSMingyou Chen WMI_FN_FN_LOCK = 11, 51dc1ec4faSMingyou Chen WMI_FN_TP_LOCK = 12, 52dc1ec4faSMingyou Chen WMI_FN_FAN_SPEEDS = 13, 53dc1ec4faSMingyou Chen WMI_FN_RGB_KB_MODE = 16, 54dc1ec4faSMingyou Chen WMI_FN_RGB_KB_COLOR = 17, 55dc1ec4faSMingyou Chen WMI_FN_RGB_KB_BRIGHTNESS = 18, 56dc1ec4faSMingyou Chen WMI_FN_SYSTEM_AC_TYPE = 19, 57dc1ec4faSMingyou Chen WMI_FN_MAX_FAN_SWITCH = 20, 58dc1ec4faSMingyou Chen WMI_FN_MAX_FAN_SPEED = 21, 59dc1ec4faSMingyou Chen WMI_FN_CPU_THERMOMETER = 22, 60dc1ec4faSMingyou Chen WMI_FN_CPU_POWER = 23, 61dc1ec4faSMingyou Chen }; 62dc1ec4faSMingyou Chen 63dc1ec4faSMingyou Chen enum bitland_system_ac_mode { 64dc1ec4faSMingyou Chen WMI_SYSTEM_AC_TYPEC = 1, 65dc1ec4faSMingyou Chen /* Unknown type, this is unused in the original driver */ 66dc1ec4faSMingyou Chen WMI_SYSTEM_AC_CIRCULARHOLE = 2, 67dc1ec4faSMingyou Chen }; 68dc1ec4faSMingyou Chen 69dc1ec4faSMingyou Chen enum bitland_mifs_power_profile { 70dc1ec4faSMingyou Chen WMI_PP_BALANCED = 0, 71dc1ec4faSMingyou Chen WMI_PP_PERFORMANCE = 1, 72dc1ec4faSMingyou Chen WMI_PP_QUIET = 2, 73dc1ec4faSMingyou Chen WMI_PP_FULL_SPEED = 3, 74dc1ec4faSMingyou Chen }; 75dc1ec4faSMingyou Chen 76dc1ec4faSMingyou Chen enum bitland_mifs_event_id { 77dc1ec4faSMingyou Chen WMI_EVENT_RESERVED_1 = 1, 78dc1ec4faSMingyou Chen WMI_EVENT_RESERVED_2 = 2, 79dc1ec4faSMingyou Chen WMI_EVENT_RESERVED_3 = 3, 80dc1ec4faSMingyou Chen WMI_EVENT_AIRPLANE_MODE = 4, 81dc1ec4faSMingyou Chen WMI_EVENT_KBD_BRIGHTNESS = 5, 82dc1ec4faSMingyou Chen WMI_EVENT_TOUCHPAD_STATE = 6, 83dc1ec4faSMingyou Chen WMI_EVENT_FNLOCK_STATE = 7, 84dc1ec4faSMingyou Chen WMI_EVENT_KBD_MODE = 8, 85dc1ec4faSMingyou Chen WMI_EVENT_CAPSLOCK_STATE = 9, 86dc1ec4faSMingyou Chen WMI_EVENT_CALCULATOR_START = 11, 87dc1ec4faSMingyou Chen WMI_EVENT_BROWSER_START = 12, 88dc1ec4faSMingyou Chen WMI_EVENT_NUMLOCK_STATE = 13, 89dc1ec4faSMingyou Chen WMI_EVENT_SCROLLLOCK_STATE = 14, 90dc1ec4faSMingyou Chen WMI_EVENT_PERFORMANCE_PLAN = 15, 91dc1ec4faSMingyou Chen WMI_EVENT_FN_J = 16, 92dc1ec4faSMingyou Chen WMI_EVENT_FN_F = 17, 93dc1ec4faSMingyou Chen WMI_EVENT_FN_0 = 18, 94dc1ec4faSMingyou Chen WMI_EVENT_FN_1 = 19, 95dc1ec4faSMingyou Chen WMI_EVENT_FN_2 = 20, 96dc1ec4faSMingyou Chen WMI_EVENT_FN_3 = 21, 97dc1ec4faSMingyou Chen WMI_EVENT_FN_4 = 22, 98dc1ec4faSMingyou Chen WMI_EVENT_FN_5 = 24, 99dc1ec4faSMingyou Chen WMI_EVENT_REFRESH_RATE = 25, 100dc1ec4faSMingyou Chen WMI_EVENT_CPU_FAN_SPEED = 26, 101dc1ec4faSMingyou Chen WMI_EVENT_GPU_FAN_SPEED = 32, 102dc1ec4faSMingyou Chen WMI_EVENT_WIN_KEY_LOCK = 33, 103dc1ec4faSMingyou Chen WMI_EVENT_RESERVED_23 = 34, 104dc1ec4faSMingyou Chen WMI_EVENT_OPEN_APP = 35, 105dc1ec4faSMingyou Chen }; 106dc1ec4faSMingyou Chen 107dc1ec4faSMingyou Chen enum bitland_mifs_event_type { 108dc1ec4faSMingyou Chen WMI_EVENT_TYPE_HOTKEY = 1, 109dc1ec4faSMingyou Chen }; 110dc1ec4faSMingyou Chen 111dc1ec4faSMingyou Chen enum bitland_wmi_device_type { 112dc1ec4faSMingyou Chen BITLAND_WMI_CONTROL = 0, 113dc1ec4faSMingyou Chen BITLAND_WMI_EVENT = 1, 114dc1ec4faSMingyou Chen }; 115dc1ec4faSMingyou Chen 116dc1ec4faSMingyou Chen struct bitland_mifs_input { 117dc1ec4faSMingyou Chen u8 reserved1; 118dc1ec4faSMingyou Chen u8 operation; 119dc1ec4faSMingyou Chen u8 reserved2; 120dc1ec4faSMingyou Chen u8 function; 121dc1ec4faSMingyou Chen u8 payload[28]; 122dc1ec4faSMingyou Chen } __packed; 123dc1ec4faSMingyou Chen 124dc1ec4faSMingyou Chen struct bitland_mifs_output { 125dc1ec4faSMingyou Chen u8 reserved1; 126dc1ec4faSMingyou Chen u8 operation; 127dc1ec4faSMingyou Chen u8 reserved2; 128dc1ec4faSMingyou Chen u8 function; 129dc1ec4faSMingyou Chen u8 data[28]; 130dc1ec4faSMingyou Chen } __packed; 131dc1ec4faSMingyou Chen 132dc1ec4faSMingyou Chen struct bitland_mifs_event { 133dc1ec4faSMingyou Chen u8 event_type; 134dc1ec4faSMingyou Chen u8 event_id; 135dc1ec4faSMingyou Chen u8 value_low; /* For most events, this is the value */ 136dc1ec4faSMingyou Chen u8 value_high; /* For fan speed events, combined with value_low */ 137dc1ec4faSMingyou Chen u8 reserved[4]; 138dc1ec4faSMingyou Chen } __packed; 139dc1ec4faSMingyou Chen 140dc1ec4faSMingyou Chen static BLOCKING_NOTIFIER_HEAD(bitland_notifier_list); 141dc1ec4faSMingyou Chen 142dc1ec4faSMingyou Chen enum bitland_notifier_actions { 143dc1ec4faSMingyou Chen BITLAND_NOTIFY_KBD_BRIGHTNESS, 144dc1ec4faSMingyou Chen BITLAND_NOTIFY_PLATFORM_PROFILE, 145dc1ec4faSMingyou Chen BITLAND_NOTIFY_HWMON, 146dc1ec4faSMingyou Chen }; 147dc1ec4faSMingyou Chen 148dc1ec4faSMingyou Chen struct bitland_fan_notify_data { 149dc1ec4faSMingyou Chen int channel; /* 0 = CPU, 1 = GPU */ 150dc1ec4faSMingyou Chen u16 speed; 151dc1ec4faSMingyou Chen }; 152dc1ec4faSMingyou Chen 153dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data { 154dc1ec4faSMingyou Chen struct wmi_device *wdev; 155dc1ec4faSMingyou Chen struct mutex lock; /* Protects WMI calls */ 156dc1ec4faSMingyou Chen struct led_classdev kbd_led; 157dc1ec4faSMingyou Chen struct notifier_block notifier; 158dc1ec4faSMingyou Chen struct input_dev *input_dev; 159dc1ec4faSMingyou Chen struct device *hwmon_dev; 160dc1ec4faSMingyou Chen struct device *pp_dev; 161dc1ec4faSMingyou Chen enum platform_profile_option saved_profile; 162dc1ec4faSMingyou Chen }; 163dc1ec4faSMingyou Chen 164dc1ec4faSMingyou Chen static int bitland_mifs_wmi_call(struct bitland_mifs_wmi_data *data, 165dc1ec4faSMingyou Chen const struct bitland_mifs_input *input, 166dc1ec4faSMingyou Chen struct bitland_mifs_output *output) 167dc1ec4faSMingyou Chen { 168dc1ec4faSMingyou Chen struct wmi_buffer in_buf = { .length = sizeof(*input), .data = (void *)input }; 169dc1ec4faSMingyou Chen struct wmi_buffer out_buf = { 0 }; 170dc1ec4faSMingyou Chen int ret; 171dc1ec4faSMingyou Chen 172dc1ec4faSMingyou Chen guard(mutex)(&data->lock); 173dc1ec4faSMingyou Chen 174578bc2a5SArmin Wolf if (!output) 175578bc2a5SArmin Wolf return wmidev_invoke_procedure(data->wdev, 0, 1, &in_buf); 176578bc2a5SArmin Wolf 17796b1b053SArmin Wolf ret = wmidev_invoke_method(data->wdev, 0, 1, &in_buf, &out_buf, sizeof(*output)); 178dc1ec4faSMingyou Chen if (ret) 179dc1ec4faSMingyou Chen return ret; 180dc1ec4faSMingyou Chen 18196b1b053SArmin Wolf memcpy(output, out_buf.data, sizeof(*output)); 18296b1b053SArmin Wolf kfree(out_buf.data); 183dc1ec4faSMingyou Chen 184dc1ec4faSMingyou Chen return 0; 185dc1ec4faSMingyou Chen } 186dc1ec4faSMingyou Chen 187dc1ec4faSMingyou Chen static int laptop_profile_get(struct device *dev, 188dc1ec4faSMingyou Chen enum platform_profile_option *profile) 189dc1ec4faSMingyou Chen { 190dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 191dc1ec4faSMingyou Chen struct bitland_mifs_input input = { 192dc1ec4faSMingyou Chen .reserved1 = 0, 193dc1ec4faSMingyou Chen .operation = WMI_METHOD_GET, 194dc1ec4faSMingyou Chen .reserved2 = 0, 195dc1ec4faSMingyou Chen .function = WMI_FN_SYSTEM_PER_MODE, 196dc1ec4faSMingyou Chen }; 197dc1ec4faSMingyou Chen struct bitland_mifs_output result; 198dc1ec4faSMingyou Chen int ret; 199dc1ec4faSMingyou Chen 200dc1ec4faSMingyou Chen ret = bitland_mifs_wmi_call(data, &input, &result); 201dc1ec4faSMingyou Chen if (ret) 202dc1ec4faSMingyou Chen return ret; 203dc1ec4faSMingyou Chen 204dc1ec4faSMingyou Chen switch (result.data[0]) { 205dc1ec4faSMingyou Chen case WMI_PP_BALANCED: 206dc1ec4faSMingyou Chen *profile = PLATFORM_PROFILE_BALANCED; 207dc1ec4faSMingyou Chen break; 208dc1ec4faSMingyou Chen case WMI_PP_PERFORMANCE: 209dc1ec4faSMingyou Chen *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE; 210dc1ec4faSMingyou Chen break; 211dc1ec4faSMingyou Chen case WMI_PP_QUIET: 212dc1ec4faSMingyou Chen *profile = PLATFORM_PROFILE_LOW_POWER; 213dc1ec4faSMingyou Chen break; 214dc1ec4faSMingyou Chen case WMI_PP_FULL_SPEED: 215dc1ec4faSMingyou Chen *profile = PLATFORM_PROFILE_PERFORMANCE; 216dc1ec4faSMingyou Chen break; 217dc1ec4faSMingyou Chen default: 218dc1ec4faSMingyou Chen return -EINVAL; 219dc1ec4faSMingyou Chen } 220dc1ec4faSMingyou Chen return 0; 221dc1ec4faSMingyou Chen } 222dc1ec4faSMingyou Chen 223dc1ec4faSMingyou Chen static int bitland_check_performance_capability(struct bitland_mifs_wmi_data *data) 224dc1ec4faSMingyou Chen { 225dc1ec4faSMingyou Chen struct bitland_mifs_input input = { 226dc1ec4faSMingyou Chen .operation = WMI_METHOD_GET, 227dc1ec4faSMingyou Chen .function = WMI_FN_SYSTEM_AC_TYPE, 228dc1ec4faSMingyou Chen }; 229dc1ec4faSMingyou Chen struct bitland_mifs_output output; 230dc1ec4faSMingyou Chen int ret; 231dc1ec4faSMingyou Chen 232dc1ec4faSMingyou Chen /* Full-speed/performance mode requires DC power (not USB-C) */ 233dc1ec4faSMingyou Chen if (!power_supply_is_system_supplied()) 234dc1ec4faSMingyou Chen return -EOPNOTSUPP; 235dc1ec4faSMingyou Chen 236dc1ec4faSMingyou Chen ret = bitland_mifs_wmi_call(data, &input, &output); 237dc1ec4faSMingyou Chen if (ret) 238dc1ec4faSMingyou Chen return ret; 239dc1ec4faSMingyou Chen 240dc1ec4faSMingyou Chen if (output.data[0] != WMI_SYSTEM_AC_CIRCULARHOLE) 241dc1ec4faSMingyou Chen return -EOPNOTSUPP; 242dc1ec4faSMingyou Chen 243dc1ec4faSMingyou Chen return 0; 244dc1ec4faSMingyou Chen } 245dc1ec4faSMingyou Chen 246dc1ec4faSMingyou Chen static int laptop_profile_set(struct device *dev, 247dc1ec4faSMingyou Chen enum platform_profile_option profile) 248dc1ec4faSMingyou Chen { 249dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 250dc1ec4faSMingyou Chen struct bitland_mifs_input input = { 251dc1ec4faSMingyou Chen .reserved1 = 0, 252dc1ec4faSMingyou Chen .operation = WMI_METHOD_SET, 253dc1ec4faSMingyou Chen .reserved2 = 0, 254dc1ec4faSMingyou Chen .function = WMI_FN_SYSTEM_PER_MODE, 255dc1ec4faSMingyou Chen }; 256dc1ec4faSMingyou Chen int ret; 257dc1ec4faSMingyou Chen u8 val; 258dc1ec4faSMingyou Chen 259dc1ec4faSMingyou Chen switch (profile) { 260dc1ec4faSMingyou Chen case PLATFORM_PROFILE_LOW_POWER: 261dc1ec4faSMingyou Chen val = WMI_PP_QUIET; 262dc1ec4faSMingyou Chen break; 263dc1ec4faSMingyou Chen case PLATFORM_PROFILE_BALANCED: 264dc1ec4faSMingyou Chen val = WMI_PP_BALANCED; 265dc1ec4faSMingyou Chen break; 266dc1ec4faSMingyou Chen case PLATFORM_PROFILE_BALANCED_PERFORMANCE: 267dc1ec4faSMingyou Chen ret = bitland_check_performance_capability(data); 268dc1ec4faSMingyou Chen if (ret) 269dc1ec4faSMingyou Chen return ret; 270dc1ec4faSMingyou Chen val = WMI_PP_PERFORMANCE; 271dc1ec4faSMingyou Chen break; 272dc1ec4faSMingyou Chen case PLATFORM_PROFILE_PERFORMANCE: 273dc1ec4faSMingyou Chen ret = bitland_check_performance_capability(data); 274dc1ec4faSMingyou Chen if (ret) 275dc1ec4faSMingyou Chen return ret; 276dc1ec4faSMingyou Chen val = WMI_PP_FULL_SPEED; 277dc1ec4faSMingyou Chen break; 278dc1ec4faSMingyou Chen default: 279dc1ec4faSMingyou Chen return -EOPNOTSUPP; 280dc1ec4faSMingyou Chen } 281dc1ec4faSMingyou Chen 282dc1ec4faSMingyou Chen input.payload[0] = val; 283dc1ec4faSMingyou Chen 284dc1ec4faSMingyou Chen return bitland_mifs_wmi_call(data, &input, NULL); 285dc1ec4faSMingyou Chen } 286dc1ec4faSMingyou Chen 287dc1ec4faSMingyou Chen static int platform_profile_probe(void *drvdata, unsigned long *choices) 288dc1ec4faSMingyou Chen { 289dc1ec4faSMingyou Chen set_bit(PLATFORM_PROFILE_LOW_POWER, choices); 290dc1ec4faSMingyou Chen set_bit(PLATFORM_PROFILE_BALANCED, choices); 291dc1ec4faSMingyou Chen set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices); 292dc1ec4faSMingyou Chen set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); 293dc1ec4faSMingyou Chen 294dc1ec4faSMingyou Chen return 0; 295dc1ec4faSMingyou Chen } 296dc1ec4faSMingyou Chen 297dc1ec4faSMingyou Chen static int bitland_mifs_wmi_suspend(struct device *dev) 298dc1ec4faSMingyou Chen { 299dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 300dc1ec4faSMingyou Chen enum platform_profile_option profile; 301dc1ec4faSMingyou Chen int ret; 302dc1ec4faSMingyou Chen 303dc1ec4faSMingyou Chen ret = laptop_profile_get(data->pp_dev, &profile); 304dc1ec4faSMingyou Chen if (ret == 0) 305dc1ec4faSMingyou Chen data->saved_profile = profile; 306dc1ec4faSMingyou Chen 307dc1ec4faSMingyou Chen return ret; 308dc1ec4faSMingyou Chen } 309dc1ec4faSMingyou Chen 310dc1ec4faSMingyou Chen static int bitland_mifs_wmi_resume(struct device *dev) 311dc1ec4faSMingyou Chen { 312dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 313dc1ec4faSMingyou Chen 314dc1ec4faSMingyou Chen dev_dbg(dev, "Resuming, restoring profile %d\n", data->saved_profile); 315dc1ec4faSMingyou Chen return laptop_profile_set(dev, data->saved_profile); 316dc1ec4faSMingyou Chen } 317dc1ec4faSMingyou Chen 318dc1ec4faSMingyou Chen static DEFINE_SIMPLE_DEV_PM_OPS(bitland_mifs_wmi_pm_ops, 319dc1ec4faSMingyou Chen bitland_mifs_wmi_suspend, 320dc1ec4faSMingyou Chen bitland_mifs_wmi_resume); 321dc1ec4faSMingyou Chen 322dc1ec4faSMingyou Chen static const struct platform_profile_ops laptop_profile_ops = { 323dc1ec4faSMingyou Chen .probe = platform_profile_probe, 324dc1ec4faSMingyou Chen .profile_get = laptop_profile_get, 325dc1ec4faSMingyou Chen .profile_set = laptop_profile_set, 326dc1ec4faSMingyou Chen }; 327dc1ec4faSMingyou Chen 328dc1ec4faSMingyou Chen static const char *const fan_labels[] = { 329dc1ec4faSMingyou Chen "CPU", /* 0 */ 330dc1ec4faSMingyou Chen "GPU", /* 1 */ 331dc1ec4faSMingyou Chen "SYS", /* 2 */ 332dc1ec4faSMingyou Chen }; 333dc1ec4faSMingyou Chen 334dc1ec4faSMingyou Chen static int laptop_hwmon_read(struct device *dev, enum hwmon_sensor_types type, 335dc1ec4faSMingyou Chen u32 attr, int channel, long *val) 336dc1ec4faSMingyou Chen { 337dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 338dc1ec4faSMingyou Chen struct bitland_mifs_input input = { 339dc1ec4faSMingyou Chen .reserved1 = 0, 340dc1ec4faSMingyou Chen .operation = WMI_METHOD_GET, 341dc1ec4faSMingyou Chen .reserved2 = 0, 342dc1ec4faSMingyou Chen }; 343dc1ec4faSMingyou Chen struct bitland_mifs_output res; 344dc1ec4faSMingyou Chen int ret; 345dc1ec4faSMingyou Chen 346dc1ec4faSMingyou Chen switch (type) { 347dc1ec4faSMingyou Chen case hwmon_temp: 348dc1ec4faSMingyou Chen input.function = WMI_FN_CPU_THERMOMETER; 349dc1ec4faSMingyou Chen ret = bitland_mifs_wmi_call(data, &input, &res); 350dc1ec4faSMingyou Chen if (!ret) 351dc1ec4faSMingyou Chen *val = res.data[0] * MILLIDEGREE_PER_DEGREE; 352dc1ec4faSMingyou Chen return ret; 353dc1ec4faSMingyou Chen case hwmon_fan: 354dc1ec4faSMingyou Chen input.function = WMI_FN_FAN_SPEEDS; 355dc1ec4faSMingyou Chen ret = bitland_mifs_wmi_call(data, &input, &res); 356dc1ec4faSMingyou Chen if (ret) 357dc1ec4faSMingyou Chen return ret; 358dc1ec4faSMingyou Chen 359dc1ec4faSMingyou Chen switch (channel) { 360dc1ec4faSMingyou Chen case 0: /* CPU */ 361dc1ec4faSMingyou Chen *val = get_unaligned_le16(&res.data[0]); 362dc1ec4faSMingyou Chen return 0; 363dc1ec4faSMingyou Chen case 1: /* GPU */ 364dc1ec4faSMingyou Chen *val = get_unaligned_le16(&res.data[2]); 365dc1ec4faSMingyou Chen return 0; 366dc1ec4faSMingyou Chen case 2: /* SYS */ 367dc1ec4faSMingyou Chen *val = get_unaligned_le16(&res.data[6]); 368dc1ec4faSMingyou Chen return 0; 369dc1ec4faSMingyou Chen default: 370dc1ec4faSMingyou Chen return -EINVAL; 371dc1ec4faSMingyou Chen } 372dc1ec4faSMingyou Chen default: 373dc1ec4faSMingyou Chen return -EINVAL; 374dc1ec4faSMingyou Chen } 375dc1ec4faSMingyou Chen } 376dc1ec4faSMingyou Chen 377dc1ec4faSMingyou Chen static int laptop_hwmon_read_string(struct device *dev, 378dc1ec4faSMingyou Chen enum hwmon_sensor_types type, u32 attr, 379dc1ec4faSMingyou Chen int channel, const char **str) 380dc1ec4faSMingyou Chen { 381dc1ec4faSMingyou Chen if (type == hwmon_fan && attr == hwmon_fan_label) { 382dc1ec4faSMingyou Chen if (channel >= 0 && channel < ARRAY_SIZE(fan_labels)) { 383dc1ec4faSMingyou Chen *str = fan_labels[channel]; 384dc1ec4faSMingyou Chen return 0; 385dc1ec4faSMingyou Chen } 386dc1ec4faSMingyou Chen } 387dc1ec4faSMingyou Chen return -EINVAL; 388dc1ec4faSMingyou Chen } 389dc1ec4faSMingyou Chen 390dc1ec4faSMingyou Chen static const struct hwmon_channel_info *laptop_hwmon_info[] = { 391dc1ec4faSMingyou Chen HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), 392dc1ec4faSMingyou Chen HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL, 393dc1ec4faSMingyou Chen HWMON_F_INPUT | HWMON_F_LABEL, 394dc1ec4faSMingyou Chen HWMON_F_INPUT | HWMON_F_LABEL), 395dc1ec4faSMingyou Chen NULL 396dc1ec4faSMingyou Chen }; 397dc1ec4faSMingyou Chen 398dc1ec4faSMingyou Chen static const struct hwmon_ops laptop_hwmon_ops = { 399dc1ec4faSMingyou Chen .visible = 0444, 400dc1ec4faSMingyou Chen .read = laptop_hwmon_read, 401dc1ec4faSMingyou Chen .read_string = laptop_hwmon_read_string, 402dc1ec4faSMingyou Chen }; 403dc1ec4faSMingyou Chen 404dc1ec4faSMingyou Chen static const struct hwmon_chip_info laptop_chip_info = { 405dc1ec4faSMingyou Chen .ops = &laptop_hwmon_ops, 406dc1ec4faSMingyou Chen .info = laptop_hwmon_info, 407dc1ec4faSMingyou Chen }; 408dc1ec4faSMingyou Chen 409dc1ec4faSMingyou Chen static int laptop_kbd_led_set(struct led_classdev *led_cdev, 410dc1ec4faSMingyou Chen enum led_brightness value) 411dc1ec4faSMingyou Chen { 412dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *data = 413dc1ec4faSMingyou Chen container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led); 414dc1ec4faSMingyou Chen struct bitland_mifs_input input = { 415dc1ec4faSMingyou Chen .reserved1 = 0, 416dc1ec4faSMingyou Chen .operation = WMI_METHOD_SET, 417dc1ec4faSMingyou Chen .reserved2 = 0, 418dc1ec4faSMingyou Chen .function = WMI_FN_RGB_KB_BRIGHTNESS, 419dc1ec4faSMingyou Chen }; 420dc1ec4faSMingyou Chen 421dc1ec4faSMingyou Chen input.payload[0] = (u8)value; 422dc1ec4faSMingyou Chen 423dc1ec4faSMingyou Chen return bitland_mifs_wmi_call(data, &input, NULL); 424dc1ec4faSMingyou Chen } 425dc1ec4faSMingyou Chen 426dc1ec4faSMingyou Chen static enum led_brightness laptop_kbd_led_get(struct led_classdev *led_cdev) 427dc1ec4faSMingyou Chen { 428dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *data = 429dc1ec4faSMingyou Chen container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led); 430dc1ec4faSMingyou Chen struct bitland_mifs_input input = { 431dc1ec4faSMingyou Chen .reserved1 = 0, 432dc1ec4faSMingyou Chen .operation = WMI_METHOD_GET, 433dc1ec4faSMingyou Chen .reserved2 = 0, 434dc1ec4faSMingyou Chen .function = WMI_FN_RGB_KB_BRIGHTNESS, 435dc1ec4faSMingyou Chen }; 436dc1ec4faSMingyou Chen struct bitland_mifs_output res; 437dc1ec4faSMingyou Chen int ret; 438dc1ec4faSMingyou Chen 439dc1ec4faSMingyou Chen ret = bitland_mifs_wmi_call(data, &input, &res); 440dc1ec4faSMingyou Chen if (ret) 441dc1ec4faSMingyou Chen return ret; 442dc1ec4faSMingyou Chen 443dc1ec4faSMingyou Chen return res.data[0]; 444dc1ec4faSMingyou Chen } 445dc1ec4faSMingyou Chen 446dc1ec4faSMingyou Chen static const char *const gpu_mode_strings[] = { 447dc1ec4faSMingyou Chen "hybrid", 448dc1ec4faSMingyou Chen "discrete", 449dc1ec4faSMingyou Chen "uma", 450dc1ec4faSMingyou Chen }; 451dc1ec4faSMingyou Chen 452dc1ec4faSMingyou Chen /* GPU Mode: 0:Hybrid, 1:Discrete, 2:UMA */ 453dc1ec4faSMingyou Chen static ssize_t gpu_mode_show(struct device *dev, struct device_attribute *attr, 454dc1ec4faSMingyou Chen char *buf) 455dc1ec4faSMingyou Chen { 456dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 457dc1ec4faSMingyou Chen struct bitland_mifs_input input = { 458dc1ec4faSMingyou Chen .reserved1 = 0, 459dc1ec4faSMingyou Chen .operation = WMI_METHOD_GET, 460dc1ec4faSMingyou Chen .reserved2 = 0, 461dc1ec4faSMingyou Chen .function = WMI_FN_GPU_MODE, 462dc1ec4faSMingyou Chen }; 463dc1ec4faSMingyou Chen struct bitland_mifs_output res; 464dc1ec4faSMingyou Chen u8 mode_val; 465dc1ec4faSMingyou Chen int ret; 466dc1ec4faSMingyou Chen 467dc1ec4faSMingyou Chen ret = bitland_mifs_wmi_call(data, &input, &res); 468dc1ec4faSMingyou Chen if (ret) 469dc1ec4faSMingyou Chen return ret; 470dc1ec4faSMingyou Chen 471dc1ec4faSMingyou Chen mode_val = res.data[0]; 472dc1ec4faSMingyou Chen if (mode_val >= ARRAY_SIZE(gpu_mode_strings)) 473dc1ec4faSMingyou Chen return -EPROTO; 474dc1ec4faSMingyou Chen 475dc1ec4faSMingyou Chen return sysfs_emit(buf, "%s\n", gpu_mode_strings[mode_val]); 476dc1ec4faSMingyou Chen } 477dc1ec4faSMingyou Chen 478dc1ec4faSMingyou Chen static ssize_t gpu_mode_store(struct device *dev, struct device_attribute *attr, 479dc1ec4faSMingyou Chen const char *buf, size_t count) 480dc1ec4faSMingyou Chen { 481dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 482dc1ec4faSMingyou Chen struct bitland_mifs_input input = { 483dc1ec4faSMingyou Chen .reserved1 = 0, 484dc1ec4faSMingyou Chen .operation = WMI_METHOD_SET, 485dc1ec4faSMingyou Chen .reserved2 = 0, 486dc1ec4faSMingyou Chen .function = WMI_FN_GPU_MODE, 487dc1ec4faSMingyou Chen }; 488dc1ec4faSMingyou Chen int val; 489dc1ec4faSMingyou Chen int ret; 490dc1ec4faSMingyou Chen 491dc1ec4faSMingyou Chen val = sysfs_match_string(gpu_mode_strings, buf); 492dc1ec4faSMingyou Chen if (val < 0) 493dc1ec4faSMingyou Chen return -EINVAL; 494dc1ec4faSMingyou Chen 495dc1ec4faSMingyou Chen input.payload[0] = (u8)val; 496dc1ec4faSMingyou Chen 497dc1ec4faSMingyou Chen ret = bitland_mifs_wmi_call(data, &input, NULL); 498dc1ec4faSMingyou Chen if (ret) 499dc1ec4faSMingyou Chen return ret; 500dc1ec4faSMingyou Chen 501dc1ec4faSMingyou Chen return count; 502dc1ec4faSMingyou Chen } 503dc1ec4faSMingyou Chen 504dc1ec4faSMingyou Chen static const char *const kb_mode_strings[] = { 505dc1ec4faSMingyou Chen "off", /* 0 */ 506dc1ec4faSMingyou Chen "cyclic", /* 1 */ 507dc1ec4faSMingyou Chen "fixed", /* 2 */ 508dc1ec4faSMingyou Chen "custom", /* 3 */ 509dc1ec4faSMingyou Chen }; 510dc1ec4faSMingyou Chen 511dc1ec4faSMingyou Chen static ssize_t kb_mode_show(struct device *dev, struct device_attribute *attr, 512dc1ec4faSMingyou Chen char *buf) 513dc1ec4faSMingyou Chen { 514dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 515dc1ec4faSMingyou Chen struct bitland_mifs_input input = { 516dc1ec4faSMingyou Chen .reserved1 = 0, 517dc1ec4faSMingyou Chen .operation = WMI_METHOD_GET, 518dc1ec4faSMingyou Chen .reserved2 = 0, 519dc1ec4faSMingyou Chen .function = WMI_FN_RGB_KB_MODE, 520dc1ec4faSMingyou Chen }; 521dc1ec4faSMingyou Chen struct bitland_mifs_output res; 522dc1ec4faSMingyou Chen u8 mode_val; 523dc1ec4faSMingyou Chen int ret; 524dc1ec4faSMingyou Chen 525dc1ec4faSMingyou Chen ret = bitland_mifs_wmi_call(data, &input, &res); 526dc1ec4faSMingyou Chen if (ret) 527dc1ec4faSMingyou Chen return ret; 528dc1ec4faSMingyou Chen 529dc1ec4faSMingyou Chen mode_val = res.data[0]; 530dc1ec4faSMingyou Chen if (mode_val >= ARRAY_SIZE(kb_mode_strings)) 531dc1ec4faSMingyou Chen return -EPROTO; 532dc1ec4faSMingyou Chen 533dc1ec4faSMingyou Chen return sysfs_emit(buf, "%s\n", kb_mode_strings[mode_val]); 534dc1ec4faSMingyou Chen } 535dc1ec4faSMingyou Chen 536dc1ec4faSMingyou Chen static ssize_t kb_mode_store(struct device *dev, struct device_attribute *attr, 537dc1ec4faSMingyou Chen const char *buf, size_t count) 538dc1ec4faSMingyou Chen { 539dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 540dc1ec4faSMingyou Chen struct bitland_mifs_input input = { 541dc1ec4faSMingyou Chen .reserved1 = 0, 542dc1ec4faSMingyou Chen .operation = WMI_METHOD_SET, 543dc1ec4faSMingyou Chen .reserved2 = 0, 544dc1ec4faSMingyou Chen .function = WMI_FN_RGB_KB_MODE, 545dc1ec4faSMingyou Chen }; 546dc1ec4faSMingyou Chen // the wmi value (0, 1, 2 or 3) 547dc1ec4faSMingyou Chen int val; 548dc1ec4faSMingyou Chen int ret; 549dc1ec4faSMingyou Chen 550dc1ec4faSMingyou Chen val = sysfs_match_string(kb_mode_strings, buf); 551dc1ec4faSMingyou Chen if (val < 0) 552dc1ec4faSMingyou Chen return -EINVAL; 553dc1ec4faSMingyou Chen 554dc1ec4faSMingyou Chen input.payload[0] = (u8)val; 555dc1ec4faSMingyou Chen 556dc1ec4faSMingyou Chen ret = bitland_mifs_wmi_call(data, &input, NULL); 557dc1ec4faSMingyou Chen if (ret) 558dc1ec4faSMingyou Chen return ret; 559dc1ec4faSMingyou Chen 560dc1ec4faSMingyou Chen return count; 561dc1ec4faSMingyou Chen } 562dc1ec4faSMingyou Chen 563dc1ec4faSMingyou Chen /* Fan Boost: 0:Normal, 1:Max Speed */ 564dc1ec4faSMingyou Chen static ssize_t fan_boost_store(struct device *dev, 565dc1ec4faSMingyou Chen struct device_attribute *attr, const char *buf, 566dc1ec4faSMingyou Chen size_t count) 567dc1ec4faSMingyou Chen { 568dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); 569dc1ec4faSMingyou Chen struct bitland_mifs_input input = { 570dc1ec4faSMingyou Chen .reserved1 = 0, 571dc1ec4faSMingyou Chen .operation = WMI_METHOD_SET, 572dc1ec4faSMingyou Chen .reserved2 = 0, 573dc1ec4faSMingyou Chen .function = WMI_FN_MAX_FAN_SWITCH, 574dc1ec4faSMingyou Chen }; 575dc1ec4faSMingyou Chen bool val; 576dc1ec4faSMingyou Chen int ret; 577dc1ec4faSMingyou Chen 578dc1ec4faSMingyou Chen if (kstrtobool(buf, &val)) 579dc1ec4faSMingyou Chen return -EINVAL; 580dc1ec4faSMingyou Chen 581dc1ec4faSMingyou Chen input.payload[0] = 0; /* CPU/GPU Fan */ 582dc1ec4faSMingyou Chen input.payload[1] = val; 583dc1ec4faSMingyou Chen 584dc1ec4faSMingyou Chen ret = bitland_mifs_wmi_call(data, &input, NULL); 585dc1ec4faSMingyou Chen if (ret) 586dc1ec4faSMingyou Chen return ret; 587dc1ec4faSMingyou Chen 588dc1ec4faSMingyou Chen return count; 589dc1ec4faSMingyou Chen } 590dc1ec4faSMingyou Chen 591dc1ec4faSMingyou Chen static const DEVICE_ATTR_RW(gpu_mode); 592dc1ec4faSMingyou Chen static const DEVICE_ATTR_RW(kb_mode); 593dc1ec4faSMingyou Chen static const DEVICE_ATTR_WO(fan_boost); 594dc1ec4faSMingyou Chen 595dc1ec4faSMingyou Chen static const struct attribute *const laptop_attrs[] = { 596dc1ec4faSMingyou Chen &dev_attr_gpu_mode.attr, 597dc1ec4faSMingyou Chen &dev_attr_kb_mode.attr, 598dc1ec4faSMingyou Chen &dev_attr_fan_boost.attr, 599dc1ec4faSMingyou Chen NULL, 600dc1ec4faSMingyou Chen }; 601dc1ec4faSMingyou Chen ATTRIBUTE_GROUPS(laptop); 602dc1ec4faSMingyou Chen 603dc1ec4faSMingyou Chen static const struct key_entry bitland_mifs_wmi_keymap[] = { 604dc1ec4faSMingyou Chen { KE_KEY, WMI_EVENT_OPEN_APP, { KEY_PROG1 } }, 605dc1ec4faSMingyou Chen { KE_KEY, WMI_EVENT_CALCULATOR_START, { KEY_CALC } }, 606dc1ec4faSMingyou Chen { KE_KEY, WMI_EVENT_BROWSER_START, { KEY_WWW } }, 607dc1ec4faSMingyou Chen { KE_IGNORE, WMI_EVENT_FN_J, { KEY_RESERVED } }, 608dc1ec4faSMingyou Chen { KE_IGNORE, WMI_EVENT_FN_F, { KEY_RESERVED } }, 609dc1ec4faSMingyou Chen { KE_IGNORE, WMI_EVENT_FN_0, { KEY_RESERVED } }, 610dc1ec4faSMingyou Chen { KE_IGNORE, WMI_EVENT_FN_1, { KEY_RESERVED } }, 611dc1ec4faSMingyou Chen { KE_IGNORE, WMI_EVENT_FN_2, { KEY_RESERVED } }, 612dc1ec4faSMingyou Chen { KE_IGNORE, WMI_EVENT_FN_3, { KEY_RESERVED } }, 613dc1ec4faSMingyou Chen { KE_IGNORE, WMI_EVENT_FN_4, { KEY_RESERVED } }, 614dc1ec4faSMingyou Chen { KE_IGNORE, WMI_EVENT_FN_5, { KEY_RESERVED } }, 615dc1ec4faSMingyou Chen { KE_END, 0 } 616dc1ec4faSMingyou Chen }; 617dc1ec4faSMingyou Chen 618dc1ec4faSMingyou Chen static void bitland_notifier_unregister(void *data) 619dc1ec4faSMingyou Chen { 620dc1ec4faSMingyou Chen struct notifier_block *nb = data; 621dc1ec4faSMingyou Chen 622dc1ec4faSMingyou Chen blocking_notifier_chain_unregister(&bitland_notifier_list, nb); 623dc1ec4faSMingyou Chen } 624dc1ec4faSMingyou Chen 625dc1ec4faSMingyou Chen static int bitland_notifier_callback(struct notifier_block *nb, 626dc1ec4faSMingyou Chen unsigned long action, void *data) 627dc1ec4faSMingyou Chen { 628dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *data_ctx = 629dc1ec4faSMingyou Chen container_of(nb, struct bitland_mifs_wmi_data, notifier); 630dc1ec4faSMingyou Chen struct bitland_fan_notify_data *fan_info; 631dc1ec4faSMingyou Chen u8 *brightness; 632dc1ec4faSMingyou Chen 633dc1ec4faSMingyou Chen switch (action) { 634dc1ec4faSMingyou Chen case BITLAND_NOTIFY_KBD_BRIGHTNESS: 635dc1ec4faSMingyou Chen brightness = data; 636dc1ec4faSMingyou Chen led_classdev_notify_brightness_hw_changed(&data_ctx->kbd_led, 637dc1ec4faSMingyou Chen *brightness); 638dc1ec4faSMingyou Chen break; 639dc1ec4faSMingyou Chen case BITLAND_NOTIFY_PLATFORM_PROFILE: 640dc1ec4faSMingyou Chen platform_profile_notify(data_ctx->pp_dev); 641dc1ec4faSMingyou Chen break; 642dc1ec4faSMingyou Chen case BITLAND_NOTIFY_HWMON: 643dc1ec4faSMingyou Chen fan_info = data; 644dc1ec4faSMingyou Chen 645dc1ec4faSMingyou Chen hwmon_notify_event(data_ctx->hwmon_dev, hwmon_fan, 646dc1ec4faSMingyou Chen hwmon_fan_input, fan_info->channel); 647dc1ec4faSMingyou Chen break; 648dc1ec4faSMingyou Chen } 649dc1ec4faSMingyou Chen 650dc1ec4faSMingyou Chen return NOTIFY_OK; 651dc1ec4faSMingyou Chen } 652dc1ec4faSMingyou Chen 653dc1ec4faSMingyou Chen static int bitland_mifs_wmi_probe(struct wmi_device *wdev, const void *context) 654dc1ec4faSMingyou Chen { 655dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *drv_data; 656dc1ec4faSMingyou Chen enum bitland_wmi_device_type dev_type = 657dc1ec4faSMingyou Chen (enum bitland_wmi_device_type)(unsigned long)context; 658dc1ec4faSMingyou Chen struct led_init_data init_data = { 659dc1ec4faSMingyou Chen .devicename = DRV_NAME, 660dc1ec4faSMingyou Chen .default_label = ":" LED_FUNCTION_KBD_BACKLIGHT, 661dc1ec4faSMingyou Chen .devname_mandatory = true, 662dc1ec4faSMingyou Chen }; 663dc1ec4faSMingyou Chen int ret; 664dc1ec4faSMingyou Chen 665dc1ec4faSMingyou Chen drv_data = devm_kzalloc(&wdev->dev, sizeof(*drv_data), GFP_KERNEL); 666dc1ec4faSMingyou Chen if (!drv_data) 667dc1ec4faSMingyou Chen return -ENOMEM; 668dc1ec4faSMingyou Chen 669dc1ec4faSMingyou Chen drv_data->wdev = wdev; 670dc1ec4faSMingyou Chen 671dc1ec4faSMingyou Chen ret = devm_mutex_init(&wdev->dev, &drv_data->lock); 672dc1ec4faSMingyou Chen if (ret) 673dc1ec4faSMingyou Chen return ret; 674dc1ec4faSMingyou Chen 675dc1ec4faSMingyou Chen dev_set_drvdata(&wdev->dev, drv_data); 676dc1ec4faSMingyou Chen 677dc1ec4faSMingyou Chen if (dev_type == BITLAND_WMI_EVENT) { 678dc1ec4faSMingyou Chen /* Register input device for hotkeys */ 679dc1ec4faSMingyou Chen drv_data->input_dev = devm_input_allocate_device(&wdev->dev); 680dc1ec4faSMingyou Chen if (!drv_data->input_dev) 681dc1ec4faSMingyou Chen return -ENOMEM; 682dc1ec4faSMingyou Chen 683dc1ec4faSMingyou Chen drv_data->input_dev->name = "Bitland MIFS WMI hotkeys"; 684dc1ec4faSMingyou Chen drv_data->input_dev->phys = "wmi/input0"; 685dc1ec4faSMingyou Chen drv_data->input_dev->id.bustype = BUS_HOST; 686dc1ec4faSMingyou Chen drv_data->input_dev->dev.parent = &wdev->dev; 687dc1ec4faSMingyou Chen 688dc1ec4faSMingyou Chen ret = sparse_keymap_setup(drv_data->input_dev, 689dc1ec4faSMingyou Chen bitland_mifs_wmi_keymap, NULL); 690dc1ec4faSMingyou Chen if (ret) 691dc1ec4faSMingyou Chen return ret; 692dc1ec4faSMingyou Chen 693dc1ec4faSMingyou Chen return input_register_device(drv_data->input_dev); 694dc1ec4faSMingyou Chen } 695dc1ec4faSMingyou Chen 696dc1ec4faSMingyou Chen /* Register platform profile */ 697dc1ec4faSMingyou Chen drv_data->pp_dev = devm_platform_profile_register(&wdev->dev, DRV_NAME, drv_data, 698dc1ec4faSMingyou Chen &laptop_profile_ops); 699dc1ec4faSMingyou Chen if (IS_ERR(drv_data->pp_dev)) 700dc1ec4faSMingyou Chen return PTR_ERR(drv_data->pp_dev); 701dc1ec4faSMingyou Chen 702dc1ec4faSMingyou Chen /* Register hwmon */ 703dc1ec4faSMingyou Chen drv_data->hwmon_dev = devm_hwmon_device_register_with_info(&wdev->dev, 704dc1ec4faSMingyou Chen "bitland_mifs", 705dc1ec4faSMingyou Chen drv_data, 706dc1ec4faSMingyou Chen &laptop_chip_info, 707dc1ec4faSMingyou Chen NULL); 708dc1ec4faSMingyou Chen if (IS_ERR(drv_data->hwmon_dev)) 709dc1ec4faSMingyou Chen return PTR_ERR(drv_data->hwmon_dev); 710dc1ec4faSMingyou Chen 711dc1ec4faSMingyou Chen /* Register keyboard LED */ 712dc1ec4faSMingyou Chen drv_data->kbd_led.max_brightness = 3; 713dc1ec4faSMingyou Chen drv_data->kbd_led.brightness_set_blocking = laptop_kbd_led_set; 714dc1ec4faSMingyou Chen drv_data->kbd_led.brightness_get = laptop_kbd_led_get; 715dc1ec4faSMingyou Chen drv_data->kbd_led.brightness = laptop_kbd_led_get(&drv_data->kbd_led); 716dc1ec4faSMingyou Chen drv_data->kbd_led.flags = LED_CORE_SUSPENDRESUME | 717dc1ec4faSMingyou Chen LED_BRIGHT_HW_CHANGED | 718dc1ec4faSMingyou Chen LED_REJECT_NAME_CONFLICT; 719dc1ec4faSMingyou Chen ret = devm_led_classdev_register_ext(&wdev->dev, &drv_data->kbd_led, &init_data); 720dc1ec4faSMingyou Chen if (ret) 721dc1ec4faSMingyou Chen return ret; 722dc1ec4faSMingyou Chen 723dc1ec4faSMingyou Chen drv_data->notifier.notifier_call = bitland_notifier_callback; 724dc1ec4faSMingyou Chen ret = blocking_notifier_chain_register(&bitland_notifier_list, &drv_data->notifier); 725dc1ec4faSMingyou Chen if (ret) 726dc1ec4faSMingyou Chen return ret; 727dc1ec4faSMingyou Chen 728dc1ec4faSMingyou Chen return devm_add_action_or_reset(&wdev->dev, 729dc1ec4faSMingyou Chen bitland_notifier_unregister, 730dc1ec4faSMingyou Chen &drv_data->notifier); 731dc1ec4faSMingyou Chen } 732dc1ec4faSMingyou Chen 733dc1ec4faSMingyou Chen static void bitland_mifs_wmi_notify(struct wmi_device *wdev, 734dc1ec4faSMingyou Chen const struct wmi_buffer *buffer) 735dc1ec4faSMingyou Chen { 736dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data *data = dev_get_drvdata(&wdev->dev); 737*2e2a3914SArmin Wolf const struct bitland_mifs_event *event = buffer->data; 738dc1ec4faSMingyou Chen struct bitland_fan_notify_data fan_data; 739dc1ec4faSMingyou Chen u8 brightness; 740dc1ec4faSMingyou Chen 741dc1ec4faSMingyou Chen /* Validate event type */ 742dc1ec4faSMingyou Chen if (event->event_type != WMI_EVENT_TYPE_HOTKEY) 743dc1ec4faSMingyou Chen return; 744dc1ec4faSMingyou Chen 745dc1ec4faSMingyou Chen dev_dbg(&wdev->dev, 746dc1ec4faSMingyou Chen "WMI event: id=0x%02x value_low=0x%02x value_high=0x%02x\n", 747dc1ec4faSMingyou Chen event->event_id, event->value_low, event->value_high); 748dc1ec4faSMingyou Chen 749dc1ec4faSMingyou Chen switch (event->event_id) { 750dc1ec4faSMingyou Chen case WMI_EVENT_KBD_BRIGHTNESS: 751dc1ec4faSMingyou Chen brightness = event->value_low; 752dc1ec4faSMingyou Chen blocking_notifier_call_chain(&bitland_notifier_list, 753dc1ec4faSMingyou Chen BITLAND_NOTIFY_KBD_BRIGHTNESS, 754dc1ec4faSMingyou Chen &brightness); 755dc1ec4faSMingyou Chen break; 756dc1ec4faSMingyou Chen 757dc1ec4faSMingyou Chen case WMI_EVENT_PERFORMANCE_PLAN: 758dc1ec4faSMingyou Chen blocking_notifier_call_chain(&bitland_notifier_list, 759dc1ec4faSMingyou Chen BITLAND_NOTIFY_PLATFORM_PROFILE, 760dc1ec4faSMingyou Chen NULL); 761dc1ec4faSMingyou Chen break; 762dc1ec4faSMingyou Chen 763dc1ec4faSMingyou Chen case WMI_EVENT_OPEN_APP: 764dc1ec4faSMingyou Chen case WMI_EVENT_CALCULATOR_START: 765dc1ec4faSMingyou Chen case WMI_EVENT_BROWSER_START: { 766dc1ec4faSMingyou Chen guard(mutex)(&data->lock); 767dc1ec4faSMingyou Chen if (!sparse_keymap_report_event(data->input_dev, 768dc1ec4faSMingyou Chen event->event_id, 1, true)) 769dc1ec4faSMingyou Chen dev_warn(&wdev->dev, "Unknown key pressed: 0x%02x\n", 770dc1ec4faSMingyou Chen event->event_id); 771dc1ec4faSMingyou Chen break; 772dc1ec4faSMingyou Chen } 773dc1ec4faSMingyou Chen 774dc1ec4faSMingyou Chen /* 775dc1ec4faSMingyou Chen * The device has 3 fans (CPU, GPU, SYS), 776dc1ec4faSMingyou Chen * but there are only the CPU and GPU fan has events 777dc1ec4faSMingyou Chen */ 778dc1ec4faSMingyou Chen case WMI_EVENT_CPU_FAN_SPEED: 779dc1ec4faSMingyou Chen case WMI_EVENT_GPU_FAN_SPEED: 780dc1ec4faSMingyou Chen if (event->event_id == WMI_EVENT_CPU_FAN_SPEED) 781dc1ec4faSMingyou Chen fan_data.channel = 0; 782dc1ec4faSMingyou Chen else 783dc1ec4faSMingyou Chen fan_data.channel = 1; 784dc1ec4faSMingyou Chen 785dc1ec4faSMingyou Chen /* Fan speed is 16-bit value (value_low is LSB, value_high is MSB) */ 786dc1ec4faSMingyou Chen fan_data.speed = (event->value_high << 8) | event->value_low; 787dc1ec4faSMingyou Chen blocking_notifier_call_chain(&bitland_notifier_list, 788dc1ec4faSMingyou Chen BITLAND_NOTIFY_HWMON, 789dc1ec4faSMingyou Chen &fan_data); 790dc1ec4faSMingyou Chen break; 791dc1ec4faSMingyou Chen 792dc1ec4faSMingyou Chen case WMI_EVENT_AIRPLANE_MODE: 793dc1ec4faSMingyou Chen case WMI_EVENT_TOUCHPAD_STATE: 794dc1ec4faSMingyou Chen case WMI_EVENT_FNLOCK_STATE: 795dc1ec4faSMingyou Chen case WMI_EVENT_KBD_MODE: 796dc1ec4faSMingyou Chen case WMI_EVENT_CAPSLOCK_STATE: 797dc1ec4faSMingyou Chen case WMI_EVENT_NUMLOCK_STATE: 798dc1ec4faSMingyou Chen case WMI_EVENT_SCROLLLOCK_STATE: 799dc1ec4faSMingyou Chen case WMI_EVENT_REFRESH_RATE: 800dc1ec4faSMingyou Chen case WMI_EVENT_WIN_KEY_LOCK: 801dc1ec4faSMingyou Chen /* These events are informational or handled by firmware */ 802dc1ec4faSMingyou Chen dev_dbg(&wdev->dev, "State change event: id=%d value=%d\n", 803dc1ec4faSMingyou Chen event->event_id, event->value_low); 804dc1ec4faSMingyou Chen break; 805dc1ec4faSMingyou Chen 806dc1ec4faSMingyou Chen default: 807dc1ec4faSMingyou Chen dev_dbg(&wdev->dev, "Unknown event: id=0x%02x value=0x%02x\n", 808dc1ec4faSMingyou Chen event->event_id, event->value_low); 809dc1ec4faSMingyou Chen break; 810dc1ec4faSMingyou Chen } 811dc1ec4faSMingyou Chen } 812dc1ec4faSMingyou Chen 813dc1ec4faSMingyou Chen static const struct wmi_device_id bitland_mifs_wmi_id_table[] = { 814dc1ec4faSMingyou Chen { BITLAND_MIFS_GUID, (void *)BITLAND_WMI_CONTROL }, 815dc1ec4faSMingyou Chen { BITLAND_EVENT_GUID, (void *)BITLAND_WMI_EVENT }, 816dc1ec4faSMingyou Chen {} 817dc1ec4faSMingyou Chen }; 818dc1ec4faSMingyou Chen MODULE_DEVICE_TABLE(wmi, bitland_mifs_wmi_id_table); 819dc1ec4faSMingyou Chen 820dc1ec4faSMingyou Chen static struct wmi_driver bitland_mifs_wmi_driver = { 821dc1ec4faSMingyou Chen .no_singleton = true, 822dc1ec4faSMingyou Chen .driver = { 823dc1ec4faSMingyou Chen .name = DRV_NAME, 824dc1ec4faSMingyou Chen .dev_groups = laptop_groups, 825dc1ec4faSMingyou Chen .pm = pm_sleep_ptr(&bitland_mifs_wmi_pm_ops), 826dc1ec4faSMingyou Chen }, 827dc1ec4faSMingyou Chen .id_table = bitland_mifs_wmi_id_table, 828*2e2a3914SArmin Wolf .min_event_size = sizeof(struct bitland_mifs_event), 829dc1ec4faSMingyou Chen .probe = bitland_mifs_wmi_probe, 830dc1ec4faSMingyou Chen .notify_new = bitland_mifs_wmi_notify, 831dc1ec4faSMingyou Chen }; 832dc1ec4faSMingyou Chen 833dc1ec4faSMingyou Chen module_wmi_driver(bitland_mifs_wmi_driver); 834dc1ec4faSMingyou Chen 835dc1ec4faSMingyou Chen MODULE_AUTHOR("Mingyou Chen <qby140326@gmail.com>"); 836dc1ec4faSMingyou Chen MODULE_DESCRIPTION("Bitland MIFS (MiInterface) WMI driver"); 837dc1ec4faSMingyou Chen MODULE_LICENSE("GPL"); 838