platform/x86: acer-wmi: add fan speed monitoring for Predator PHN16-71
[sfrench/cifs-2.6.git] / drivers / platform / x86 / acer-wmi.c
index 55fedc2e656eae2aef1e70f3a41638ebd054bc21..88b826e88ebd79218aefb3da3a83ade71390dd9a 100644 (file)
@@ -30,6 +30,8 @@
 #include <linux/input.h>
 #include <linux/input/sparse-keymap.h>
 #include <acpi/video.h>
+#include <linux/hwmon.h>
+#include <linux/bitfield.h>
 
 MODULE_AUTHOR("Carlos Corbacho");
 MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver");
@@ -69,6 +71,8 @@ MODULE_LICENSE("GPL");
 
 #define ACER_PREDATOR_V4_THERMAL_PROFILE_EC_OFFSET 0x54
 
+#define ACER_PREDATOR_V4_FAN_SPEED_READ_BIT_MASK GENMASK(20, 8)
+
 /*
  * Acer ACPI method GUIDs
  */
@@ -96,6 +100,8 @@ enum acer_wmi_event_ids {
 
 enum acer_wmi_predator_v4_sys_info_command {
        ACER_WMID_CMD_GET_PREDATOR_V4_BAT_STATUS = 0x02,
+       ACER_WMID_CMD_GET_PREDATOR_V4_CPU_FAN_SPEED = 0x0201,
+       ACER_WMID_CMD_GET_PREDATOR_V4_GPU_FAN_SPEED = 0x0601,
 };
 
 static const struct key_entry acer_wmi_keymap[] __initconst = {
@@ -241,6 +247,7 @@ struct hotkey_function_type_aa {
 #define ACER_CAP_TURBO_LED             BIT(8)
 #define ACER_CAP_TURBO_FAN             BIT(9)
 #define ACER_CAP_PLATFORM_PROFILE      BIT(10)
+#define ACER_CAP_FAN_SPEED_READ                BIT(11)
 
 /*
  * Interface type flags
@@ -352,7 +359,8 @@ static void __init set_quirks(void)
                                         | ACER_CAP_TURBO_FAN;
 
        if (quirks->predator_v4)
-               interface->capability |= ACER_CAP_PLATFORM_PROFILE;
+               interface->capability |= ACER_CAP_PLATFORM_PROFILE |
+                                        ACER_CAP_FAN_SPEED_READ;
 }
 
 static int __init dmi_matched(const struct dmi_system_id *dmi)
@@ -1718,6 +1726,26 @@ static int acer_gsensor_event(void)
        return 0;
 }
 
+static int acer_get_fan_speed(int fan)
+{
+       if (quirks->predator_v4) {
+               acpi_status status;
+               u64 fanspeed;
+
+               status = WMI_gaming_execute_u64(
+                       ACER_WMID_GET_GAMING_SYS_INFO_METHODID,
+                       fan == 0 ? ACER_WMID_CMD_GET_PREDATOR_V4_CPU_FAN_SPEED :
+                                  ACER_WMID_CMD_GET_PREDATOR_V4_GPU_FAN_SPEED,
+                       &fanspeed);
+
+               if (ACPI_FAILURE(status))
+                       return -EIO;
+
+               return FIELD_GET(ACER_PREDATOR_V4_FAN_SPEED_READ_BIT_MASK, fanspeed);
+       }
+       return -EOPNOTSUPP;
+}
+
 /*
  *  Predator series turbo button
  */
@@ -2472,6 +2500,8 @@ static u32 get_wmid_devices(void)
        return devices;
 }
 
+static int acer_wmi_hwmon_init(void);
+
 /*
  * Platform device
  */
@@ -2501,8 +2531,17 @@ static int acer_platform_probe(struct platform_device *device)
                        goto error_platform_profile;
        }
 
+       if (has_cap(ACER_CAP_FAN_SPEED_READ)) {
+               err = acer_wmi_hwmon_init();
+               if (err)
+                       goto error_hwmon;
+       }
+
        return 0;
 
+error_hwmon:
+       if (platform_profile_support)
+               platform_profile_remove();
 error_platform_profile:
        acer_rfkill_exit();
 error_rfkill:
@@ -2612,6 +2651,73 @@ static void __init create_debugfs(void)
                           &interface->debug.wmid_devices);
 }
 
+static umode_t acer_wmi_hwmon_is_visible(const void *data,
+                                        enum hwmon_sensor_types type, u32 attr,
+                                        int channel)
+{
+       switch (type) {
+       case hwmon_fan:
+               if (acer_get_fan_speed(channel) >= 0)
+                       return 0444;
+               break;
+       default:
+               return 0;
+       }
+
+       return 0;
+}
+
+static int acer_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+                              u32 attr, int channel, long *val)
+{
+       int ret;
+
+       switch (type) {
+       case hwmon_fan:
+               ret = acer_get_fan_speed(channel);
+               if (ret < 0)
+                       return ret;
+               *val = ret;
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
+static const struct hwmon_channel_info *const acer_wmi_hwmon_info[] = {
+       HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), NULL
+};
+
+static const struct hwmon_ops acer_wmi_hwmon_ops = {
+       .read = acer_wmi_hwmon_read,
+       .is_visible = acer_wmi_hwmon_is_visible,
+};
+
+static const struct hwmon_chip_info acer_wmi_hwmon_chip_info = {
+       .ops = &acer_wmi_hwmon_ops,
+       .info = acer_wmi_hwmon_info,
+};
+
+static int acer_wmi_hwmon_init(void)
+{
+       struct device *dev = &acer_platform_device->dev;
+       struct device *hwmon;
+
+       hwmon = devm_hwmon_device_register_with_info(dev, "acer",
+                                                    &acer_platform_driver,
+                                                    &acer_wmi_hwmon_chip_info,
+                                                    NULL);
+
+       if (IS_ERR(hwmon)) {
+               dev_err(dev, "Could not register acer hwmon device\n");
+               return PTR_ERR(hwmon);
+       }
+
+       return 0;
+}
+
 static int __init acer_wmi_init(void)
 {
        int err;