diff options
Diffstat (limited to 'drivers/hwmon')
-rw-r--r-- | drivers/hwmon/Kconfig | 32 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 3 | ||||
-rw-r--r-- | drivers/hwmon/asus_atk0110.c | 1009 | ||||
-rw-r--r-- | drivers/hwmon/g760a.c | 272 | ||||
-rw-r--r-- | drivers/hwmon/hp_accel.c | 1 | ||||
-rw-r--r-- | drivers/hwmon/lm95241.c | 112 | ||||
-rw-r--r-- | drivers/hwmon/sht15.c | 692 |
7 files changed, 2055 insertions, 66 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index ce52bf2f235..d73f5f473e3 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -248,6 +248,18 @@ config SENSORS_ASB100 This driver can also be built as a module. If so, the module will be called asb100. +config SENSORS_ATK0110 + tristate "ASUS ATK0110 ACPI hwmon" + depends on X86 && ACPI && EXPERIMENTAL + help + If you say yes here you get support for the ACPI hardware + monitoring interface found in many ASUS motherboards. This + driver will provide readings of fans, voltages and temperatures + through the system firmware. + + This driver can also be built as a module. If so, the module + will be called asus_atk0110. + config SENSORS_ATXP1 tristate "Attansic ATXP1 VID controller" depends on I2C && EXPERIMENTAL @@ -358,6 +370,16 @@ config SENSORS_FSCHMD This driver can also be built as a module. If so, the module will be called fschmd. +config SENSORS_G760A + tristate "GMT G760A" + depends on I2C + help + If you say yes here you get support for Global Mixed-mode + Technology Inc G760A fan speed PWM controller chips. + + This driver can also be built as a module. If so, the module + will be called g760a. + config SENSORS_GL518SM tristate "Genesys Logic GL518SM" depends on I2C @@ -670,6 +692,16 @@ config SENSORS_PCF8591 These devices are hard to detect and rarely found on mainstream hardware. If unsure, say N. +config SENSORS_SHT15 + tristate "Sensiron humidity and temperature sensors. SHT15 and compat." + depends on GENERIC_GPIO + help + If you say yes here you get support for the Sensiron SHT10, SHT11, + SHT15, SHT71, SHT75 humidity and temperature sensors. + + This driver can also be built as a module. If so, the module + will be called sht15. + config SENSORS_SIS5595 tristate "Silicon Integrated Systems Corp. SiS5595" depends on PCI diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 3a6b1f06f8f..0ae26984ba4 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o obj-$(CONFIG_SENSORS_AMS) += ams/ +obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o obj-$(CONFIG_SENSORS_DME1737) += dme1737.o @@ -42,6 +43,7 @@ obj-$(CONFIG_SENSORS_F75375S) += f75375s.o obj-$(CONFIG_SENSORS_FSCHER) += fscher.o obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o obj-$(CONFIG_SENSORS_FSCPOS) += fscpos.o +obj-$(CONFIG_SENSORS_G760A) += g760a.o obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o @@ -74,6 +76,7 @@ obj-$(CONFIG_SENSORS_MAX6650) += max6650.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o +obj-$(CONFIG_SENSORS_SHT15) += sht15.o obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o diff --git a/drivers/hwmon/asus_atk0110.c b/drivers/hwmon/asus_atk0110.c new file mode 100644 index 00000000000..0897edef257 --- /dev/null +++ b/drivers/hwmon/asus_atk0110.c @@ -0,0 +1,1009 @@ +/* + * Copyright (C) 2007-2009 Luca Tettamanti <kronos.it@gmail.com> + * + * This file is released under the GPLv2 + * See COPYING in the top level directory of the kernel tree. + */ + +#include <linux/kernel.h> +#include <linux/hwmon.h> +#include <linux/list.h> +#include <linux/module.h> + +#include <acpi/acpi.h> +#include <acpi/acpixf.h> +#include <acpi/acpi_drivers.h> +#include <acpi/acpi_bus.h> + + +#define ATK_HID "ATK0110" + +/* Minimum time between readings, enforced in order to avoid + * hogging the CPU. + */ +#define CACHE_TIME HZ + +#define BOARD_ID "MBIF" +#define METHOD_ENUMERATE "GGRP" +#define METHOD_READ "GITM" +#define METHOD_WRITE "SITM" +#define METHOD_OLD_READ_TMP "RTMP" +#define METHOD_OLD_READ_VLT "RVLT" +#define METHOD_OLD_READ_FAN "RFAN" +#define METHOD_OLD_ENUM_TMP "TSIF" +#define METHOD_OLD_ENUM_VLT "VSIF" +#define METHOD_OLD_ENUM_FAN "FSIF" + +#define ATK_MUX_HWMON 0x00000006ULL + +#define ATK_CLASS_MASK 0xff000000ULL +#define ATK_CLASS_FREQ_CTL 0x03000000ULL +#define ATK_CLASS_FAN_CTL 0x04000000ULL +#define ATK_CLASS_HWMON 0x06000000ULL + +#define ATK_TYPE_MASK 0x00ff0000ULL +#define HWMON_TYPE_VOLT 0x00020000ULL +#define HWMON_TYPE_TEMP 0x00030000ULL +#define HWMON_TYPE_FAN 0x00040000ULL + +#define HWMON_SENSOR_ID_MASK 0x0000ffffULL + +enum atk_pack_member { + HWMON_PACK_FLAGS, + HWMON_PACK_NAME, + HWMON_PACK_LIMIT1, + HWMON_PACK_LIMIT2, + HWMON_PACK_ENABLE +}; + +/* New package format */ +#define _HWMON_NEW_PACK_SIZE 7 +#define _HWMON_NEW_PACK_FLAGS 0 +#define _HWMON_NEW_PACK_NAME 1 +#define _HWMON_NEW_PACK_UNK1 2 +#define _HWMON_NEW_PACK_UNK2 3 +#define _HWMON_NEW_PACK_LIMIT1 4 +#define _HWMON_NEW_PACK_LIMIT2 5 +#define _HWMON_NEW_PACK_ENABLE 6 + +/* Old package format */ +#define _HWMON_OLD_PACK_SIZE 5 +#define _HWMON_OLD_PACK_FLAGS 0 +#define _HWMON_OLD_PACK_NAME 1 +#define _HWMON_OLD_PACK_LIMIT1 2 +#define _HWMON_OLD_PACK_LIMIT2 3 +#define _HWMON_OLD_PACK_ENABLE 4 + + +struct atk_data { + struct device *hwmon_dev; + acpi_handle atk_handle; + struct acpi_device *acpi_dev; + + bool old_interface; + + /* old interface */ + acpi_handle rtmp_handle; + acpi_handle rvlt_handle; + acpi_handle rfan_handle; + /* new inteface */ + acpi_handle enumerate_handle; + acpi_handle read_handle; + + int voltage_count; + int temperature_count; + int fan_count; + struct list_head sensor_list; +}; + + +typedef ssize_t (*sysfs_show_func)(struct device *dev, + struct device_attribute *attr, char *buf); + +static const struct acpi_device_id atk_ids[] = { + {ATK_HID, 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, atk_ids); + +#define ATTR_NAME_SIZE 16 /* Worst case is "tempN_input" */ + +struct atk_sensor_data { + struct list_head list; + struct atk_data *data; + struct device_attribute label_attr; + struct device_attribute input_attr; + struct device_attribute limit1_attr; + struct device_attribute limit2_attr; + char label_attr_name[ATTR_NAME_SIZE]; + char input_attr_name[ATTR_NAME_SIZE]; + char limit1_attr_name[ATTR_NAME_SIZE]; + char limit2_attr_name[ATTR_NAME_SIZE]; + u64 id; + u64 type; + u64 limit1; + u64 limit2; + u64 cached_value; + unsigned long last_updated; /* in jiffies */ + bool is_valid; + char const *acpi_name; +}; + +struct atk_acpi_buffer_u64 { + union acpi_object buf; + u64 value; +}; + +static int atk_add(struct acpi_device *device); +static int atk_remove(struct acpi_device *device, int type); +static void atk_print_sensor(struct atk_data *data, union acpi_object *obj); +static int atk_read_value(struct atk_sensor_data *sensor, u64 *value); +static void atk_free_sensors(struct atk_data *data); + +static struct acpi_driver atk_driver = { + .name = ATK_HID, + .class = "hwmon", + .ids = atk_ids, + .ops = { + .add = atk_add, + .remove = atk_remove, + }, +}; + +#define input_to_atk_sensor(attr) \ + container_of(attr, struct atk_sensor_data, input_attr) + +#define label_to_atk_sensor(attr) \ + container_of(attr, struct atk_sensor_data, label_attr) + +#define limit1_to_atk_sensor(attr) \ + container_of(attr, struct atk_sensor_data, limit1_attr) + +#define limit2_to_atk_sensor(attr) \ + container_of(attr, struct atk_sensor_data, limit2_attr) + +static ssize_t atk_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atk_sensor_data *s = input_to_atk_sensor(attr); + u64 value; + int err; + + err = atk_read_value(s, &value); + if (err) + return err; + + if (s->type == HWMON_TYPE_TEMP) + /* ACPI returns decidegree */ + value *= 100; + + return sprintf(buf, "%llu\n", value); +} + +static ssize_t atk_label_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atk_sensor_data *s = label_to_atk_sensor(attr); + + return sprintf(buf, "%s\n", s->acpi_name); +} + +static ssize_t atk_limit1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atk_sensor_data *s = limit1_to_atk_sensor(attr); + u64 value = s->limit1; + + if (s->type == HWMON_TYPE_TEMP) + value *= 100; + + return sprintf(buf, "%lld\n", value); +} + +static ssize_t atk_limit2_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atk_sensor_data *s = limit2_to_atk_sensor(attr); + u64 value = s->limit2; + + if (s->type == HWMON_TYPE_TEMP) + value *= 100; + + return sprintf(buf, "%lld\n", value); +} + +static ssize_t atk_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "atk0110\n"); +} +static struct device_attribute atk_name_attr = + __ATTR(name, 0444, atk_name_show, NULL); + +static void atk_init_attribute(struct device_attribute *attr, char *name, + sysfs_show_func show) +{ + attr->attr.name = name; + attr->attr.mode = 0444; + attr->show = show; + attr->store = NULL; +} + + +static union acpi_object *atk_get_pack_member(struct atk_data *data, + union acpi_object *pack, + enum atk_pack_member m) +{ + bool old_if = data->old_interface; + int offset; + + switch (m) { + case HWMON_PACK_FLAGS: + offset = old_if ? _HWMON_OLD_PACK_FLAGS : _HWMON_NEW_PACK_FLAGS; + break; + case HWMON_PACK_NAME: + offset = old_if ? _HWMON_OLD_PACK_NAME : _HWMON_NEW_PACK_NAME; + break; + case HWMON_PACK_LIMIT1: + offset = old_if ? _HWMON_OLD_PACK_LIMIT1 : + _HWMON_NEW_PACK_LIMIT1; + break; + case HWMON_PACK_LIMIT2: + offset = old_if ? _HWMON_OLD_PACK_LIMIT2 : + _HWMON_NEW_PACK_LIMIT2; + break; + case HWMON_PACK_ENABLE: + offset = old_if ? _HWMON_OLD_PACK_ENABLE : + _HWMON_NEW_PACK_ENABLE; + break; + default: + return NULL; + } + + return &pack->package.elements[offset]; +} + + +/* New package format is: + * - flag (int) + * class - used for de-muxing the request to the correct GITn + * type (volt, temp, fan) + * sensor id | + * sensor id - used for de-muxing the request _inside_ the GITn + * - name (str) + * - unknown (int) + * - unknown (int) + * - limit1 (int) + * - limit2 (int) + * - enable (int) + * + * The old package has the same format but it's missing the two unknown fields. + */ +static int validate_hwmon_pack(struct atk_data *data, union acpi_object *obj) +{ + struct device *dev = &data->acpi_dev->dev; + union acpi_object *tmp; + bool old_if = data->old_interface; + int const expected_size = old_if ? _HWMON_OLD_PACK_SIZE : + _HWMON_NEW_PACK_SIZE; + + if (obj->type != ACPI_TYPE_PACKAGE) { + dev_warn(dev, "Invalid type: %d\n", obj->type); + return -EINVAL; + } + + if (obj->package.count != expected_size) { + dev_warn(dev, "Invalid package size: %d, expected: %d\n", + obj->package.count, expected_size); + return -EINVAL; + } + + tmp = atk_get_pack_member(data, obj, HWMON_PACK_FLAGS); + if (tmp->type != ACPI_TYPE_INTEGER) { + dev_warn(dev, "Invalid type (flag): %d\n", tmp->type); + return -EINVAL; + } + + tmp = atk_get_pack_member(data, obj, HWMON_PACK_NAME); + if (tmp->type != ACPI_TYPE_STRING) { + dev_warn(dev, "Invalid type (name): %d\n", tmp->type); + return -EINVAL; + } + + /* Don't check... we don't know what they're useful for anyway */ +#if 0 + tmp = &obj->package.elements[HWMON_PACK_UNK1]; + if (tmp->type != ACPI_TYPE_INTEGER) { + dev_warn(dev, "Invalid type (unk1): %d\n", tmp->type); + return -EINVAL; + } + + tmp = &obj->package.elements[HWMON_PACK_UNK2]; + if (tmp->type != ACPI_TYPE_INTEGER) { + dev_warn(dev, "Invalid type (unk2): %d\n", tmp->type); + return -EINVAL; + } +#endif + + tmp = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT1); + if (tmp->type != ACPI_TYPE_INTEGER) { + dev_warn(dev, "Invalid type (limit1): %d\n", tmp->type); + return -EINVAL; + } + + tmp = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT2); + if (tmp->type != ACPI_TYPE_INTEGER) { + dev_warn(dev, "Invalid type (limit2): %d\n", tmp->type); + return -EINVAL; + } + + tmp = atk_get_pack_member(data, obj, HWMON_PACK_ENABLE); + if (tmp->type != ACPI_TYPE_INTEGER) { + dev_warn(dev, "Invalid type (enable): %d\n", tmp->type); + return -EINVAL; + } + + atk_print_sensor(data, obj); + + return 0; +} + +static char const *atk_sensor_type(union acpi_object *flags) +{ + u64 type = flags->integer.value & ATK_TYPE_MASK; + char const *what; + + switch (type) { + case HWMON_TYPE_VOLT: + what = "voltage"; + break; + case HWMON_TYPE_TEMP: + what = "temperature"; + break; + case HWMON_TYPE_FAN: + what = "fan"; + break; + default: + what = "unknown"; + break; + } + + return what; +} + +static void atk_print_sensor(struct atk_data *data, union acpi_object *obj) +{ +#ifdef DEBUG + struct device *dev = &data->acpi_dev->dev; + union acpi_object *flags; + union acpi_object *name; + union acpi_object *limit1; + union acpi_object *limit2; + union acpi_object *enable; + char const *what; + + flags = atk_get_pack_member(data, obj, HWMON_PACK_FLAGS); + name = atk_get_pack_member(data, obj, HWMON_PACK_NAME); + limit1 = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT1); + limit2 = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT2); + enable = atk_get_pack_member(data, obj, HWMON_PACK_ENABLE); + + what = atk_sensor_type(flags); + + dev_dbg(dev, "%s: %#llx %s [%llu-%llu] %s\n", what, + flags->integer.value, + name->string.pointer, + limit1->integer.value, limit2->integer.value, + enable->integer.value ? "enabled" : "disabled"); +#endif +} + +static int atk_read_value_old(struct atk_sensor_data *sensor, u64 *value) +{ + struct atk_data *data = sensor->data; + struct device *dev = &data->acpi_dev->dev; + struct acpi_object_list params; + union acpi_object id; + acpi_status status; + acpi_handle method; + + switch (sensor->type) { + case HWMON_TYPE_VOLT: + method = data->rvlt_handle; + break; + case HWMON_TYPE_TEMP: + method = data->rtmp_handle; + break; + case HWMON_TYPE_FAN: + method = data->rfan_handle; + break; + default: + return -EINVAL; + } + + id.type = ACPI_TYPE_INTEGER; + id.integer.value = sensor->id; + + params.count = 1; + params.pointer = &id; + + status = acpi_evaluate_integer(method, NULL, ¶ms, value); + if (status != AE_OK) { + dev_warn(dev, "%s: ACPI exception: %s\n", __func__, + acpi_format_exception(status)); + return -EIO; + } + + return 0; +} + +static int atk_read_value_new(struct atk_sensor_data *sensor, u64 *value) +{ + struct atk_data *data = sensor->data; + struct device *dev = &data->acpi_dev->dev; + struct acpi_object_list params; + struct acpi_buffer ret; + union acpi_object id; + struct atk_acpi_buffer_u64 tmp; + acpi_status status; + + id.type = ACPI_TYPE_INTEGER; + id.integer.value = sensor->id; + + params.count = 1; + params.pointer = &id; + + tmp.buf.type = ACPI_TYPE_BUFFER; + tmp.buf.buffer.pointer = (u8 *)&tmp.value; + tmp.buf.buffer.length = sizeof(u64); + ret.length = sizeof(tmp); + ret.pointer = &tmp; + + status = acpi_evaluate_object_typed(data->read_handle, NULL, ¶ms, + &ret, ACPI_TYPE_BUFFER); + if (status != AE_OK) { + dev_warn(dev, "%s: ACPI exception: %s\n", __func__, + acpi_format_exception(status)); + return -EIO; + } + + /* Return buffer format: + * [0-3] "value" is valid flag + * [4-7] value + */ + if (!(tmp.value & 0xffffffff)) { + /* The reading is not valid, possible causes: + * - sensor failure + * - enumeration was FUBAR (and we didn't notice) + */ + dev_info(dev, "Failure: %#llx\n", tmp.value); + return -EIO; + } + + *value = (tmp.value & 0xffffffff00000000ULL) >> 32; + + return 0; +} + +static int atk_read_value(struct atk_sensor_data *sensor, u64 *value) +{ + int err; + + if (!sensor->is_valid || + time_after(jiffies, sensor->last_updated + CACHE_TIME)) { + if (sensor->data->old_interface) + err = atk_read_value_old(sensor, value); + else + err = atk_read_value_new(sensor, value); + + sensor->is_valid = true; + sensor->last_updated = jiffies; + sensor->cached_value = *value; + } else { + *value = sensor->cached_value; + err = 0; + } + + return err; +} + +static int atk_add_sensor(struct atk_data *data, union acpi_object *obj) +{ + struct device *dev = &data->acpi_dev->dev; + union acpi_object *flags; + union acpi_object *name; + union acpi_object *limit1; + union acpi_object *limit2; + union acpi_object *enable; + struct atk_sensor_data *sensor; + char const *base_name; + char const *limit1_name; + char const *limit2_name; + u64 type; + int err; + int *num; + int start; + + if (obj->type != ACPI_TYPE_PACKAGE) { + /* wft is this? */ + dev_warn(dev, "Unknown type for ACPI object: (%d)\n", + obj->type); + return -EINVAL; + } + + err = validate_hwmon_pack(data, obj); + if (err) + return err; + + /* Ok, we have a valid hwmon package */ + type = atk_get_pack_member(data, obj, HWMON_PACK_FLAGS)->integer.value + & ATK_TYPE_MASK; + + switch (type) { + case HWMON_TYPE_VOLT: + base_name = "in"; + limit1_name = "min"; + limit2_name = "max"; + num = &data->voltage_count; + start = 0; + break; + case HWMON_TYPE_TEMP: + base_name = "temp"; + limit1_name = "max"; + limit2_name = "crit"; + num = &data->temperature_count; + start = 1; + break; + case HWMON_TYPE_FAN: + base_name = "fan"; + limit1_name = "min"; + limit2_name = "max"; + num = &data->fan_count; + start = 1; + break; + default: + dev_warn(dev, "Unknown sensor type: %#llx\n", type); + return -EINVAL; + } + + enable = atk_get_pack_member(data, obj, HWMON_PACK_ENABLE); + if (!enable->integer.value) + /* sensor is disabled */ + return 0; + + flags = atk_get_pack_member(data, obj, HWMON_PACK_FLAGS); + name = atk_get_pack_member(data, obj, HWMON_PACK_NAME); + limit1 = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT1); + limit2 = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT2); + + sensor = kzalloc(sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->acpi_name = kstrdup(name->string.pointer, GFP_KERNEL); + if (!sensor->acpi_name) { + err = -ENOMEM; + goto out; + } + + INIT_LIST_HEAD(&sensor->list); + sensor->type = type; + sensor->data = data; + sensor->id = flags->integer.value; + sensor->limit1 = limit1->integer.value; + sensor->limit2 = limit2->integer.value; + + snprintf(sensor->input_attr_name, ATTR_NAME_SIZE, + "%s%d_input", base_name, start + *num); + atk_init_attribute(&sensor->input_attr, + sensor->input_attr_name, + atk_input_show); + + snprintf(sensor->label_attr_name, ATTR_NAME_SIZE, + "%s%d_label", base_name, start + *num); + atk_init_attribute(&sensor->label_attr, + sensor->label_attr_name, + atk_label_show); + + snprintf(sensor->limit1_attr_name, ATTR_NAME_SIZE, + "%s%d_%s", base_name, start + *num, limit1_name); + atk_init_attribute(&sensor->limit1_attr, + sensor->limit1_attr_name, + atk_limit1_show); + + snprintf(sensor->limit2_attr_name, ATTR_NAME_SIZE, + "%s%d_%s", base_name, start + *num, limit2_name); + atk_init_attribute(&sensor->limit2_attr, + sensor->limit2_attr_name, + atk_limit2_show); + + list_add(&sensor->list, &data->sensor_list); + (*num)++; + + return 1; +out: + kfree(sensor->acpi_name); + kfree(sensor); + return err; +} + +static int atk_enumerate_old_hwmon(struct atk_data *data) +{ + struct device *dev = &data->acpi_dev->dev; + struct acpi_buffer buf; + union acpi_object *pack; + acpi_status status; + int i, ret; + int count = 0; + + /* Voltages */ + buf.length = ACPI_ALLOCATE_BUFFER; + status = acpi_evaluate_object_typed(data->atk_handle, + METHOD_OLD_ENUM_VLT, NULL, &buf, ACPI_TYPE_PACKAGE); + if (status != AE_OK) { + dev_warn(dev, METHOD_OLD_ENUM_VLT ": ACPI exception: %s\n", + acpi_format_exception(status)); + + return -ENODEV; + } + + pack = buf.pointer; + for (i = 1; i < pack->package.count; i++) { + union acpi_object *obj = &pack->package.elements[i]; + + ret = atk_add_sensor(data, obj); + if (ret > 0) + count++; + } + ACPI_FREE(buf.pointer); + + /* Temperatures */ + buf.length = ACPI_ALLOCATE_BUFFER; + status = acpi_evaluate_object_typed(data->atk_handle, + METHOD_OLD_ENUM_TMP, NULL, &buf, ACPI_TYPE_PACKAGE); + if (status != AE_OK) { + dev_warn(dev, METHOD_OLD_ENUM_TMP ": ACPI exception: %s\n", + acpi_format_exception(status)); + + ret = -ENODEV; + goto cleanup; + } + + pack = buf.pointer; + for (i = 1; i < pack->package.count; i++) { + union acpi_object *obj = &pack->package.elements[i]; + + ret = atk_add_sensor(data, obj); + if (ret > 0) + count++; + } + ACPI_FREE(buf.pointer); + + /* Fans */ + buf.length = ACPI_ALLOCATE_BUFFER; + status = acpi_evaluate_object_typed(data->atk_handle, + METHOD_OLD_ENUM_FAN, NULL, &buf, ACPI_TYPE_PACKAGE); + if (status != AE_OK) { + dev_warn(dev, METHOD_OLD_ENUM_FAN ": ACPI exception: %s\n", + acpi_format_exception(status)); + + ret = -ENODEV; + goto cleanup; + } + + pack = buf.pointer; + for (i = 1; i < pack->package.count; i++) { + union acpi_object *obj = &pack->package.elements[i]; + + ret = atk_add_sensor(data, obj); + if (ret > 0) + count++; + } + ACPI_FREE(buf.pointer); + + return count; +cleanup: + atk_free_sensors(data); + return ret; +} + +static int atk_enumerate_new_hwmon(struct atk_data *data) +{ + struct device *dev = &data->acpi_dev->dev; + struct acpi_buffer buf; + acpi_status ret; + struct acpi_object_list params; + union acpi_object id; + union acpi_object *pack; + int err; + int i; + + dev_dbg(dev, "Enumerating hwmon sensors\n"); + + id.type = ACPI_TYPE_INTEGER; + id.integer.value = ATK_MUX_HWMON; + params.count = 1; + params.pointer = &id; + + buf.length = ACPI_ALLOCATE_BUFFER; + ret = acpi_evaluate_object_typed(data->enumerate_handle, NULL, ¶ms, + &buf, ACPI_TYPE_PACKAGE); + if (ret != AE_OK) { + dev_warn(dev, METHOD_ENUMERATE ": ACPI exception: %s\n", + acpi_format_exception(ret)); + return -ENODEV; + } + + /* Result must be a package */ + pack = buf.pointer; + + if (pack->package.count < 1) { + dev_dbg(dev, "%s: hwmon package is too small: %d\n", __func__, + pack->package.count); + err = -EINVAL; + goto out; + } + + for (i = 0; i < pack->package.count; i++) { + union acpi_object *obj = &pack->package.elements[i]; + + atk_add_sensor(data, obj); + } + + err = data->voltage_count + data->temperature_count + data->fan_count; + +out: + ACPI_FREE(buf.pointer); + return err; +} + +static int atk_create_files(struct atk_data *data) +{ + struct atk_sensor_data *s; + int err; + + list_for_each_entry(s, &data->sensor_list, list) { + err = device_create_file(data->hwmon_dev, &s->input_attr); + if (err) + return err; + err = device_create_file(data->hwmon_dev, &s->label_attr); + if (err) + return err; + err = device_create_file(data->hwmon_dev, &s->limit1_attr); + if (err) + return err; + err = device_create_file(data->hwmon_dev, &s->limit2_attr); + if (err) + return err; + } + + err = device_create_file(data->hwmon_dev, &atk_name_attr); + + return err; +} + +static void atk_remove_files(struct atk_data *data) +{ + struct atk_sensor_data *s; + + list_for_each_entry(s, &data->sensor_list, list) { + device_remove_file(data->hwmon_dev, &s->input_attr); + device_remove_file(data->hwmon_dev, &s->label_attr); + device_remove_file(data->hwmon_dev, &s->limit1_attr); + device_remove_file(data->hwmon_dev, &s->limit2_attr); + } + device_remove_file(data->hwmon_dev, &atk_name_attr); +} + +static void atk_free_sensors(struct atk_data *data) +{ + struct list_head *head = &data->sensor_list; + struct atk_sensor_data *s, *tmp; + + list_for_each_entry_safe(s, tmp, head, list) { + kfree(s->acpi_name); + kfree(s); + } +} + +static int atk_register_hwmon(struct atk_data *data) +{ + struct device *dev = &data->acpi_dev->dev; + int err; + + dev_dbg(dev, "registering hwmon device\n"); + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) + return PTR_ERR(data->hwmon_dev); + + dev_dbg(dev, "populating sysfs directory\n"); + err = atk_create_files(data); + if (err) + goto remove; + + return 0; +remove: + /* Cleanup the registered files */ + atk_remove_files(data); + hwmon_device_unregister(data->hwmon_dev); + return err; +} + +static int atk_check_old_if(struct atk_data *data) +{ + struct device *dev = &data->acpi_dev->dev; + acpi_handle ret; + acpi_status status; + + /* RTMP: read temperature */ + status = acpi_get_handle(data->atk_handle, METHOD_OLD_READ_TMP, &ret); + if (status != AE_OK) { + dev_dbg(dev, "method " METHOD_OLD_READ_TMP " not found: %s\n", + acpi_format_exception(status)); + return -ENODEV; + } + data->rtmp_handle = ret; + + /* RVLT: read voltage */ + status = acpi_get_handle(data->atk_handle, METHOD_OLD_READ_VLT, &ret); + if (status != AE_OK) { + dev_dbg(dev, "method " METHOD_OLD_READ_VLT " not found: %s\n", + acpi_format_exception(status)); + return -ENODEV; + } + data->rvlt_handle = ret; + + /* RFAN: read fan status */ + status = acpi_get_handle(data->atk_handle, METHOD_OLD_READ_FAN, &ret); + if (status != AE_OK) { + dev_dbg(dev, "method " METHOD_OLD_READ_FAN " not found: %s\n", + acpi_format_exception(status)); + return -ENODEV; + } + data->rfan_handle = ret; + + return 0; +} + +static int atk_check_new_if(struct atk_data *data) +{ + struct device *dev = &data->acpi_dev->dev; + acpi_handle ret; + acpi_status status; + + /* Enumeration */ + status = acpi_get_handle(data->atk_handle, METHOD_ENUMERATE, &ret); + if (status != AE_OK) { + dev_dbg(dev, "method " METHOD_ENUMERATE " not found: %s\n", + acpi_format_exception(status)); + return -ENODEV; + } + data->enumerate_handle = ret; + + /* De-multiplexer (read) */ + status = acpi_get_handle(data->atk_handle, METHOD_READ, &ret); + if (status != AE_OK) { + dev_dbg(dev, "method " METHOD_READ " not found: %s\n", + acpi_format_exception(status)); + return -ENODEV; + } + data->read_handle = ret; + + return 0; +} + +static int atk_add(struct acpi_device *device) +{ + acpi_status ret; + int err; + struct acpi_buffer buf; + union acpi_object *obj; + struct atk_data *data; + + dev_dbg(&device->dev, "adding...\n"); + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->acpi_dev = device; + data->atk_handle = device->handle; + INIT_LIST_HEAD(&data->sensor_list); + + buf.length = ACPI_ALLOCATE_BUFFER; + ret = acpi_evaluate_object_typed(data->atk_handle, BOARD_ID, NULL, + &buf, ACPI_TYPE_PACKAGE); + if (ret != AE_OK) { + dev_dbg(&device->dev, "atk: method MBIF not found\n"); + err = -ENODEV; + goto out; + } + + obj = buf.pointer; + if (obj->package.count >= 2 && + obj->package.elements[1].type == ACPI_TYPE_STRING) { + dev_dbg(&device->dev, "board ID = %s\n", + obj->package.elements[1].string.pointer); + } + ACPI_FREE(buf.pointer); + + /* Check for hwmon methods: first check "old" style methods; note that + * both may be present: in this case we stick to the old interface; + * analysis of multiple DSDTs indicates that when both interfaces + * are present the new one (GGRP/GITM) is not functional. + */ + err = atk_check_old_if(data); + if (!err) { + dev_dbg(&device->dev, "Using old hwmon interface\n"); + data->old_interface = true; + } else { + err = atk_check_new_if(data); + if (err) + goto out; + + dev_dbg(&device->dev, "Using new hwmon interface\n"); + data->old_interface = false; + } + + if (data->old_interface) + err = atk_enumerate_old_hwmon(data); + else + err = atk_enumerate_new_hwmon(data); + if (err < 0) + goto out; + if (err == 0) { + dev_info(&device->dev, + "No usable sensor detected, bailing out\n"); + err = -ENODEV; + goto out; + } + + err = atk_register_hwmon(data); + if (err) + goto cleanup; + + device->driver_data = data; + return 0; +cleanup: + atk_free_sensors(data); +out: + kfree(data); + return err; +} + +static int atk_remove(struct acpi_device *device, int type) +{ + struct atk_data *data = device->driver_data; + dev_dbg(&device->dev, "removing...\n"); + + device->driver_data = NULL; + + atk_remove_files(data); + atk_free_sensors(data); + hwmon_device_unregister(data->hwmon_dev); + + kfree(data); + + return 0; +} + +static int __init atk0110_init(void) +{ + int ret; + + ret = acpi_bus_register_driver(&atk_driver); + if (ret) + pr_info("atk: acpi_bus_register_driver failed: %d\n", ret); + + return ret; +} + +static void __exit atk0110_exit(void) +{ + acpi_bus_unregister_driver(&atk_driver); +} + +module_init(atk0110_init); +module_exit(atk0110_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/g760a.c b/drivers/hwmon/g760a.c new file mode 100644 index 00000000000..19c01a49f6b --- /dev/null +++ b/drivers/hwmon/g760a.c @@ -0,0 +1,272 @@ +/* + g760a - Driver for the Global Mixed-mode Technology Inc. G760A + fan speed PWM controller chip + + Copyright (C) 2007 Herbert Valerio Riedel <hvr@gnu.org> + + Complete datasheet is available at GMT's website: + http://www.gmt.com.tw/datasheet/g760a.pdf + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/sysfs.h> + +static const struct i2c_device_id g760a_id[] = { + { "g760a", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, g760a_id); + +enum g760a_regs { + G760A_REG_SET_CNT = 0x00, + G760A_REG_ACT_CNT = 0x01, + G760A_REG_FAN_STA = 0x02 +}; + +#define G760A_REG_FAN_STA_RPM_OFF 0x1 /* +/-20% off */ +#define G760A_REG_FAN_STA_RPM_LOW 0x2 /* below 1920rpm */ + +/* register data is read (and cached) at most once per second */ +#define G760A_UPDATE_INTERVAL (HZ) + +struct g760a_data { + struct i2c_client *client; + struct device *hwmon_dev; + struct mutex update_lock; + + /* board specific parameters */ + u32 clk; /* default 32kHz */ + u16 fan_div; /* default P=2 */ + + /* g760a register cache */ + unsigned int valid:1; + unsigned long last_updated; /* In jiffies */ + + u8 set_cnt; /* PWM (period) count number; 0xff stops fan */ + u8 act_cnt; /* formula: cnt = (CLK * 30)/(rpm * P) */ + u8 fan_sta; /* bit 0: set when actual fan speed more than 20% + * outside requested fan speed + * bit 1: set when fan speed below 1920 rpm */ +}; + +#define G760A_DEFAULT_CLK 32768 +#define G760A_DEFAULT_FAN_DIV 2 + +#define PWM_FROM_CNT(cnt) (0xff-(cnt)) +#define PWM_TO_CNT(pwm) (0xff-(pwm)) + +unsigned int rpm_from_cnt(u8 val, u32 clk, u16 div) +{ + return ((val == 0x00) ? 0 : ((clk*30)/(val*div))); +} + +/* new-style driver model */ +static int g760a_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int g760a_remove(struct i2c_client *client); + +static struct i2c_driver g760a_driver = { + .driver = { + .name = "g760a", + }, + .probe = g760a_probe, + .remove = g760a_remove, + .id_table = g760a_id, +}; + +/* read/write wrappers */ +static int g760a_read_value(struct i2c_client *client, enum g760a_regs reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int g760a_write_value(struct i2c_client *client, enum g760a_regs reg, + u16 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +/**************************************************************************** + * sysfs attributes + */ + +static struct g760a_data *g760a_update_client(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct g760a_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + G760A_UPDATE_INTERVAL) + || !data->valid) { + dev_dbg(&client->dev, "Starting g760a update\n"); + + data->set_cnt = g760a_read_value(client, G760A_REG_SET_CNT); + data->act_cnt = g760a_read_value(client, G760A_REG_ACT_CNT); + data->fan_sta = g760a_read_value(client, G760A_REG_FAN_STA); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static ssize_t show_fan(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct g760a_data *data = g760a_update_client(dev); + unsigned int rpm = 0; + + mutex_lock(&data->update_lock); + if (!(data->fan_sta & G760A_REG_FAN_STA_RPM_LOW)) + rpm = rpm_from_cnt(data->act_cnt, data->clk, data->fan_div); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", rpm); +} + +static ssize_t show_fan_alarm(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct g760a_data *data = g760a_update_client(dev); + + int fan_alarm = (data->fan_sta & G760A_REG_FAN_STA_RPM_OFF) ? 1 : 0; + + return sprintf(buf, "%d\n", fan_alarm); +} + +static ssize_t get_pwm(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct g760a_data *data = g760a_update_client(dev); + + return sprintf(buf, "%d\n", PWM_FROM_CNT(data->set_cnt)); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct g760a_data *data = g760a_update_client(dev); + unsigned long val; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->set_cnt = PWM_TO_CNT(SENSORS_LIMIT(val, 0, 255)); + g760a_write_value(client, G760A_REG_SET_CNT, data->set_cnt); + mutex_unlock(&data->update_lock); + + return count; +} + +static DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm, set_pwm); +static DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL); +static DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL); + +static struct attribute *g760a_attributes[] = { + &dev_attr_pwm1.attr, + &dev_attr_fan1_input.attr, + &dev_attr_fan1_alarm.attr, + NULL +}; + +static const struct attribute_group g760a_group = { + .attrs = g760a_attributes, +}; + +/**************************************************************************** + * new-style driver model code + */ + +static int g760a_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct g760a_data *data; + int err; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + data = kzalloc(sizeof(struct g760a_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + + data->client = client; + mutex_init(&data->update_lock); + + /* setup default configuration for now */ + data->fan_div = G760A_DEFAULT_FAN_DIV; + data->clk = G760A_DEFAULT_CLK; + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &g760a_group); + if (err) + goto error_sysfs_create_group; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto error_hwmon_device_register; + } + + return 0; + +error_hwmon_device_register: + sysfs_remove_group(&client->dev.kobj, &g760a_group); +error_sysfs_create_group: + kfree(data); + i2c_set_clientdata(client, NULL); + + return err; +} + +static int g760a_remove(struct i2c_client *client) +{ + struct g760a_data *data = i2c_get_clientdata(client); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &g760a_group); + kfree(data); + i2c_set_clientdata(client, NULL); + + return 0; +} + +/* module management */ + +static int __init g760a_init(void) +{ + return i2c_add_driver(&g760a_driver); +} + +static void __exit g760a_exit(void) +{ + i2c_del_driver(&g760a_driver); +} + +MODULE_AUTHOR("Herbert Valerio Riedel <hvr@gnu.org>"); +MODULE_DESCRIPTION("GMT G760A driver"); +MODULE_LICENSE("GPL"); + +module_init(g760a_init); +module_exit(g760a_exit); diff --git a/drivers/hwmon/hp_accel.c b/drivers/hwmon/hp_accel.c index 55d3dc565be..abca7e9f953 100644 --- a/drivers/hwmon/hp_accel.c +++ b/drivers/hwmon/hp_accel.c @@ -34,7 +34,6 @@ #include <linux/wait.h> #include <linux/poll.h> #include <linux/freezer.h> -#include <linux/version.h> #include <linux/uaccess.h> #include <linux/leds.h> #include <acpi/acpi_drivers.h> diff --git a/drivers/hwmon/lm95241.c b/drivers/hwmon/lm95241.c index 091d95f38aa..e34f9e402a2 100644 --- a/drivers/hwmon/lm95241.c +++ b/drivers/hwmon/lm95241.c @@ -87,25 +87,11 @@ I2C_CLIENT_INSMOD_1(lm95241); (val_h)) * 1000 + (val_l) * 1000 / 256) /* Functions declaration */ -static int lm95241_attach_adapter(struct i2c_adapter *adapter); -static int lm95241_detect(struct i2c_adapter *adapter, int address, - int kind); static void lm95241_init_client(struct i2c_client *client); -static int lm95241_detach_client(struct i2c_client *client); static struct lm95241_data *lm95241_update_device(struct device *dev); -/* Driver data (common to all clients) */ -static struct i2c_driver lm95241_driver = { - .driver = { - .name = "lm95241", - }, - .attach_adapter = lm95241_attach_adapter, - .detach_client = lm95241_detach_client, -}; - /* Client data (each client gets its own) */ struct lm95241_data { - struct i2c_client client; struct device *hwmon_dev; struct mutex update_lock; unsigned long last_updated, rate; /* in jiffies */ @@ -323,42 +309,16 @@ static const struct attribute_group lm95241_group = { .attrs = lm95241_attributes, }; -/* Init/exit code */ -static int lm95241_attach_adapter(struct i2c_adapter *adapter) +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int lm95241_detect(struct i2c_client *new_client, int kind, + struct i2c_board_info *info) { - if (!(adapter->class & I2C_CLASS_HWMON)) - return 0; - return i2c_probe(adapter, &addr_data, lm95241_detect); -} - -/* - * The following function does more than just detection. If detection - * succeeds, it also registers the new chip. - */ -static int lm95241_detect(struct i2c_adapter *adapter, int address, int kind) -{ - struct i2c_client *new_client; - struct lm95241_data *data; - int err = 0; + struct i2c_adapter *adapter = new_client->adapter; + int address = new_client->addr; const char *name = ""; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) - goto exit; - - data = kzalloc(sizeof(struct lm95241_data), GFP_KERNEL); - if (!data) { - err = -ENOMEM; - goto exit; - } - - /* The common I2C client data is placed right before the - LM95241-specific data. */ - new_client = &data->client; - i2c_set_clientdata(new_client, data); - new_client->addr = address; - new_client->adapter = adapter; - new_client->driver = &lm95241_driver; - new_client->flags = 0; + return -ENODEV; /* * Now we do the remaining detection. A negative kind means that @@ -378,7 +338,7 @@ static int lm95241_detect(struct i2c_adapter *adapter, int address, int kind) dev_dbg(&adapter->dev, "LM95241 detection failed at 0x%02x.\n", address); - goto exit_free; + return -ENODEV; } } @@ -392,23 +352,32 @@ static int lm95241_detect(struct i2c_adapter *adapter, int address, int kind) if (kind <= 0) { /* identification failed */ dev_info(&adapter->dev, "Unsupported chip\n"); - goto exit_free; + return -ENODEV; } } } + /* Fill the i2c board info */ if (kind == lm95241) name = "lm95241"; + strlcpy(info->type, name, I2C_NAME_SIZE); + return 0; +} - /* We can fill in the remaining client fields */ - strlcpy(new_client->name, name, I2C_NAME_SIZE); - data->valid = 0; - mutex_init(&data->update_lock); +static int lm95241_probe(struct i2c_client *new_client, + const struct i2c_device_id *id) +{ + struct lm95241_data *data; + int err; - /* Tell the I2C layer a new client has arrived */ - err = i2c_attach_client(new_client); - if (err) - goto exit_free; + data = kzalloc(sizeof(struct lm95241_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(new_client, data); + mutex_init(&data->update_lock); /* Initialize the LM95241 chip */ lm95241_init_client(new_client); @@ -416,7 +385,7 @@ static int lm95241_detect(struct i2c_adapter *adapter, int address, int kind) /* Register sysfs hooks */ err = sysfs_create_group(&new_client->dev.kobj, &lm95241_group); if (err) - goto exit_detach; + goto exit_free; data->hwmon_dev = hwmon_device_register(&new_client->dev); if (IS_ERR(data->hwmon_dev)) { @@ -428,8 +397,6 @@ static int lm95241_detect(struct i2c_adapter *adapter, int address, int kind) exit_remove_files: sysfs_remove_group(&new_client->dev.kobj, &lm95241_group); -exit_detach: - i2c_detach_client(new_client); exit_free: kfree(data); exit: @@ -456,18 +423,14 @@ static void lm95241_init_client(struct i2c_client *client) data->model); } -static int lm95241_detach_client(struct i2c_client *client) +static int lm95241_remove(struct i2c_client *client) { struct lm95241_data *data = i2c_get_clientdata(client); - int err; hwmon_device_unregister(data->hwmon_dev); sysfs_remove_group(&client->dev.kobj, &lm95241_group); - err = i2c_detach_client(client); - if (err) - return err; - + i2c_set_clientdata(client, NULL); kfree(data); return 0; } @@ -509,6 +472,25 @@ static struct lm95241_data *lm95241_update_device(struct device *dev) return data; } +/* Driver data (common to all clients) */ +static const struct i2c_device_id lm95241_id[] = { + { "lm95241", lm95241 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm95241_id); + +static struct i2c_driver lm95241_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm95241", + }, + .probe = lm95241_probe, + .remove = lm95241_remove, + .id_table = lm95241_id, + .detect = lm95241_detect, + .address_data = &addr_data, +}; + static int __init sensors_lm95241_init(void) { return i2c_add_driver(&lm95241_driver); diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c new file mode 100644 index 00000000000..6cbdc2fea73 --- /dev/null +++ b/drivers/hwmon/sht15.c @@ -0,0 +1,692 @@ +/* + * sht15.c - support for the SHT15 Temperature and Humidity Sensor + * + * Copyright (c) 2009 Jonathan Cameron + * + * Copyright (c) 2007 Wouter Horre + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Currently ignoring checksum on readings. + * Default resolution only (14bit temp, 12bit humidity) + * Ignoring battery status. + * Heater not enabled. + * Timings are all conservative. + * + * Data sheet available (1/2009) at + * http://www.sensirion.ch/en/pdf/product_information/Datasheet-humidity-sensor-SHT1x.pdf + * + * Regulator supply name = vcc + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/jiffies.h> +#include <linux/err.h> +#include <linux/sht15.h> +#include <linux/regulator/consumer.h> +#include <asm/atomic.h> + +#define SHT15_MEASURE_TEMP 3 +#define SHT15_MEASURE_RH 5 + +#define SHT15_READING_NOTHING 0 +#define SHT15_READING_TEMP 1 +#define SHT15_READING_HUMID 2 + +/* Min timings in nsecs */ +#define SHT15_TSCKL 100 /* clock low */ +#define SHT15_TSCKH 100 /* clock high */ +#define SHT15_TSU 150 /* data setup time */ + +/** + * struct sht15_temppair - elements of voltage dependant temp calc + * @vdd: supply voltage in microvolts + * @d1: see data sheet + */ +struct sht15_temppair { + int vdd; /* microvolts */ + int d1; +}; + +/* Table 9 from data sheet - relates temperature calculation + * to supply voltage. + */ +static const struct sht15_temppair temppoints[] = { + { 2500000, -39400 }, + { 3000000, -39600 }, + { 3500000, -39700 }, + { 4000000, -39800 }, + { 5000000, -40100 }, +}; + +/** + * struct sht15_data - device instance specific data + * @pdata: platform data (gpio's etc) + * @read_work: bh of interrupt handler + * @wait_queue: wait queue for getting values from device + * @val_temp: last temperature value read from device + * @val_humid: last humidity value read from device + * @flag: status flag used to identify what the last request was + * @valid: are the current stored values valid (start condition) + * @last_updat: time of last update + * @read_lock: mutex to ensure only one read in progress + * at a time. + * @dev: associate device structure + * @hwmon_dev: device associated with hwmon subsystem + * @reg: associated regulator (if specified) + * @nb: notifier block to handle notifications of voltage changes + * @supply_uV: local copy of supply voltage used to allow + * use of regulator consumer if available + * @supply_uV_valid: indicates that an updated value has not yet + * been obtained from the regulator and so any calculations + * based upon it will be invalid. + * @update_supply_work: work struct that is used to update the supply_uV + * @interrupt_handled: flag used to indicate a hander has been scheduled + */ +struct sht15_data { + struct sht15_platform_data *pdata; + struct work_struct read_work; + wait_queue_head_t wait_queue; + uint16_t val_temp; + uint16_t val_humid; + u8 flag; + u8 valid; + unsigned long last_updat; + struct mutex read_lock; + struct device *dev; + struct device *hwmon_dev; + struct regulator *reg; + struct notifier_block nb; + int supply_uV; + int supply_uV_valid; + struct work_struct update_supply_work; + atomic_t interrupt_handled; +}; + +/** + * sht15_connection_reset() - reset the comms interface + * @data: sht15 specific data + * + * This implements section 3.4 of the data sheet + */ +static void sht15_connection_reset(struct sht15_data *data) +{ + int i; + gpio_direction_output(data->pdata->gpio_data, 1); + ndelay(SHT15_TSCKL); + gpio_set_value(data->pdata->gpio_sck, 0); + ndelay(SHT15_TSCKL); + for (i = 0; i < 9; ++i) { + gpio_set_value(data->pdata->gpio_sck, 1); + ndelay(SHT15_TSCKH); + gpio_set_value(data->pdata->gpio_sck, 0); + ndelay(SHT15_TSCKL); + } +} +/** + * sht15_send_bit() - send an individual bit to the device + * @data: device state data + * @val: value of bit to be sent + **/ +static inline void sht15_send_bit(struct sht15_data *data, int val) +{ + + gpio_set_value(data->pdata->gpio_data, val); + ndelay(SHT15_TSU); + gpio_set_value(data->pdata->gpio_sck, 1); + ndelay(SHT15_TSCKH); + gpio_set_value(data->pdata->gpio_sck, 0); + ndelay(SHT15_TSCKL); /* clock low time */ +} + +/** + * sht15_transmission_start() - specific sequence for new transmission + * + * @data: device state data + * Timings for this are not documented on the data sheet, so very + * conservative ones used in implementation. This implements + * figure 12 on the data sheet. + **/ +static void sht15_transmission_start(struct sht15_data *data) +{ + /* ensure data is high and output */ + gpio_direction_output(data->pdata->gpio_data, 1); + ndelay(SHT15_TSU); + gpio_set_value(data->pdata->gpio_sck, 0); + ndelay(SHT15_TSCKL); + gpio_set_value(data->pdata->gpio_sck, 1); + ndelay(SHT15_TSCKH); + gpio_set_value(data->pdata->gpio_data, 0); + ndelay(SHT15_TSU); + gpio_set_value(data->pdata->gpio_sck, 0); + ndelay(SHT15_TSCKL); + gpio_set_value(data->pdata->gpio_sck, 1); + ndelay(SHT15_TSCKH); + gpio_set_value(data->pdata->gpio_data, 1); + ndelay(SHT15_TSU); + gpio_set_value(data->pdata->gpio_sck, 0); + ndelay(SHT15_TSCKL); +} +/** + * sht15_send_byte() - send a single byte to the device + * @data: device state + * @byte: value to be sent + **/ +static void sht15_send_byte(struct sht15_data *data, u8 byte) +{ + int i; + for (i = 0; i < 8; i++) { + sht15_send_bit(data, !!(byte & 0x80)); + byte <<= 1; + } +} +/** + * sht15_wait_for_response() - checks for ack from device + * @data: device state + **/ +static int sht15_wait_for_response(struct sht15_data *data) +{ + gpio_direction_input(data->pdata->gpio_data); + gpio_set_value(data->pdata->gpio_sck, 1); + ndelay(SHT15_TSCKH); + if (gpio_get_value(data->pdata->gpio_data)) { + gpio_set_value(data->pdata->gpio_sck, 0); + dev_err(data->dev, "Command not acknowledged\n"); + sht15_connection_reset(data); + return -EIO; + } + gpio_set_value(data->pdata->gpio_sck, 0); + ndelay(SHT15_TSCKL); + return 0; +} + +/** + * sht15_send_cmd() - Sends a command to the device. + * @data: device state + * @cmd: command byte to be sent + * + * On entry, sck is output low, data is output pull high + * and the interrupt disabled. + **/ +static int sht15_send_cmd(struct sht15_data *data, u8 cmd) +{ + int ret = 0; + sht15_transmission_start(data); + sht15_send_byte(data, cmd); + ret = sht15_wait_for_response(data); + return ret; +} +/** + * sht15_update_single_val() - get a new value from device + * @data: device instance specific data + * @command: command sent to request value + * @timeout_msecs: timeout after which comms are assumed + * to have failed are reset. + **/ +static inline int sht15_update_single_val(struct sht15_data *data, + int command, + int timeout_msecs) +{ + int ret; + ret = sht15_send_cmd(data, command); + if (ret) + return ret; + + gpio_direction_input(data->pdata->gpio_data); + atomic_set(&data->interrupt_handled, 0); + + enable_irq(gpio_to_irq(data->pdata->gpio_data)); + if (gpio_get_value(data->pdata->gpio_data) == 0) { + disable_irq_nosync(gpio_to_irq(data->pdata->gpio_data)); + /* Only relevant if the interrupt hasn't occured. */ + if (!atomic_read(&data->interrupt_handled)) + schedule_work(&data->read_work); + } + ret = wait_event_timeout(data->wait_queue, + (data->flag == SHT15_READING_NOTHING), + msecs_to_jiffies(timeout_msecs)); + if (ret == 0) {/* timeout occurred */ + disable_irq_nosync(gpio_to_irq(data->pdata->gpio_data));; + sht15_connection_reset(data); + return -ETIME; + } + return 0; +} + +/** + * sht15_update_vals() - get updated readings from device if too old + * @data: device state + **/ +static int sht15_update_vals(struct sht15_data *data) +{ + int ret = 0; + int timeout = HZ; + + mutex_lock(&data->read_lock); + if (time_after(jiffies, data->last_updat + timeout) + || !data->valid) { + data->flag = SHT15_READING_HUMID; + ret = sht15_update_single_val(data, SHT15_MEASURE_RH, 160); + if (ret) + goto error_ret; + data->flag = SHT15_READING_TEMP; + ret = sht15_update_single_val(data, SHT15_MEASURE_TEMP, 400); + if (ret) + goto error_ret; + data->valid = 1; + data->last_updat = jiffies; + } +error_ret: + mutex_unlock(&data->read_lock); + + return ret; +} + +/** + * sht15_calc_temp() - convert the raw reading to a temperature + * @data: device state + * + * As per section 4.3 of the data sheet. + **/ +static inline int sht15_calc_temp(struct sht15_data *data) +{ + int d1 = 0; + int i; + + for (i = 1; i < ARRAY_SIZE(temppoints) - 1; i++) + /* Find pointer to interpolate */ + if (data->supply_uV > temppoints[i - 1].vdd) { + d1 = (data->supply_uV/1000 - temppoints[i - 1].vdd) + * (temppoints[i].d1 - temppoints[i - 1].d1) + / (temppoints[i].vdd - temppoints[i - 1].vdd) + + temppoints[i - 1].d1; + break; + } + + return data->val_temp*10 + d1; +} + +/** + * sht15_calc_humid() - using last temperature convert raw to humid + * @data: device state + * + * This is the temperature compensated version as per section 4.2 of + * the data sheet. + **/ +static inline int sht15_calc_humid(struct sht15_data *data) +{ + int RHlinear; /* milli percent */ + int temp = sht15_calc_temp(data); + + const int c1 = -4; + const int c2 = 40500; /* x 10 ^ -6 */ + const int c3 = 2800; /* x10 ^ -9 */ + + RHlinear = c1*1000 + + c2 * data->val_humid/1000 + + (data->val_humid * data->val_humid * c3)/1000000; + return (temp - 25000) * (10000 + 800 * data->val_humid) + / 1000000 + RHlinear; +} + +static ssize_t sht15_show_temp(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + struct sht15_data *data = dev_get_drvdata(dev); + + /* Technically no need to read humidity as well */ + ret = sht15_update_vals(data); + + return ret ? ret : sprintf(buf, "%d\n", + sht15_calc_temp(data)); +} + +static ssize_t sht15_show_humidity(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + struct sht15_data *data = dev_get_drvdata(dev); + + ret = sht15_update_vals(data); + + return ret ? ret : sprintf(buf, "%d\n", sht15_calc_humid(data)); + +}; +static ssize_t show_name(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + return sprintf(buf, "%s\n", pdev->name); +} + +static SENSOR_DEVICE_ATTR(temp1_input, + S_IRUGO, sht15_show_temp, + NULL, 0); +static SENSOR_DEVICE_ATTR(humidity1_input, + S_IRUGO, sht15_show_humidity, + NULL, 0); +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); +static struct attribute *sht15_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_humidity1_input.dev_attr.attr, + &dev_attr_name.attr, + NULL, +}; + +static const struct attribute_group sht15_attr_group = { + .attrs = sht15_attrs, +}; + +static irqreturn_t sht15_interrupt_fired(int irq, void *d) +{ + struct sht15_data *data = d; + /* First disable the interrupt */ + disable_irq_nosync(irq); + atomic_inc(&data->interrupt_handled); + /* Then schedule a reading work struct */ + if (data->flag != SHT15_READING_NOTHING) + schedule_work(&data->read_work); + return IRQ_HANDLED; +} + +/* Each byte of data is acknowledged by pulling the data line + * low for one clock pulse. + */ +static void sht15_ack(struct sht15_data *data) +{ + gpio_direction_output(data->pdata->gpio_data, 0); + ndelay(SHT15_TSU); + gpio_set_value(data->pdata->gpio_sck, 1); + ndelay(SHT15_TSU); + gpio_set_value(data->pdata->gpio_sck, 0); + ndelay(SHT15_TSU); + gpio_set_value(data->pdata->gpio_data, 1); + + gpio_direction_input(data->pdata->gpio_data); +} +/** + * sht15_end_transmission() - notify device of end of transmission + * @data: device state + * + * This is basically a NAK. (single clock pulse, data high) + **/ +static void sht15_end_transmission(struct sht15_data *data) +{ + gpio_direction_output(data->pdata->gpio_data, 1); + ndelay(SHT15_TSU); + gpio_set_value(data->pdata->gpio_sck, 1); + ndelay(SHT15_TSCKH); + gpio_set_value(data->pdata->gpio_sck, 0); + ndelay(SHT15_TSCKL); +} + +static void sht15_bh_read_data(struct work_struct *work_s) +{ + int i; + uint16_t val = 0; + struct sht15_data *data + = container_of(work_s, struct sht15_data, + read_work); + /* Firstly, verify the line is low */ + if (gpio_get_value(data->pdata->gpio_data)) { + /* If not, then start the interrupt again - care + here as could have gone low in meantime so verify + it hasn't! + */ + atomic_set(&data->interrupt_handled, 0); + enable_irq(gpio_to_irq(data->pdata->gpio_data)); + /* If still not occured or another handler has been scheduled */ + if (gpio_get_value(data->pdata->gpio_data) + || atomic_read(&data->interrupt_handled)) + return; + } + /* Read the data back from the device */ + for (i = 0; i < 16; ++i) { + val <<= 1; + gpio_set_value(data->pdata->gpio_sck, 1); + ndelay(SHT15_TSCKH); + val |= !!gpio_get_value(data->pdata->gpio_data); + gpio_set_value(data->pdata->gpio_sck, 0); + ndelay(SHT15_TSCKL); + if (i == 7) + sht15_ack(data); + } + /* Tell the device we are done */ + sht15_end_transmission(data); + + switch (data->flag) { + case SHT15_READING_TEMP: + data->val_temp = val; + break; + case SHT15_READING_HUMID: + data->val_humid = val; + break; + } + + data->flag = SHT15_READING_NOTHING; + wake_up(&data->wait_queue); +} + +static void sht15_update_voltage(struct work_struct *work_s) +{ + struct sht15_data *data + = container_of(work_s, struct sht15_data, + update_supply_work); + data->supply_uV = regulator_get_voltage(data->reg); +} + +/** + * sht15_invalidate_voltage() - mark supply voltage invalid when notified by reg + * @nb: associated notification structure + * @event: voltage regulator state change event code + * @ignored: function parameter - ignored here + * + * Note that as the notification code holds the regulator lock, we have + * to schedule an update of the supply voltage rather than getting it directly. + **/ +static int sht15_invalidate_voltage(struct notifier_block *nb, + unsigned long event, + void *ignored) +{ + struct sht15_data *data = container_of(nb, struct sht15_data, nb); + + if (event == REGULATOR_EVENT_VOLTAGE_CHANGE) + data->supply_uV_valid = false; + schedule_work(&data->update_supply_work); + + return NOTIFY_OK; +} + +static int __devinit sht15_probe(struct platform_device *pdev) +{ + int ret = 0; + struct sht15_data *data = kzalloc(sizeof(*data), GFP_KERNEL); + + if (!data) { + ret = -ENOMEM; + dev_err(&pdev->dev, "kzalloc failed"); + goto error_ret; + } + + INIT_WORK(&data->read_work, sht15_bh_read_data); + INIT_WORK(&data->update_supply_work, sht15_update_voltage); + platform_set_drvdata(pdev, data); + mutex_init(&data->read_lock); + data->dev = &pdev->dev; + init_waitqueue_head(&data->wait_queue); + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "no platform data supplied"); + goto err_free_data; + } + data->pdata = pdev->dev.platform_data; + data->supply_uV = data->pdata->supply_mv*1000; + +/* If a regulator is available, query what the supply voltage actually is!*/ + data->reg = regulator_get(data->dev, "vcc"); + if (!IS_ERR(data->reg)) { + data->supply_uV = regulator_get_voltage(data->reg); + regulator_enable(data->reg); + /* setup a notifier block to update this if another device + * causes the voltage to change */ + data->nb.notifier_call = &sht15_invalidate_voltage; + ret = regulator_register_notifier(data->reg, &data->nb); + } +/* Try requesting the GPIOs */ + ret = gpio_request(data->pdata->gpio_sck, "SHT15 sck"); + if (ret) { + dev_err(&pdev->dev, "gpio request failed"); + goto err_free_data; + } + gpio_direction_output(data->pdata->gpio_sck, 0); + ret = gpio_request(data->pdata->gpio_data, "SHT15 data"); + if (ret) { + dev_err(&pdev->dev, "gpio request failed"); + goto err_release_gpio_sck; + } + ret = sysfs_create_group(&pdev->dev.kobj, &sht15_attr_group); + if (ret) { + dev_err(&pdev->dev, "sysfs create failed"); + goto err_free_data; + } + + ret = request_irq(gpio_to_irq(data->pdata->gpio_data), + sht15_interrupt_fired, + IRQF_TRIGGER_FALLING, + "sht15 data", + data); + if (ret) { + dev_err(&pdev->dev, "failed to get irq for data line"); + goto err_release_gpio_data; + } + disable_irq_nosync(gpio_to_irq(data->pdata->gpio_data)); + sht15_connection_reset(data); + sht15_send_cmd(data, 0x1E); + + data->hwmon_dev = hwmon_device_register(data->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto err_release_gpio_data; + } + return 0; + +err_release_gpio_data: + gpio_free(data->pdata->gpio_data); +err_release_gpio_sck: + gpio_free(data->pdata->gpio_sck); +err_free_data: + kfree(data); +error_ret: + + return ret; +} + +static int __devexit sht15_remove(struct platform_device *pdev) +{ + struct sht15_data *data = platform_get_drvdata(pdev); + + /* Make sure any reads from the device are done and + * prevent new ones beginnning */ + mutex_lock(&data->read_lock); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &sht15_attr_group); + if (!IS_ERR(data->reg)) { + regulator_unregister_notifier(data->reg, &data->nb); + regulator_disable(data->reg); + regulator_put(data->reg); + } + + free_irq(gpio_to_irq(data->pdata->gpio_data), data); + gpio_free(data->pdata->gpio_data); + gpio_free(data->pdata->gpio_sck); + mutex_unlock(&data->read_lock); + kfree(data); + return 0; +} + + +static struct platform_driver sht_drivers[] = { + { + .driver = { + .name = "sht10", + .owner = THIS_MODULE, + }, + .probe = sht15_probe, + .remove = sht15_remove, + }, { + .driver = { + .name = "sht11", + .owner = THIS_MODULE, + }, + .probe = sht15_probe, + .remove = sht15_remove, + }, { + .driver = { + .name = "sht15", + .owner = THIS_MODULE, + }, + .probe = sht15_probe, + .remove = sht15_remove, + }, { + .driver = { + .name = "sht71", + .owner = THIS_MODULE, + }, + .probe = sht15_probe, + .remove = sht15_remove, + }, { + .driver = { + .name = "sht75", + .owner = THIS_MODULE, + }, + .probe = sht15_probe, + .remove = sht15_remove, + }, +}; + + +static int __init sht15_init(void) +{ + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(sht_drivers); i++) { + ret = platform_driver_register(&sht_drivers[i]); + if (ret) + goto error_unreg; + } + + return 0; + +error_unreg: + while (--i >= 0) + platform_driver_unregister(&sht_drivers[i]); + + return ret; +} +module_init(sht15_init); + +static void __exit sht15_exit(void) +{ + int i; + for (i = ARRAY_SIZE(sht_drivers) - 1; i >= 0; i--) + platform_driver_unregister(&sht_drivers[i]); +} +module_exit(sht15_exit); + +MODULE_LICENSE("GPL"); |