summaryrefslogtreecommitdiffstats
path: root/drivers/platform/x86/asus-wmi.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2019-07-14 16:51:47 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2019-07-14 16:51:47 -0700
commit55167453111d3a1e600e29ba6c8e63906bb4821b (patch)
tree2d8abea34b7d47ff1191c24781a0b50b1bcb1b09 /drivers/platform/x86/asus-wmi.c
parentfde7dc63b1caa6dedf9af7cbf79895589629bc95 (diff)
parent7d67c8ac25fbc66ee254aa3e33329d1c9bc152ce (diff)
Merge tag 'platform-drivers-x86-v5.3-1' of git://git.infradead.org/linux-platform-drivers-x86
Pull x86 platform driver updates from Andy Shevchenko: "Gathered a bunch of x86 platform driver changes. It's rather big, since includes two big refactors and completely new driver: - ASUS WMI driver got a big refactoring in order to support the TUF Gaming laptops. Besides that, the regression with backlight being permanently off on various EeePC laptops has been fixed. - Accelerometer on HP ProBook 450 G0 shows wrong measurements due to X axis being inverted. This has been fixed. - Intel PMC core driver has been extended to be ACPI enumerated if the DSDT provides device with _HID "INT33A1". This allows to convert the driver to be pure platform and support new hardware purely based on ACPI DSDT. - From now on the Intel Speed Select Technology is supported thru a corresponding driver. This driver provides an access to the features of the ISST, such as Performance Profile, Core Power, Base frequency and Turbo Frequency. - Mellanox platform drivers has been refactored and now extended to support more systems, including new coming ones. - The OLPC XO-1.75 platform is now supported. - CB4063 Beckhoff Automation board is using PMC clocks, provided via pmc_atom driver, for ethernet controllers in a way that they can't be managed by the clock driver. The quirk has been extended to cover this case. - Touchscreen on Chuwi Hi10 Plus tablet has been enabled. Meanwhile the information of Chuwi Hi10 Air has been fixed to cover more models based on the same platform. - Xiaomi notebooks have WMI interface enabled. Thus, the driver to support it has been provided. It required some extension of the generic WMI library, which allows to propagate opaque context to the ->probe() of the individual drivers. This release includes debugfs clean up from Greg KH for several drivers that drop return code check and make debugfs absence or failure non-fatal. Also miscellaneous fixes here and there, mostly for Acer WMI and various Intel drivers" * tag 'platform-drivers-x86-v5.3-1' of git://git.infradead.org/linux-platform-drivers-x86: (74 commits) platform/x86: Fix PCENGINES_APU2 Kconfig warning tools/power/x86/intel-speed-select: Add .gitignore file platform/x86: mlx-platform: Fix error handling in mlxplat_init() platform/x86: intel_pmc_core: Attach using APCI HID "INT33A1" platform/x86: intel_pmc_core: transform Pkg C-state residency from TSC ticks into microseconds platform/x86: asus-wmi: Use dev_get_drvdata() Documentation/ABI: Add new attribute for mlxreg-io sysfs interfaces platform/x86: mlx-platform: Add more reset cause attributes platform/x86: mlx-platform: Modify DMI matching order platform/x86: mlx-platform: Add regmap structure for the next generation systems platform/x86: mlx-platform: Change API for i2c-mlxcpld driver activation platform/x86: mlx-platform: Move regmap initialization before all drivers activation MAINTAINERS: Update for Intel Speed Select Technology tools/power/x86: A tool to validate Intel Speed Select commands platform/x86: ISST: Restore state on resume platform/x86: ISST: Add Intel Speed Select PUNIT MSR interface platform/x86: ISST: Add Intel Speed Select mailbox interface via MSRs platform/x86: ISST: Add Intel Speed Select mailbox interface via PCI platform/x86: ISST: Add Intel Speed Select mmio interface platform/x86: ISST: Add IOCTL to Translate Linux logical CPU to PUNIT CPU number ...
Diffstat (limited to 'drivers/platform/x86/asus-wmi.c')
-rw-r--r--drivers/platform/x86/asus-wmi.c477
1 files changed, 350 insertions, 127 deletions
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 9b18a184e0aa..18f3a8bad52f 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -57,6 +57,7 @@ MODULE_LICENSE("GPL");
#define NOTIFY_KBD_BRTUP 0xc4
#define NOTIFY_KBD_BRTDWN 0xc5
#define NOTIFY_KBD_BRTTOGGLE 0xc7
+#define NOTIFY_KBD_FBM 0x99
#define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0)
@@ -67,9 +68,27 @@ MODULE_LICENSE("GPL");
#define ASUS_FAN_CTRL_MANUAL 1
#define ASUS_FAN_CTRL_AUTO 2
+#define ASUS_FAN_MODE_NORMAL 0
+#define ASUS_FAN_MODE_OVERBOOST 1
+#define ASUS_FAN_MODE_OVERBOOST_MASK 0x01
+#define ASUS_FAN_MODE_SILENT 2
+#define ASUS_FAN_MODE_SILENT_MASK 0x02
+#define ASUS_FAN_MODES_MASK 0x03
+
#define USB_INTEL_XUSB2PR 0xD0
#define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI 0x9c31
+#define ASUS_ACPI_UID_ASUSWMI "ASUSWMI"
+#define ASUS_ACPI_UID_ATK "ATK"
+
+#define WMI_EVENT_QUEUE_SIZE 0x10
+#define WMI_EVENT_QUEUE_END 0x1
+#define WMI_EVENT_MASK 0xFFFF
+/* The WMI hotkey event value is always the same. */
+#define WMI_EVENT_VALUE_ATK 0xFF
+
+#define WMI_EVENT_MASK 0xFFFF
+
static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL };
static bool ashs_present(void)
@@ -85,6 +104,7 @@ static bool ashs_present(void)
struct bios_args {
u32 arg0;
u32 arg1;
+ u32 arg2; /* At least TUF Gaming series uses 3 dword input buffer. */
} __packed;
/*
@@ -132,6 +152,7 @@ struct asus_wmi {
int dsts_id;
int spec;
int sfun;
+ bool wmi_event_queue;
struct input_dev *inputdev;
struct backlight_device *backlight_device;
@@ -161,6 +182,10 @@ struct asus_wmi {
int asus_hwmon_num_fans;
int asus_hwmon_pwm;
+ bool fan_mode_available;
+ u8 fan_mode_mask;
+ u8 fan_mode;
+
struct hotplug_slot hotplug_slot;
struct mutex hotplug_lock;
struct mutex wmi_lock;
@@ -174,6 +199,8 @@ struct asus_wmi {
struct asus_wmi_driver *driver;
};
+/* Input **********************************************************************/
+
static int asus_wmi_input_init(struct asus_wmi *asus)
{
int err;
@@ -211,11 +238,15 @@ static void asus_wmi_input_exit(struct asus_wmi *asus)
asus->inputdev = NULL;
}
-int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
+/* WMI ************************************************************************/
+
+static int asus_wmi_evaluate_method3(u32 method_id,
+ u32 arg0, u32 arg1, u32 arg2, u32 *retval)
{
struct bios_args args = {
.arg0 = arg0,
.arg1 = arg1,
+ .arg2 = arg2,
};
struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
@@ -227,7 +258,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
&input, &output);
if (ACPI_FAILURE(status))
- goto exit;
+ return -EIO;
obj = (union acpi_object *)output.pointer;
if (obj && obj->type == ACPI_TYPE_INTEGER)
@@ -238,15 +269,16 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
kfree(obj);
-exit:
- if (ACPI_FAILURE(status))
- return -EIO;
-
if (tmp == ASUS_WMI_UNSUPPORTED_METHOD)
return -ENODEV;
return 0;
}
+
+int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
+{
+ return asus_wmi_evaluate_method3(method_id, arg0, arg1, 0, retval);
+}
EXPORT_SYMBOL_GPL(asus_wmi_evaluate_method);
static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer args)
@@ -320,9 +352,8 @@ static int asus_wmi_get_devstate_simple(struct asus_wmi *asus, u32 dev_id)
ASUS_WMI_DSTS_STATUS_BIT);
}
-/*
- * LEDs
- */
+/* LEDs ***********************************************************************/
+
/*
* These functions actually update the LED's, and are called from a
* workqueue. By doing this as separate work rather than when the LED
@@ -427,6 +458,10 @@ static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
static void kbd_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
+ /* Prevent disabling keyboard backlight on module unregister */
+ if (led_cdev->flags & LED_UNREGISTERING)
+ return;
+
do_kbd_led_set(led_cdev, value);
}
@@ -582,8 +617,7 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
goto error;
}
- led_val = kbd_led_read(asus, NULL, NULL);
- if (led_val >= 0) {
+ if (!kbd_led_read(asus, &led_val, NULL)) {
asus->kbd_led_wk = led_val;
asus->kbd_led.name = "asus::kbd_backlight";
asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
@@ -633,6 +667,7 @@ error:
return rv;
}
+/* RF *************************************************************************/
/*
* PCI hotplug (for wlan rfkill)
@@ -1055,6 +1090,8 @@ exit:
return result;
}
+/* Quirks *********************************************************************/
+
static void asus_wmi_set_xusb2pr(struct asus_wmi *asus)
{
struct pci_dev *xhci_pdev;
@@ -1087,9 +1124,8 @@ static void asus_wmi_set_als(void)
asus_wmi_set_devstate(ASUS_WMI_DEVID_ALS_ENABLE, 1, NULL);
}
-/*
- * Hwmon device
- */
+/* Hwmon device ***************************************************************/
+
static int asus_hwmon_agfn_fan_speed_read(struct asus_wmi *asus, int fan,
int *speed)
{
@@ -1353,8 +1389,7 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
struct attribute *attr, int idx)
{
struct device *dev = container_of(kobj, struct device, kobj);
- struct platform_device *pdev = to_platform_device(dev->parent);
- struct asus_wmi *asus = platform_get_drvdata(pdev);
+ struct asus_wmi *asus = dev_get_drvdata(dev->parent);
int dev_id = -1;
int fan_attr = -1;
u32 value = ASUS_WMI_UNSUPPORTED_METHOD;
@@ -1395,8 +1430,11 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
else
ok = fan_attr <= asus->asus_hwmon_num_fans;
} else if (dev_id == ASUS_WMI_DEVID_THERMAL_CTRL) {
- /* If value is zero, something is clearly wrong */
- if (!value)
+ /*
+ * If the temperature value in deci-Kelvin is near the absolute
+ * zero temperature, something is clearly wrong
+ */
+ if (value == 0 || value == 1)
ok = false;
} else if (fan_attr <= asus->asus_hwmon_num_fans && fan_attr != -1) {
ok = true;
@@ -1415,11 +1453,12 @@ __ATTRIBUTE_GROUPS(hwmon_attribute);
static int asus_wmi_hwmon_init(struct asus_wmi *asus)
{
+ struct device *dev = &asus->platform_device->dev;
struct device *hwmon;
- hwmon = hwmon_device_register_with_groups(&asus->platform_device->dev,
- "asus", asus,
- hwmon_attribute_groups);
+ hwmon = devm_hwmon_device_register_with_groups(dev, "asus", asus,
+ hwmon_attribute_groups);
+
if (IS_ERR(hwmon)) {
pr_err("Could not register asus hwmon device\n");
return PTR_ERR(hwmon);
@@ -1427,9 +1466,137 @@ static int asus_wmi_hwmon_init(struct asus_wmi *asus)
return 0;
}
-/*
- * Backlight
- */
+static int asus_wmi_fan_init(struct asus_wmi *asus)
+{
+ int status;
+
+ asus->asus_hwmon_pwm = -1;
+ asus->asus_hwmon_num_fans = -1;
+ asus->asus_hwmon_fan_manual_mode = false;
+
+ status = asus_hwmon_get_fan_number(asus, &asus->asus_hwmon_num_fans);
+ if (status) {
+ asus->asus_hwmon_num_fans = 0;
+ pr_warn("Could not determine number of fans: %d\n", status);
+ return -ENXIO;
+ }
+
+ pr_info("Number of fans: %d\n", asus->asus_hwmon_num_fans);
+ return 0;
+}
+
+/* Fan mode *******************************************************************/
+
+static int fan_mode_check_present(struct asus_wmi *asus)
+{
+ u32 result;
+ int err;
+
+ asus->fan_mode_available = false;
+
+ err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_MODE, &result);
+ if (err) {
+ if (err == -ENODEV)
+ return 0;
+ else
+ return err;
+ }
+
+ if ((result & ASUS_WMI_DSTS_PRESENCE_BIT) &&
+ (result & ASUS_FAN_MODES_MASK)) {
+ asus->fan_mode_available = true;
+ asus->fan_mode_mask = result & ASUS_FAN_MODES_MASK;
+ }
+
+ return 0;
+}
+
+static int fan_mode_write(struct asus_wmi *asus)
+{
+ int err;
+ u8 value;
+ u32 retval;
+
+ value = asus->fan_mode;
+
+ pr_info("Set fan mode: %u\n", value);
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_FAN_MODE, value, &retval);
+
+ if (err) {
+ pr_warn("Failed to set fan mode: %d\n", err);
+ return err;
+ }
+
+ if (retval != 1) {
+ pr_warn("Failed to set fan mode (retval): 0x%x\n", retval);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int fan_mode_switch_next(struct asus_wmi *asus)
+{
+ if (asus->fan_mode == ASUS_FAN_MODE_NORMAL) {
+ if (asus->fan_mode_mask & ASUS_FAN_MODE_OVERBOOST_MASK)
+ asus->fan_mode = ASUS_FAN_MODE_OVERBOOST;
+ else if (asus->fan_mode_mask & ASUS_FAN_MODE_SILENT_MASK)
+ asus->fan_mode = ASUS_FAN_MODE_SILENT;
+ } else if (asus->fan_mode == ASUS_FAN_MODE_OVERBOOST) {
+ if (asus->fan_mode_mask & ASUS_FAN_MODE_SILENT_MASK)
+ asus->fan_mode = ASUS_FAN_MODE_SILENT;
+ else
+ asus->fan_mode = ASUS_FAN_MODE_NORMAL;
+ } else {
+ asus->fan_mode = ASUS_FAN_MODE_NORMAL;
+ }
+
+ return fan_mode_write(asus);
+}
+
+static ssize_t fan_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", asus->fan_mode);
+}
+
+static ssize_t fan_mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int result;
+ u8 new_mode;
+
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ result = kstrtou8(buf, 10, &new_mode);
+ if (result < 0) {
+ pr_warn("Trying to store invalid value\n");
+ return result;
+ }
+
+ if (new_mode == ASUS_FAN_MODE_OVERBOOST) {
+ if (!(asus->fan_mode_mask & ASUS_FAN_MODE_OVERBOOST_MASK))
+ return -EINVAL;
+ } else if (new_mode == ASUS_FAN_MODE_SILENT) {
+ if (!(asus->fan_mode_mask & ASUS_FAN_MODE_SILENT_MASK))
+ return -EINVAL;
+ } else if (new_mode != ASUS_FAN_MODE_NORMAL) {
+ return -EINVAL;
+ }
+
+ asus->fan_mode = new_mode;
+ fan_mode_write(asus);
+
+ return result;
+}
+
+// Fan mode: 0 - normal, 1 - overboost, 2 - silent
+static DEVICE_ATTR_RW(fan_mode);
+
+/* Backlight ******************************************************************/
+
static int read_backlight_power(struct asus_wmi *asus)
{
int ret;
@@ -1611,6 +1778,8 @@ static int is_display_toggle(int code)
return 0;
}
+/* Fn-lock ********************************************************************/
+
static bool asus_wmi_has_fnlock_key(struct asus_wmi *asus)
{
u32 result;
@@ -1628,88 +1797,148 @@ static void asus_wmi_fnlock_update(struct asus_wmi *asus)
asus_wmi_set_devstate(ASUS_WMI_DEVID_FNLOCK, mode, NULL);
}
-static void asus_wmi_notify(u32 value, void *context)
+/* WMI events *****************************************************************/
+
+static int asus_wmi_get_event_code(u32 value)
{
- struct asus_wmi *asus = context;
struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *obj;
acpi_status status;
int code;
- int orig_code;
- unsigned int key_value = 1;
- bool autorelease = 1;
status = wmi_get_event_data(value, &response);
- if (status != AE_OK) {
- pr_err("bad event status 0x%x\n", status);
- return;
+ if (ACPI_FAILURE(status)) {
+ pr_warn("Failed to get WMI notify code: %s\n",
+ acpi_format_exception(status));
+ return -EIO;
}
obj = (union acpi_object *)response.pointer;
- if (!obj || obj->type != ACPI_TYPE_INTEGER)
- goto exit;
+ if (obj && obj->type == ACPI_TYPE_INTEGER)
+ code = (int)(obj->integer.value & WMI_EVENT_MASK);
+ else
+ code = -EIO;
+
+ kfree(obj);
+ return code;
+}
+
+static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
+{
+ int orig_code;
+ unsigned int key_value = 1;
+ bool autorelease = 1;
- code = obj->integer.value;
orig_code = code;
if (asus->driver->key_filter) {
asus->driver->key_filter(asus->driver, &code, &key_value,
&autorelease);
if (code == ASUS_WMI_KEY_IGNORE)
- goto exit;
+ return;
}
if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX)
code = ASUS_WMI_BRN_UP;
- else if (code >= NOTIFY_BRNDOWN_MIN &&
- code <= NOTIFY_BRNDOWN_MAX)
+ else if (code >= NOTIFY_BRNDOWN_MIN && code <= NOTIFY_BRNDOWN_MAX)
code = ASUS_WMI_BRN_DOWN;
if (code == ASUS_WMI_BRN_DOWN || code == ASUS_WMI_BRN_UP) {
if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
asus_wmi_backlight_notify(asus, orig_code);
- goto exit;
+ return;
}
}
if (code == NOTIFY_KBD_BRTUP) {
kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
- goto exit;
+ return;
}
if (code == NOTIFY_KBD_BRTDWN) {
kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1);
- goto exit;
+ return;
}
if (code == NOTIFY_KBD_BRTTOGGLE) {
if (asus->kbd_led_wk == asus->kbd_led.max_brightness)
kbd_led_set_by_kbd(asus, 0);
else
kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
- goto exit;
+ return;
}
if (code == NOTIFY_FNLOCK_TOGGLE) {
asus->fnlock_locked = !asus->fnlock_locked;
asus_wmi_fnlock_update(asus);
- goto exit;
+ return;
}
- if (is_display_toggle(code) &&
- asus->driver->quirks->no_display_toggle)
- goto exit;
+ if (asus->fan_mode_available && code == NOTIFY_KBD_FBM) {
+ fan_mode_switch_next(asus);
+ return;
+ }
+
+ if (is_display_toggle(code) && asus->driver->quirks->no_display_toggle)
+ return;
if (!sparse_keymap_report_event(asus->inputdev, code,
key_value, autorelease))
pr_info("Unknown key %x pressed\n", code);
+}
-exit:
- kfree(obj);
+static void asus_wmi_notify(u32 value, void *context)
+{
+ struct asus_wmi *asus = context;
+ int code;
+ int i;
+
+ for (i = 0; i < WMI_EVENT_QUEUE_SIZE + 1; i++) {
+ code = asus_wmi_get_event_code(value);
+
+ if (code < 0) {
+ pr_warn("Failed to get notify code: %d\n", code);
+ return;
+ }
+
+ if (code == WMI_EVENT_QUEUE_END || code == WMI_EVENT_MASK)
+ return;
+
+ asus_wmi_handle_event_code(code, asus);
+
+ /*
+ * Double check that queue is present:
+ * ATK (with queue) uses 0xff, ASUSWMI (without) 0xd2.
+ */
+ if (!asus->wmi_event_queue || value != WMI_EVENT_VALUE_ATK)
+ return;
+ }
+
+ pr_warn("Failed to process event queue, last code: 0x%x\n", code);
}
-/*
- * Sys helpers
- */
+static int asus_wmi_notify_queue_flush(struct asus_wmi *asus)
+{
+ int code;
+ int i;
+
+ for (i = 0; i < WMI_EVENT_QUEUE_SIZE + 1; i++) {
+ code = asus_wmi_get_event_code(WMI_EVENT_VALUE_ATK);
+
+ if (code < 0) {
+ pr_warn("Failed to get event during flush: %d\n", code);
+ return code;
+ }
+
+ if (code == WMI_EVENT_QUEUE_END || code == WMI_EVENT_MASK)
+ return 0;
+ }
+
+ pr_warn("Failed to flush event queue\n");
+ return -EIO;
+}
+
+/* Sysfs **********************************************************************/
+
static int parse_arg(const char *buf, unsigned long count, int *val)
{
if (!count)
@@ -1805,6 +2034,7 @@ static struct attribute *platform_attributes[] = {
&dev_attr_touchpad.attr,
&dev_attr_lid_resume.attr,
&dev_attr_als_enable.attr,
+ &dev_attr_fan_mode.attr,
NULL
};
@@ -1826,6 +2056,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
devid = ASUS_WMI_DEVID_LID_RESUME;
else if (attr == &dev_attr_als_enable.attr)
devid = ASUS_WMI_DEVID_ALS_ENABLE;
+ else if (attr == &dev_attr_fan_mode.attr)
+ ok = asus->fan_mode_available;
if (devid != -1)
ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0);
@@ -1848,11 +2080,12 @@ static int asus_wmi_sysfs_init(struct platform_device *device)
return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
}
-/*
- * Platform device
- */
+/* Platform device ************************************************************/
+
static int asus_wmi_platform_init(struct asus_wmi *asus)
{
+ struct device *dev = &asus->platform_device->dev;
+ char *wmi_uid;
int rv;
/* INIT enable hotkeys on some models */
@@ -1882,11 +2115,41 @@ static int asus_wmi_platform_init(struct asus_wmi *asus)
* Note, on most Eeepc, there is no way to check if a method exist
* or note, while on notebooks, they returns 0xFFFFFFFE on failure,
* but once again, SPEC may probably be used for that kind of things.
+ *
+ * Additionally at least TUF Gaming series laptops return nothing for
+ * unknown methods, so the detection in this way is not possible.
+ *
+ * There is strong indication that only ACPI WMI devices that have _UID
+ * equal to "ASUSWMI" use DCTS whereas those with "ATK" use DSTS.
*/
- if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, 0, 0, NULL))
+ wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID);
+ if (!wmi_uid)
+ return -ENODEV;
+
+ if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI)) {
+ dev_info(dev, "Detected ASUSWMI, use DCTS\n");
+ asus->dsts_id = ASUS_WMI_METHODID_DCTS;
+ } else {
+ dev_info(dev, "Detected %s, not ASUSWMI, use DSTS\n", wmi_uid);
asus->dsts_id = ASUS_WMI_METHODID_DSTS;
- else
- asus->dsts_id = ASUS_WMI_METHODID_DSTS2;
+ }
+
+ /*
+ * Some devices can have multiple event codes stored in a queue before
+ * the module load if it was unloaded intermittently after calling
+ * the INIT method (enables event handling). The WMI notify handler is
+ * expected to retrieve all event codes until a retrieved code equals
+ * queue end marker (One or Ones). Old codes are flushed from the queue
+ * upon module load. Not enabling this when it should be has minimal
+ * visible impact so fall back if anything goes wrong.
+ */
+ wmi_uid = wmi_get_acpi_device_uid(asus->driver->event_guid);
+ if (wmi_uid && !strcmp(wmi_uid, ASUS_ACPI_UID_ATK)) {
+ dev_info(dev, "Detected ATK, enable event queue\n");
+
+ if (!asus_wmi_notify_queue_flush(asus))
+ asus->wmi_event_queue = true;
+ }
/* CWAP allow to define the behavior of the Fn+F2 key,
* this method doesn't seems to be present on Eee PCs */
@@ -1894,17 +2157,11 @@ static int asus_wmi_platform_init(struct asus_wmi *asus)
asus_wmi_set_devstate(ASUS_WMI_DEVID_CWAP,
asus->driver->quirks->wapf, NULL);
- return asus_wmi_sysfs_init(asus->platform_device);
+ return 0;
}
-static void asus_wmi_platform_exit(struct asus_wmi *asus)
-{
- asus_wmi_sysfs_exit(asus->platform_device);
-}
+/* debugfs ********************************************************************/
-/*
- * debugfs
- */
struct asus_wmi_debugfs_node {
struct asus_wmi *asus;
char *name;
@@ -2005,74 +2262,33 @@ static void asus_wmi_debugfs_exit(struct asus_wmi *asus)
debugfs_remove_recursive(asus->debug.root);
}
-static int asus_wmi_debugfs_init(struct asus_wmi *asus)
+static void asus_wmi_debugfs_init(struct asus_wmi *asus)
{
- struct dentry *dent;
int i;
asus->debug.root = debugfs_create_dir(asus->driver->name, NULL);
- if (!asus->debug.root) {
- pr_err("failed to create debugfs directory\n");
- goto error_debugfs;
- }
- dent = debugfs_create_x32("method_id", S_IRUGO | S_IWUSR,
- asus->debug.root, &asus->debug.method_id);
- if (!dent)
- goto error_debugfs;
+ debugfs_create_x32("method_id", S_IRUGO | S_IWUSR, asus->debug.root,
+ &asus->debug.method_id);
- dent = debugfs_create_x32("dev_id", S_IRUGO | S_IWUSR,
- asus->debug.root, &asus->debug.dev_id);
- if (!dent)
- goto error_debugfs;
+ debugfs_create_x32("dev_id", S_IRUGO | S_IWUSR, asus->debug.root,
+ &asus->debug.dev_id);
- dent = debugfs_create_x32("ctrl_param", S_IRUGO | S_IWUSR,
- asus->debug.root, &asus->debug.ctrl_param);
- if (!dent)
- goto error_debugfs;
+ debugfs_create_x32("ctrl_param", S_IRUGO | S_IWUSR, asus->debug.root,
+ &asus->debug.ctrl_param);
for (i = 0; i < ARRAY_SIZE(asus_wmi_debug_files); i++) {
struct asus_wmi_debugfs_node *node = &asus_wmi_debug_files[i];
node->asus = asus;
- dent = debugfs_create_file(node->name, S_IFREG | S_IRUGO,
- asus->debug.root, node,
- &asus_wmi_debugfs_io_ops);
- if (!dent) {
- pr_err("failed to create debug file: %s\n", node->name);
- goto error_debugfs;
- }
+ debugfs_create_file(node->name, S_IFREG | S_IRUGO,
+ asus->debug.root, node,
+ &asus_wmi_debugfs_io_ops);
}
-
- return 0;
-
-error_debugfs:
- asus_wmi_debugfs_exit(asus);
- return -ENOMEM;
}
-static int asus_wmi_fan_init(struct asus_wmi *asus)
-{
- int status;
-
- asus->asus_hwmon_pwm = -1;
- asus->asus_hwmon_num_fans = -1;
- asus->asus_hwmon_fan_manual_mode = false;
-
- status = asus_hwmon_get_fan_number(asus, &asus->asus_hwmon_num_fans);
- if (status) {
- asus->asus_hwmon_num_fans = 0;
- pr_warn("Could not determine number of fans: %d\n", status);
- return -ENXIO;
- }
-
- pr_info("Number of fans: %d\n", asus->asus_hwmon_num_fans);
- return 0;
-}
+/* Init / exit ****************************************************************/
-/*
- * WMI Driver
- */
static int asus_wmi_add(struct platform_device *pdev)
{
struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver);
@@ -2099,6 +2315,14 @@ static int asus_wmi_add(struct platform_device *pdev)
if (err)
goto fail_platform;
+ err = fan_mode_check_present(asus);
+ if (err)
+ goto fail_fan_mode;
+
+ err = asus_wmi_sysfs_init(asus->platform_device);
+ if (err)
+ goto fail_sysfs;
+
err = asus_wmi_input_init(asus);
if (err)
goto fail_input;
@@ -2162,14 +2386,10 @@ static int asus_wmi_add(struct platform_device *pdev)
goto fail_wmi_handler;
}
- err = asus_wmi_debugfs_init(asus);
- if (err)
- goto fail_debugfs;
+ asus_wmi_debugfs_init(asus);
return 0;
-fail_debugfs:
- wmi_remove_notify_handler(asus->driver->event_guid);
fail_wmi_handler:
asus_wmi_backlight_exit(asus);
fail_backlight:
@@ -2180,7 +2400,9 @@ fail_leds:
fail_hwmon:
asus_wmi_input_exit(asus);
fail_input:
- asus_wmi_platform_exit(asus);
+ asus_wmi_sysfs_exit(asus->platform_device);
+fail_sysfs:
+fail_fan_mode:
fail_platform:
kfree(asus);
return err;
@@ -2197,16 +2419,15 @@ static int asus_wmi_remove(struct platform_device *device)
asus_wmi_led_exit(asus);
asus_wmi_rfkill_exit(asus);
asus_wmi_debugfs_exit(asus);
- asus_wmi_platform_exit(asus);
+ asus_wmi_sysfs_exit(asus->platform_device);
asus_hwmon_fan_set_auto(asus);
kfree(asus);
return 0;
}
-/*
- * Platform driver - hibernate/resume callbacks
- */
+/* Platform driver - hibernate/resume callbacks *******************************/
+
static int asus_hotk_thaw(struct device *device)
{
struct asus_wmi *asus = dev_get_drvdata(device);
@@ -2282,6 +2503,8 @@ static const struct dev_pm_ops asus_pm_ops = {
.resume = asus_hotk_resume,
};
+/* Registration ***************************************************************/
+
static int asus_wmi_probe(struct platform_device *pdev)
{
struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver);