From fe98a52ce7540fb3a19d57488a08864110cf4d5c Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 24 Apr 2007 11:48:17 -0300 Subject: ACPI: thinkpad-acpi: add sysfs support to fan subdriver Export sysfs attributes to monitor and control the internal thinkpad fan (some thinkpads have more than one fan, but thinkpad-acpi doesn't support the second fan yet). The sysfs interface follows the hwmon design guide for fan devices. Also, fix some stray "thermal" files in the fan procfs description that have been there forever, and officially support "full-speed" as the name for the PWM-disabled state of the fan controller to keep it in line with the hwmon interface. It is much better a name for that mode than the unobvious "disengaged" anyway. Change the procfs interface to also accept full-speed as a fan level, but still report it as disengaged for backwards compatibility. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 326 ++++++++++++++++++++++++++++++++++++++++--- drivers/misc/thinkpad_acpi.h | 6 + 2 files changed, 309 insertions(+), 23 deletions(-) (limited to 'drivers') diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index d5526e882dd..a4d7ee47239 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -2695,6 +2695,7 @@ static enum fan_control_access_mode fan_control_access_mode; static enum fan_control_commands fan_control_commands; static u8 fan_control_initial_status; +static u8 fan_control_desired_level; static void fan_watchdog_fire(struct work_struct *ignored); static int fan_watchdog_maxinterval; @@ -2708,8 +2709,222 @@ IBM_HANDLE(sfan, ec, "SFAN", /* 570 */ "JFNS", /* 770x-JL */ ); /* all others */ +/* + * SYSFS fan layout: hwmon compatible (device) + * + * pwm*_enable: + * 0: "disengaged" mode + * 1: manual mode + * 2: native EC "auto" mode (recommended, hardware default) + * + * pwm*: set speed in manual mode, ignored otherwise. + * 0 is level 0; 255 is level 7. Intermediate points done with linear + * interpolation. + * + * fan*_input: tachometer reading, RPM + * + * + * SYSFS fan layout: extensions + * + * fan_watchdog (driver): + * fan watchdog interval in seconds, 0 disables (default), max 120 + */ + +/* sysfs fan pwm1_enable ----------------------------------------------- */ +static ssize_t fan_pwm1_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res, mode; + u8 status; + + res = fan_get_status_safe(&status); + if (res) + return res; + + if (unlikely(tp_features.fan_ctrl_status_undef)) { + if (status != fan_control_initial_status) { + tp_features.fan_ctrl_status_undef = 0; + } else { + /* Return most likely status. In fact, it + * might be the only possible status */ + status = TP_EC_FAN_AUTO; + } + } + + if (status & TP_EC_FAN_FULLSPEED) { + mode = 0; + } else if (status & TP_EC_FAN_AUTO) { + mode = 2; + } else + mode = 1; + + return snprintf(buf, PAGE_SIZE, "%d\n", mode); +} + +static ssize_t fan_pwm1_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res, level; + + if (parse_strtoul(buf, 2, &t)) + return -EINVAL; + + switch (t) { + case 0: + level = TP_EC_FAN_FULLSPEED; + break; + case 1: + level = TPACPI_FAN_LAST_LEVEL; + break; + case 2: + level = TP_EC_FAN_AUTO; + break; + case 3: + /* reserved for software-controlled auto mode */ + return -ENOSYS; + default: + return -EINVAL; + } + + res = fan_set_level_safe(level); + if (res < 0) + return res; + + fan_watchdog_reset(); + + return count; +} + +static struct device_attribute dev_attr_fan_pwm1_enable = + __ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + fan_pwm1_enable_show, fan_pwm1_enable_store); + +/* sysfs fan pwm1 ------------------------------------------------------ */ +static ssize_t fan_pwm1_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res; + u8 status; + + res = fan_get_status_safe(&status); + if (res) + return res; + + if (unlikely(tp_features.fan_ctrl_status_undef)) { + if (status != fan_control_initial_status) { + tp_features.fan_ctrl_status_undef = 0; + } else { + status = TP_EC_FAN_AUTO; + } + } + + if ((status & + (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) != 0) + status = fan_control_desired_level; + + if (status > 7) + status = 7; + + return snprintf(buf, PAGE_SIZE, "%u\n", (status * 255) / 7); +} + +static ssize_t fan_pwm1_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long s; + int rc; + u8 status, newlevel; + + if (parse_strtoul(buf, 255, &s)) + return -EINVAL; + + /* scale down from 0-255 to 0-7 */ + newlevel = (s >> 5) & 0x07; + + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return rc; + + rc = fan_get_status(&status); + if (!rc && (status & + (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) { + rc = fan_set_level(newlevel); + if (!rc) + fan_update_desired_level(newlevel); + fan_watchdog_reset(); + } + + mutex_unlock(&fan_mutex); + return (rc)? rc : count; +} + +static struct device_attribute dev_attr_fan_pwm1 = + __ATTR(pwm1, S_IWUSR | S_IRUGO, + fan_pwm1_show, fan_pwm1_store); + +/* sysfs fan fan1_input ------------------------------------------------ */ +static ssize_t fan_fan1_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res; + unsigned int speed; + + res = fan_get_speed(&speed); + if (res < 0) + return res; + + return snprintf(buf, PAGE_SIZE, "%u\n", speed); +} + +static struct device_attribute dev_attr_fan_fan1_input = + __ATTR(fan1_input, S_IRUGO, + fan_fan1_input_show, NULL); + +/* sysfs fan fan_watchdog (driver) ------------------------------------- */ +static ssize_t fan_fan_watchdog_show(struct device_driver *drv, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", fan_watchdog_maxinterval); +} + +static ssize_t fan_fan_watchdog_store(struct device_driver *drv, + const char *buf, size_t count) +{ + unsigned long t; + + if (parse_strtoul(buf, 120, &t)) + return -EINVAL; + + fan_watchdog_maxinterval = t; + fan_watchdog_reset(); + + return count; +} + +static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO, + fan_fan_watchdog_show, fan_fan_watchdog_store); + +/* --------------------------------------------------------------------- */ +static struct attribute *fan_attributes[] = { + &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr, + &dev_attr_fan_fan1_input.attr, + NULL +}; + +static const struct attribute_group fan_attr_group = { + .attrs = fan_attributes, +}; + static int __init fan_init(struct ibm_init_struct *iibm) { + int rc; + vdbg_printk(TPACPI_DBG_INIT, "initializing fan subdriver\n"); mutex_init(&fan_mutex); @@ -2718,6 +2933,7 @@ static int __init fan_init(struct ibm_init_struct *iibm) fan_control_commands = 0; fan_watchdog_maxinterval = 0; tp_features.fan_ctrl_status_undef = 0; + fan_control_desired_level = 7; IBM_ACPIHANDLE_INIT(fans); IBM_ACPIHANDLE_INIT(gfan); @@ -2796,9 +3012,36 @@ static int __init fan_init(struct ibm_init_struct *iibm) fan_control_access_mode != TPACPI_FAN_WR_NONE), fan_status_access_mode, fan_control_access_mode); - return (fan_status_access_mode != TPACPI_FAN_NONE || - fan_control_access_mode != TPACPI_FAN_WR_NONE)? - 0 : 1; + /* update fan_control_desired_level */ + if (fan_status_access_mode != TPACPI_FAN_NONE) + fan_get_status_safe(NULL); + + if (fan_status_access_mode != TPACPI_FAN_NONE || + fan_control_access_mode != TPACPI_FAN_WR_NONE) { + rc = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &fan_attr_group); + if (!(rc < 0)) + rc = driver_create_file(&tpacpi_pdriver.driver, + &driver_attr_fan_watchdog); + if (rc < 0) + return rc; + return 0; + } else + return 1; +} + +/* + * Call with fan_mutex held + */ +static void fan_update_desired_level(u8 status) +{ + if ((status & + (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) { + if (status > 7) + fan_control_desired_level = 7; + else + fan_control_desired_level = status; + } } static int fan_get_status(u8 *status) @@ -2837,9 +3080,33 @@ static int fan_get_status(u8 *status) return 0; } +static int fan_get_status_safe(u8 *status) +{ + int rc; + u8 s; + + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return rc; + rc = fan_get_status(&s); + if (!rc) + fan_update_desired_level(s); + mutex_unlock(&fan_mutex); + + if (status) + *status = s; + + return rc; +} + static void fan_exit(void) { vdbg_printk(TPACPI_DBG_EXIT, "cancelling any pending fan watchdog tasks\n"); + + /* FIXME: can we really do this unconditionally? */ + sysfs_remove_group(&tpacpi_pdev->dev.kobj, &fan_attr_group); + driver_remove_file(&tpacpi_pdriver.driver, &driver_attr_fan_watchdog); + cancel_delayed_work(&fan_watchdog_task); flush_scheduled_work(); } @@ -2902,17 +3169,10 @@ static void fan_watchdog_reset(void) static int fan_set_level(int level) { - int res; - switch (fan_control_access_mode) { case TPACPI_FAN_WR_ACPI_SFAN: if (level >= 0 && level <= 7) { - res = mutex_lock_interruptible(&fan_mutex); - if (res < 0) - return res; - res = acpi_evalf(sfan_handle, NULL, NULL, "vd", level); - mutex_unlock(&fan_mutex); - if (!res) + if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level)) return -EIO; } else return -EINVAL; @@ -2925,12 +3185,7 @@ static int fan_set_level(int level) ((level < 0) || (level > 7))) return -EINVAL; - res = mutex_lock_interruptible(&fan_mutex); - if (res < 0) - return res; - res = acpi_ec_write(fan_status_offset, level); - mutex_unlock(&fan_mutex); - if (!res) + if (!acpi_ec_write(fan_status_offset, level)) return -EIO; else tp_features.fan_ctrl_status_undef = 0; @@ -2942,6 +3197,25 @@ static int fan_set_level(int level) return 0; } +static int fan_set_level_safe(int level) +{ + int rc; + + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return rc; + + if (level == TPACPI_FAN_LAST_LEVEL) + level = fan_control_desired_level; + + rc = fan_set_level(level); + if (!rc) + fan_update_desired_level(level); + + mutex_unlock(&fan_mutex); + return rc; +} + static int fan_set_enable(void) { u8 s; @@ -3009,19 +3283,24 @@ static int fan_set_disable(void) case TPACPI_FAN_WR_TPEC: if (!acpi_ec_write(fan_status_offset, 0x00)) rc = -EIO; - else + else { + fan_control_desired_level = 0; tp_features.fan_ctrl_status_undef = 0; + } break; case TPACPI_FAN_WR_ACPI_SFAN: if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00)) rc = -EIO; + else + fan_control_desired_level = 0; break; default: rc = -ENXIO; } + mutex_unlock(&fan_mutex); return rc; } @@ -3063,7 +3342,7 @@ static int fan_read(char *p) switch (fan_status_access_mode) { case TPACPI_FAN_RD_ACPI_GFAN: /* 570, 600e/x, 770e, 770x */ - if ((rc = fan_get_status(&status)) < 0) + if ((rc = fan_get_status_safe(&status)) < 0) return rc; len += sprintf(p + len, "status:\t\t%s\n" @@ -3073,7 +3352,7 @@ static int fan_read(char *p) case TPACPI_FAN_RD_TPEC: /* all except 570, 600e/x, 770e, 770x */ - if ((rc = fan_get_status(&status)) < 0) + if ((rc = fan_get_status_safe(&status)) < 0) return rc; if (unlikely(tp_features.fan_ctrl_status_undef)) { @@ -3117,7 +3396,7 @@ static int fan_read(char *p) default: len += sprintf(p + len, " ( is 0-7, " - "auto, disengaged)\n"); + "auto, disengaged, full-speed)\n"); break; } } @@ -3140,12 +3419,13 @@ static int fan_write_cmd_level(const char *cmd, int *rc) if (strlencmp(cmd, "level auto") == 0) level = TP_EC_FAN_AUTO; - else if (strlencmp(cmd, "level disengaged") == 0) + else if ((strlencmp(cmd, "level disengaged") == 0) | + (strlencmp(cmd, "level full-speed") == 0)) level = TP_EC_FAN_FULLSPEED; else if (sscanf(cmd, "level %d", &level) != 1) return 0; - if ((*rc = fan_set_level(level)) == -ENXIO) + if ((*rc = fan_set_level_safe(level)) == -ENXIO) printk(IBM_ERR "level command accepted for unsupported " "access mode %d", fan_control_access_mode); diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index e833ff3caf3..2fe4d61cc27 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -349,6 +349,8 @@ enum { /* Fan control constants */ TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */ TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */ + + TPACPI_FAN_LAST_LEVEL = 0x100, /* Use cached last-seen fan level */ }; enum fan_status_access_mode { @@ -375,6 +377,7 @@ static enum fan_status_access_mode fan_status_access_mode; static enum fan_control_access_mode fan_control_access_mode; static enum fan_control_commands fan_control_commands; static u8 fan_control_initial_status; +static u8 fan_control_desired_level; static int fan_watchdog_maxinterval; struct mutex fan_mutex; @@ -384,10 +387,13 @@ static acpi_handle fans_handle, gfan_handle, sfan_handle; static int fan_init(struct ibm_init_struct *iibm); static void fan_exit(void); static int fan_get_status(u8 *status); +static int fan_get_status_safe(u8 *status); static int fan_get_speed(unsigned int *speed); +static void fan_update_desired_level(u8 status); static void fan_watchdog_fire(struct work_struct *ignored); static void fan_watchdog_reset(void); static int fan_set_level(int level); +static int fan_set_level_safe(int level); static int fan_set_enable(void); static int fan_set_disable(void); static int fan_set_speed(int speed); -- cgit v1.2.3-18-g5258