eeepc-laptop: add hwmon fan control
authorCorentin Chary <corentincj@iksaif.net>
Thu, 13 Mar 2008 11:57:18 +0000 (12:57 +0100)
committerLen Brown <len.brown@intel.com>
Tue, 29 Apr 2008 14:08:07 +0000 (10:08 -0400)
Adds an hwmon interface to control the fan.

Signed-off-by: Corentin Chary <corentincj@iksaif.net>
Signed-off-by: Len Brown <len.brown@intel.com>
drivers/misc/Kconfig
drivers/misc/eeepc-laptop.c

index 01b7deba91e8d997f28e4fef2252d1d1945367d9..7a4869a9db868939deb741c6be64135c7c2bacae 100644 (file)
@@ -356,6 +356,7 @@ config EEEPC_LAPTOP
        depends on X86
        depends on ACPI
        depends on BACKLIGHT_CLASS_DEVICE
+       depends on HWMON
        depends on EXPERIMENTAL
        ---help---
          This driver supports the Fn-Fx keys on Eee PC laptops.
index 2b59b7ed4b1456d9373b5ee13bc1fd9595f47ecb..6d727609097fc4676e65cc381403b511197eec0b 100644 (file)
@@ -23,6 +23,8 @@
 #include <linux/platform_device.h>
 #include <linux/backlight.h>
 #include <linux/fb.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
 #include <acpi/acpi_drivers.h>
 #include <acpi/acpi_bus.h>
 #include <linux/uaccess.h>
@@ -103,6 +105,15 @@ const char *cm_setv[] = {
        "CRDS", NULL
 };
 
+#define EEEPC_EC       "\\_SB.PCI0.SBRG.EC0."
+
+#define EEEPC_EC_FAN_PWM       EEEPC_EC "SC02" /* Fan PWM duty cycle (%) */
+#define EEEPC_EC_SC02          0x63
+#define EEEPC_EC_FAN_HRPM      EEEPC_EC "SC05" /* High byte, fan speed (RPM) */
+#define EEEPC_EC_FAN_LRPM      EEEPC_EC "SC06" /* Low byte, fan speed (RPM) */
+#define EEEPC_EC_FAN_CTRL      EEEPC_EC "SFB3" /* Byte containing SF25  */
+#define EEEPC_EC_SFB3          0xD3
+
 /*
  * This is the main structure, we can use it to store useful information
  * about the hotk device
@@ -154,6 +165,9 @@ static struct acpi_driver eeepc_hotk_driver = {
 /* The backlight device /sys/class/backlight */
 static struct backlight_device *eeepc_backlight_device;
 
+/* The hwmon device */
+static struct device *eeepc_hwmon_device;
+
 /*
  * The backlight class declaration
  */
@@ -428,6 +442,100 @@ static int eeepc_hotk_remove(struct acpi_device *device, int type)
        return 0;
 }
 
+/*
+ * Hwmon
+ */
+static int eeepc_get_fan_pwm(void)
+{
+       int value = 0;
+
+       read_acpi_int(NULL, EEEPC_EC_FAN_PWM, &value);
+       return (value);
+}
+
+static void eeepc_set_fan_pwm(int value)
+{
+       value = SENSORS_LIMIT(value, 0, 100);
+       ec_write(EEEPC_EC_SC02, value);
+}
+
+static int eeepc_get_fan_rpm(void)
+{
+       int high = 0;
+       int low = 0;
+
+       read_acpi_int(NULL, EEEPC_EC_FAN_HRPM, &high);
+       read_acpi_int(NULL, EEEPC_EC_FAN_LRPM, &low);
+       return (high << 8 | low);
+}
+
+static int eeepc_get_fan_ctrl(void)
+{
+       int value = 0;
+
+       read_acpi_int(NULL, EEEPC_EC_FAN_CTRL, &value);
+       return ((value & 0x02 ? 1 : 0));
+}
+
+static void eeepc_set_fan_ctrl(int manual)
+{
+       int value = 0;
+
+       read_acpi_int(NULL, EEEPC_EC_FAN_CTRL, &value);
+       if (manual)
+               value |= 0x02;
+       else
+               value &= ~0x02;
+       ec_write(EEEPC_EC_SFB3, value);
+}
+
+static ssize_t store_sys_hwmon(void (*set)(int), const char *buf, size_t count)
+{
+       int rv, value;
+
+       rv = parse_arg(buf, count, &value);
+       if (rv > 0)
+               set(value);
+       return rv;
+}
+
+static ssize_t show_sys_hwmon(int (*get)(void), char *buf)
+{
+       return sprintf(buf, "%d\n", get());
+}
+
+#define EEEPC_CREATE_SENSOR_ATTR(_name, _mode, _set, _get)             \
+       static ssize_t show_##_name(struct device *dev,                 \
+                                   struct device_attribute *attr,      \
+                                   char *buf)                          \
+       {                                                               \
+               return show_sys_hwmon(_set, buf);                       \
+       }                                                               \
+       static ssize_t store_##_name(struct device *dev,                \
+                                    struct device_attribute *attr,     \
+                                    const char *buf, size_t count)     \
+       {                                                               \
+               return store_sys_hwmon(_get, buf, count);               \
+       }                                                               \
+       static SENSOR_DEVICE_ATTR(_name, _mode, show_##_name, store_##_name, 0);
+
+EEEPC_CREATE_SENSOR_ATTR(fan1_input, S_IRUGO, eeepc_get_fan_rpm, NULL);
+EEEPC_CREATE_SENSOR_ATTR(fan1_pwm, S_IRUGO | S_IWUSR,
+                        eeepc_get_fan_pwm, eeepc_set_fan_pwm);
+EEEPC_CREATE_SENSOR_ATTR(pwm1_enable, S_IRUGO | S_IWUSR,
+                        eeepc_get_fan_ctrl, eeepc_set_fan_ctrl);
+
+static struct attribute *hwmon_attributes[] = {
+       &sensor_dev_attr_fan1_pwm.dev_attr.attr,
+       &sensor_dev_attr_fan1_input.dev_attr.attr,
+       &sensor_dev_attr_pwm1_enable.dev_attr.attr,
+       NULL
+};
+
+static struct attribute_group hwmon_attribute_group = {
+       .attrs = hwmon_attributes
+};
+
 /*
  * exit/init
  */
@@ -438,9 +546,23 @@ static void eeepc_backlight_exit(void)
        eeepc_backlight_device = NULL;
 }
 
+static void eeepc_hwmon_exit(void)
+{
+       struct device *hwmon;
+
+       hwmon = eeepc_hwmon_device;
+       if (!hwmon)
+               return ;
+       hwmon_device_unregister(hwmon);
+       sysfs_remove_group(&hwmon->kobj,
+                          &hwmon_attribute_group);
+       eeepc_hwmon_device = NULL;
+}
+
 static void __exit eeepc_laptop_exit(void)
 {
        eeepc_backlight_exit();
+       eeepc_hwmon_exit();
        acpi_bus_unregister_driver(&eeepc_hotk_driver);
        sysfs_remove_group(&platform_device->dev.kobj,
                           &platform_attribute_group);
@@ -468,6 +590,26 @@ static int eeepc_backlight_init(struct device *dev)
        return 0;
 }
 
+static int eeepc_hwmon_init(struct device *dev)
+{
+       struct device *hwmon;
+       int result;
+
+       hwmon = hwmon_device_register(dev);
+       if (IS_ERR(hwmon)) {
+               printk(EEEPC_ERR
+                      "Could not register eeepc hwmon device\n");
+               eeepc_hwmon_device = NULL;
+               return PTR_ERR(hwmon);
+       }
+       eeepc_hwmon_device = hwmon;
+       result = sysfs_create_group(&hwmon->kobj,
+                                   &hwmon_attribute_group);
+       if (result)
+               eeepc_hwmon_exit();
+       return result;
+}
+
 static int __init eeepc_laptop_init(void)
 {
        struct device *dev;
@@ -486,6 +628,9 @@ static int __init eeepc_laptop_init(void)
        result = eeepc_backlight_init(dev);
        if (result)
                goto fail_backlight;
+       result = eeepc_hwmon_init(dev);
+       if (result)
+               goto fail_hwmon;
        /* Register platform stuff */
        result = platform_driver_register(&platform_driver);
        if (result)
@@ -510,6 +655,8 @@ fail_platform_device2:
 fail_platform_device1:
        platform_driver_unregister(&platform_driver);
 fail_platform_driver:
+       eeepc_hwmon_exit();
+fail_hwmon:
        eeepc_backlight_exit();
 fail_backlight:
        return result;