1913 lines
47 KiB
C
1913 lines
47 KiB
C
// 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 <W_Armin@gmx.de>
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/array_size.h>
|
|
#include <linux/bits.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/cleanup.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/device/driver.h>
|
|
#include <linux/dmi.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/fixp-arith.h>
|
|
#include <linux/hwmon.h>
|
|
#include <linux/hwmon-sysfs.h>
|
|
#include <linux/init.h>
|
|
#include <linux/input.h>
|
|
#include <linux/input/sparse-keymap.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/kstrtox.h>
|
|
#include <linux/leds.h>
|
|
#include <linux/led-class-multicolor.h>
|
|
#include <linux/limits.h>
|
|
#include <linux/list.h>
|
|
#include <linux/minmax.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/string.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/types.h>
|
|
#include <linux/units.h>
|
|
|
|
#include <acpi/battery.h>
|
|
|
|
#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 <W_Armin@gmx.de>");
|
|
MODULE_DESCRIPTION("Uniwill notebook driver");
|
|
MODULE_LICENSE("GPL");
|