*/
#define IBM_VERSION "0.14"
+#define TPACPI_SYSFS_VERSION 0x000100
/*
* Changelog:
int i;
acpi_status status;
+ vdbg_printk(TPACPI_DBG_INIT, "trying to locate ACPI handle for %s\n",
+ name);
+
for (i = 0; i < num_paths; i++) {
status = acpi_get_handle(parent, paths[i], handle);
if (ACPI_SUCCESS(status)) {
*path = paths[i];
+ dbg_printk(TPACPI_DBG_INIT,
+ "Found ACPI handle %s for %s\n",
+ *path, name);
return;
}
}
+ vdbg_printk(TPACPI_DBG_INIT, "ACPI handle for %s not found\n",
+ name);
*handle = NULL;
}
static int __init setup_acpi_notify(struct ibm_struct *ibm)
{
acpi_status status;
- int ret;
+ int rc;
BUG_ON(!ibm->acpi);
if (!*ibm->acpi->handle)
return 0;
- dbg_printk(TPACPI_DBG_INIT,
+ vdbg_printk(TPACPI_DBG_INIT,
"setting up ACPI notify for %s\n", ibm->name);
- ret = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device);
- if (ret < 0) {
- printk(IBM_ERR "%s device not present\n", ibm->name);
+ rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device);
+ if (rc < 0) {
+ printk(IBM_ERR "acpi_bus_get_device(%s) failed: %d\n",
+ ibm->name, rc);
return -ENODEV;
}
static int __init register_tpacpi_subdriver(struct ibm_struct *ibm)
{
- int ret;
+ int rc;
dbg_printk(TPACPI_DBG_INIT,
"registering %s as an ACPI driver\n", ibm->name);
ibm->acpi->driver->ids = ibm->acpi->hid;
ibm->acpi->driver->ops.add = &tpacpi_device_add;
- ret = acpi_bus_register_driver(ibm->acpi->driver);
- if (ret < 0) {
+ rc = acpi_bus_register_driver(ibm->acpi->driver);
+ if (rc < 0) {
printk(IBM_ERR "acpi_bus_register_driver(%s) failed: %d\n",
- ibm->acpi->hid, ret);
+ ibm->acpi->hid, rc);
kfree(ibm->acpi->driver);
ibm->acpi->driver = NULL;
- } else if (!ret)
+ } else if (!rc)
ibm->flags.acpi_driver_registered = 1;
- return ret;
+ return rc;
}
}
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Device model: hwmon and platform
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+static struct platform_device *tpacpi_pdev = NULL;
+static struct class_device *tpacpi_hwmon = NULL;
+
+static struct platform_driver tpacpi_pdriver = {
+ .driver = {
+ .name = IBM_DRVR_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+
+/*************************************************************************
+ * thinkpad-acpi driver attributes
+ */
+
+/* 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,
+};
+
+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;
+} __attribute__((packed));
+
+static struct attribute_set *create_attr_set(unsigned int max_members,
+ const char* name)
+{
+ struct attribute_set_obj *sobj;
+
+ if (max_members == 0)
+ return NULL;
+
+ /* Allocates space for implicit NULL at the end too */
+ sobj = kzalloc(sizeof(struct attribute_set_obj) +
+ max_members * sizeof(struct attribute *),
+ GFP_KERNEL);
+ if (!sobj)
+ return NULL;
+ sobj->s.max_members = max_members;
+ sobj->s.group.attrs = &sobj->a;
+ sobj->s.group.name = name;
+
+ return &sobj->s;
+}
+
+/* not multi-threaded safe, use it in a single thread per set */
+static int add_to_attr_set(struct attribute_set* s, struct attribute *attr)
+{
+ if (!s || !attr)
+ return -EINVAL;
+
+ if (s->members >= s->max_members)
+ return -ENOMEM;
+
+ s->group.attrs[s->members] = attr;
+ s->members++;
+
+ return 0;
+}
+
+static int add_many_to_attr_set(struct attribute_set* s,
+ struct attribute **attr,
+ unsigned int count)
+{
+ int i, res;
+
+ for (i = 0; i < count; i++) {
+ res = add_to_attr_set(s, attr[i]);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static void delete_attr_set(struct attribute_set* s, struct kobject *kobj)
+{
+ sysfs_remove_group(kobj, &s->group);
+ destroy_attr_set(s);
+}
+
+static int parse_strtoul(const char *buf,
+ unsigned long max, unsigned long *value)
+{
+ char *endp;
+
+ *value = simple_strtoul(buf, &endp, 0);
+ while (*endp && isspace(*endp))
+ endp++;
+ if (*endp || *value > max)
+ return -EINVAL;
+
+ return 0;
+}
+
/****************************************************************************
****************************************************************************
*
static int hotkey_orig_status;
static int hotkey_orig_mask;
+static struct attribute_set *hotkey_dev_attributes = NULL;
+
+/* sysfs hotkey enable ------------------------------------------------- */
+static ssize_t hotkey_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int res, status, mask;
+
+ res = hotkey_get(&status, &mask);
+ if (res)
+ return res;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", status);
+}
+
+static ssize_t hotkey_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+ int res, status, mask;
+
+ if (parse_strtoul(buf, 1, &t))
+ return -EINVAL;
+
+ res = hotkey_get(&status, &mask);
+ if (!res)
+ res = hotkey_set(t, mask);
+
+ return (res) ? res : count;
+}
+
+static struct device_attribute dev_attr_hotkey_enable =
+ __ATTR(enable, S_IWUSR | S_IRUGO,
+ hotkey_enable_show, hotkey_enable_store);
+
+/* sysfs hotkey mask --------------------------------------------------- */
+static ssize_t hotkey_mask_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int res, status, mask;
+
+ res = hotkey_get(&status, &mask);
+ if (res)
+ return res;
+
+ return snprintf(buf, PAGE_SIZE, "0x%04x\n", mask);
+}
+
+static ssize_t hotkey_mask_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+ int res, status, mask;
+
+ if (parse_strtoul(buf, 0xffff, &t))
+ return -EINVAL;
+
+ res = hotkey_get(&status, &mask);
+ if (!res)
+ hotkey_set(status, t);
+
+ return (res) ? res : count;
+}
+
+static struct device_attribute dev_attr_hotkey_mask =
+ __ATTR(mask, S_IWUSR | S_IRUGO,
+ hotkey_mask_show, hotkey_mask_store);
+
+/* sysfs hotkey bios_enabled ------------------------------------------- */
+static ssize_t hotkey_bios_enabled_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_orig_status);
+}
+
+static struct device_attribute dev_attr_hotkey_bios_enabled =
+ __ATTR(bios_enabled, S_IRUGO, hotkey_bios_enabled_show, NULL);
+
+/* sysfs hotkey bios_mask ---------------------------------------------- */
+static ssize_t hotkey_bios_mask_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "0x%04x\n", hotkey_orig_mask);
+}
+
+static struct device_attribute dev_attr_hotkey_bios_mask =
+ __ATTR(bios_mask, S_IRUGO, hotkey_bios_mask_show, NULL);
+
+/* --------------------------------------------------------------------- */
+
+static struct attribute *hotkey_mask_attributes[] = {
+ &dev_attr_hotkey_mask.attr,
+ &dev_attr_hotkey_bios_enabled.attr,
+ &dev_attr_hotkey_bios_mask.attr,
+};
+
static int __init hotkey_init(struct ibm_init_struct *iibm)
{
int res;
vdbg_printk(TPACPI_DBG_INIT, "initializing hotkey subdriver\n");
IBM_ACPIHANDLE_INIT(hkey);
+ mutex_init(&hotkey_mutex);
/* hotkey not supported on 570 */
tp_features.hotkey = hkey_handle != NULL;
str_supported(tp_features.hotkey));
if (tp_features.hotkey) {
+ hotkey_dev_attributes = create_attr_set(4,
+ TPACPI_HOTKEY_SYSFS_GROUP);
+ if (!hotkey_dev_attributes)
+ return -ENOMEM;
+ res = add_to_attr_set(hotkey_dev_attributes,
+ &dev_attr_hotkey_enable.attr);
+ if (res)
+ return res;
+
/* mask not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
A30, R30, R31, T20-22, X20-21, X22-24 */
tp_features.hotkey_mask =
str_supported(tp_features.hotkey_mask));
res = hotkey_get(&hotkey_orig_status, &hotkey_orig_mask);
+ if (!res && tp_features.hotkey_mask) {
+ res = add_many_to_attr_set(hotkey_dev_attributes,
+ hotkey_mask_attributes,
+ ARRAY_SIZE(hotkey_mask_attributes));
+ }
+ if (!res)
+ res = register_attr_set_with_sysfs(
+ hotkey_dev_attributes,
+ &tpacpi_pdev->dev.kobj);
+
if (res)
return res;
}
if (res)
printk(IBM_ERR "failed to restore hotkey to BIOS defaults\n");
}
+
+ if (hotkey_dev_attributes) {
+ delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj);
+ hotkey_dev_attributes = NULL;
+ }
}
static void hotkey_notify(struct ibm_struct *ibm, u32 event)
}
}
+/*
+ * Call with hotkey_mutex held
+ */
static int hotkey_get(int *status, int *mask)
{
if (!acpi_evalf(hkey_handle, status, "DHKC", "d"))
return 0;
}
+/*
+ * Call with hotkey_mutex held
+ */
static int hotkey_set(int status, int mask)
{
int i;
return 0;
}
+/* procfs -------------------------------------------------------------- */
static int hotkey_read(char *p)
{
int res, status, mask;
return len;
}
+ res = mutex_lock_interruptible(&hotkey_mutex);
+ if (res < 0)
+ return res;
res = hotkey_get(&status, &mask);
+ mutex_unlock(&hotkey_mutex);
if (res)
return res;
if (!tp_features.hotkey)
return -ENODEV;
+ res = mutex_lock_interruptible(&hotkey_mutex);
+ if (res < 0)
+ return res;
+
res = hotkey_get(&status, &mask);
if (res)
- return res;
+ goto errexit;
+ res = 0;
while ((cmd = next_cmd(&buf))) {
if (strlencmp(cmd, "enable") == 0) {
status = 1;
/* mask set */
} else if (sscanf(cmd, "%x", &mask) == 1) {
/* mask set */
- } else
- return -EINVAL;
+ } else {
+ res = -EINVAL;
+ goto errexit;
+ }
do_cmd = 1;
}
- if (do_cmd) {
+ if (do_cmd)
res = hotkey_set(status, mask);
- if (res)
- return res;
- }
- return 0;
+errexit:
+ mutex_unlock(&hotkey_mutex);
+ return res;
}
static struct tp_acpi_drv_struct ibm_hotkey_acpidriver = {
* Bluetooth subdriver
*/
+/* sysfs bluetooth enable ---------------------------------------------- */
+static ssize_t bluetooth_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int status;
+
+ status = bluetooth_get_radiosw();
+ if (status < 0)
+ return status;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0);
+}
+
+static ssize_t bluetooth_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+ int res;
+
+ if (parse_strtoul(buf, 1, &t))
+ return -EINVAL;
+
+ res = bluetooth_set_radiosw(t);
+
+ return (res) ? res : count;
+}
+
+static struct device_attribute dev_attr_bluetooth_enable =
+ __ATTR(enable, S_IWUSR | S_IRUGO,
+ bluetooth_enable_show, bluetooth_enable_store);
+
+/* --------------------------------------------------------------------- */
+
+static struct attribute *bluetooth_attributes[] = {
+ &dev_attr_bluetooth_enable.attr,
+ NULL
+};
+
+static const struct attribute_group bluetooth_attr_group = {
+ .name = TPACPI_BLUETH_SYSFS_GROUP,
+ .attrs = bluetooth_attributes,
+};
+
static int __init bluetooth_init(struct ibm_init_struct *iibm)
{
+ int res;
int status = 0;
vdbg_printk(TPACPI_DBG_INIT, "initializing bluetooth subdriver\n");
str_supported(tp_features.bluetooth),
status);
- if (tp_features.bluetooth &&
- !(status & TP_ACPI_BLUETOOTH_HWPRESENT)) {
- /* no bluetooth hardware present in system */
- tp_features.bluetooth = 0;
- dbg_printk(TPACPI_DBG_INIT,
- "bluetooth hardware not installed\n");
+ if (tp_features.bluetooth) {
+ if (!(status & TP_ACPI_BLUETOOTH_HWPRESENT)) {
+ /* no bluetooth hardware present in system */
+ tp_features.bluetooth = 0;
+ dbg_printk(TPACPI_DBG_INIT,
+ "bluetooth hardware not installed\n");
+ } else {
+ res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+ &bluetooth_attr_group);
+ if (res)
+ return res;
+ }
}
return (tp_features.bluetooth)? 0 : 1;
}
+static void bluetooth_exit(void)
+{
+ sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+ &bluetooth_attr_group);
+}
+
static int bluetooth_get_radiosw(void)
{
int status;
return 0;
}
+/* procfs -------------------------------------------------------------- */
static int bluetooth_read(char *p)
{
int len = 0;
.name = "bluetooth",
.read = bluetooth_read,
.write = bluetooth_write,
+ .exit = bluetooth_exit,
};
/*************************************************************************
* Wan subdriver
*/
+/* sysfs wan enable ---------------------------------------------------- */
+static ssize_t wan_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int status;
+
+ status = wan_get_radiosw();
+ if (status < 0)
+ return status;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0);
+}
+
+static ssize_t wan_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+ int res;
+
+ if (parse_strtoul(buf, 1, &t))
+ return -EINVAL;
+
+ res = wan_set_radiosw(t);
+
+ return (res) ? res : count;
+}
+
+static struct device_attribute dev_attr_wan_enable =
+ __ATTR(enable, S_IWUSR | S_IRUGO,
+ wan_enable_show, wan_enable_store);
+
+/* --------------------------------------------------------------------- */
+
+static struct attribute *wan_attributes[] = {
+ &dev_attr_wan_enable.attr,
+ NULL
+};
+
+static const struct attribute_group wan_attr_group = {
+ .name = TPACPI_WAN_SYSFS_GROUP,
+ .attrs = wan_attributes,
+};
+
static int __init wan_init(struct ibm_init_struct *iibm)
{
+ int res;
int status = 0;
vdbg_printk(TPACPI_DBG_INIT, "initializing wan subdriver\n");
str_supported(tp_features.wan),
status);
- if (tp_features.wan &&
- !(status & TP_ACPI_WANCARD_HWPRESENT)) {
- /* no wan hardware present in system */
- tp_features.wan = 0;
- dbg_printk(TPACPI_DBG_INIT,
- "wan hardware not installed\n");
+ if (tp_features.wan) {
+ if (!(status & TP_ACPI_WANCARD_HWPRESENT)) {
+ /* no wan hardware present in system */
+ tp_features.wan = 0;
+ dbg_printk(TPACPI_DBG_INIT,
+ "wan hardware not installed\n");
+ } else {
+ res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+ &wan_attr_group);
+ if (res)
+ return res;
+ }
}
return (tp_features.wan)? 0 : 1;
}
+static void wan_exit(void)
+{
+ sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+ &wan_attr_group);
+}
+
static int wan_get_radiosw(void)
{
int status;
return 0;
}
+/* procfs -------------------------------------------------------------- */
static int wan_read(char *p)
{
int len = 0;
.name = "wan",
.read = wan_read,
.write = wan_write,
+ .exit = wan_exit,
.flags.experimental = 1,
};
/* don't list other alternatives as we install a notify handler on the 570 */
IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */
+static struct tp_acpi_drv_struct ibm_dock_acpidriver[2] = {
+ {
+ .notify = dock_notify,
+ .handle = &dock_handle,
+ .type = ACPI_SYSTEM_NOTIFY,
+ },
+ {
+ .hid = IBM_PCI_HID,
+ .notify = dock_notify,
+ .handle = &pci_handle,
+ .type = ACPI_SYSTEM_NOTIFY,
+ },
+};
+
+static struct ibm_struct dock_driver_data[2] = {
+ {
+ .name = "dock",
+ .read = dock_read,
+ .write = dock_write,
+ .acpi = &ibm_dock_acpidriver[0],
+ },
+ {
+ .name = "dock",
+ .acpi = &ibm_dock_acpidriver[1],
+ },
+};
+
#define dock_docked() (_sta(dock_handle) & 1)
static int __init dock_init(struct ibm_init_struct *iibm)
vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver\n");
IBM_ACPIHANDLE_INIT(dock);
- IBM_ACPIHANDLE_INIT(pci);
vdbg_printk(TPACPI_DBG_INIT, "dock is %s\n",
str_supported(dock_handle != NULL));
return (dock_handle)? 0 : 1;
}
+static int __init dock_init2(struct ibm_init_struct *iibm)
+{
+ int dock2_needed;
+
+ vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver part 2\n");
+
+ if (dock_driver_data[0].flags.acpi_driver_registered &&
+ dock_driver_data[0].flags.acpi_notify_installed) {
+ IBM_ACPIHANDLE_INIT(pci);
+ dock2_needed = (pci_handle != NULL);
+ vdbg_printk(TPACPI_DBG_INIT,
+ "dock PCI handler for the TP 570 is %s\n",
+ str_supported(dock2_needed));
+ } else {
+ vdbg_printk(TPACPI_DBG_INIT,
+ "dock subdriver part 2 not required\n");
+ dock2_needed = 0;
+ }
+
+ return (dock2_needed)? 0 : 1;
+}
+
static void dock_notify(struct ibm_struct *ibm, u32 event)
{
int docked = dock_docked();
return 0;
}
-static struct tp_acpi_drv_struct ibm_dock_acpidriver[2] = {
- {
- .notify = dock_notify,
- .handle = &dock_handle,
- .type = ACPI_SYSTEM_NOTIFY,
- },
- {
- .hid = IBM_PCI_HID,
- .notify = dock_notify,
- .handle = &pci_handle,
- .type = ACPI_SYSTEM_NOTIFY,
- },
-};
-
-static struct ibm_struct dock_driver_data[2] = {
- {
- .name = "dock",
- .read = dock_read,
- .write = dock_write,
- .acpi = &ibm_dock_acpidriver[0],
- },
- {
- .name = "dock",
- .acpi = &ibm_dock_acpidriver[1],
- },
-};
-
#endif /* CONFIG_THINKPAD_ACPI_DOCK */
/*************************************************************************
* CMOS subdriver
*/
+/* sysfs cmos_command -------------------------------------------------- */
+static ssize_t cmos_command_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long cmos_cmd;
+ int res;
+
+ if (parse_strtoul(buf, 21, &cmos_cmd))
+ return -EINVAL;
+
+ res = issue_thinkpad_cmos_command(cmos_cmd);
+ return (res)? res : count;
+}
+
+static struct device_attribute dev_attr_cmos_command =
+ __ATTR(cmos_command, S_IWUSR, NULL, cmos_command_store);
+
+/* --------------------------------------------------------------------- */
+
static int __init cmos_init(struct ibm_init_struct *iibm)
{
+ int res;
+
vdbg_printk(TPACPI_DBG_INIT,
"initializing cmos commands subdriver\n");
IBM_ACPIHANDLE_INIT(cmos);
- vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n",
- str_supported(cmos_handle != NULL));
+ vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n",
+ str_supported(cmos_handle != NULL));
+
+ res = device_create_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
+ if (res)
+ return res;
+
return (cmos_handle)? 0 : 1;
}
+static void cmos_exit(void)
+{
+ device_remove_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
+}
+
static int cmos_read(char *p)
{
int len = 0;
.name = "cmos",
.read = cmos_read,
.write = cmos_write,
+ .exit = cmos_exit,
};
/*************************************************************************
static enum thermal_access_mode thermal_read_mode;
+/* sysfs temp##_input -------------------------------------------------- */
+
+static ssize_t thermal_temp_input_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sensor_device_attribute *sensor_attr =
+ to_sensor_dev_attr(attr);
+ int idx = sensor_attr->index;
+ s32 value;
+ int res;
+
+ res = thermal_get_sensor(idx, &value);
+ if (res)
+ return res;
+ if (value == TP_EC_THERMAL_TMP_NA * 1000)
+ return -ENXIO;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", value);
+}
+
+#define THERMAL_SENSOR_ATTR_TEMP(_idxA, _idxB) \
+ SENSOR_ATTR(temp##_idxA##_input, S_IRUGO, thermal_temp_input_show, NULL, _idxB)
+
+static struct sensor_device_attribute sensor_dev_attr_thermal_temp_input[] = {
+ THERMAL_SENSOR_ATTR_TEMP(1, 0),
+ THERMAL_SENSOR_ATTR_TEMP(2, 1),
+ THERMAL_SENSOR_ATTR_TEMP(3, 2),
+ THERMAL_SENSOR_ATTR_TEMP(4, 3),
+ THERMAL_SENSOR_ATTR_TEMP(5, 4),
+ THERMAL_SENSOR_ATTR_TEMP(6, 5),
+ THERMAL_SENSOR_ATTR_TEMP(7, 6),
+ THERMAL_SENSOR_ATTR_TEMP(8, 7),
+ THERMAL_SENSOR_ATTR_TEMP(9, 8),
+ THERMAL_SENSOR_ATTR_TEMP(10, 9),
+ THERMAL_SENSOR_ATTR_TEMP(11, 10),
+ THERMAL_SENSOR_ATTR_TEMP(12, 11),
+ THERMAL_SENSOR_ATTR_TEMP(13, 12),
+ THERMAL_SENSOR_ATTR_TEMP(14, 13),
+ THERMAL_SENSOR_ATTR_TEMP(15, 14),
+ THERMAL_SENSOR_ATTR_TEMP(16, 15),
+};
+
+#define THERMAL_ATTRS(X) \
+ &sensor_dev_attr_thermal_temp_input[X].dev_attr.attr
+
+static struct attribute *thermal_temp_input_attr[] = {
+ THERMAL_ATTRS(8),
+ THERMAL_ATTRS(9),
+ THERMAL_ATTRS(10),
+ THERMAL_ATTRS(11),
+ THERMAL_ATTRS(12),
+ THERMAL_ATTRS(13),
+ THERMAL_ATTRS(14),
+ THERMAL_ATTRS(15),
+ THERMAL_ATTRS(0),
+ THERMAL_ATTRS(1),
+ THERMAL_ATTRS(2),
+ THERMAL_ATTRS(3),
+ THERMAL_ATTRS(4),
+ THERMAL_ATTRS(5),
+ THERMAL_ATTRS(6),
+ THERMAL_ATTRS(7),
+ NULL
+};
+
+static const struct attribute_group thermal_temp_input16_group = {
+ .attrs = thermal_temp_input_attr
+};
+
+static const struct attribute_group thermal_temp_input8_group = {
+ .attrs = &thermal_temp_input_attr[8]
+};
+
+#undef THERMAL_SENSOR_ATTR_TEMP
+#undef THERMAL_ATTRS
+
+/* --------------------------------------------------------------------- */
+
static int __init thermal_init(struct ibm_init_struct *iibm)
{
u8 t, ta1, ta2;
int i;
int acpi_tmp7;
+ int res;
vdbg_printk(TPACPI_DBG_INIT, "initializing thermal subdriver\n");
str_supported(thermal_read_mode != TPACPI_THERMAL_NONE),
thermal_read_mode);
- return (thermal_read_mode != TPACPI_THERMAL_NONE)? 0 : 1;
+ switch(thermal_read_mode) {
+ case TPACPI_THERMAL_TPEC_16:
+ res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+ &thermal_temp_input16_group);
+ if (res)
+ return res;
+ break;
+ case TPACPI_THERMAL_TPEC_8:
+ case TPACPI_THERMAL_ACPI_TMP07:
+ case TPACPI_THERMAL_ACPI_UPDT:
+ res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+ &thermal_temp_input8_group);
+ if (res)
+ return res;
+ break;
+ case TPACPI_THERMAL_NONE:
+ default:
+ return 1;
+ }
+
+ return 0;
+}
+
+static void thermal_exit(void)
+{
+ switch(thermal_read_mode) {
+ case TPACPI_THERMAL_TPEC_16:
+ sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+ &thermal_temp_input16_group);
+ break;
+ case TPACPI_THERMAL_TPEC_8:
+ case TPACPI_THERMAL_ACPI_TMP07:
+ case TPACPI_THERMAL_ACPI_UPDT:
+ sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+ &thermal_temp_input16_group);
+ break;
+ case TPACPI_THERMAL_NONE:
+ default:
+ break;
+ }
}
/* idx is zero-based */
static struct ibm_struct thermal_driver_data = {
.name = "thermal",
.read = thermal_read,
+ .exit = thermal_exit,
};
/*************************************************************************
if (b < 0)
return b;
- ibm_backlight_device = backlight_device_register("ibm", NULL, NULL,
- &ibm_backlight_data);
+ ibm_backlight_device = backlight_device_register(
+ TPACPI_BACKLIGHT_DEV_NAME, NULL, NULL,
+ &ibm_backlight_data);
if (IS_ERR(ibm_backlight_device)) {
printk(IBM_ERR "Could not register backlight device\n");
return PTR_ERR(ibm_backlight_device);
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;
"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 == -ENXIO)
+ return -EINVAL;
+ else 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 == -ENXIO)
+ rc = -EINVAL;
+ else 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;
+
+ if (!fan_control_allowed)
+ return -EPERM;
+
+ 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);
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);
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;
+ /* 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_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)
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 watchdogs\n");
+ 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();
}
static void fan_watchdog_fire(struct work_struct *ignored)
{
+ int rc;
+
printk(IBM_NOTICE "fan watchdog: enabling fan\n");
- if (fan_set_enable()) {
- printk(IBM_ERR "fan watchdog: error while 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();
}
{
static int fan_watchdog_active = 0;
+ if (fan_control_access_mode == TPACPI_FAN_WR_NONE)
+ return;
+
if (fan_watchdog_active)
cancel_delayed_work(&fan_watchdog_task);
static int fan_set_level(int level)
{
+ if (!fan_control_allowed)
+ return -EPERM;
+
switch (fan_control_access_mode) {
case TPACPI_FAN_WR_ACPI_SFAN:
if (level >= 0 && level <= 7) {
((level < 0) || (level > 7)))
return -EINVAL;
+ /* 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 */
+
if (!acpi_ec_write(fan_status_offset, level))
return -EIO;
else
return 0;
}
+static int fan_set_level_safe(int level)
+{
+ int rc;
+
+ if (!fan_control_allowed)
+ return -EPERM;
+
+ 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;
int rc;
+ if (!fan_control_allowed)
+ return -EPERM;
+
+ rc = mutex_lock_interruptible(&fan_mutex);
+ if (rc < 0)
+ return rc;
+
switch (fan_control_access_mode) {
case TPACPI_FAN_WR_ACPI_FANS:
case TPACPI_FAN_WR_TPEC:
- if ((rc = fan_get_status(&s)) < 0)
- return rc;
+ rc = fan_get_status(&s);
+ if (rc < 0)
+ break;
/* Don't go out of emergency fan mode */
- if (s != 7)
- s = TP_EC_FAN_AUTO;
+ if (s != 7) {
+ s &= 0x07;
+ s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */
+ }
if (!acpi_ec_write(fan_status_offset, s))
- return -EIO;
- else
+ rc = -EIO;
+ else {
tp_features.fan_ctrl_status_undef = 0;
+ rc = 0;
+ }
break;
case TPACPI_FAN_WR_ACPI_SFAN:
- if ((rc = fan_get_status(&s)) < 0)
- return rc;
+ rc = fan_get_status(&s);
+ if (rc < 0)
+ break;
s &= 0x07;
/* Set fan to at least level 4 */
- if (s < 4)
- s = 4;
+ s |= 4;
if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s))
- return -EIO;
+ rc= -EIO;
+ else
+ rc = 0;
break;
default:
- return -ENXIO;
+ rc = -ENXIO;
}
- return 0;
+
+ mutex_unlock(&fan_mutex);
+ return rc;
}
static int fan_set_disable(void)
{
+ int rc;
+
+ if (!fan_control_allowed)
+ return -EPERM;
+
+ rc = mutex_lock_interruptible(&fan_mutex);
+ if (rc < 0)
+ return rc;
+
+ 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))
- return -EIO;
- else
+ rc = -EIO;
+ 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))
- return -EIO;
+ rc = -EIO;
+ else
+ fan_control_desired_level = 0;
break;
default:
- return -ENXIO;
+ rc = -ENXIO;
}
- return 0;
+
+
+ mutex_unlock(&fan_mutex);
+ return rc;
}
static int fan_set_speed(int speed)
{
+ int rc;
+
+ if (!fan_control_allowed)
+ return -EPERM;
+
+ rc = mutex_lock_interruptible(&fan_mutex);
+ if (rc < 0)
+ return rc;
+
+ 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))
- return -EIO;
+ rc = -EIO;
} else
- return -EINVAL;
+ rc = -EINVAL;
break;
default:
- return -ENXIO;
+ rc = -ENXIO;
}
- return 0;
+
+ mutex_unlock(&fan_mutex);
+ return rc;
}
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"
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)) {
default:
len += sprintf(p + len, " (<level> is 0-7, "
- "auto, disengaged)\n");
+ "auto, disengaged, full-speed)\n");
break;
}
}
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);
.read = fan_read,
.write = fan_write,
.exit = fan_exit,
- .flags.experimental = 1,
};
/****************************************************************************
.data = &dock_driver_data[0],
},
{
+ .init = dock_init2,
.data = &dock_driver_data[1],
},
#endif
static int force_load;
module_param(force_load, int, 0);
+static int fan_control_allowed;
+module_param_named(fan_control, fan_control_allowed, int, 0);
+
#define IBM_PARAM(feature) \
module_param_call(feature, set_ibm_param, NULL, NULL, 0)
{
int ret, i;
+ /* Driver-level probe */
ret = probe_for_thinkpad();
if (ret)
return ret;
+ /* Driver initialization */
ibm_thinkpad_ec_found = check_dmi_for_ec();
IBM_ACPIHANDLE_INIT(ecrd);
IBM_ACPIHANDLE_INIT(ecwr);
}
proc_dir->owner = THIS_MODULE;
+ ret = platform_driver_register(&tpacpi_pdriver);
+ if (ret) {
+ printk(IBM_ERR "unable to register platform driver\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+ ret = tpacpi_create_driver_attributes(&tpacpi_pdriver.driver);
+ if (ret) {
+ printk(IBM_ERR "unable to create sysfs driver attributes\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+
+
+ /* Device initialization */
+ tpacpi_pdev = platform_device_register_simple(IBM_DRVR_NAME, -1,
+ NULL, 0);
+ if (IS_ERR(tpacpi_pdev)) {
+ ret = PTR_ERR(tpacpi_pdev);
+ tpacpi_pdev = NULL;
+ printk(IBM_ERR "unable to register platform device\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+ tpacpi_hwmon = hwmon_device_register(&tpacpi_pdev->dev);
+ if (IS_ERR(tpacpi_hwmon)) {
+ ret = PTR_ERR(tpacpi_hwmon);
+ tpacpi_hwmon = NULL;
+ printk(IBM_ERR "unable to register hwmon device\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
ret = ibm_init(&ibms_init[i]);
if (ret >= 0 && *ibms_init[i].param)
dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n");
+ if (tpacpi_hwmon)
+ hwmon_device_unregister(tpacpi_hwmon);
+
+ if (tpacpi_pdev)
+ platform_device_unregister(tpacpi_pdev);
+
+ tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver);
+ platform_driver_unregister(&tpacpi_pdriver);
+
if (proc_dir)
remove_proc_entry(IBM_PROC_DIR, acpi_root_dir);