#include <linux/pci_ids.h>
-/****************************************************************************
- * Main driver
- */
-
-#define IBM_NAME "thinkpad"
-#define IBM_DESC "ThinkPad ACPI Extras"
-#define IBM_FILE IBM_NAME "_acpi"
-#define IBM_URL "http://ibm-acpi.sf.net/"
-#define IBM_MAIL "ibm-acpi-devel@lists.sourceforge.net"
-
-#define IBM_PROC_DIR "ibm"
-#define IBM_ACPI_EVENT_PREFIX "ibm"
-#define IBM_DRVR_NAME IBM_FILE
-#define IBM_HWMON_DRVR_NAME IBM_NAME "_hwmon"
-
-#define IBM_LOG IBM_FILE ": "
-#define IBM_ERR KERN_ERR IBM_LOG
-#define IBM_NOTICE KERN_NOTICE IBM_LOG
-#define IBM_INFO KERN_INFO IBM_LOG
-#define IBM_DEBUG KERN_DEBUG IBM_LOG
-
-#define IBM_MAX_ACPI_ARGS 3
/* ThinkPad CMOS commands */
#define TP_CMOS_VOLUME_DOWN 0
TP_NVRAM_POS_LEVEL_VOLUME = 0,
};
-#define onoff(status,bit) ((status) & (1 << (bit)) ? "on" : "off")
-#define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
-#define strlencmp(a,b) (strncmp((a), (b), strlen(b)))
+/* ACPI HIDs */
+#define IBM_HKEY_HID "IBM0068"
+
+/* Input IDs */
+#define TPACPI_HKEY_INPUT_PRODUCT 0x5054 /* "TP" */
+#define TPACPI_HKEY_INPUT_VERSION 0x4101
+
+
+/****************************************************************************
+ * Main driver
+ */
+
+/* Module */
+#define IBM_NAME "thinkpad"
+#define IBM_DESC "ThinkPad ACPI Extras"
+#define IBM_FILE IBM_NAME "_acpi"
+#define IBM_URL "http://ibm-acpi.sf.net/"
+#define IBM_MAIL "ibm-acpi-devel@lists.sourceforge.net"
+
+#define IBM_PROC_DIR "ibm"
+#define IBM_ACPI_EVENT_PREFIX "ibm"
+#define IBM_DRVR_NAME IBM_FILE
+#define IBM_HWMON_DRVR_NAME IBM_NAME "_hwmon"
+
+#define IBM_MAX_ACPI_ARGS 3
+
+MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh");
+MODULE_DESCRIPTION(IBM_DESC);
+MODULE_VERSION(IBM_VERSION);
+MODULE_LICENSE("GPL");
+
+/* Please remove this in year 2009 */
+MODULE_ALIAS("ibm_acpi");
+
+/*
+ * DMI matching for module autoloading
+ *
+ * See http://thinkwiki.org/wiki/List_of_DMI_IDs
+ * See http://thinkwiki.org/wiki/BIOS_Upgrade_Downloads
+ *
+ * Only models listed in thinkwiki will be supported, so add yours
+ * if it is not there yet.
+ */
+#define IBM_BIOS_MODULE_ALIAS(__type) \
+ MODULE_ALIAS("dmi:bvnIBM:bvr" __type "ET??WW")
+
+/* Non-ancient thinkpads */
+MODULE_ALIAS("dmi:bvnIBM:*:svnIBM:*:pvrThinkPad*:rvnIBM:*");
+MODULE_ALIAS("dmi:bvnLENOVO:*:svnLENOVO:*:pvrThinkPad*:rvnLENOVO:*");
+
+/* Ancient thinkpad BIOSes have to be identified by
+ * BIOS type or model number, and there are far less
+ * BIOS types than model numbers... */
+IBM_BIOS_MODULE_ALIAS("I[B,D,H,I,M,N,O,T,W,V,Y,Z]");
+IBM_BIOS_MODULE_ALIAS("1[0,3,6,8,A-G,I,K,M-P,S,T]");
+IBM_BIOS_MODULE_ALIAS("K[U,X-Z]");
/* Debugging */
+#define IBM_LOG IBM_FILE ": "
+#define IBM_ERR KERN_ERR IBM_LOG
+#define IBM_NOTICE KERN_NOTICE IBM_LOG
+#define IBM_INFO KERN_INFO IBM_LOG
+#define IBM_DEBUG KERN_DEBUG IBM_LOG
+
#define TPACPI_DBG_ALL 0xffff
#define TPACPI_DBG_ALL 0xffff
#define TPACPI_DBG_INIT 0x0001
#define vdbg_printk(a_dbg_level, format, arg...)
#endif
-/* Input IDs */
-#define TPACPI_HKEY_INPUT_VENDOR PCI_VENDOR_ID_IBM
-#define TPACPI_HKEY_INPUT_PRODUCT 0x5054 /* "TP" */
-#define TPACPI_HKEY_INPUT_VERSION 0x4101
-
-/* ACPI HIDs */
-#define IBM_HKEY_HID "IBM0068"
-
-/* sysfs support */
-struct attribute_set {
- unsigned int members, max_members;
- struct attribute_group group;
-};
-
-/* Helpers */
-static int parse_strtoul(const char *buf, unsigned long max,
- unsigned long *value);
-
-/* Module */
-static int experimental;
-static u32 dbg_level;
-static int force_load;
-static unsigned int hotkey_report_mode;
+#define onoff(status,bit) ((status) & (1 << (bit)) ? "on" : "off")
+#define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
+#define strlencmp(a,b) (strncmp((a), (b), strlen(b)))
/****************************************************************************
- * Subdrivers
+ * Driver-wide structs and misc. variables
*/
struct ibm_struct;
};
static struct thinkpad_id_data thinkpad_id;
-static LIST_HEAD(tpacpi_all_drivers);
-
-MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh");
-MODULE_DESCRIPTION(IBM_DESC);
-MODULE_VERSION(IBM_VERSION);
-MODULE_LICENSE("GPL");
-
-/* Please remove this in year 2009 */
-MODULE_ALIAS("ibm_acpi");
-
-/*
- * DMI matching for module autoloading
- *
- * See http://thinkwiki.org/wiki/List_of_DMI_IDs
- * See http://thinkwiki.org/wiki/BIOS_Upgrade_Downloads
- *
- * Only models listed in thinkwiki will be supported, so add yours
- * if it is not there yet.
- */
-#define IBM_BIOS_MODULE_ALIAS(__type) \
- MODULE_ALIAS("dmi:bvnIBM:bvr" __type "ET??WW")
-
-/* Non-ancient thinkpads */
-MODULE_ALIAS("dmi:bvnIBM:*:svnIBM:*:pvrThinkPad*:rvnIBM:*");
-MODULE_ALIAS("dmi:bvnLENOVO:*:svnLENOVO:*:pvrThinkPad*:rvnLENOVO:*");
-
-/* Ancient thinkpad BIOSes have to be identified by
- * BIOS type or model number, and there are far less
- * BIOS types than model numbers... */
-IBM_BIOS_MODULE_ALIAS("I[B,D,H,I,M,N,O,T,W,V,Y,Z]");
-IBM_BIOS_MODULE_ALIAS("1[0,3,6,8,A-G,I,K,M-P,S,T]");
-IBM_BIOS_MODULE_ALIAS("K[U,X-Z]");
-
#define __unused __attribute__ ((unused))
static enum {
TPACPI_LIFE_EXITING,
} tpacpi_lifecycle;
+static int experimental;
+static u32 dbg_level;
+
/****************************************************************************
****************************************************************************
*
IBM_HANDLE(ecrd, ec, "ECRD"); /* 570 */
IBM_HANDLE(ecwr, ec, "ECWR"); /* 570 */
-
-/*************************************************************************
- * Misc ACPI handles
- */
-
IBM_HANDLE(cmos, root, "\\UCMS", /* R50, R50e, R50p, R51, T4x, X31, X40 */
"\\CMOS", /* A3x, G4x, R32, T23, T30, X22-24, X30 */
"\\CMS", /* R40, R40e */
static struct device *tpacpi_hwmon;
static struct input_dev *tpacpi_inputdev;
static struct mutex tpacpi_inputdev_send_mutex;
-
+static LIST_HEAD(tpacpi_all_drivers);
static int tpacpi_resume_handler(struct platform_device *pdev)
{
};
/*************************************************************************
- * thinkpad-acpi driver attributes
+ * sysfs support helpers
*/
-/* interface_version --------------------------------------------------- */
-static ssize_t tpacpi_driver_interface_version_show(
- struct device_driver *drv,
- char *buf)
-{
- return snprintf(buf, PAGE_SIZE, "0x%08x\n", TPACPI_SYSFS_VERSION);
-}
-
-static DRIVER_ATTR(interface_version, S_IRUGO,
- tpacpi_driver_interface_version_show, NULL);
-
-/* debug_level --------------------------------------------------------- */
-static ssize_t tpacpi_driver_debug_show(struct device_driver *drv,
- char *buf)
-{
- return snprintf(buf, PAGE_SIZE, "0x%04x\n", dbg_level);
-}
-
-static ssize_t tpacpi_driver_debug_store(struct device_driver *drv,
- const char *buf, size_t count)
-{
- unsigned long t;
-
- if (parse_strtoul(buf, 0xffff, &t))
- return -EINVAL;
-
- dbg_level = t;
-
- return count;
-}
-
-static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO,
- tpacpi_driver_debug_show, tpacpi_driver_debug_store);
-
-/* version ------------------------------------------------------------- */
-static ssize_t tpacpi_driver_version_show(struct device_driver *drv,
- char *buf)
-{
- return snprintf(buf, PAGE_SIZE, "%s v%s\n", IBM_DESC, IBM_VERSION);
-}
-
-static DRIVER_ATTR(version, S_IRUGO,
- tpacpi_driver_version_show, NULL);
-
-/* --------------------------------------------------------------------- */
-
-static struct driver_attribute* tpacpi_driver_attributes[] = {
- &driver_attr_debug_level, &driver_attr_version,
- &driver_attr_interface_version,
+struct attribute_set {
+ unsigned int members, max_members;
+ struct attribute_group group;
};
-static int __init tpacpi_create_driver_attributes(struct device_driver *drv)
-{
- int i, res;
-
- i = 0;
- res = 0;
- while (!res && i < ARRAY_SIZE(tpacpi_driver_attributes)) {
- res = driver_create_file(drv, tpacpi_driver_attributes[i]);
- i++;
- }
-
- return res;
-}
-
-static void tpacpi_remove_driver_attributes(struct device_driver *drv)
-{
- int i;
-
- for(i = 0; i < ARRAY_SIZE(tpacpi_driver_attributes); i++)
- driver_remove_file(drv, tpacpi_driver_attributes[i]);
-}
-
-/*************************************************************************
- * sysfs support helpers
- */
-
struct attribute_set_obj {
struct attribute_set s;
struct attribute *a;
return 0;
}
-/****************************************************************************
- ****************************************************************************
- *
- * Subdrivers
- *
- ****************************************************************************
- ****************************************************************************/
-
/*************************************************************************
- * thinkpad-acpi init subdriver
+ * thinkpad-acpi driver attributes
*/
-static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
+/* interface_version --------------------------------------------------- */
+static ssize_t tpacpi_driver_interface_version_show(
+ struct device_driver *drv,
+ char *buf)
{
- printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION);
- printk(IBM_INFO "%s\n", IBM_URL);
-
- printk(IBM_INFO "ThinkPad BIOS %s, EC %s\n",
- (thinkpad_id.bios_version_str) ?
- thinkpad_id.bios_version_str : "unknown",
- (thinkpad_id.ec_version_str) ?
- thinkpad_id.ec_version_str : "unknown");
+ return snprintf(buf, PAGE_SIZE, "0x%08x\n", TPACPI_SYSFS_VERSION);
+}
+
+static DRIVER_ATTR(interface_version, S_IRUGO,
+ tpacpi_driver_interface_version_show, NULL);
+
+/* debug_level --------------------------------------------------------- */
+static ssize_t tpacpi_driver_debug_show(struct device_driver *drv,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "0x%04x\n", dbg_level);
+}
+
+static ssize_t tpacpi_driver_debug_store(struct device_driver *drv,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+
+ if (parse_strtoul(buf, 0xffff, &t))
+ return -EINVAL;
+
+ dbg_level = t;
+
+ return count;
+}
+
+static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO,
+ tpacpi_driver_debug_show, tpacpi_driver_debug_store);
+
+/* version ------------------------------------------------------------- */
+static ssize_t tpacpi_driver_version_show(struct device_driver *drv,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%s v%s\n", IBM_DESC, IBM_VERSION);
+}
+
+static DRIVER_ATTR(version, S_IRUGO,
+ tpacpi_driver_version_show, NULL);
+
+/* --------------------------------------------------------------------- */
+
+static struct driver_attribute* tpacpi_driver_attributes[] = {
+ &driver_attr_debug_level, &driver_attr_version,
+ &driver_attr_interface_version,
+};
+
+static int __init tpacpi_create_driver_attributes(struct device_driver *drv)
+{
+ int i, res;
+
+ i = 0;
+ res = 0;
+ while (!res && i < ARRAY_SIZE(tpacpi_driver_attributes)) {
+ res = driver_create_file(drv, tpacpi_driver_attributes[i]);
+ i++;
+ }
+
+ return res;
+}
+
+static void tpacpi_remove_driver_attributes(struct device_driver *drv)
+{
+ int i;
+
+ for(i = 0; i < ARRAY_SIZE(tpacpi_driver_attributes); i++)
+ driver_remove_file(drv, tpacpi_driver_attributes[i]);
+}
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Subdrivers
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+/*************************************************************************
+ * thinkpad-acpi init subdriver
+ */
+
+static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
+{
+ printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION);
+ printk(IBM_INFO "%s\n", IBM_URL);
+
+ printk(IBM_INFO "ThinkPad BIOS %s, EC %s\n",
+ (thinkpad_id.bios_version_str) ?
+ thinkpad_id.bios_version_str : "unknown",
+ (thinkpad_id.ec_version_str) ?
+ thinkpad_id.ec_version_str : "unknown");
if (thinkpad_id.vendor && thinkpad_id.model_str)
printk(IBM_INFO "%s %s\n",
static u32 hotkey_reserved_mask;
static u32 hotkey_mask;
+static unsigned int hotkey_report_mode;
+
static u16 *hotkey_keycode_map;
static struct attribute_set *hotkey_dev_attributes;
static enum thermal_access_mode thermal_read_mode;
-static int thermal_get_sensor(int idx, s32 *value);
-static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s);
+/* idx is zero-based */
+static int thermal_get_sensor(int idx, s32 *value)
+{
+ int t;
+ s8 tmp;
+ char tmpi[5];
+
+ t = TP_EC_THERMAL_TMP0;
+
+ switch (thermal_read_mode) {
+#if TPACPI_MAX_THERMAL_SENSORS >= 16
+ case TPACPI_THERMAL_TPEC_16:
+ if (idx >= 8 && idx <= 15) {
+ t = TP_EC_THERMAL_TMP8;
+ idx -= 8;
+ }
+ /* fallthrough */
+#endif
+ case TPACPI_THERMAL_TPEC_8:
+ if (idx <= 7) {
+ if (!acpi_ec_read(t + idx, &tmp))
+ return -EIO;
+ *value = tmp * 1000;
+ return 0;
+ }
+ break;
+
+ case TPACPI_THERMAL_ACPI_UPDT:
+ if (idx <= 7) {
+ snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
+ if (!acpi_evalf(ec_handle, NULL, "UPDT", "v"))
+ return -EIO;
+ if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
+ return -EIO;
+ *value = (t - 2732) * 100;
+ return 0;
+ }
+ break;
+
+ case TPACPI_THERMAL_ACPI_TMP07:
+ if (idx <= 7) {
+ snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
+ if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
+ return -EIO;
+ if (t > 127 || t < -127)
+ t = TP_EC_THERMAL_TMP_NA;
+ *value = t * 1000;
+ return 0;
+ }
+ break;
+
+ case TPACPI_THERMAL_NONE:
+ default:
+ return -ENOSYS;
+ }
+
+ return -EINVAL;
+}
+
+static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s)
+{
+ int res, i;
+ int n;
+
+ n = 8;
+ i = 0;
+
+ if (!s)
+ return -EINVAL;
+
+ if (thermal_read_mode == TPACPI_THERMAL_TPEC_16)
+ n = 16;
+
+ for(i = 0 ; i < n; i++) {
+ res = thermal_get_sensor(i, &s->temp[i]);
+ if (res)
+ return res;
+ }
+
+ return n;
+}
/* sysfs temp##_input -------------------------------------------------- */
}
}
-/* idx is zero-based */
-static int thermal_get_sensor(int idx, s32 *value)
-{
- int t;
- s8 tmp;
- char tmpi[5];
-
- t = TP_EC_THERMAL_TMP0;
-
- switch (thermal_read_mode) {
-#if TPACPI_MAX_THERMAL_SENSORS >= 16
- case TPACPI_THERMAL_TPEC_16:
- if (idx >= 8 && idx <= 15) {
- t = TP_EC_THERMAL_TMP8;
- idx -= 8;
- }
- /* fallthrough */
-#endif
- case TPACPI_THERMAL_TPEC_8:
- if (idx <= 7) {
- if (!acpi_ec_read(t + idx, &tmp))
- return -EIO;
- *value = tmp * 1000;
- return 0;
- }
- break;
-
- case TPACPI_THERMAL_ACPI_UPDT:
- if (idx <= 7) {
- snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
- if (!acpi_evalf(ec_handle, NULL, "UPDT", "v"))
- return -EIO;
- if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
- return -EIO;
- *value = (t - 2732) * 100;
- return 0;
- }
- break;
-
- case TPACPI_THERMAL_ACPI_TMP07:
- if (idx <= 7) {
- snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
- if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
- return -EIO;
- if (t > 127 || t < -127)
- t = TP_EC_THERMAL_TMP_NA;
- *value = t * 1000;
- return 0;
- }
- break;
-
- case TPACPI_THERMAL_NONE:
- default:
- return -ENOSYS;
- }
-
- return -EINVAL;
-}
-
-static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s)
-{
- int res, i;
- int n;
-
- n = 8;
- i = 0;
-
- if (!s)
- return -EINVAL;
-
- if (thermal_read_mode == TPACPI_THERMAL_TPEC_16)
- n = 16;
-
- for(i = 0 ; i < n; i++) {
- res = thermal_get_sensor(i, &s->temp[i]);
- if (res)
- return res;
- }
-
- return n;
-}
-
static int thermal_read(char *p)
{
int len = 0;
static int brightness_mode;
static unsigned int brightness_enable = 2; /* 2 = auto, 0 = no, 1 = yes */
-static int brightness_get(struct backlight_device *bd);
-static int brightness_set(int value);
-static int brightness_update_status(struct backlight_device *bd);
+static struct mutex brightness_mutex;
-static struct backlight_ops ibm_backlight_data = {
- .get_brightness = brightness_get,
+/*
+ * ThinkPads can read brightness from two places: EC 0x31, or
+ * CMOS NVRAM byte 0x5E, bits 0-3.
+ */
+static int brightness_get(struct backlight_device *bd)
+{
+ u8 lec = 0, lcmos = 0, level = 0;
+
+ if (brightness_mode & 1) {
+ if (!acpi_ec_read(brightness_offset, &lec))
+ return -EIO;
+ lec &= (tp_features.bright_16levels)? 0x0f : 0x07;
+ level = lec;
+ };
+ if (brightness_mode & 2) {
+ lcmos = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
+ & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
+ >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
+ lcmos &= (tp_features.bright_16levels)? 0x0f : 0x07;
+ level = lcmos;
+ }
+
+ if (brightness_mode == 3 && lec != lcmos) {
+ printk(IBM_ERR
+ "CMOS NVRAM (%u) and EC (%u) do not agree "
+ "on display brightness level\n",
+ (unsigned int) lcmos,
+ (unsigned int) lec);
+ return -EIO;
+ }
+
+ return level;
+}
+
+/* May return EINTR which can always be mapped to ERESTARTSYS */
+static int brightness_set(int value)
+{
+ int cmos_cmd, inc, i, res;
+ int current_value;
+
+ if (value > ((tp_features.bright_16levels)? 15 : 7))
+ return -EINVAL;
+
+ res = mutex_lock_interruptible(&brightness_mutex);
+ if (res < 0)
+ return res;
+
+ current_value = brightness_get(NULL);
+ if (current_value < 0) {
+ res = current_value;
+ goto errout;
+ }
+
+ cmos_cmd = value > current_value ?
+ TP_CMOS_BRIGHTNESS_UP :
+ TP_CMOS_BRIGHTNESS_DOWN;
+ inc = (value > current_value)? 1 : -1;
+
+ res = 0;
+ for (i = current_value; i != value; i += inc) {
+ if ((brightness_mode & 2) &&
+ issue_thinkpad_cmos_command(cmos_cmd)) {
+ res = -EIO;
+ goto errout;
+ }
+ if ((brightness_mode & 1) &&
+ !acpi_ec_write(brightness_offset, i + inc)) {
+ res = -EIO;
+ goto errout;;
+ }
+ }
+
+errout:
+ mutex_unlock(&brightness_mutex);
+ return res;
+}
+
+/* sysfs backlight class ----------------------------------------------- */
+
+static int brightness_update_status(struct backlight_device *bd)
+{
+ /* it is the backlight class's job (caller) to handle
+ * EINTR and other errors properly */
+ return brightness_set(
+ (bd->props.fb_blank == FB_BLANK_UNBLANK &&
+ bd->props.power == FB_BLANK_UNBLANK) ?
+ bd->props.brightness : 0);
+}
+
+static struct backlight_ops ibm_backlight_data = {
+ .get_brightness = brightness_get,
.update_status = brightness_update_status,
};
-static struct mutex brightness_mutex;
+/* --------------------------------------------------------------------- */
static int __init tpacpi_query_bcll_levels(acpi_handle handle)
{
}
}
-static int brightness_update_status(struct backlight_device *bd)
-{
- /* it is the backlight class's job (caller) to handle
- * EINTR and other errors properly */
- return brightness_set(
- (bd->props.fb_blank == FB_BLANK_UNBLANK &&
- bd->props.power == FB_BLANK_UNBLANK) ?
- bd->props.brightness : 0);
-}
-
-/*
- * ThinkPads can read brightness from two places: EC 0x31, or
- * CMOS NVRAM byte 0x5E, bits 0-3.
- */
-static int brightness_get(struct backlight_device *bd)
-{
- u8 lec = 0, lcmos = 0, level = 0;
-
- if (brightness_mode & 1) {
- if (!acpi_ec_read(brightness_offset, &lec))
- return -EIO;
- lec &= (tp_features.bright_16levels)? 0x0f : 0x07;
- level = lec;
- };
- if (brightness_mode & 2) {
- lcmos = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
- & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
- >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
- lcmos &= (tp_features.bright_16levels)? 0x0f : 0x07;
- level = lcmos;
- }
-
- if (brightness_mode == 3 && lec != lcmos) {
- printk(IBM_ERR
- "CMOS NVRAM (%u) and EC (%u) do not agree "
- "on display brightness level\n",
- (unsigned int) lcmos,
- (unsigned int) lec);
- return -EIO;
- }
-
- return level;
-}
-
-/* May return EINTR which can always be mapped to ERESTARTSYS */
-static int brightness_set(int value)
-{
- int cmos_cmd, inc, i, res;
- int current_value;
-
- if (value > ((tp_features.bright_16levels)? 15 : 7))
- return -EINVAL;
-
- res = mutex_lock_interruptible(&brightness_mutex);
- if (res < 0)
- return res;
-
- current_value = brightness_get(NULL);
- if (current_value < 0) {
- res = current_value;
- goto errout;
- }
-
- cmos_cmd = value > current_value ?
- TP_CMOS_BRIGHTNESS_UP :
- TP_CMOS_BRIGHTNESS_DOWN;
- inc = (value > current_value)? 1 : -1;
-
- res = 0;
- for (i = current_value; i != value; i += inc) {
- if ((brightness_mode & 2) &&
- issue_thinkpad_cmos_command(cmos_cmd)) {
- res = -EIO;
- goto errout;
- }
- if ((brightness_mode & 1) &&
- !acpi_ec_write(brightness_offset, i + inc)) {
- res = -EIO;
- goto errout;;
- }
- }
-
-errout:
- mutex_unlock(&brightness_mutex);
- return res;
-}
-
static int brightness_read(char *p)
{
int len = 0;
static struct mutex fan_mutex;
-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 DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire);
IBM_HANDLE(fans, ec, "FANS"); /* X31, X40, X41 */
); /* 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
+ * Call with fan_mutex held
*/
-
-/* sysfs fan pwm1_enable ----------------------------------------------- */
-static ssize_t fan_pwm1_enable_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+static void fan_update_desired_level(u8 status)
{
- int res, mode;
- 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;
+ }
+}
- res = fan_get_status_safe(&status);
- if (res)
- return res;
+static int fan_get_status(u8 *status)
+{
+ u8 s;
- 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;
- }
- }
+ /* TODO:
+ * Add TPACPI_FAN_RD_ACPI_FANS ? */
- if (status & TP_EC_FAN_FULLSPEED) {
- mode = 0;
- } else if (status & TP_EC_FAN_AUTO) {
- mode = 2;
- } else
- mode = 1;
+ switch (fan_status_access_mode) {
+ case TPACPI_FAN_RD_ACPI_GFAN:
+ /* 570, 600e/x, 770e, 770x */
- return snprintf(buf, PAGE_SIZE, "%d\n", mode);
-}
+ if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d")))
+ return -EIO;
-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 (likely(status))
+ *status = s & 0x07;
- if (parse_strtoul(buf, 2, &t))
- return -EINVAL;
+ break;
+
+ case TPACPI_FAN_RD_TPEC:
+ /* all except 570, 600e/x, 770e, 770x */
+ if (unlikely(!acpi_ec_read(fan_status_offset, &s)))
+ return -EIO;
+
+ if (likely(status))
+ *status = s;
- 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;
+ return -ENXIO;
}
- res = fan_set_level_safe(level);
- if (res == -ENXIO)
- return -EINVAL;
- else if (res < 0)
- return res;
+ return 0;
+}
- fan_watchdog_reset();
+static int fan_get_status_safe(u8 *status)
+{
+ int rc;
+ u8 s;
- return count;
-}
+ if (mutex_lock_interruptible(&fan_mutex))
+ return -ERESTARTSYS;
+ rc = fan_get_status(&s);
+ if (!rc)
+ fan_update_desired_level(s);
+ mutex_unlock(&fan_mutex);
-static struct device_attribute dev_attr_fan_pwm1_enable =
- __ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
- fan_pwm1_enable_show, fan_pwm1_enable_store);
+ if (status)
+ *status = s;
-/* sysfs fan pwm1 ------------------------------------------------------ */
-static ssize_t fan_pwm1_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+ return rc;
+}
+
+static int fan_get_speed(unsigned int *speed)
{
- int res;
- u8 status;
+ u8 hi, lo;
- res = fan_get_status_safe(&status);
- if (res)
- return res;
+ switch (fan_status_access_mode) {
+ case TPACPI_FAN_RD_TPEC:
+ /* all except 570, 600e/x, 770e, 770x */
+ if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) ||
+ !acpi_ec_read(fan_rpm_offset + 1, &hi)))
+ return -EIO;
- 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 (likely(speed))
+ *speed = (hi << 8) | lo;
- if ((status &
- (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) != 0)
- status = fan_control_desired_level;
+ break;
- if (status > 7)
- status = 7;
+ default:
+ return -ENXIO;
+ }
- return snprintf(buf, PAGE_SIZE, "%u\n", (status * 255) / 7);
+ return 0;
}
-static ssize_t fan_pwm1_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
+static int fan_set_level(int level)
{
- unsigned long s;
- int rc;
- u8 status, newlevel;
+ if (!fan_control_allowed)
+ return -EPERM;
- if (parse_strtoul(buf, 255, &s))
- return -EINVAL;
+ switch (fan_control_access_mode) {
+ case TPACPI_FAN_WR_ACPI_SFAN:
+ if (level >= 0 && level <= 7) {
+ if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level))
+ return -EIO;
+ } else
+ return -EINVAL;
+ break;
- /* scale down from 0-255 to 0-7 */
- newlevel = (s >> 5) & 0x07;
+ case TPACPI_FAN_WR_ACPI_FANS:
+ case TPACPI_FAN_WR_TPEC:
+ if ((level != TP_EC_FAN_AUTO) &&
+ (level != TP_EC_FAN_FULLSPEED) &&
+ ((level < 0) || (level > 7)))
+ return -EINVAL;
- if (mutex_lock_interruptible(&fan_mutex))
- return -ERESTARTSYS;
+ /* safety net should the EC not support AUTO
+ * or FULLSPEED mode bits and just ignore them */
+ if (level & TP_EC_FAN_FULLSPEED)
+ level |= 7; /* safety min speed 7 */
+ else if (level & TP_EC_FAN_FULLSPEED)
+ level |= 4; /* safety min speed 4 */
- rc = fan_get_status(&status);
- if (!rc && (status &
- (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
- rc = fan_set_level(newlevel);
- if (rc == -ENXIO)
- rc = -EINVAL;
- else if (!rc) {
- fan_update_desired_level(newlevel);
- fan_watchdog_reset();
- }
- }
+ if (!acpi_ec_write(fan_status_offset, level))
+ return -EIO;
+ else
+ tp_features.fan_ctrl_status_undef = 0;
+ break;
- mutex_unlock(&fan_mutex);
- return (rc)? rc : count;
+ default:
+ return -ENXIO;
+ }
+ return 0;
}
-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)
+static int fan_set_level_safe(int level)
{
- int res;
- unsigned int speed;
+ int rc;
- res = fan_get_speed(&speed);
- if (res < 0)
- return res;
+ if (!fan_control_allowed)
+ return -EPERM;
- return snprintf(buf, PAGE_SIZE, "%u\n", speed);
-}
+ if (mutex_lock_interruptible(&fan_mutex))
+ return -ERESTARTSYS;
-static struct device_attribute dev_attr_fan_fan1_input =
- __ATTR(fan1_input, S_IRUGO,
- fan_fan1_input_show, NULL);
+ if (level == TPACPI_FAN_LAST_LEVEL)
+ level = fan_control_desired_level;
-/* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */
-static ssize_t fan_fan_watchdog_show(struct device_driver *drv,
- char *buf)
-{
- return snprintf(buf, PAGE_SIZE, "%u\n", fan_watchdog_maxinterval);
+ rc = fan_set_level(level);
+ if (!rc)
+ fan_update_desired_level(level);
+
+ mutex_unlock(&fan_mutex);
+ return rc;
}
-static ssize_t fan_fan_watchdog_store(struct device_driver *drv,
- const char *buf, size_t count)
+static int fan_set_enable(void)
{
- unsigned long t;
-
- if (parse_strtoul(buf, 120, &t))
- return -EINVAL;
+ u8 s;
+ int rc;
if (!fan_control_allowed)
return -EPERM;
- fan_watchdog_maxinterval = t;
- fan_watchdog_reset();
+ if (mutex_lock_interruptible(&fan_mutex))
+ return -ERESTARTSYS;
- return count;
-}
+ switch (fan_control_access_mode) {
+ case TPACPI_FAN_WR_ACPI_FANS:
+ case TPACPI_FAN_WR_TPEC:
+ rc = fan_get_status(&s);
+ if (rc < 0)
+ break;
-static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO,
- fan_fan_watchdog_show, fan_fan_watchdog_store);
+ /* Don't go out of emergency fan mode */
+ if (s != 7) {
+ s &= 0x07;
+ s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */
+ }
-/* --------------------------------------------------------------------- */
-static struct attribute *fan_attributes[] = {
- &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr,
- &dev_attr_fan_fan1_input.attr,
- NULL
-};
+ if (!acpi_ec_write(fan_status_offset, s))
+ rc = -EIO;
+ else {
+ tp_features.fan_ctrl_status_undef = 0;
+ rc = 0;
+ }
+ break;
-static const struct attribute_group fan_attr_group = {
- .attrs = fan_attributes,
-};
+ case TPACPI_FAN_WR_ACPI_SFAN:
+ rc = fan_get_status(&s);
+ if (rc < 0)
+ break;
-static int __init fan_init(struct ibm_init_struct *iibm)
-{
- int rc;
+ s &= 0x07;
- vdbg_printk(TPACPI_DBG_INIT, "initializing fan subdriver\n");
+ /* Set fan to at least level 4 */
+ s |= 4;
- mutex_init(&fan_mutex);
- fan_status_access_mode = TPACPI_FAN_NONE;
- fan_control_access_mode = TPACPI_FAN_WR_NONE;
- fan_control_commands = 0;
- fan_watchdog_maxinterval = 0;
- tp_features.fan_ctrl_status_undef = 0;
- fan_control_desired_level = 7;
+ if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s))
+ rc= -EIO;
+ else
+ rc = 0;
+ break;
- IBM_ACPIHANDLE_INIT(fans);
- IBM_ACPIHANDLE_INIT(gfan);
- IBM_ACPIHANDLE_INIT(sfan);
+ default:
+ rc = -ENXIO;
+ }
- if (gfan_handle) {
- /* 570, 600e/x, 770e, 770x */
- fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN;
- } else {
- /* all other ThinkPads: note that even old-style
- * ThinkPad ECs supports the fan control register */
- if (likely(acpi_ec_read(fan_status_offset,
- &fan_control_initial_status))) {
- fan_status_access_mode = TPACPI_FAN_RD_TPEC;
-
- /* In some ThinkPads, neither the EC nor the ACPI
- * DSDT initialize the fan status, and it ends up
- * being set to 0x07 when it *could* be either
- * 0x07 or 0x80.
- *
- * Enable for TP-1Y (T43), TP-78 (R51e),
- * TP-76 (R52), TP-70 (T43, R52), which are known
- * to be buggy. */
- if (fan_control_initial_status == 0x07) {
- switch (thinkpad_id.ec_model) {
- case 0x5931: /* TP-1Y */
- case 0x3837: /* TP-78 */
- case 0x3637: /* TP-76 */
- case 0x3037: /* TP-70 */
- printk(IBM_NOTICE
- "fan_init: initial fan status is "
- "unknown, assuming it is in auto "
- "mode\n");
- tp_features.fan_ctrl_status_undef = 1;
- ;;
- }
- }
- } else {
- printk(IBM_ERR
- "ThinkPad ACPI EC access misbehaving, "
- "fan status and control unavailable\n");
- return 1;
- }
- }
-
- if (sfan_handle) {
- /* 570, 770x-JL */
- fan_control_access_mode = TPACPI_FAN_WR_ACPI_SFAN;
- fan_control_commands |=
- TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_ENABLE;
- } else {
- if (!gfan_handle) {
- /* gfan without sfan means no fan control */
- /* all other models implement TP EC 0x2f control */
-
- if (fans_handle) {
- /* X31, X40, X41 */
- fan_control_access_mode =
- TPACPI_FAN_WR_ACPI_FANS;
- fan_control_commands |=
- TPACPI_FAN_CMD_SPEED |
- TPACPI_FAN_CMD_LEVEL |
- TPACPI_FAN_CMD_ENABLE;
- } else {
- fan_control_access_mode = TPACPI_FAN_WR_TPEC;
- fan_control_commands |=
- TPACPI_FAN_CMD_LEVEL |
- TPACPI_FAN_CMD_ENABLE;
- }
- }
- }
-
- vdbg_printk(TPACPI_DBG_INIT, "fan is %s, modes %d, %d\n",
- str_supported(fan_status_access_mode != TPACPI_FAN_NONE ||
- fan_control_access_mode != TPACPI_FAN_WR_NONE),
- fan_status_access_mode, fan_control_access_mode);
-
- /* fan control master switch */
- if (!fan_control_allowed) {
- fan_control_access_mode = TPACPI_FAN_WR_NONE;
- fan_control_commands = 0;
- dbg_printk(TPACPI_DBG_INIT,
- "fan control features disabled by parameter\n");
- }
-
- /* 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_sensors_pdev->dev.kobj,
- &fan_attr_group);
- if (!(rc < 0))
- rc = driver_create_file(&tpacpi_hwmon_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;
- }
+ mutex_unlock(&fan_mutex);
+ return rc;
}
-static int fan_get_status(u8 *status)
+static int fan_set_disable(void)
{
- u8 s;
-
- /* TODO:
- * Add TPACPI_FAN_RD_ACPI_FANS ? */
-
- switch (fan_status_access_mode) {
- case TPACPI_FAN_RD_ACPI_GFAN:
- /* 570, 600e/x, 770e, 770x */
+ int rc;
- if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d")))
- return -EIO;
+ if (!fan_control_allowed)
+ return -EPERM;
- if (likely(status))
- *status = s & 0x07;
+ if (mutex_lock_interruptible(&fan_mutex))
+ return -ERESTARTSYS;
+ rc = 0;
+ switch (fan_control_access_mode) {
+ case TPACPI_FAN_WR_ACPI_FANS:
+ case TPACPI_FAN_WR_TPEC:
+ if (!acpi_ec_write(fan_status_offset, 0x00))
+ rc = -EIO;
+ else {
+ fan_control_desired_level = 0;
+ tp_features.fan_ctrl_status_undef = 0;
+ }
break;
- case TPACPI_FAN_RD_TPEC:
- /* all except 570, 600e/x, 770e, 770x */
- if (unlikely(!acpi_ec_read(fan_status_offset, &s)))
- return -EIO;
-
- if (likely(status))
- *status = s;
-
+ 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:
- return -ENXIO;
+ rc = -ENXIO;
}
- return 0;
-}
-
-static int fan_get_status_safe(u8 *status)
-{
- int rc;
- u8 s;
- if (mutex_lock_interruptible(&fan_mutex))
- return -ERESTARTSYS;
- 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_sensors_pdev->dev.kobj, &fan_attr_group);
- driver_remove_file(&tpacpi_hwmon_pdriver.driver, &driver_attr_fan_watchdog);
-
- cancel_delayed_work(&fan_watchdog_task);
- flush_scheduled_work();
-}
-
-static int fan_get_speed(unsigned int *speed)
+static int fan_set_speed(int speed)
{
- u8 hi, lo;
+ int rc;
- switch (fan_status_access_mode) {
- case TPACPI_FAN_RD_TPEC:
- /* all except 570, 600e/x, 770e, 770x */
- if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) ||
- !acpi_ec_read(fan_rpm_offset + 1, &hi)))
- return -EIO;
+ if (!fan_control_allowed)
+ return -EPERM;
- if (likely(speed))
- *speed = (hi << 8) | lo;
+ if (mutex_lock_interruptible(&fan_mutex))
+ return -ERESTARTSYS;
+ rc = 0;
+ switch (fan_control_access_mode) {
+ case TPACPI_FAN_WR_ACPI_FANS:
+ if (speed >= 0 && speed <= 65535) {
+ if (!acpi_evalf(fans_handle, NULL, NULL, "vddd",
+ speed, speed, speed))
+ rc = -EIO;
+ } else
+ rc = -EINVAL;
break;
default:
- return -ENXIO;
+ rc = -ENXIO;
}
- return 0;
-}
-
-static void fan_watchdog_fire(struct work_struct *ignored)
-{
- int rc;
-
- if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
- return;
-
- printk(IBM_NOTICE "fan watchdog: enabling fan\n");
- rc = fan_set_enable();
- if (rc < 0) {
- printk(IBM_ERR "fan watchdog: error %d while enabling fan, "
- "will try again later...\n", -rc);
- /* reschedule for later */
- fan_watchdog_reset();
- }
+ mutex_unlock(&fan_mutex);
+ return rc;
}
static void fan_watchdog_reset(void)
fan_watchdog_active = 0;
}
-static int fan_set_level(int level)
+static void fan_watchdog_fire(struct work_struct *ignored)
{
- if (!fan_control_allowed)
- return -EPERM;
-
- switch (fan_control_access_mode) {
- case TPACPI_FAN_WR_ACPI_SFAN:
- if (level >= 0 && level <= 7) {
- if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level))
- return -EIO;
- } else
- return -EINVAL;
- break;
+ int rc;
- case TPACPI_FAN_WR_ACPI_FANS:
- case TPACPI_FAN_WR_TPEC:
- if ((level != TP_EC_FAN_AUTO) &&
- (level != TP_EC_FAN_FULLSPEED) &&
- ((level < 0) || (level > 7)))
- return -EINVAL;
+ if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
+ return;
- /* safety net should the EC not support AUTO
- * or FULLSPEED mode bits and just ignore them */
- if (level & TP_EC_FAN_FULLSPEED)
- level |= 7; /* safety min speed 7 */
- else if (level & TP_EC_FAN_FULLSPEED)
- level |= 4; /* safety min speed 4 */
+ printk(IBM_NOTICE "fan watchdog: enabling fan\n");
+ rc = fan_set_enable();
+ if (rc < 0) {
+ printk(IBM_ERR "fan watchdog: error %d while enabling fan, "
+ "will try again later...\n", -rc);
+ /* reschedule for later */
+ fan_watchdog_reset();
+ }
+}
- if (!acpi_ec_write(fan_status_offset, level))
- return -EIO;
- else
+/*
+ * 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;
- break;
+ } 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 -ENXIO;
+ return -EINVAL;
}
- return 0;
+
+ res = fan_set_level_safe(level);
+ if (res == -ENXIO)
+ return -EINVAL;
+ else if (res < 0)
+ return res;
+
+ fan_watchdog_reset();
+
+ return count;
}
-static int fan_set_level_safe(int level)
+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 rc;
+ int res;
+ u8 status;
- if (!fan_control_allowed)
- return -EPERM;
+ res = fan_get_status_safe(&status);
+ if (res)
+ return res;
- if (mutex_lock_interruptible(&fan_mutex))
- return -ERESTARTSYS;
+ 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 (level == TPACPI_FAN_LAST_LEVEL)
- level = fan_control_desired_level;
+ if ((status &
+ (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) != 0)
+ status = fan_control_desired_level;
- rc = fan_set_level(level);
- if (!rc)
- fan_update_desired_level(level);
+ if (status > 7)
+ status = 7;
- mutex_unlock(&fan_mutex);
- return rc;
+ return snprintf(buf, PAGE_SIZE, "%u\n", (status * 255) / 7);
}
-static int fan_set_enable(void)
+static ssize_t fan_pwm1_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
{
- u8 s;
+ unsigned long s;
int rc;
+ u8 status, newlevel;
- if (!fan_control_allowed)
- return -EPERM;
+ if (parse_strtoul(buf, 255, &s))
+ return -EINVAL;
+
+ /* scale down from 0-255 to 0-7 */
+ newlevel = (s >> 5) & 0x07;
if (mutex_lock_interruptible(&fan_mutex))
return -ERESTARTSYS;
- switch (fan_control_access_mode) {
- case TPACPI_FAN_WR_ACPI_FANS:
- case TPACPI_FAN_WR_TPEC:
- rc = fan_get_status(&s);
- if (rc < 0)
- break;
-
- /* Don't go out of emergency fan mode */
- if (s != 7) {
- s &= 0x07;
- s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */
+ rc = fan_get_status(&status);
+ if (!rc && (status &
+ (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
+ rc = fan_set_level(newlevel);
+ if (rc == -ENXIO)
+ rc = -EINVAL;
+ else if (!rc) {
+ fan_update_desired_level(newlevel);
+ fan_watchdog_reset();
}
+ }
- if (!acpi_ec_write(fan_status_offset, s))
- rc = -EIO;
- else {
- tp_features.fan_ctrl_status_undef = 0;
- rc = 0;
- }
- break;
+ mutex_unlock(&fan_mutex);
+ return (rc)? rc : count;
+}
- case TPACPI_FAN_WR_ACPI_SFAN:
- rc = fan_get_status(&s);
- if (rc < 0)
- break;
+static struct device_attribute dev_attr_fan_pwm1 =
+ __ATTR(pwm1, S_IWUSR | S_IRUGO,
+ fan_pwm1_show, fan_pwm1_store);
- s &= 0x07;
+/* 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;
- /* Set fan to at least level 4 */
- s |= 4;
+ res = fan_get_speed(&speed);
+ if (res < 0)
+ return res;
- if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s))
- rc= -EIO;
- else
- rc = 0;
- break;
+ return snprintf(buf, PAGE_SIZE, "%u\n", speed);
+}
- default:
- rc = -ENXIO;
- }
+static struct device_attribute dev_attr_fan_fan1_input =
+ __ATTR(fan1_input, S_IRUGO,
+ fan_fan1_input_show, NULL);
- mutex_unlock(&fan_mutex);
- return rc;
+/* sysfs fan fan_watchdog (hwmon 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 int fan_set_disable(void)
+static ssize_t fan_fan_watchdog_store(struct device_driver *drv,
+ const char *buf, size_t count)
{
- int rc;
+ unsigned long t;
+
+ if (parse_strtoul(buf, 120, &t))
+ return -EINVAL;
if (!fan_control_allowed)
return -EPERM;
- if (mutex_lock_interruptible(&fan_mutex))
- return -ERESTARTSYS;
+ fan_watchdog_maxinterval = t;
+ fan_watchdog_reset();
- rc = 0;
- switch (fan_control_access_mode) {
- case TPACPI_FAN_WR_ACPI_FANS:
- case TPACPI_FAN_WR_TPEC:
- if (!acpi_ec_write(fan_status_offset, 0x00))
- rc = -EIO;
- else {
- fan_control_desired_level = 0;
- tp_features.fan_ctrl_status_undef = 0;
+ 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);
+ fan_status_access_mode = TPACPI_FAN_NONE;
+ fan_control_access_mode = TPACPI_FAN_WR_NONE;
+ 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);
+ IBM_ACPIHANDLE_INIT(sfan);
+
+ if (gfan_handle) {
+ /* 570, 600e/x, 770e, 770x */
+ fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN;
+ } else {
+ /* all other ThinkPads: note that even old-style
+ * ThinkPad ECs supports the fan control register */
+ if (likely(acpi_ec_read(fan_status_offset,
+ &fan_control_initial_status))) {
+ fan_status_access_mode = TPACPI_FAN_RD_TPEC;
+
+ /* In some ThinkPads, neither the EC nor the ACPI
+ * DSDT initialize the fan status, and it ends up
+ * being set to 0x07 when it *could* be either
+ * 0x07 or 0x80.
+ *
+ * Enable for TP-1Y (T43), TP-78 (R51e),
+ * TP-76 (R52), TP-70 (T43, R52), which are known
+ * to be buggy. */
+ if (fan_control_initial_status == 0x07) {
+ switch (thinkpad_id.ec_model) {
+ case 0x5931: /* TP-1Y */
+ case 0x3837: /* TP-78 */
+ case 0x3637: /* TP-76 */
+ case 0x3037: /* TP-70 */
+ printk(IBM_NOTICE
+ "fan_init: initial fan status is "
+ "unknown, assuming it is in auto "
+ "mode\n");
+ tp_features.fan_ctrl_status_undef = 1;
+ ;;
+ }
+ }
+ } else {
+ printk(IBM_ERR
+ "ThinkPad ACPI EC access misbehaving, "
+ "fan status and control unavailable\n");
+ return 1;
+ }
+ }
+
+ if (sfan_handle) {
+ /* 570, 770x-JL */
+ fan_control_access_mode = TPACPI_FAN_WR_ACPI_SFAN;
+ fan_control_commands |=
+ TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_ENABLE;
+ } else {
+ if (!gfan_handle) {
+ /* gfan without sfan means no fan control */
+ /* all other models implement TP EC 0x2f control */
+
+ if (fans_handle) {
+ /* X31, X40, X41 */
+ fan_control_access_mode =
+ TPACPI_FAN_WR_ACPI_FANS;
+ fan_control_commands |=
+ TPACPI_FAN_CMD_SPEED |
+ TPACPI_FAN_CMD_LEVEL |
+ TPACPI_FAN_CMD_ENABLE;
+ } else {
+ fan_control_access_mode = TPACPI_FAN_WR_TPEC;
+ fan_control_commands |=
+ TPACPI_FAN_CMD_LEVEL |
+ TPACPI_FAN_CMD_ENABLE;
+ }
}
- 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;
+ vdbg_printk(TPACPI_DBG_INIT, "fan is %s, modes %d, %d\n",
+ str_supported(fan_status_access_mode != TPACPI_FAN_NONE ||
+ fan_control_access_mode != TPACPI_FAN_WR_NONE),
+ fan_status_access_mode, fan_control_access_mode);
- default:
- rc = -ENXIO;
+ /* fan control master switch */
+ if (!fan_control_allowed) {
+ fan_control_access_mode = TPACPI_FAN_WR_NONE;
+ fan_control_commands = 0;
+ dbg_printk(TPACPI_DBG_INIT,
+ "fan control features disabled by parameter\n");
}
+ /* update fan_control_desired_level */
+ if (fan_status_access_mode != TPACPI_FAN_NONE)
+ fan_get_status_safe(NULL);
- mutex_unlock(&fan_mutex);
- return rc;
+ if (fan_status_access_mode != TPACPI_FAN_NONE ||
+ fan_control_access_mode != TPACPI_FAN_WR_NONE) {
+ rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
+ &fan_attr_group);
+ if (!(rc < 0))
+ rc = driver_create_file(&tpacpi_hwmon_pdriver.driver,
+ &driver_attr_fan_watchdog);
+ if (rc < 0)
+ return rc;
+ return 0;
+ } else
+ return 1;
}
-static int fan_set_speed(int speed)
+static void fan_exit(void)
{
- int rc;
-
- if (!fan_control_allowed)
- return -EPERM;
-
- if (mutex_lock_interruptible(&fan_mutex))
- return -ERESTARTSYS;
-
- rc = 0;
- switch (fan_control_access_mode) {
- case TPACPI_FAN_WR_ACPI_FANS:
- if (speed >= 0 && speed <= 65535) {
- if (!acpi_evalf(fans_handle, NULL, NULL, "vddd",
- speed, speed, speed))
- rc = -EIO;
- } else
- rc = -EINVAL;
- break;
+ vdbg_printk(TPACPI_DBG_EXIT, "cancelling any pending fan watchdog tasks\n");
- default:
- rc = -ENXIO;
- }
+ /* FIXME: can we really do this unconditionally? */
+ sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj, &fan_attr_group);
+ driver_remove_file(&tpacpi_hwmon_pdriver.driver, &driver_attr_fan_watchdog);
- mutex_unlock(&fan_mutex);
- return rc;
+ cancel_delayed_work(&fan_watchdog_task);
+ flush_scheduled_work();
}
static int fan_read(char *p)
/* /proc support */
static struct proc_dir_entry *proc_dir;
-
/*
* Module and infrastructure proble, init and exit handling
*/
+static int force_load;
+
#ifdef CONFIG_THINKPAD_ACPI_DEBUG
static const char * __init str_supported(int is_supported)
{
}
#endif /* CONFIG_THINKPAD_ACPI_DEBUG */
-static void ibm_exit(struct ibm_struct *ibm);
+static void ibm_exit(struct ibm_struct *ibm)
+{
+ dbg_printk(TPACPI_DBG_EXIT, "removing %s\n", ibm->name);
+
+ list_del_init(&ibm->all_drivers);
+
+ if (ibm->flags.acpi_notify_installed) {
+ dbg_printk(TPACPI_DBG_EXIT,
+ "%s: acpi_remove_notify_handler\n", ibm->name);
+ BUG_ON(!ibm->acpi);
+ acpi_remove_notify_handler(*ibm->acpi->handle,
+ ibm->acpi->type,
+ dispatch_acpi_notify);
+ ibm->flags.acpi_notify_installed = 0;
+ ibm->flags.acpi_notify_installed = 0;
+ }
+
+ if (ibm->flags.proc_created) {
+ dbg_printk(TPACPI_DBG_EXIT,
+ "%s: remove_proc_entry\n", ibm->name);
+ remove_proc_entry(ibm->name, proc_dir);
+ ibm->flags.proc_created = 0;
+ }
+
+ if (ibm->flags.acpi_driver_registered) {
+ dbg_printk(TPACPI_DBG_EXIT,
+ "%s: acpi_bus_unregister_driver\n", ibm->name);
+ BUG_ON(!ibm->acpi);
+ acpi_bus_unregister_driver(ibm->acpi->driver);
+ kfree(ibm->acpi->driver);
+ ibm->acpi->driver = NULL;
+ ibm->flags.acpi_driver_registered = 0;
+ }
+
+ if (ibm->flags.init_called && ibm->exit) {
+ ibm->exit();
+ ibm->flags.init_called = 0;
+ }
+
+ dbg_printk(TPACPI_DBG_INIT, "finished removing %s\n", ibm->name);
+}
static int __init ibm_init(struct ibm_init_struct *iibm)
{
return (ret < 0)? ret : 0;
}
-static void ibm_exit(struct ibm_struct *ibm)
-{
- dbg_printk(TPACPI_DBG_EXIT, "removing %s\n", ibm->name);
-
- list_del_init(&ibm->all_drivers);
-
- if (ibm->flags.acpi_notify_installed) {
- dbg_printk(TPACPI_DBG_EXIT,
- "%s: acpi_remove_notify_handler\n", ibm->name);
- BUG_ON(!ibm->acpi);
- acpi_remove_notify_handler(*ibm->acpi->handle,
- ibm->acpi->type,
- dispatch_acpi_notify);
- ibm->flags.acpi_notify_installed = 0;
- ibm->flags.acpi_notify_installed = 0;
- }
-
- if (ibm->flags.proc_created) {
- dbg_printk(TPACPI_DBG_EXIT,
- "%s: remove_proc_entry\n", ibm->name);
- remove_proc_entry(ibm->name, proc_dir);
- ibm->flags.proc_created = 0;
- }
-
- if (ibm->flags.acpi_driver_registered) {
- dbg_printk(TPACPI_DBG_EXIT,
- "%s: acpi_bus_unregister_driver\n", ibm->name);
- BUG_ON(!ibm->acpi);
- acpi_bus_unregister_driver(ibm->acpi->driver);
- kfree(ibm->acpi->driver);
- ibm->acpi->driver = NULL;
- ibm->flags.acpi_driver_registered = 0;
- }
-
- if (ibm->flags.init_called && ibm->exit) {
- ibm->exit();
- ibm->flags.init_called = 0;
- }
-
- dbg_printk(TPACPI_DBG_INIT, "finished removing %s\n", ibm->name);
-}
-
/* Probing */
static void __init get_thinkpad_model_data(struct thinkpad_id_data *tp)
IBM_PARAM(volume);
IBM_PARAM(fan);
-static void thinkpad_acpi_module_exit(void);
+static void thinkpad_acpi_module_exit(void)
+{
+ struct ibm_struct *ibm, *itmp;
+
+ tpacpi_lifecycle = TPACPI_LIFE_EXITING;
+
+ list_for_each_entry_safe_reverse(ibm, itmp,
+ &tpacpi_all_drivers,
+ all_drivers) {
+ ibm_exit(ibm);
+ }
+
+ dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n");
+
+ if (tpacpi_inputdev) {
+ if (tp_features.input_device_registered)
+ input_unregister_device(tpacpi_inputdev);
+ else
+ input_free_device(tpacpi_inputdev);
+ }
+
+ if (tpacpi_hwmon)
+ hwmon_device_unregister(tpacpi_hwmon);
+
+ if (tp_features.sensors_pdev_attrs_registered)
+ device_remove_file(&tpacpi_sensors_pdev->dev,
+ &dev_attr_thinkpad_acpi_pdev_name);
+ if (tpacpi_sensors_pdev)
+ platform_device_unregister(tpacpi_sensors_pdev);
+ if (tpacpi_pdev)
+ platform_device_unregister(tpacpi_pdev);
+
+ if (tp_features.sensors_pdrv_attrs_registered)
+ tpacpi_remove_driver_attributes(&tpacpi_hwmon_pdriver.driver);
+ if (tp_features.platform_drv_attrs_registered)
+ tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver);
+
+ if (tp_features.sensors_pdrv_registered)
+ platform_driver_unregister(&tpacpi_hwmon_pdriver);
+
+ if (tp_features.platform_drv_registered)
+ platform_driver_unregister(&tpacpi_pdriver);
+
+ if (proc_dir)
+ remove_proc_entry(IBM_PROC_DIR, acpi_root_dir);
+
+ kfree(thinkpad_id.bios_version_str);
+ kfree(thinkpad_id.ec_version_str);
+ kfree(thinkpad_id.model_str);
+}
+
static int __init thinkpad_acpi_module_init(void)
{
return 0;
}
-static void thinkpad_acpi_module_exit(void)
-{
- struct ibm_struct *ibm, *itmp;
-
- tpacpi_lifecycle = TPACPI_LIFE_EXITING;
-
- list_for_each_entry_safe_reverse(ibm, itmp,
- &tpacpi_all_drivers,
- all_drivers) {
- ibm_exit(ibm);
- }
-
- dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n");
-
- if (tpacpi_inputdev) {
- if (tp_features.input_device_registered)
- input_unregister_device(tpacpi_inputdev);
- else
- input_free_device(tpacpi_inputdev);
- }
-
- if (tpacpi_hwmon)
- hwmon_device_unregister(tpacpi_hwmon);
-
- if (tp_features.sensors_pdev_attrs_registered)
- device_remove_file(&tpacpi_sensors_pdev->dev,
- &dev_attr_thinkpad_acpi_pdev_name);
- if (tpacpi_sensors_pdev)
- platform_device_unregister(tpacpi_sensors_pdev);
- if (tpacpi_pdev)
- platform_device_unregister(tpacpi_pdev);
-
- if (tp_features.sensors_pdrv_attrs_registered)
- tpacpi_remove_driver_attributes(&tpacpi_hwmon_pdriver.driver);
- if (tp_features.platform_drv_attrs_registered)
- tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver);
-
- if (tp_features.sensors_pdrv_registered)
- platform_driver_unregister(&tpacpi_hwmon_pdriver);
-
- if (tp_features.platform_drv_registered)
- platform_driver_unregister(&tpacpi_pdriver);
-
- if (proc_dir)
- remove_proc_entry(IBM_PROC_DIR, acpi_root_dir);
-
- kfree(thinkpad_id.bios_version_str);
- kfree(thinkpad_id.ec_version_str);
- kfree(thinkpad_id.model_str);
-}
-
module_init(thinkpad_acpi_module_init);
module_exit(thinkpad_acpi_module_exit);