// SPDX-License-Identifier: GPL-2.0-or-later /* * Linux driver for Uniwill notebooks. * * Special thanks go to Pőcze Barnabás, Christoffer Sandberg and Werner Sembach * for supporting the development of this driver either through prior work or * by answering questions regarding the underlying ACPI and WMI interfaces. * * Copyright (C) 2025 Armin Wolf */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "uniwill-wmi.h" #define EC_ADDR_BAT_POWER_UNIT_1 0x0400 #define EC_ADDR_BAT_POWER_UNIT_2 0x0401 #define EC_ADDR_BAT_DESIGN_CAPACITY_1 0x0402 #define EC_ADDR_BAT_DESIGN_CAPACITY_2 0x0403 #define EC_ADDR_BAT_FULL_CAPACITY_1 0x0404 #define EC_ADDR_BAT_FULL_CAPACITY_2 0x0405 #define EC_ADDR_BAT_DESIGN_VOLTAGE_1 0x0408 #define EC_ADDR_BAT_DESIGN_VOLTAGE_2 0x0409 #define EC_ADDR_BAT_STATUS_1 0x0432 #define BAT_DISCHARGING BIT(0) #define EC_ADDR_BAT_STATUS_2 0x0433 #define EC_ADDR_BAT_CURRENT_1 0x0434 #define EC_ADDR_BAT_CURRENT_2 0x0435 #define EC_ADDR_BAT_REMAIN_CAPACITY_1 0x0436 #define EC_ADDR_BAT_REMAIN_CAPACITY_2 0x0437 #define EC_ADDR_BAT_VOLTAGE_1 0x0438 #define EC_ADDR_BAT_VOLTAGE_2 0x0439 #define EC_ADDR_CPU_TEMP 0x043E #define EC_ADDR_GPU_TEMP 0x044F #define EC_ADDR_MAIN_FAN_RPM_1 0x0464 #define EC_ADDR_MAIN_FAN_RPM_2 0x0465 #define EC_ADDR_SECOND_FAN_RPM_1 0x046C #define EC_ADDR_SECOND_FAN_RPM_2 0x046D #define EC_ADDR_DEVICE_STATUS 0x047B #define WIFI_STATUS_ON BIT(7) /* BIT(5) is also unset depending on the rfkill state (bluetooth?) */ #define EC_ADDR_BAT_ALERT 0x0494 #define EC_ADDR_BAT_CYCLE_COUNT_1 0x04A6 #define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7 #define EC_ADDR_PROJECT_ID 0x0740 #define EC_ADDR_AP_OEM 0x0741 #define ENABLE_MANUAL_CTRL BIT(0) #define ITE_KBD_EFFECT_REACTIVE BIT(3) #define FAN_ABNORMAL BIT(5) #define EC_ADDR_SUPPORT_5 0x0742 #define FAN_TURBO_SUPPORTED BIT(4) #define FAN_SUPPORT BIT(5) #define EC_ADDR_CTGP_DB_CTRL 0x0743 #define CTGP_DB_GENERAL_ENABLE BIT(0) #define CTGP_DB_DB_ENABLE BIT(1) #define CTGP_DB_CTGP_ENABLE BIT(2) #define EC_ADDR_CTGP_OFFSET 0x0744 #define EC_ADDR_TPP_OFFSET 0x0745 #define EC_ADDR_MAX_TGP 0x0746 #define EC_ADDR_LIGHTBAR_AC_CTRL 0x0748 #define LIGHTBAR_APP_EXISTS BIT(0) #define LIGHTBAR_POWER_SAVE BIT(1) #define LIGHTBAR_S0_OFF BIT(2) #define LIGHTBAR_S3_OFF BIT(3) // Breathing animation when suspended #define LIGHTBAR_WELCOME BIT(7) // Rainbow animation #define EC_ADDR_LIGHTBAR_AC_RED 0x0749 #define EC_ADDR_LIGHTBAR_AC_GREEN 0x074A #define EC_ADDR_LIGHTBAR_AC_BLUE 0x074B #define EC_ADDR_BIOS_OEM 0x074E #define FN_LOCK_STATUS BIT(4) #define EC_ADDR_MANUAL_FAN_CTRL 0x0751 #define FAN_LEVEL_MASK GENMASK(2, 0) #define FAN_MODE_TURBO BIT(4) #define FAN_MODE_HIGH BIT(5) #define FAN_MODE_BOOST BIT(6) #define FAN_MODE_USER BIT(7) #define EC_ADDR_PWM_1 0x075B #define EC_ADDR_PWM_2 0x075C /* Unreliable */ #define EC_ADDR_SUPPORT_1 0x0765 #define AIRPLANE_MODE BIT(0) #define GPS_SWITCH BIT(1) #define OVERCLOCK BIT(2) #define MACRO_KEY BIT(3) #define SHORTCUT_KEY BIT(4) #define SUPER_KEY_LOCK BIT(5) #define LIGHTBAR BIT(6) #define FAN_BOOST BIT(7) #define EC_ADDR_SUPPORT_2 0x0766 #define SILENT_MODE BIT(0) #define USB_CHARGING BIT(1) #define RGB_KEYBOARD BIT(2) #define CHINA_MODE BIT(5) #define MY_BATTERY BIT(6) #define EC_ADDR_TRIGGER 0x0767 #define TRIGGER_SUPER_KEY_LOCK BIT(0) #define TRIGGER_LIGHTBAR BIT(1) #define TRIGGER_FAN_BOOST BIT(2) #define TRIGGER_SILENT_MODE BIT(3) #define TRIGGER_USB_CHARGING BIT(4) #define RGB_APPLY_COLOR BIT(5) #define RGB_LOGO_EFFECT BIT(6) #define RGB_RAINBOW_EFFECT BIT(7) #define EC_ADDR_SWITCH_STATUS 0x0768 #define SUPER_KEY_LOCK_STATUS BIT(0) #define LIGHTBAR_STATUS BIT(1) #define FAN_BOOST_STATUS BIT(2) #define MACRO_KEY_STATUS BIT(3) #define MY_BAT_POWER_BAT_STATUS BIT(4) #define EC_ADDR_RGB_RED 0x0769 #define EC_ADDR_RGB_GREEN 0x076A #define EC_ADDR_RGB_BLUE 0x076B #define EC_ADDR_ROMID_START 0x0770 #define ROMID_LENGTH 14 #define EC_ADDR_ROMID_EXTRA_1 0x077E #define EC_ADDR_ROMID_EXTRA_2 0x077F #define EC_ADDR_BIOS_OEM_2 0x0782 #define FAN_V2_NEW BIT(0) #define FAN_QKEY BIT(1) #define FAN_TABLE_OFFICE_MODE BIT(2) #define FAN_V3 BIT(3) #define DEFAULT_MODE BIT(4) #define EC_ADDR_PL1_SETTING 0x0783 #define EC_ADDR_PL2_SETTING 0x0784 #define EC_ADDR_PL4_SETTING 0x0785 #define EC_ADDR_FAN_DEFAULT 0x0786 #define FAN_CURVE_LENGTH 5 #define EC_ADDR_KBD_STATUS 0x078C #define KBD_WHITE_ONLY BIT(0) // ~single color #define KBD_SINGLE_COLOR_OFF BIT(1) #define KBD_TURBO_LEVEL_MASK GENMASK(3, 2) #define KBD_APPLY BIT(4) #define KBD_BRIGHTNESS GENMASK(7, 5) #define EC_ADDR_FAN_CTRL 0x078E #define FAN3P5 BIT(1) #define CHARGING_PROFILE BIT(3) #define UNIVERSAL_FAN_CTRL BIT(6) #define EC_ADDR_BIOS_OEM_3 0x07A3 #define FAN_REDUCED_DURY_CYCLE BIT(5) #define FAN_ALWAYS_ON BIT(6) #define EC_ADDR_BIOS_BYTE 0x07A4 #define FN_LOCK_SWITCH BIT(3) #define EC_ADDR_OEM_3 0x07A5 #define POWER_LED_MASK GENMASK(1, 0) #define POWER_LED_LEFT 0x00 #define POWER_LED_BOTH 0x01 #define POWER_LED_NONE 0x02 #define FAN_QUIET BIT(2) #define OVERBOOST BIT(4) #define HIGH_POWER BIT(7) #define EC_ADDR_OEM_4 0x07A6 #define OVERBOOST_DYN_TEMP_OFF BIT(1) #define TOUCHPAD_TOGGLE_OFF BIT(6) #define EC_ADDR_CHARGE_CTRL 0x07B9 #define CHARGE_CTRL_MASK GENMASK(6, 0) #define CHARGE_CTRL_REACHED BIT(7) #define EC_ADDR_UNIVERSAL_FAN_CTRL 0x07C5 #define SPLIT_TABLES BIT(7) #define EC_ADDR_AP_OEM_6 0x07C6 #define ENABLE_UNIVERSAL_FAN_CTRL BIT(2) #define BATTERY_CHARGE_FULL_OVER_24H BIT(3) #define BATTERY_ERM_STATUS_REACHED BIT(4) #define EC_ADDR_CHARGE_PRIO 0x07CC #define CHARGING_PERFORMANCE BIT(7) /* Same bits as EC_ADDR_LIGHTBAR_AC_CTRL except LIGHTBAR_S3_OFF */ #define EC_ADDR_LIGHTBAR_BAT_CTRL 0x07E2 #define EC_ADDR_LIGHTBAR_BAT_RED 0x07E3 #define EC_ADDR_LIGHTBAR_BAT_GREEN 0x07E4 #define EC_ADDR_LIGHTBAR_BAT_BLUE 0x07E5 #define EC_ADDR_CPU_TEMP_END_TABLE 0x0F00 #define EC_ADDR_CPU_TEMP_START_TABLE 0x0F10 #define EC_ADDR_CPU_FAN_SPEED_TABLE 0x0F20 #define EC_ADDR_GPU_TEMP_END_TABLE 0x0F30 #define EC_ADDR_GPU_TEMP_START_TABLE 0x0F40 #define EC_ADDR_GPU_FAN_SPEED_TABLE 0x0F50 /* * Those two registers technically allow for manual fan control, * but are unstable on some models and are likely not meant to * be used by applications as they are only accessible when using * the WMI interface. */ #define EC_ADDR_PWM_1_WRITEABLE 0x1804 #define EC_ADDR_PWM_2_WRITEABLE 0x1809 #define DRIVER_NAME "uniwill" /* * The OEM software always sleeps up to 6 ms after reading/writing EC * registers, so we emulate this behaviour for maximum compatibility. */ #define UNIWILL_EC_DELAY_US 6000 #define PWM_MAX 200 #define FAN_TABLE_LENGTH 16 #define LED_CHANNELS 3 #define LED_MAX_BRIGHTNESS 200 #define UNIWILL_FEATURE_FN_LOCK_TOGGLE BIT(0) #define UNIWILL_FEATURE_SUPER_KEY_TOGGLE BIT(1) #define UNIWILL_FEATURE_TOUCHPAD_TOGGLE BIT(2) #define UNIWILL_FEATURE_LIGHTBAR BIT(3) #define UNIWILL_FEATURE_BATTERY BIT(4) #define UNIWILL_FEATURE_HWMON BIT(5) struct uniwill_data { struct device *dev; acpi_handle handle; struct regmap *regmap; struct acpi_battery_hook hook; unsigned int last_charge_ctrl; struct mutex battery_lock; /* Protects the list of currently registered batteries */ unsigned int last_switch_status; struct mutex super_key_lock; /* Protects the toggling of the super key lock state */ struct list_head batteries; struct mutex led_lock; /* Protects writes to the lightbar registers */ struct led_classdev_mc led_mc_cdev; struct mc_subled led_mc_subled_info[LED_CHANNELS]; struct mutex input_lock; /* Protects input sequence during notify */ struct input_dev *input_device; struct notifier_block nb; }; struct uniwill_battery_entry { struct list_head head; struct power_supply *battery; }; static bool force; module_param_unsafe(force, bool, 0); MODULE_PARM_DESC(force, "Force loading without checking for supported devices\n"); /* Feature bitmask since the associated registers are not reliable */ static unsigned int supported_features; static const char * const uniwill_temp_labels[] = { "CPU", "GPU", }; static const char * const uniwill_fan_labels[] = { "Main", "Secondary", }; static const struct key_entry uniwill_keymap[] = { /* Reported via keyboard controller */ { KE_IGNORE, UNIWILL_OSD_CAPSLOCK, { KEY_CAPSLOCK }}, { KE_IGNORE, UNIWILL_OSD_NUMLOCK, { KEY_NUMLOCK }}, /* Reported when the user locks/unlocks the super key */ { KE_IGNORE, UNIWILL_OSD_SUPER_KEY_LOCK_ENABLE, { KEY_UNKNOWN }}, { KE_IGNORE, UNIWILL_OSD_SUPER_KEY_LOCK_DISABLE, { KEY_UNKNOWN }}, /* Optional, might not be reported by all devices */ { KE_IGNORE, UNIWILL_OSD_SUPER_KEY_LOCK_CHANGED, { KEY_UNKNOWN }}, /* Reported in manual mode when toggling the airplane mode status */ { KE_KEY, UNIWILL_OSD_RFKILL, { KEY_RFKILL }}, { KE_IGNORE, UNIWILL_OSD_RADIOON, { KEY_UNKNOWN }}, { KE_IGNORE, UNIWILL_OSD_RADIOOFF, { KEY_UNKNOWN }}, /* Reported when user wants to cycle the platform profile */ { KE_KEY, UNIWILL_OSD_PERFORMANCE_MODE_TOGGLE, { KEY_F14 }}, /* Reported when the user wants to adjust the brightness of the keyboard */ { KE_KEY, UNIWILL_OSD_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }}, { KE_KEY, UNIWILL_OSD_KBDILLUMUP, { KEY_KBDILLUMUP }}, /* Reported when the user wants to toggle the microphone mute status */ { KE_KEY, UNIWILL_OSD_MIC_MUTE, { KEY_MICMUTE }}, /* Reported when the user wants to toggle the mute status */ { KE_IGNORE, UNIWILL_OSD_MUTE, { KEY_MUTE }}, /* Reported when the user locks/unlocks the Fn key */ { KE_IGNORE, UNIWILL_OSD_FN_LOCK, { KEY_FN_ESC }}, /* Reported when the user wants to toggle the brightness of the keyboard */ { KE_KEY, UNIWILL_OSD_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }}, { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL0, { KEY_KBDILLUMTOGGLE }}, { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL1, { KEY_KBDILLUMTOGGLE }}, { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL2, { KEY_KBDILLUMTOGGLE }}, { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL3, { KEY_KBDILLUMTOGGLE }}, { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL4, { KEY_KBDILLUMTOGGLE }}, /* FIXME: find out the exact meaning of those events */ { KE_IGNORE, UNIWILL_OSD_BAT_CHARGE_FULL_24_H, { KEY_UNKNOWN }}, { KE_IGNORE, UNIWILL_OSD_BAT_ERM_UPDATE, { KEY_UNKNOWN }}, /* Reported when the user wants to toggle the benchmark mode status */ { KE_IGNORE, UNIWILL_OSD_BENCHMARK_MODE_TOGGLE, { KEY_UNKNOWN }}, /* Reported when the user wants to toggle the webcam */ { KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }}, { KE_END } }; static int uniwill_ec_reg_write(void *context, unsigned int reg, unsigned int val) { union acpi_object params[2] = { { .integer = { .type = ACPI_TYPE_INTEGER, .value = reg, }, }, { .integer = { .type = ACPI_TYPE_INTEGER, .value = val, }, }, }; struct uniwill_data *data = context; struct acpi_object_list input = { .count = ARRAY_SIZE(params), .pointer = params, }; acpi_status status; status = acpi_evaluate_object(data->handle, "ECRW", &input, NULL); if (ACPI_FAILURE(status)) return -EIO; usleep_range(UNIWILL_EC_DELAY_US, UNIWILL_EC_DELAY_US * 2); return 0; } static int uniwill_ec_reg_read(void *context, unsigned int reg, unsigned int *val) { union acpi_object params[1] = { { .integer = { .type = ACPI_TYPE_INTEGER, .value = reg, }, }, }; struct uniwill_data *data = context; struct acpi_object_list input = { .count = ARRAY_SIZE(params), .pointer = params, }; unsigned long long output; acpi_status status; status = acpi_evaluate_integer(data->handle, "ECRR", &input, &output); if (ACPI_FAILURE(status)) return -EIO; if (output > U8_MAX) return -ENXIO; usleep_range(UNIWILL_EC_DELAY_US, UNIWILL_EC_DELAY_US * 2); *val = output; return 0; } static const struct regmap_bus uniwill_ec_bus = { .reg_write = uniwill_ec_reg_write, .reg_read = uniwill_ec_reg_read, .reg_format_endian_default = REGMAP_ENDIAN_LITTLE, .val_format_endian_default = REGMAP_ENDIAN_LITTLE, }; static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { case EC_ADDR_AP_OEM: case EC_ADDR_LIGHTBAR_AC_CTRL: case EC_ADDR_LIGHTBAR_AC_RED: case EC_ADDR_LIGHTBAR_AC_GREEN: case EC_ADDR_LIGHTBAR_AC_BLUE: case EC_ADDR_BIOS_OEM: case EC_ADDR_TRIGGER: case EC_ADDR_OEM_4: case EC_ADDR_CHARGE_CTRL: case EC_ADDR_LIGHTBAR_BAT_CTRL: case EC_ADDR_LIGHTBAR_BAT_RED: case EC_ADDR_LIGHTBAR_BAT_GREEN: case EC_ADDR_LIGHTBAR_BAT_BLUE: return true; default: return false; } } static bool uniwill_readable_reg(struct device *dev, unsigned int reg) { switch (reg) { case EC_ADDR_CPU_TEMP: case EC_ADDR_GPU_TEMP: case EC_ADDR_MAIN_FAN_RPM_1: case EC_ADDR_MAIN_FAN_RPM_2: case EC_ADDR_SECOND_FAN_RPM_1: case EC_ADDR_SECOND_FAN_RPM_2: case EC_ADDR_BAT_ALERT: case EC_ADDR_PROJECT_ID: case EC_ADDR_AP_OEM: case EC_ADDR_LIGHTBAR_AC_CTRL: case EC_ADDR_LIGHTBAR_AC_RED: case EC_ADDR_LIGHTBAR_AC_GREEN: case EC_ADDR_LIGHTBAR_AC_BLUE: case EC_ADDR_BIOS_OEM: case EC_ADDR_PWM_1: case EC_ADDR_PWM_2: case EC_ADDR_TRIGGER: case EC_ADDR_SWITCH_STATUS: case EC_ADDR_OEM_4: case EC_ADDR_CHARGE_CTRL: case EC_ADDR_LIGHTBAR_BAT_CTRL: case EC_ADDR_LIGHTBAR_BAT_RED: case EC_ADDR_LIGHTBAR_BAT_GREEN: case EC_ADDR_LIGHTBAR_BAT_BLUE: return true; default: return false; } } static bool uniwill_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { case EC_ADDR_CPU_TEMP: case EC_ADDR_GPU_TEMP: case EC_ADDR_MAIN_FAN_RPM_1: case EC_ADDR_MAIN_FAN_RPM_2: case EC_ADDR_SECOND_FAN_RPM_1: case EC_ADDR_SECOND_FAN_RPM_2: case EC_ADDR_BAT_ALERT: case EC_ADDR_PWM_1: case EC_ADDR_PWM_2: case EC_ADDR_TRIGGER: case EC_ADDR_SWITCH_STATUS: case EC_ADDR_CHARGE_CTRL: return true; default: return false; } } static const struct regmap_config uniwill_ec_config = { .reg_bits = 16, .val_bits = 8, .writeable_reg = uniwill_writeable_reg, .readable_reg = uniwill_readable_reg, .volatile_reg = uniwill_volatile_reg, .can_sleep = true, .max_register = 0xFFF, .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; static ssize_t fn_lock_toggle_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct uniwill_data *data = dev_get_drvdata(dev); unsigned int value; bool enable; int ret; ret = kstrtobool(buf, &enable); if (ret < 0) return ret; if (enable) value = FN_LOCK_STATUS; else value = 0; ret = regmap_update_bits(data->regmap, EC_ADDR_BIOS_OEM, FN_LOCK_STATUS, value); if (ret < 0) return ret; return count; } static ssize_t fn_lock_toggle_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct uniwill_data *data = dev_get_drvdata(dev); unsigned int value; int ret; ret = regmap_read(data->regmap, EC_ADDR_BIOS_OEM, &value); if (ret < 0) return ret; return sysfs_emit(buf, "%d\n", !!(value & FN_LOCK_STATUS)); } static DEVICE_ATTR_RW(fn_lock_toggle_enable); static ssize_t super_key_toggle_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct uniwill_data *data = dev_get_drvdata(dev); unsigned int value; bool enable; int ret; ret = kstrtobool(buf, &enable); if (ret < 0) return ret; guard(mutex)(&data->super_key_lock); ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value); if (ret < 0) return ret; /* * We can only toggle the super key lock, so we return early if the setting * is already in the correct state. */ if (enable == !(value & SUPER_KEY_LOCK_STATUS)) return count; ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_SUPER_KEY_LOCK, TRIGGER_SUPER_KEY_LOCK); if (ret < 0) return ret; return count; } static ssize_t super_key_toggle_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct uniwill_data *data = dev_get_drvdata(dev); unsigned int value; int ret; ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value); if (ret < 0) return ret; return sysfs_emit(buf, "%d\n", !(value & SUPER_KEY_LOCK_STATUS)); } static DEVICE_ATTR_RW(super_key_toggle_enable); static ssize_t touchpad_toggle_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct uniwill_data *data = dev_get_drvdata(dev); unsigned int value; bool enable; int ret; ret = kstrtobool(buf, &enable); if (ret < 0) return ret; if (enable) value = 0; else value = TOUCHPAD_TOGGLE_OFF; ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_4, TOUCHPAD_TOGGLE_OFF, value); if (ret < 0) return ret; return count; } static ssize_t touchpad_toggle_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct uniwill_data *data = dev_get_drvdata(dev); unsigned int value; int ret; ret = regmap_read(data->regmap, EC_ADDR_OEM_4, &value); if (ret < 0) return ret; return sysfs_emit(buf, "%d\n", !(value & TOUCHPAD_TOGGLE_OFF)); } static DEVICE_ATTR_RW(touchpad_toggle_enable); static ssize_t rainbow_animation_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct uniwill_data *data = dev_get_drvdata(dev); unsigned int value; bool enable; int ret; ret = kstrtobool(buf, &enable); if (ret < 0) return ret; if (enable) value = LIGHTBAR_WELCOME; else value = 0; guard(mutex)(&data->led_lock); ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_WELCOME, value); if (ret < 0) return ret; ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_WELCOME, value); if (ret < 0) return ret; return count; } static ssize_t rainbow_animation_show(struct device *dev, struct device_attribute *attr, char *buf) { struct uniwill_data *data = dev_get_drvdata(dev); unsigned int value; int ret; ret = regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value); if (ret < 0) return ret; return sysfs_emit(buf, "%d\n", !!(value & LIGHTBAR_WELCOME)); } static DEVICE_ATTR_RW(rainbow_animation); static ssize_t breathing_in_suspend_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct uniwill_data *data = dev_get_drvdata(dev); unsigned int value; bool enable; int ret; ret = kstrtobool(buf, &enable); if (ret < 0) return ret; if (enable) value = 0; else value = LIGHTBAR_S3_OFF; /* We only access a single register here, so we do not need to use data->led_lock */ ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_S3_OFF, value); if (ret < 0) return ret; return count; } static ssize_t breathing_in_suspend_show(struct device *dev, struct device_attribute *attr, char *buf) { struct uniwill_data *data = dev_get_drvdata(dev); unsigned int value; int ret; ret = regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value); if (ret < 0) return ret; return sysfs_emit(buf, "%d\n", !(value & LIGHTBAR_S3_OFF)); } static DEVICE_ATTR_RW(breathing_in_suspend); static struct attribute *uniwill_attrs[] = { /* Keyboard-related */ &dev_attr_fn_lock_toggle_enable.attr, &dev_attr_super_key_toggle_enable.attr, &dev_attr_touchpad_toggle_enable.attr, /* Lightbar-related */ &dev_attr_rainbow_animation.attr, &dev_attr_breathing_in_suspend.attr, NULL }; static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { if (attr == &dev_attr_fn_lock_toggle_enable.attr) { if (supported_features & UNIWILL_FEATURE_FN_LOCK_TOGGLE) return attr->mode; } if (attr == &dev_attr_super_key_toggle_enable.attr) { if (supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE) return attr->mode; } if (attr == &dev_attr_touchpad_toggle_enable.attr) { if (supported_features & UNIWILL_FEATURE_TOUCHPAD_TOGGLE) return attr->mode; } if (attr == &dev_attr_rainbow_animation.attr || attr == &dev_attr_breathing_in_suspend.attr) { if (supported_features & UNIWILL_FEATURE_LIGHTBAR) return attr->mode; } return 0; } static const struct attribute_group uniwill_group = { .is_visible = uniwill_attr_is_visible, .attrs = uniwill_attrs, }; static const struct attribute_group *uniwill_groups[] = { &uniwill_group, NULL }; static int uniwill_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { struct uniwill_data *data = dev_get_drvdata(dev); unsigned int value; __be16 rpm; int ret; switch (type) { case hwmon_temp: switch (channel) { case 0: ret = regmap_read(data->regmap, EC_ADDR_CPU_TEMP, &value); break; case 1: ret = regmap_read(data->regmap, EC_ADDR_GPU_TEMP, &value); break; default: return -EOPNOTSUPP; } if (ret < 0) return ret; *val = value * MILLIDEGREE_PER_DEGREE; return 0; case hwmon_fan: switch (channel) { case 0: ret = regmap_bulk_read(data->regmap, EC_ADDR_MAIN_FAN_RPM_1, &rpm, sizeof(rpm)); break; case 1: ret = regmap_bulk_read(data->regmap, EC_ADDR_SECOND_FAN_RPM_1, &rpm, sizeof(rpm)); break; default: return -EOPNOTSUPP; } if (ret < 0) return ret; *val = be16_to_cpu(rpm); return 0; case hwmon_pwm: switch (channel) { case 0: ret = regmap_read(data->regmap, EC_ADDR_PWM_1, &value); break; case 1: ret = regmap_read(data->regmap, EC_ADDR_PWM_2, &value); break; default: return -EOPNOTSUPP; } if (ret < 0) return ret; *val = fixp_linear_interpolate(0, 0, PWM_MAX, U8_MAX, value); return 0; default: return -EOPNOTSUPP; } } static int uniwill_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, const char **str) { switch (type) { case hwmon_temp: *str = uniwill_temp_labels[channel]; return 0; case hwmon_fan: *str = uniwill_fan_labels[channel]; return 0; default: return -EOPNOTSUPP; } } static const struct hwmon_ops uniwill_ops = { .visible = 0444, .read = uniwill_read, .read_string = uniwill_read_string, }; static const struct hwmon_channel_info * const uniwill_info[] = { HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL), HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL, HWMON_F_INPUT | HWMON_F_LABEL), HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT, HWMON_PWM_INPUT), NULL }; static const struct hwmon_chip_info uniwill_chip_info = { .ops = &uniwill_ops, .info = uniwill_info, }; static int uniwill_hwmon_init(struct uniwill_data *data) { struct device *hdev; if (!(supported_features & UNIWILL_FEATURE_HWMON)) return 0; hdev = devm_hwmon_device_register_with_info(data->dev, "uniwill", data, &uniwill_chip_info, NULL); return PTR_ERR_OR_ZERO(hdev); } static const unsigned int uniwill_led_channel_to_bat_reg[LED_CHANNELS] = { EC_ADDR_LIGHTBAR_BAT_RED, EC_ADDR_LIGHTBAR_BAT_GREEN, EC_ADDR_LIGHTBAR_BAT_BLUE, }; static const unsigned int uniwill_led_channel_to_ac_reg[LED_CHANNELS] = { EC_ADDR_LIGHTBAR_AC_RED, EC_ADDR_LIGHTBAR_AC_GREEN, EC_ADDR_LIGHTBAR_AC_BLUE, }; static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, led_mc_cdev); unsigned int value; int ret; ret = led_mc_calc_color_components(led_mc_cdev, brightness); if (ret < 0) return ret; guard(mutex)(&data->led_lock); for (int i = 0; i < LED_CHANNELS; i++) { /* Prevent the brightness values from overflowing */ value = min(LED_MAX_BRIGHTNESS, data->led_mc_subled_info[i].brightness); ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); if (ret < 0) return ret; ret = regmap_write(data->regmap, uniwill_led_channel_to_bat_reg[i], value); if (ret < 0) return ret; } if (brightness) value = 0; else value = LIGHTBAR_S0_OFF; ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_S0_OFF, value); if (ret < 0) return ret; return regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_S0_OFF, value); } #define LIGHTBAR_MASK (LIGHTBAR_APP_EXISTS | LIGHTBAR_S0_OFF | LIGHTBAR_S3_OFF | LIGHTBAR_WELCOME) static int uniwill_led_init(struct uniwill_data *data) { struct led_init_data init_data = { .devicename = DRIVER_NAME, .default_label = "multicolor:" LED_FUNCTION_STATUS, .devname_mandatory = true, }; unsigned int color_indices[3] = { LED_COLOR_ID_RED, LED_COLOR_ID_GREEN, LED_COLOR_ID_BLUE, }; unsigned int value; int ret; if (!(supported_features & UNIWILL_FEATURE_LIGHTBAR)) return 0; ret = devm_mutex_init(data->dev, &data->led_lock); if (ret < 0) return ret; /* * The EC has separate lightbar settings for AC and battery mode, * so we have to ensure that both settings are the same. */ ret = regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value); if (ret < 0) return ret; value |= LIGHTBAR_APP_EXISTS; ret = regmap_write(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, value); if (ret < 0) return ret; /* * The breathing animation during suspend is not supported when * running on battery power. */ value |= LIGHTBAR_S3_OFF; ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_MASK, value); if (ret < 0) return ret; data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; data->led_mc_cdev.led_cdev.max_brightness = LED_MAX_BRIGHTNESS; data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT; data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set; if (value & LIGHTBAR_S0_OFF) data->led_mc_cdev.led_cdev.brightness = 0; else data->led_mc_cdev.led_cdev.brightness = LED_MAX_BRIGHTNESS; for (int i = 0; i < LED_CHANNELS; i++) { data->led_mc_subled_info[i].color_index = color_indices[i]; ret = regmap_read(data->regmap, uniwill_led_channel_to_ac_reg[i], &value); if (ret < 0) return ret; /* * Make sure that the initial intensity value is not greater than * the maximum brightness. */ value = min(LED_MAX_BRIGHTNESS, value); ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); if (ret < 0) return ret; ret = regmap_write(data->regmap, uniwill_led_channel_to_bat_reg[i], value); if (ret < 0) return ret; data->led_mc_subled_info[i].intensity = value; data->led_mc_subled_info[i].channel = i; } data->led_mc_cdev.subled_info = data->led_mc_subled_info; data->led_mc_cdev.num_colors = LED_CHANNELS; return devm_led_classdev_multicolor_register_ext(data->dev, &data->led_mc_cdev, &init_data); } static int uniwill_get_property(struct power_supply *psy, const struct power_supply_ext *ext, void *drvdata, enum power_supply_property psp, union power_supply_propval *val) { struct uniwill_data *data = drvdata; union power_supply_propval prop; unsigned int regval; int ret; switch (psp) { case POWER_SUPPLY_PROP_HEALTH: ret = power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_PRESENT, &prop); if (ret < 0) return ret; if (!prop.intval) { val->intval = POWER_SUPPLY_HEALTH_NO_BATTERY; return 0; } ret = power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_STATUS, &prop); if (ret < 0) return ret; if (prop.intval == POWER_SUPPLY_STATUS_UNKNOWN) { val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; return 0; } ret = regmap_read(data->regmap, EC_ADDR_BAT_ALERT, ®val); if (ret < 0) return ret; if (regval) { /* Charging issue */ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; return 0; } val->intval = POWER_SUPPLY_HEALTH_GOOD; return 0; case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: ret = regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, ®val); if (ret < 0) return ret; val->intval = clamp_val(FIELD_GET(CHARGE_CTRL_MASK, regval), 0, 100); return 0; default: return -EINVAL; } } static int uniwill_set_property(struct power_supply *psy, const struct power_supply_ext *ext, void *drvdata, enum power_supply_property psp, const union power_supply_propval *val) { struct uniwill_data *data = drvdata; switch (psp) { case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: if (val->intval < 1 || val->intval > 100) return -EINVAL; return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK, val->intval); default: return -EINVAL; } } static int uniwill_property_is_writeable(struct power_supply *psy, const struct power_supply_ext *ext, void *drvdata, enum power_supply_property psp) { if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) return true; return false; } static const enum power_supply_property uniwill_properties[] = { POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, }; static const struct power_supply_ext uniwill_extension = { .name = DRIVER_NAME, .properties = uniwill_properties, .num_properties = ARRAY_SIZE(uniwill_properties), .get_property = uniwill_get_property, .set_property = uniwill_set_property, .property_is_writeable = uniwill_property_is_writeable, }; static int uniwill_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook) { struct uniwill_data *data = container_of(hook, struct uniwill_data, hook); struct uniwill_battery_entry *entry; int ret; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM; ret = power_supply_register_extension(battery, &uniwill_extension, data->dev, data); if (ret < 0) { kfree(entry); return ret; } guard(mutex)(&data->battery_lock); entry->battery = battery; list_add(&entry->head, &data->batteries); return 0; } static int uniwill_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook) { struct uniwill_data *data = container_of(hook, struct uniwill_data, hook); struct uniwill_battery_entry *entry, *tmp; scoped_guard(mutex, &data->battery_lock) { list_for_each_entry_safe(entry, tmp, &data->batteries, head) { if (entry->battery == battery) { list_del(&entry->head); kfree(entry); break; } } } power_supply_unregister_extension(battery, &uniwill_extension); return 0; } static int uniwill_battery_init(struct uniwill_data *data) { int ret; if (!(supported_features & UNIWILL_FEATURE_BATTERY)) return 0; ret = devm_mutex_init(data->dev, &data->battery_lock); if (ret < 0) return ret; INIT_LIST_HEAD(&data->batteries); data->hook.name = "Uniwill Battery Extension"; data->hook.add_battery = uniwill_add_battery; data->hook.remove_battery = uniwill_remove_battery; return devm_battery_hook_register(data->dev, &data->hook); } static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action, void *dummy) { struct uniwill_data *data = container_of(nb, struct uniwill_data, nb); struct uniwill_battery_entry *entry; switch (action) { case UNIWILL_OSD_BATTERY_ALERT: mutex_lock(&data->battery_lock); list_for_each_entry(entry, &data->batteries, head) { power_supply_changed(entry->battery); } mutex_unlock(&data->battery_lock); return NOTIFY_OK; case UNIWILL_OSD_DC_ADAPTER_CHANGED: /* noop for the time being, will change once charging priority * gets implemented. */ return NOTIFY_OK; default: mutex_lock(&data->input_lock); sparse_keymap_report_event(data->input_device, action, 1, true); mutex_unlock(&data->input_lock); return NOTIFY_OK; } } static int uniwill_input_init(struct uniwill_data *data) { int ret; ret = devm_mutex_init(data->dev, &data->input_lock); if (ret < 0) return ret; data->input_device = devm_input_allocate_device(data->dev); if (!data->input_device) return -ENOMEM; ret = sparse_keymap_setup(data->input_device, uniwill_keymap, NULL); if (ret < 0) return ret; data->input_device->name = "Uniwill WMI hotkeys"; data->input_device->phys = "wmi/input0"; data->input_device->id.bustype = BUS_HOST; ret = input_register_device(data->input_device); if (ret < 0) return ret; data->nb.notifier_call = uniwill_notifier_call; return devm_uniwill_wmi_register_notifier(data->dev, &data->nb); } static void uniwill_disable_manual_control(void *context) { struct uniwill_data *data = context; regmap_clear_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); } static int uniwill_ec_init(struct uniwill_data *data) { unsigned int value; int ret; ret = regmap_read(data->regmap, EC_ADDR_PROJECT_ID, &value); if (ret < 0) return ret; dev_dbg(data->dev, "Project ID: %u\n", value); ret = regmap_set_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); if (ret < 0) return ret; return devm_add_action_or_reset(data->dev, uniwill_disable_manual_control, data); } static int uniwill_probe(struct platform_device *pdev) { struct uniwill_data *data; struct regmap *regmap; acpi_handle handle; int ret; handle = ACPI_HANDLE(&pdev->dev); if (!handle) return -ENODEV; data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; data->dev = &pdev->dev; data->handle = handle; platform_set_drvdata(pdev, data); regmap = devm_regmap_init(&pdev->dev, &uniwill_ec_bus, data, &uniwill_ec_config); if (IS_ERR(regmap)) return PTR_ERR(regmap); data->regmap = regmap; ret = devm_mutex_init(&pdev->dev, &data->super_key_lock); if (ret < 0) return ret; ret = uniwill_ec_init(data); if (ret < 0) return ret; ret = uniwill_battery_init(data); if (ret < 0) return ret; ret = uniwill_led_init(data); if (ret < 0) return ret; ret = uniwill_hwmon_init(data); if (ret < 0) return ret; return uniwill_input_init(data); } static void uniwill_shutdown(struct platform_device *pdev) { struct uniwill_data *data = platform_get_drvdata(pdev); regmap_clear_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); } static int uniwill_suspend_keyboard(struct uniwill_data *data) { if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE)) return 0; /* * The EC_ADDR_SWITCH_STATUS is marked as volatile, so we have to restore it * ourselves. */ return regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &data->last_switch_status); } static int uniwill_suspend_battery(struct uniwill_data *data) { if (!(supported_features & UNIWILL_FEATURE_BATTERY)) return 0; /* * Save the current charge limit in order to restore it during resume. * We cannot use the regmap code for that since this register needs to * be declared as volatile due to CHARGE_CTRL_REACHED. */ return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl); } static int uniwill_suspend(struct device *dev) { struct uniwill_data *data = dev_get_drvdata(dev); int ret; ret = uniwill_suspend_keyboard(data); if (ret < 0) return ret; ret = uniwill_suspend_battery(data); if (ret < 0) return ret; regcache_cache_only(data->regmap, true); regcache_mark_dirty(data->regmap); return 0; } static int uniwill_resume_keyboard(struct uniwill_data *data) { unsigned int value; int ret; if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE)) return 0; ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value); if (ret < 0) return ret; if ((data->last_switch_status & SUPER_KEY_LOCK_STATUS) == (value & SUPER_KEY_LOCK_STATUS)) return 0; return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_SUPER_KEY_LOCK, TRIGGER_SUPER_KEY_LOCK); } static int uniwill_resume_battery(struct uniwill_data *data) { if (!(supported_features & UNIWILL_FEATURE_BATTERY)) return 0; return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK, data->last_charge_ctrl); } static int uniwill_resume(struct device *dev) { struct uniwill_data *data = dev_get_drvdata(dev); int ret; regcache_cache_only(data->regmap, false); ret = regcache_sync(data->regmap); if (ret < 0) return ret; ret = uniwill_resume_keyboard(data); if (ret < 0) return ret; return uniwill_resume_battery(data); } static DEFINE_SIMPLE_DEV_PM_OPS(uniwill_pm_ops, uniwill_suspend, uniwill_resume); /* * We only use the DMI table for auoloading because the ACPI device itself * does not guarantee that the underlying EC implementation is supported. */ static const struct acpi_device_id uniwill_id_table[] = { { "INOU0000" }, { }, }; static struct platform_driver uniwill_driver = { .driver = { .name = DRIVER_NAME, .dev_groups = uniwill_groups, .probe_type = PROBE_PREFER_ASYNCHRONOUS, .acpi_match_table = uniwill_id_table, .pm = pm_sleep_ptr(&uniwill_pm_ops), }, .probe = uniwill_probe, .shutdown = uniwill_shutdown, }; static const struct dmi_system_id uniwill_dmi_table[] __initconst = { { .ident = "XMG FUSION 15", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71A"), }, }, { .ident = "XMG FUSION 15", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71B"), }, }, { .ident = "Intel NUC x15", .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPAC71H"), }, .driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE | UNIWILL_FEATURE_SUPER_KEY_TOGGLE | UNIWILL_FEATURE_TOUCHPAD_TOGGLE | UNIWILL_FEATURE_BATTERY | UNIWILL_FEATURE_HWMON), }, { .ident = "Intel NUC x15", .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71F"), }, .driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE | UNIWILL_FEATURE_SUPER_KEY_TOGGLE | UNIWILL_FEATURE_TOUCHPAD_TOGGLE | UNIWILL_FEATURE_LIGHTBAR | UNIWILL_FEATURE_BATTERY | UNIWILL_FEATURE_HWMON), }, { .ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTxX1"), }, }, { .ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTQx1"), }, }, { .ident = "TUXEDO InfinityBook Pro 14/16 Gen7 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxARX1_PHxAQF1"), }, }, { .ident = "TUXEDO InfinityBook Pro 16 Gen7 Intel/Commodore Omnia-Book Pro Gen 7", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6AG01_PH6AQ71_PH6AQI1"), }, }, { .ident = "TUXEDO InfinityBook Pro 14/16 Gen8 Intel/Commodore Omnia-Book Pro Gen 8", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PRX1_PH6PRX1"), }, }, { .ident = "TUXEDO InfinityBook Pro 14 Gen8 Intel/Commodore Omnia-Book Pro Gen 8", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PG31"), }, }, { .ident = "TUXEDO InfinityBook Pro 16 Gen8 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6PG01_PH6PG71"), }, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"), }, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"), }, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"), }, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"), }, }, { .ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"), }, }, { .ident = "TUXEDO InfinityBook Max 15 Gen10 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"), }, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"), }, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"), }, }, { .ident = "TUXEDO InfinityBook Max 15 Gen10 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"), }, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"), }, }, { .ident = "TUXEDO Polaris 15 Gen1 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A1650TI"), }, }, { .ident = "TUXEDO Polaris 15 Gen1 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A2060"), }, }, { .ident = "TUXEDO Polaris 17 Gen1 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A1650TI"), }, }, { .ident = "TUXEDO Polaris 17 Gen1 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A2060"), }, }, { .ident = "TUXEDO Polaris 15 Gen1 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I1650TI"), }, }, { .ident = "TUXEDO Polaris 15 Gen1 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I2060"), }, }, { .ident = "TUXEDO Polaris 17 Gen1 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I1650TI"), }, }, { .ident = "TUXEDO Polaris 17 Gen1 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I2060"), }, }, { .ident = "TUXEDO Trinity 15 Intel Gen1", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1501I"), }, }, { .ident = "TUXEDO Trinity 17 Intel Gen1", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1701I"), }, }, { .ident = "TUXEDO Polaris 15/17 Gen2 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxMGxx"), }, }, { .ident = "TUXEDO Polaris 15/17 Gen2 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxNGxx"), }, }, { .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxZGxx"), }, }, { .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxTGxx"), }, }, { .ident = "TUXEDO Stellaris/Polaris 15/17 Gen4 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxRGxx"), }, }, { .ident = "TUXEDO Stellaris 15 Gen4 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxAGxx"), }, }, { .ident = "TUXEDO Polaris 15/17 Gen5 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxXGxx"), }, }, { .ident = "TUXEDO Stellaris 16 Gen5 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6XGxX"), }, }, { .ident = "TUXEDO Stellaris 16/17 Gen5 Intel/Commodore ORION Gen 5", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxPXxx"), }, }, { .ident = "TUXEDO Stellaris Slim 15 Gen6 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"), }, }, { .ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"), }, }, { .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"), }, }, { .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"), }, }, { .ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"), }, }, { .ident = "TUXEDO Stellaris 16 Gen7 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"), }, }, { .ident = "TUXEDO Stellaris 16 Gen7 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"), }, }, { .ident = "TUXEDO Stellaris 16 Gen7 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"), }, }, { .ident = "TUXEDO Pulse 14 Gen1 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1401"), }, }, { .ident = "TUXEDO Pulse 15 Gen1 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1501"), }, }, { .ident = "TUXEDO Pulse 15 Gen2 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5LUXG"), }, }, { } }; MODULE_DEVICE_TABLE(dmi, uniwill_dmi_table); static int __init uniwill_init(void) { const struct dmi_system_id *id; int ret; id = dmi_first_match(uniwill_dmi_table); if (!id) { if (!force) return -ENODEV; /* Assume that the device supports all features */ supported_features = UINT_MAX; pr_warn("Loading on a potentially unsupported device\n"); } else { supported_features = (uintptr_t)id->driver_data; } ret = platform_driver_register(&uniwill_driver); if (ret < 0) return ret; ret = uniwill_wmi_register_driver(); if (ret < 0) { platform_driver_unregister(&uniwill_driver); return ret; } return 0; } module_init(uniwill_init); static void __exit uniwill_exit(void) { uniwill_wmi_unregister_driver(); platform_driver_unregister(&uniwill_driver); } module_exit(uniwill_exit); MODULE_AUTHOR("Armin Wolf "); MODULE_DESCRIPTION("Uniwill notebook driver"); MODULE_LICENSE("GPL");