From 4ebb24f707187196937607c60810d42f7112d7aa Mon Sep 17 00:00:00 2001 From: Grant Likely Date: Tue, 22 Feb 2011 20:01:33 -0700 Subject: dt/sparc: Eliminate users of of_platform_{,un}register_driver Get rid of old users of of_platform_driver in arch/sparc. Most of_platform_driver users can be converted to use the platform_bus directly. Signed-off-by: Grant Likely --- drivers/hwmon/ultra45_env.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/ultra45_env.c b/drivers/hwmon/ultra45_env.c index d863e13a50b..1f36c635d93 100644 --- a/drivers/hwmon/ultra45_env.c +++ b/drivers/hwmon/ultra45_env.c @@ -234,8 +234,7 @@ static const struct attribute_group env_group = { .attrs = env_attributes, }; -static int __devinit env_probe(struct platform_device *op, - const struct of_device_id *match) +static int __devinit env_probe(struct platform_device *op) { struct env *p = kzalloc(sizeof(*p), GFP_KERNEL); int err = -ENOMEM; @@ -299,7 +298,7 @@ static const struct of_device_id env_match[] = { }; MODULE_DEVICE_TABLE(of, env_match); -static struct of_platform_driver env_driver = { +static struct platform_driver env_driver = { .driver = { .name = "ultra45_env", .owner = THIS_MODULE, @@ -311,12 +310,12 @@ static struct of_platform_driver env_driver = { static int __init env_init(void) { - return of_register_platform_driver(&env_driver); + return platform_driver_register(&env_driver); } static void __exit env_exit(void) { - of_unregister_platform_driver(&env_driver); + platform_driver_unregister(&env_driver); } module_init(env_init); -- cgit v1.2.3-70-g09d2 From a5b79d62f2e6d1b0d44bcdafce46f47bf355b9fe Mon Sep 17 00:00:00 2001 From: "stigge@antcom.de" Date: Thu, 20 Jan 2011 18:42:55 +0100 Subject: hwmon: Driver for Maxim MAX6639 2-Channel Temperature Monitor with Dual PWM Fan-Speed Controller Signed-off-by: Roland Stigge Signed-off-by: Guenter Roeck --- Documentation/hwmon/max6639 | 49 ++++ drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/max6639.c | 639 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/i2c/max6639.h | 14 + 5 files changed, 713 insertions(+) create mode 100644 Documentation/hwmon/max6639 create mode 100644 drivers/hwmon/max6639.c create mode 100644 include/linux/i2c/max6639.h (limited to 'drivers/hwmon') diff --git a/Documentation/hwmon/max6639 b/Documentation/hwmon/max6639 new file mode 100644 index 00000000000..dc49f8be716 --- /dev/null +++ b/Documentation/hwmon/max6639 @@ -0,0 +1,49 @@ +Kernel driver max6639 +===================== + +Supported chips: + * Maxim MAX6639 + Prefix: 'max6639' + Addresses scanned: I2C 0x2c, 0x2e, 0x2f + Datasheet: http://pdfserv.maxim-ic.com/en/ds/MAX6639.pdf + +Authors: + He Changqing + Roland Stigge + +Description +----------- + +This driver implements support for the Maxim MAX6639. This chip is a 2-channel +temperature monitor with dual PWM fan speed controller. It can monitor its own +temperature and one external diode-connected transistor or two external +diode-connected transistors. + +The following device attributes are implemented via sysfs: + +Attribute R/W Contents +---------------------------------------------------------------------------- +temp1_input R Temperature channel 1 input (0..150 C) +temp2_input R Temperature channel 2 input (0..150 C) +temp1_fault R Temperature channel 1 diode fault +temp2_fault R Temperature channel 2 diode fault +temp1_max RW Set THERM temperature for input 1 + (in C, see datasheet) +temp2_max RW Set THERM temperature for input 2 +temp1_crit RW Set ALERT temperature for input 1 +temp2_crit RW Set ALERT temperature for input 2 +temp1_emergency RW Set OT temperature for input 1 + (in C, see datasheet) +temp2_emergency RW Set OT temperature for input 2 +pwm1 RW Fan 1 target duty cycle (0..255) +pwm2 RW Fan 2 target duty cycle (0..255) +fan1_input R TACH1 fan tachometer input (in RPM) +fan2_input R TACH2 fan tachometer input (in RPM) +fan1_fault R Fan 1 fault +fan2_fault R Fan 2 fault +temp1_max_alarm R Alarm on THERM temperature on channel 1 +temp2_max_alarm R Alarm on THERM temperature on channel 2 +temp1_crit_alarm R Alarm on ALERT temperature on channel 1 +temp2_crit_alarm R Alarm on ALERT temperature on channel 2 +temp1_emergency_alarm R Alarm on OT temperature on channel 1 +temp2_emergency_alarm R Alarm on OT temperature on channel 2 diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 297bc9a7d6e..3bd993b0a3c 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -685,6 +685,16 @@ config SENSORS_MAX1619 This driver can also be built as a module. If so, the module will be called max1619. +config SENSORS_MAX6639 + tristate "Maxim MAX6639 sensor chip" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for the MAX6639 + sensor chips. + + This driver can also be built as a module. If so, the module + will be called max6639. + config SENSORS_MAX6650 tristate "Maxim MAX6650 sensor chip" depends on I2C && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index dde02d99c23..f69783150b7 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -84,6 +84,7 @@ obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o obj-$(CONFIG_SENSORS_MAX1111) += max1111.o obj-$(CONFIG_SENSORS_MAX1619) += max1619.o +obj-$(CONFIG_SENSORS_MAX6639) += max6639.o obj-$(CONFIG_SENSORS_MAX6650) += max6650.o obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o diff --git a/drivers/hwmon/max6639.c b/drivers/hwmon/max6639.c new file mode 100644 index 00000000000..40fd878e53d --- /dev/null +++ b/drivers/hwmon/max6639.c @@ -0,0 +1,639 @@ +/* + * max6639.c - Support for Maxim MAX6639 + * + * 2-Channel Temperature Monitor with Dual PWM Fan-Speed Controller + * + * Copyright (C) 2010, 2011 Roland Stigge + * + * based on the initial MAX6639 support from semptian.net + * by He Changqing + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END }; + +/* The MAX6639 registers, valid channel numbers: 0, 1 */ +#define MAX6639_REG_TEMP(ch) (0x00 + (ch)) +#define MAX6639_REG_STATUS 0x02 +#define MAX6639_REG_OUTPUT_MASK 0x03 +#define MAX6639_REG_GCONFIG 0x04 +#define MAX6639_REG_TEMP_EXT(ch) (0x05 + (ch)) +#define MAX6639_REG_ALERT_LIMIT(ch) (0x08 + (ch)) +#define MAX6639_REG_OT_LIMIT(ch) (0x0A + (ch)) +#define MAX6639_REG_THERM_LIMIT(ch) (0x0C + (ch)) +#define MAX6639_REG_FAN_CONFIG1(ch) (0x10 + (ch) * 4) +#define MAX6639_REG_FAN_CONFIG2a(ch) (0x11 + (ch) * 4) +#define MAX6639_REG_FAN_CONFIG2b(ch) (0x12 + (ch) * 4) +#define MAX6639_REG_FAN_CONFIG3(ch) (0x13 + (ch) * 4) +#define MAX6639_REG_FAN_CNT(ch) (0x20 + (ch)) +#define MAX6639_REG_TARGET_CNT(ch) (0x22 + (ch)) +#define MAX6639_REG_FAN_PPR(ch) (0x24 + (ch)) +#define MAX6639_REG_TARGTDUTY(ch) (0x26 + (ch)) +#define MAX6639_REG_FAN_START_TEMP(ch) (0x28 + (ch)) +#define MAX6639_REG_DEVID 0x3D +#define MAX6639_REG_MANUID 0x3E +#define MAX6639_REG_DEVREV 0x3F + +/* Register bits */ +#define MAX6639_GCONFIG_STANDBY 0x80 +#define MAX6639_GCONFIG_POR 0x40 +#define MAX6639_GCONFIG_DISABLE_TIMEOUT 0x20 +#define MAX6639_GCONFIG_CH2_LOCAL 0x10 + +#define MAX6639_FAN_CONFIG1_PWM 0x80 + +static const int rpm_ranges[] = { 2000, 4000, 8000, 16000 }; + +#define FAN_FROM_REG(val, div, rpm_range) ((val) == 0 ? -1 : \ + (val) == 255 ? 0 : (rpm_ranges[rpm_range] * 30) / ((div + 1) * (val))) +#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT((val) / 1000, 0, 255) + +/* + * Client data (each client gets its own) + */ +struct max6639_data { + struct device *hwmon_dev; + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* Register values sampled regularly */ + u16 temp[2]; /* Temperature, in 1/8 C, 0..255 C */ + bool temp_fault[2]; /* Detected temperature diode failure */ + u8 fan[2]; /* Register value: TACH count for fans >=30 */ + u8 status; /* Detected channel alarms and fan failures */ + + /* Register values only written to */ + u8 pwm[2]; /* Register value: Duty cycle 0..120 */ + u8 temp_therm[2]; /* THERM Temperature, 0..255 C (->_max) */ + u8 temp_alert[2]; /* ALERT Temperature, 0..255 C (->_crit) */ + u8 temp_ot[2]; /* OT Temperature, 0..255 C (->_emergency) */ + + /* Register values initialized only once */ + u8 ppr; /* Pulses per rotation 0..3 for 1..4 ppr */ + u8 rpm_range; /* Index in above rpm_ranges table */ +}; + +static struct max6639_data *max6639_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct max6639_data *ret = data; + int i; + int status_reg; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + 2 * HZ) || !data->valid) { + int res; + + dev_dbg(&client->dev, "Starting max6639 update\n"); + + status_reg = i2c_smbus_read_byte_data(client, + MAX6639_REG_STATUS); + if (status_reg < 0) { + ret = ERR_PTR(status_reg); + goto abort; + } + + data->status = status_reg; + + for (i = 0; i < 2; i++) { + res = i2c_smbus_read_byte_data(client, + MAX6639_REG_FAN_CNT(i)); + if (res < 0) { + ret = ERR_PTR(res); + goto abort; + } + data->fan[i] = res; + + res = i2c_smbus_read_byte_data(client, + MAX6639_REG_TEMP_EXT(i)); + if (res < 0) { + ret = ERR_PTR(res); + goto abort; + } + data->temp[i] = res >> 5; + data->temp_fault[i] = res & 0x01; + + res = i2c_smbus_read_byte_data(client, + MAX6639_REG_TEMP(i)); + if (res < 0) { + ret = ERR_PTR(res); + goto abort; + } + data->temp[i] |= res << 3; + } + + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + + return ret; +} + +static ssize_t show_temp_input(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + long temp; + struct max6639_data *data = max6639_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + temp = data->temp[attr->index] * 125; + return sprintf(buf, "%ld\n", temp); +} + +static ssize_t show_temp_fault(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct max6639_data *data = max6639_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->temp_fault[attr->index]); +} + +static ssize_t show_temp_max(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + return sprintf(buf, "%d\n", (data->temp_therm[attr->index] * 1000)); +} + +static ssize_t set_temp_max(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + unsigned long val; + int res; + + res = strict_strtoul(buf, 10, &val); + if (res) + return res; + + mutex_lock(&data->update_lock); + data->temp_therm[attr->index] = TEMP_LIMIT_TO_REG(val); + i2c_smbus_write_byte_data(client, + MAX6639_REG_THERM_LIMIT(attr->index), + data->temp_therm[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp_crit(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + return sprintf(buf, "%d\n", (data->temp_alert[attr->index] * 1000)); +} + +static ssize_t set_temp_crit(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + unsigned long val; + int res; + + res = strict_strtoul(buf, 10, &val); + if (res) + return res; + + mutex_lock(&data->update_lock); + data->temp_alert[attr->index] = TEMP_LIMIT_TO_REG(val); + i2c_smbus_write_byte_data(client, + MAX6639_REG_ALERT_LIMIT(attr->index), + data->temp_alert[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp_emergency(struct device *dev, + struct device_attribute *dev_attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + return sprintf(buf, "%d\n", (data->temp_ot[attr->index] * 1000)); +} + +static ssize_t set_temp_emergency(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + unsigned long val; + int res; + + res = strict_strtoul(buf, 10, &val); + if (res) + return res; + + mutex_lock(&data->update_lock); + data->temp_ot[attr->index] = TEMP_LIMIT_TO_REG(val); + i2c_smbus_write_byte_data(client, + MAX6639_REG_OT_LIMIT(attr->index), + data->temp_ot[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + return sprintf(buf, "%d\n", data->pwm[attr->index] * 255 / 120); +} + +static ssize_t set_pwm(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + unsigned long val; + int res; + + res = strict_strtoul(buf, 10, &val); + if (res) + return res; + + val = SENSORS_LIMIT(val, 0, 255); + + mutex_lock(&data->update_lock); + data->pwm[attr->index] = (u8)(val * 120 / 255); + i2c_smbus_write_byte_data(client, + MAX6639_REG_TARGTDUTY(attr->index), + data->pwm[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_fan_input(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct max6639_data *data = max6639_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[attr->index], + data->ppr, data->rpm_range)); +} + +static ssize_t show_alarm(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct max6639_data *data = max6639_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", !!(data->status & (1 << attr->index))); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 1); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, show_temp_crit, + set_temp_crit, 0); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IWUSR | S_IRUGO, show_temp_crit, + set_temp_crit, 1); +static SENSOR_DEVICE_ATTR(temp1_emergency, S_IWUSR | S_IRUGO, + show_temp_emergency, set_temp_emergency, 0); +static SENSOR_DEVICE_ATTR(temp2_emergency, S_IWUSR | S_IRUGO, + show_temp_emergency, set_temp_emergency, 1); +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1); +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_input, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_input, NULL, 1); +static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(temp1_emergency_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp2_emergency_alarm, S_IRUGO, show_alarm, NULL, 4); + + +static struct attribute *max6639_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp1_emergency.dev_attr.attr, + &sensor_dev_attr_temp2_emergency.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan1_fault.dev_attr.attr, + &sensor_dev_attr_fan2_fault.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_emergency_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_emergency_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group max6639_group = { + .attrs = max6639_attributes, +}; + +/* + * returns respective index in rpm_ranges table + * 1 by default on invalid range + */ +static int rpm_range_to_reg(int range) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rpm_ranges); i++) { + if (rpm_ranges[i] == range) + return i; + } + + return 1; /* default: 4000 RPM */ +} + +static int max6639_init_client(struct i2c_client *client) +{ + struct max6639_data *data = i2c_get_clientdata(client); + struct max6639_platform_data *max6639_info = + client->dev.platform_data; + int i = 0; + int rpm_range = 1; /* default: 4000 RPM */ + int err = 0; + + /* Reset chip to default values */ + err = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG, + MAX6639_GCONFIG_POR); + if (err) + goto exit; + + /* Fans pulse per revolution is 2 by default */ + if (max6639_info && max6639_info->ppr > 0 && + max6639_info->ppr < 5) + data->ppr = max6639_info->ppr; + else + data->ppr = 2; + data->ppr -= 1; + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_PPR(i), + data->ppr << 5); + if (err) + goto exit; + + if (max6639_info) + rpm_range = rpm_range_to_reg(max6639_info->rpm_range); + data->rpm_range = rpm_range; + + for (i = 0; i < 2; i++) { + + /* Fans config PWM, RPM */ + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_CONFIG1(i), + MAX6639_FAN_CONFIG1_PWM | rpm_range); + if (err) + goto exit; + + /* Fans PWM polarity high by default */ + if (max6639_info && max6639_info->pwm_polarity == 0) + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_CONFIG2a(i), 0x00); + else + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_CONFIG2a(i), 0x02); + if (err) + goto exit; + + /* Max. temp. 80C/90C/100C */ + data->temp_therm[i] = 80; + data->temp_alert[i] = 90; + data->temp_ot[i] = 100; + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_THERM_LIMIT(i), + data->temp_therm[i]); + if (err) + goto exit; + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_ALERT_LIMIT(i), + data->temp_alert[i]); + if (err) + goto exit; + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_OT_LIMIT(i), data->temp_ot[i]); + if (err) + goto exit; + + /* PWM 120/120 (i.e. 100%) */ + data->pwm[i] = 120; + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_TARGTDUTY(i), data->pwm[i]); + if (err) + goto exit; + } + /* Start monitoring */ + err = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG, + MAX6639_GCONFIG_DISABLE_TIMEOUT | MAX6639_GCONFIG_CH2_LOCAL); +exit: + return err; +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int max6639_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int dev_id, manu_id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* Actual detection via device and manufacturer ID */ + dev_id = i2c_smbus_read_byte_data(client, MAX6639_REG_DEVID); + manu_id = i2c_smbus_read_byte_data(client, MAX6639_REG_MANUID); + if (dev_id != 0x58 || manu_id != 0x4D) + return -ENODEV; + + strlcpy(info->type, "max6639", I2C_NAME_SIZE); + + return 0; +} + +static int max6639_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max6639_data *data; + int err; + + data = kzalloc(sizeof(struct max6639_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Initialize the max6639 chip */ + err = max6639_init_client(client); + if (err < 0) + goto error_free; + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &max6639_group); + if (err) + goto error_free; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto error_remove; + } + + dev_info(&client->dev, "temperature sensor and fan control found\n"); + + return 0; + +error_remove: + sysfs_remove_group(&client->dev.kobj, &max6639_group); +error_free: + kfree(data); +exit: + return err; +} + +static int max6639_remove(struct i2c_client *client) +{ + struct max6639_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &max6639_group); + + kfree(data); + return 0; +} + +static int max6639_suspend(struct i2c_client *client, pm_message_t mesg) +{ + int data = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG); + if (data < 0) + return data; + + return i2c_smbus_write_byte_data(client, + MAX6639_REG_GCONFIG, data | MAX6639_GCONFIG_STANDBY); +} + +static int max6639_resume(struct i2c_client *client) +{ + int data = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG); + if (data < 0) + return data; + + return i2c_smbus_write_byte_data(client, + MAX6639_REG_GCONFIG, data & ~MAX6639_GCONFIG_STANDBY); +} + +static const struct i2c_device_id max6639_id[] = { + {"max6639", 0}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, max6639_id); + +static struct i2c_driver max6639_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "max6639", + }, + .probe = max6639_probe, + .remove = max6639_remove, + .suspend = max6639_suspend, + .resume = max6639_resume, + .id_table = max6639_id, + .detect = max6639_detect, + .address_list = normal_i2c, +}; + +static int __init max6639_init(void) +{ + return i2c_add_driver(&max6639_driver); +} + +static void __exit max6639_exit(void) +{ + i2c_del_driver(&max6639_driver); +} + +MODULE_AUTHOR("Roland Stigge "); +MODULE_DESCRIPTION("max6639 driver"); +MODULE_LICENSE("GPL"); + +module_init(max6639_init); +module_exit(max6639_exit); diff --git a/include/linux/i2c/max6639.h b/include/linux/i2c/max6639.h new file mode 100644 index 00000000000..6011c42034d --- /dev/null +++ b/include/linux/i2c/max6639.h @@ -0,0 +1,14 @@ +#ifndef _LINUX_MAX6639_H +#define _LINUX_MAX6639_H + +#include + +/* platform data for the MAX6639 temperature sensor and fan control */ + +struct max6639_platform_data { + bool pwm_polarity; /* Polarity low (0) or high (1, default) */ + int ppr; /* Pulses per rotation 1..4 (default == 2) */ + int rpm_range; /* 2000, 4000 (default), 8000 or 16000 */ +}; + +#endif /* _LINUX_MAX6639_H */ -- cgit v1.2.3-70-g09d2 From 177f3b920cf6d1ab6d97f0978f866a8a711918e6 Mon Sep 17 00:00:00 2001 From: "stigge@antcom.de" Date: Sat, 29 Jan 2011 17:04:01 +0100 Subject: hwmon: (max6639) Set reasonable default PWM frequency This patch initializes register CONFIG3 to a reasonable default PWM frequency of 25kHz, to prevent audible sound in fan. Signed-off-by: Roland Stigge Signed-off-by: Guenter Roeck --- drivers/hwmon/max6639.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/max6639.c b/drivers/hwmon/max6639.c index 40fd878e53d..f20d9978ee7 100644 --- a/drivers/hwmon/max6639.c +++ b/drivers/hwmon/max6639.c @@ -64,9 +64,12 @@ static unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END }; #define MAX6639_GCONFIG_POR 0x40 #define MAX6639_GCONFIG_DISABLE_TIMEOUT 0x20 #define MAX6639_GCONFIG_CH2_LOCAL 0x10 +#define MAX6639_GCONFIG_PWM_FREQ_HI 0x08 #define MAX6639_FAN_CONFIG1_PWM 0x80 +#define MAX6639_FAN_CONFIG3_THERM_FULL_SPEED 0x40 + static const int rpm_ranges[] = { 2000, 4000, 8000, 16000 }; #define FAN_FROM_REG(val, div, rpm_range) ((val) == 0 ? -1 : \ @@ -430,7 +433,7 @@ static int max6639_init_client(struct i2c_client *client) int rpm_range = 1; /* default: 4000 RPM */ int err = 0; - /* Reset chip to default values */ + /* Reset chip to default values, see below for GCONFIG setup */ err = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG, MAX6639_GCONFIG_POR); if (err) @@ -472,6 +475,16 @@ static int max6639_init_client(struct i2c_client *client) if (err) goto exit; + /* + * /THERM full speed enable, + * PWM frequency 25kHz, see also GCONFIG below + */ + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_CONFIG3(i), + MAX6639_FAN_CONFIG3_THERM_FULL_SPEED | 0x03); + if (err) + goto exit; + /* Max. temp. 80C/90C/100C */ data->temp_therm[i] = 80; data->temp_alert[i] = 90; @@ -500,7 +513,8 @@ static int max6639_init_client(struct i2c_client *client) } /* Start monitoring */ err = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG, - MAX6639_GCONFIG_DISABLE_TIMEOUT | MAX6639_GCONFIG_CH2_LOCAL); + MAX6639_GCONFIG_DISABLE_TIMEOUT | MAX6639_GCONFIG_CH2_LOCAL | + MAX6639_GCONFIG_PWM_FREQ_HI); exit: return err; } -- cgit v1.2.3-70-g09d2 From 4e9be650597bffec5885a8c1abad1e4ffb0bcca3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 20 Jan 2011 21:27:56 +0000 Subject: hwmon: (lis3lv02d) Convert SPI to dev_pm_ops There is a move to deprecate bus-specific PM operations and move to using dev_pm_ops instead in order to reduce the amount of boilerplate code in buses and facilitiate updates to the PM core. Do this move for the lis3lv02d SPI driver. Signed-off-by: Mark Brown Signed-off-by: Guenter Roeck --- drivers/hwmon/lis3lv02d_spi.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/lis3lv02d_spi.c b/drivers/hwmon/lis3lv02d_spi.c index 2549de1de4e..c1f8a8fbf69 100644 --- a/drivers/hwmon/lis3lv02d_spi.c +++ b/drivers/hwmon/lis3lv02d_spi.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "lis3lv02d.h" @@ -88,9 +89,10 @@ static int __devexit lis302dl_spi_remove(struct spi_device *spi) return lis3lv02d_remove_fs(&lis3_dev); } -#ifdef CONFIG_PM -static int lis3lv02d_spi_suspend(struct spi_device *spi, pm_message_t mesg) +#ifdef CONFIG_PM_SLEEP +static int lis3lv02d_spi_suspend(struct device *dev) { + struct spi_device *spi = to_spi_device(dev); struct lis3lv02d *lis3 = spi_get_drvdata(spi); if (!lis3->pdata || !lis3->pdata->wakeup_flags) @@ -99,8 +101,9 @@ static int lis3lv02d_spi_suspend(struct spi_device *spi, pm_message_t mesg) return 0; } -static int lis3lv02d_spi_resume(struct spi_device *spi) +static int lis3lv02d_spi_resume(struct device *dev) { + struct spi_device *spi = to_spi_device(dev); struct lis3lv02d *lis3 = spi_get_drvdata(spi); if (!lis3->pdata || !lis3->pdata->wakeup_flags) @@ -108,21 +111,19 @@ static int lis3lv02d_spi_resume(struct spi_device *spi) return 0; } - -#else -#define lis3lv02d_spi_suspend NULL -#define lis3lv02d_spi_resume NULL #endif +static SIMPLE_DEV_PM_OPS(lis3lv02d_spi_pm, lis3lv02d_spi_suspend, + lis3lv02d_spi_resume); + static struct spi_driver lis302dl_spi_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, + .pm = &lis3lv02d_spi_pm, }, .probe = lis302dl_spi_probe, .remove = __devexit_p(lis302dl_spi_remove), - .suspend = lis3lv02d_spi_suspend, - .resume = lis3lv02d_spi_resume, }; static int __init lis302dl_init(void) -- cgit v1.2.3-70-g09d2 From 502b5a0199209001b34b623132ea313790acbd5d Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 29 Sep 2010 20:12:08 -0700 Subject: hwmon: Add support for Lineage Compact Power Line PEM devices This patch adds support for hardware monitoring of Lineage Compact Power Line Power Entry Modules. Reviewed-by: Tom Grennan Signed-off-by: Guenter Roeck --- Documentation/hwmon/lineage-pem | 77 ++++++ drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/lineage-pem.c | 589 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 678 insertions(+) create mode 100644 Documentation/hwmon/lineage-pem create mode 100644 drivers/hwmon/lineage-pem.c (limited to 'drivers/hwmon') diff --git a/Documentation/hwmon/lineage-pem b/Documentation/hwmon/lineage-pem new file mode 100644 index 00000000000..2ba5ed12685 --- /dev/null +++ b/Documentation/hwmon/lineage-pem @@ -0,0 +1,77 @@ +Kernel driver lineage-pem +========================= + +Supported devices: + * Lineage Compact Power Line Power Entry Modules + Prefix: 'lineage-pem' + Addresses scanned: - + Documentation: + http://www.lineagepower.com/oem/pdf/CPLI2C.pdf + +Author: Guenter Roeck + + +Description +----------- + +This driver supports various Lineage Compact Power Line DC/DC and AC/DC +converters such as CP1800, CP2000AC, CP2000DC, CP2100DC, and others. + +Lineage CPL power entry modules are nominally PMBus compliant. However, most +standard PMBus commands are not supported. Specifically, all hardware monitoring +and status reporting commands are non-standard. For this reason, a standard +PMBus driver can not be used. + + +Usage Notes +----------- + +This driver does not probe for Lineage CPL devices, since there is no register +which can be safely used to identify the chip. You will have to instantiate +the devices explicitly. + +Example: the following will load the driver for a Lineage PEM at address 0x40 +on I2C bus #1: +$ modprobe lineage-pem +$ echo lineage-pem 0x40 > /sys/bus/i2c/devices/i2c-1/new_device + +All Lineage CPL power entry modules have a built-in I2C bus master selector +(PCA9541). To ensure device access, this driver should only be used as client +driver to the pca9541 I2C master selector driver. + + +Sysfs entries +------------- + +All Lineage CPL devices report output voltage and device temperature as well as +alarms for output voltage, temperature, input voltage, input current, input power, +and fan status. + +Input voltage, input current, input power, and fan speed measurement is only +supported on newer devices. The driver detects if those attributes are supported, +and only creates respective sysfs entries if they are. + +in1_input Output voltage (mV) +in1_min_alarm Output undervoltage alarm +in1_max_alarm Output overvoltage alarm +in1_crit Output voltage critical alarm + +in2_input Input voltage (mV, optional) +in2_alarm Input voltage alarm + +curr1_input Input current (mA, optional) +curr1_alarm Input overcurrent alarm + +power1_input Input power (uW, optional) +power1_alarm Input power alarm + +fan1_input Fan 1 speed (rpm, optional) +fan2_input Fan 2 speed (rpm, optional) +fan3_input Fan 3 speed (rpm, optional) + +temp1_input +temp1_max +temp1_crit +temp1_alarm +temp1_crit_alarm +temp1_fault diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 3bd993b0a3c..d4ffc605208 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -467,6 +467,17 @@ config SENSORS_JC42 This driver can also be built as a module. If so, the module will be called jc42. +config SENSORS_LINEAGE + tristate "Lineage Compact Power Line Power Entry Module" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for the Lineage Compact Power Line + series of DC/DC and AC/DC converters such as CP1800, CP2000AC, + CP2000DC, CP2725, and others. + + This driver can also be built as a module. If so, the module + will be called lineage-pem. + config SENSORS_LM63 tristate "National Semiconductor LM63 and LM64" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index f69783150b7..46819818ef5 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_SENSORS_JC42) += jc42.o obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o +obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o hp_accel.o obj-$(CONFIG_SENSORS_LIS3_SPI) += lis3lv02d.o lis3lv02d_spi.o obj-$(CONFIG_SENSORS_LIS3_I2C) += lis3lv02d.o lis3lv02d_i2c.o diff --git a/drivers/hwmon/lineage-pem.c b/drivers/hwmon/lineage-pem.c new file mode 100644 index 00000000000..d39ee24e52f --- /dev/null +++ b/drivers/hwmon/lineage-pem.c @@ -0,0 +1,589 @@ +/* + * Driver for Lineage Compact Power Line series of power entry modules. + * + * Copyright (C) 2010, 2011 Ericsson AB. + * + * Documentation: + * http://www.lineagepower.com/oem/pdf/CPLI2C.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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * This driver supports various Lineage Compact Power Line DC/DC and AC/DC + * converters such as CP1800, CP2000AC, CP2000DC, CP2100DC, and others. + * + * The devices are nominally PMBus compliant. However, most standard PMBus + * commands are not supported. Specifically, all hardware monitoring and + * status reporting commands are non-standard. For this reason, a standard + * PMBus driver can not be used. + * + * All Lineage CPL devices have a built-in I2C bus master selector (PCA9541). + * To ensure device access, this driver should only be used as client driver + * to the pca9541 I2C master selector driver. + */ + +/* Command codes */ +#define PEM_OPERATION 0x01 +#define PEM_CLEAR_INFO_FLAGS 0x03 +#define PEM_VOUT_COMMAND 0x21 +#define PEM_VOUT_OV_FAULT_LIMIT 0x40 +#define PEM_READ_DATA_STRING 0xd0 +#define PEM_READ_INPUT_STRING 0xdc +#define PEM_READ_FIRMWARE_REV 0xdd +#define PEM_READ_RUN_TIMER 0xde +#define PEM_FAN_HI_SPEED 0xdf +#define PEM_FAN_NORMAL_SPEED 0xe0 +#define PEM_READ_FAN_SPEED 0xe1 + +/* offsets in data string */ +#define PEM_DATA_STATUS_2 0 +#define PEM_DATA_STATUS_1 1 +#define PEM_DATA_ALARM_2 2 +#define PEM_DATA_ALARM_1 3 +#define PEM_DATA_VOUT_LSB 4 +#define PEM_DATA_VOUT_MSB 5 +#define PEM_DATA_CURRENT 6 +#define PEM_DATA_TEMP 7 + +/* Virtual entries, to report constants */ +#define PEM_DATA_TEMP_MAX 10 +#define PEM_DATA_TEMP_CRIT 11 + +/* offsets in input string */ +#define PEM_INPUT_VOLTAGE 0 +#define PEM_INPUT_POWER_LSB 1 +#define PEM_INPUT_POWER_MSB 2 + +/* offsets in fan data */ +#define PEM_FAN_ADJUSTMENT 0 +#define PEM_FAN_FAN1 1 +#define PEM_FAN_FAN2 2 +#define PEM_FAN_FAN3 3 + +/* Status register bits */ +#define STS1_OUTPUT_ON (1 << 0) +#define STS1_LEDS_FLASHING (1 << 1) +#define STS1_EXT_FAULT (1 << 2) +#define STS1_SERVICE_LED_ON (1 << 3) +#define STS1_SHUTDOWN_OCCURRED (1 << 4) +#define STS1_INT_FAULT (1 << 5) +#define STS1_ISOLATION_TEST_OK (1 << 6) + +#define STS2_ENABLE_PIN_HI (1 << 0) +#define STS2_DATA_OUT_RANGE (1 << 1) +#define STS2_RESTARTED_OK (1 << 1) +#define STS2_ISOLATION_TEST_FAIL (1 << 3) +#define STS2_HIGH_POWER_CAP (1 << 4) +#define STS2_INVALID_INSTR (1 << 5) +#define STS2_WILL_RESTART (1 << 6) +#define STS2_PEC_ERR (1 << 7) + +/* Alarm register bits */ +#define ALRM1_VIN_OUT_LIMIT (1 << 0) +#define ALRM1_VOUT_OUT_LIMIT (1 << 1) +#define ALRM1_OV_VOLT_SHUTDOWN (1 << 2) +#define ALRM1_VIN_OVERCURRENT (1 << 3) +#define ALRM1_TEMP_WARNING (1 << 4) +#define ALRM1_TEMP_SHUTDOWN (1 << 5) +#define ALRM1_PRIMARY_FAULT (1 << 6) +#define ALRM1_POWER_LIMIT (1 << 7) + +#define ALRM2_5V_OUT_LIMIT (1 << 1) +#define ALRM2_TEMP_FAULT (1 << 2) +#define ALRM2_OV_LOW (1 << 3) +#define ALRM2_DCDC_TEMP_HIGH (1 << 4) +#define ALRM2_PRI_TEMP_HIGH (1 << 5) +#define ALRM2_NO_PRIMARY (1 << 6) +#define ALRM2_FAN_FAULT (1 << 7) + +#define FIRMWARE_REV_LEN 4 +#define DATA_STRING_LEN 9 +#define INPUT_STRING_LEN 5 /* 4 for most devices */ +#define FAN_SPEED_LEN 5 + +struct pem_data { + struct device *hwmon_dev; + + struct mutex update_lock; + bool valid; + bool fans_supported; + int input_length; + unsigned long last_updated; /* in jiffies */ + + u8 firmware_rev[FIRMWARE_REV_LEN]; + u8 data_string[DATA_STRING_LEN]; + u8 input_string[INPUT_STRING_LEN]; + u8 fan_speed[FAN_SPEED_LEN]; +}; + +static int pem_read_block(struct i2c_client *client, u8 command, u8 *data, + int data_len) +{ + u8 block_buffer[I2C_SMBUS_BLOCK_MAX]; + int result; + + result = i2c_smbus_read_block_data(client, command, block_buffer); + if (unlikely(result < 0)) + goto abort; + if (unlikely(result == 0xff || result != data_len)) { + result = -EIO; + goto abort; + } + memcpy(data, block_buffer, data_len); + result = 0; +abort: + return result; +} + +static struct pem_data *pem_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pem_data *data = i2c_get_clientdata(client); + struct pem_data *ret = data; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + int result; + + /* Read data string */ + result = pem_read_block(client, PEM_READ_DATA_STRING, + data->data_string, + sizeof(data->data_string)); + if (unlikely(result < 0)) { + ret = ERR_PTR(result); + goto abort; + } + + /* Read input string */ + if (data->input_length) { + result = pem_read_block(client, PEM_READ_INPUT_STRING, + data->input_string, + data->input_length); + if (unlikely(result < 0)) { + ret = ERR_PTR(result); + goto abort; + } + } + + /* Read fan speeds */ + if (data->fans_supported) { + result = pem_read_block(client, PEM_READ_FAN_SPEED, + data->fan_speed, + sizeof(data->fan_speed)); + if (unlikely(result < 0)) { + ret = ERR_PTR(result); + goto abort; + } + } + + i2c_smbus_write_byte(client, PEM_CLEAR_INFO_FLAGS); + + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +static long pem_get_data(u8 *data, int len, int index) +{ + long val; + + switch (index) { + case PEM_DATA_VOUT_LSB: + val = (data[index] + (data[index+1] << 8)) * 5 / 2; + break; + case PEM_DATA_CURRENT: + val = data[index] * 200; + break; + case PEM_DATA_TEMP: + val = data[index] * 1000; + break; + case PEM_DATA_TEMP_MAX: + val = 97 * 1000; /* 97 degrees C per datasheet */ + break; + case PEM_DATA_TEMP_CRIT: + val = 107 * 1000; /* 107 degrees C per datasheet */ + break; + default: + WARN_ON_ONCE(1); + val = 0; + } + return val; +} + +static long pem_get_input(u8 *data, int len, int index) +{ + long val; + + switch (index) { + case PEM_INPUT_VOLTAGE: + if (len == INPUT_STRING_LEN) + val = (data[index] + (data[index+1] << 8) - 75) * 1000; + else + val = (data[index] - 75) * 1000; + break; + case PEM_INPUT_POWER_LSB: + if (len == INPUT_STRING_LEN) + index++; + val = (data[index] + (data[index+1] << 8)) * 1000000L; + break; + default: + WARN_ON_ONCE(1); + val = 0; + } + return val; +} + +static long pem_get_fan(u8 *data, int len, int index) +{ + long val; + + switch (index) { + case PEM_FAN_FAN1: + case PEM_FAN_FAN2: + case PEM_FAN_FAN3: + val = data[index] * 100; + break; + default: + WARN_ON_ONCE(1); + val = 0; + } + return val; +} + +/* + * Show boolean, either a fault or an alarm. + * .nr points to the register, .index is the bit mask to check + */ +static ssize_t pem_show_bool(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(da); + struct pem_data *data = pem_update_device(dev); + u8 status; + + if (IS_ERR(data)) + return PTR_ERR(data); + + status = data->data_string[attr->nr] & attr->index; + return snprintf(buf, PAGE_SIZE, "%d\n", !!status); +} + +static ssize_t pem_show_data(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pem_data *data = pem_update_device(dev); + long value; + + if (IS_ERR(data)) + return PTR_ERR(data); + + value = pem_get_data(data->data_string, sizeof(data->data_string), + attr->index); + + return snprintf(buf, PAGE_SIZE, "%ld\n", value); +} + +static ssize_t pem_show_input(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pem_data *data = pem_update_device(dev); + long value; + + if (IS_ERR(data)) + return PTR_ERR(data); + + value = pem_get_input(data->input_string, sizeof(data->input_string), + attr->index); + + return snprintf(buf, PAGE_SIZE, "%ld\n", value); +} + +static ssize_t pem_show_fan(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pem_data *data = pem_update_device(dev); + long value; + + if (IS_ERR(data)) + return PTR_ERR(data); + + value = pem_get_fan(data->fan_speed, sizeof(data->fan_speed), + attr->index); + + return snprintf(buf, PAGE_SIZE, "%ld\n", value); +} + +/* Voltages */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, pem_show_data, NULL, + PEM_DATA_VOUT_LSB); +static SENSOR_DEVICE_ATTR_2(in1_min_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_2, ALRM2_OV_LOW); +static SENSOR_DEVICE_ATTR_2(in1_max_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_VOUT_OUT_LIMIT); +static SENSOR_DEVICE_ATTR_2(in1_crit_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_OV_VOLT_SHUTDOWN); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, pem_show_input, NULL, + PEM_INPUT_VOLTAGE); +static SENSOR_DEVICE_ATTR_2(in2_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, + ALRM1_VIN_OUT_LIMIT | ALRM1_PRIMARY_FAULT); + +/* Currents */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, pem_show_data, NULL, + PEM_DATA_CURRENT); +static SENSOR_DEVICE_ATTR_2(curr1_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_VIN_OVERCURRENT); + +/* Power */ +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, pem_show_input, NULL, + PEM_INPUT_POWER_LSB); +static SENSOR_DEVICE_ATTR_2(power1_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_POWER_LIMIT); + +/* Fans */ +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, pem_show_fan, NULL, + PEM_FAN_FAN1); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, pem_show_fan, NULL, + PEM_FAN_FAN2); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, pem_show_fan, NULL, + PEM_FAN_FAN3); +static SENSOR_DEVICE_ATTR_2(fan1_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_2, ALRM2_FAN_FAULT); + +/* Temperatures */ +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, pem_show_data, NULL, + PEM_DATA_TEMP); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, pem_show_data, NULL, + PEM_DATA_TEMP_MAX); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, pem_show_data, NULL, + PEM_DATA_TEMP_CRIT); +static SENSOR_DEVICE_ATTR_2(temp1_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_TEMP_WARNING); +static SENSOR_DEVICE_ATTR_2(temp1_crit_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_TEMP_SHUTDOWN); +static SENSOR_DEVICE_ATTR_2(temp1_fault, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_2, ALRM2_TEMP_FAULT); + +static struct attribute *pem_attributes[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min_alarm.dev_attr.attr, + &sensor_dev_attr_in1_max_alarm.dev_attr.attr, + &sensor_dev_attr_in1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + + &sensor_dev_attr_curr1_alarm.dev_attr.attr, + + &sensor_dev_attr_power1_alarm.dev_attr.attr, + + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + + NULL, +}; + +static const struct attribute_group pem_group = { + .attrs = pem_attributes, +}; + +static struct attribute *pem_input_attributes[] = { + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_power1_input.dev_attr.attr, +}; + +static const struct attribute_group pem_input_group = { + .attrs = pem_input_attributes, +}; + +static struct attribute *pem_fan_attributes[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, +}; + +static const struct attribute_group pem_fan_group = { + .attrs = pem_fan_attributes, +}; + +static int pem_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct pem_data *data; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BLOCK_DATA + | I2C_FUNC_SMBUS_WRITE_BYTE)) + return -ENODEV; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* + * We use the next two commands to determine if the device is really + * there. + */ + ret = pem_read_block(client, PEM_READ_FIRMWARE_REV, + data->firmware_rev, sizeof(data->firmware_rev)); + if (ret < 0) + goto out_kfree; + + ret = i2c_smbus_write_byte(client, PEM_CLEAR_INFO_FLAGS); + if (ret < 0) + goto out_kfree; + + dev_info(&client->dev, "Firmware revision %d.%d.%d\n", + data->firmware_rev[0], data->firmware_rev[1], + data->firmware_rev[2]); + + /* Register sysfs hooks */ + ret = sysfs_create_group(&client->dev.kobj, &pem_group); + if (ret) + goto out_kfree; + + /* + * Check if input readings are supported. + * This is the case if we can read input data, + * and if the returned data is not all zeros. + * Note that input alarms are always supported. + */ + ret = pem_read_block(client, PEM_READ_INPUT_STRING, + data->input_string, + sizeof(data->input_string) - 1); + if (!ret && (data->input_string[0] || data->input_string[1] || + data->input_string[2])) + data->input_length = sizeof(data->input_string) - 1; + else if (ret < 0) { + /* Input string is one byte longer for some devices */ + ret = pem_read_block(client, PEM_READ_INPUT_STRING, + data->input_string, + sizeof(data->input_string)); + if (!ret && (data->input_string[0] || data->input_string[1] || + data->input_string[2] || data->input_string[3])) + data->input_length = sizeof(data->input_string); + } + ret = 0; + if (data->input_length) { + ret = sysfs_create_group(&client->dev.kobj, &pem_input_group); + if (ret) + goto out_remove_groups; + } + + /* + * Check if fan speed readings are supported. + * This is the case if we can read fan speed data, + * and if the returned data is not all zeros. + * Note that the fan alarm is always supported. + */ + ret = pem_read_block(client, PEM_READ_FAN_SPEED, + data->fan_speed, + sizeof(data->fan_speed)); + if (!ret && (data->fan_speed[0] || data->fan_speed[1] || + data->fan_speed[2] || data->fan_speed[3])) { + data->fans_supported = true; + ret = sysfs_create_group(&client->dev.kobj, &pem_fan_group); + if (ret) + goto out_remove_groups; + } + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto out_remove_groups; + } + + return 0; + +out_remove_groups: + sysfs_remove_group(&client->dev.kobj, &pem_input_group); + sysfs_remove_group(&client->dev.kobj, &pem_fan_group); + sysfs_remove_group(&client->dev.kobj, &pem_group); +out_kfree: + kfree(data); + return ret; +} + +static int pem_remove(struct i2c_client *client) +{ + struct pem_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + + sysfs_remove_group(&client->dev.kobj, &pem_input_group); + sysfs_remove_group(&client->dev.kobj, &pem_fan_group); + sysfs_remove_group(&client->dev.kobj, &pem_group); + + kfree(data); + return 0; +} + +static const struct i2c_device_id pem_id[] = { + {"lineage_pem", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, pem_id); + +static struct i2c_driver pem_driver = { + .driver = { + .name = "lineage_pem", + }, + .probe = pem_probe, + .remove = pem_remove, + .id_table = pem_id, +}; + +static int __init pem_init(void) +{ + return i2c_add_driver(&pem_driver); +} + +static void __exit pem_exit(void) +{ + i2c_del_driver(&pem_driver); +} + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("Lineage CPL PEM hardware monitoring driver"); +MODULE_LICENSE("GPL"); + +module_init(pem_init); +module_exit(pem_exit); -- cgit v1.2.3-70-g09d2 From de24880586b28f1dc4cf91f5db962f0083baab2a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 25 Feb 2011 08:19:42 -0800 Subject: hwmon: (lm85) Use boolean to determine if VID5 is configured ADT7463 and ADT7468 optionally support VID5 instead of the standard +12V measurement input. Use a boolean to identify the chip configuration instead of hardcoding it several times. Signed-off-by: Guenter Roeck Acked-by: Jean Delvare --- drivers/hwmon/lm85.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/lm85.c b/drivers/hwmon/lm85.c index d2cc2866081..22536761a7f 100644 --- a/drivers/hwmon/lm85.c +++ b/drivers/hwmon/lm85.c @@ -306,6 +306,8 @@ struct lm85_data { const int *freq_map; enum chips type; + bool has_vid5; /* true if VID5 is configured for ADT7463 or ADT7468 */ + struct mutex update_lock; int valid; /* !=0 if following fields are valid */ unsigned long last_reading; /* In jiffies */ @@ -420,8 +422,7 @@ static ssize_t show_vid_reg(struct device *dev, struct device_attribute *attr, struct lm85_data *data = lm85_update_device(dev); int vid; - if ((data->type == adt7463 || data->type == adt7468) && - (data->vid & 0x80)) { + if (data->has_vid5) { /* 6-pin VID (VRM 10) */ vid = vid_from_reg(data->vid & 0x3f, data->vrm); } else { @@ -1321,9 +1322,13 @@ static int lm85_probe(struct i2c_client *client, /* The ADT7463/68 have an optional VRM 10 mode where pin 21 is used as a sixth digital VID input rather than an analog input. */ - data->vid = lm85_read_value(client, LM85_REG_VID); - if (!((data->type == adt7463 || data->type == adt7468) && - (data->vid & 0x80))) + if (data->type == adt7463 || data->type == adt7468) { + u8 vid = lm85_read_value(client, LM85_REG_VID); + if (vid & 0x80) + data->has_vid5 = true; + } + + if (!data->has_vid5) if ((err = sysfs_create_group(&client->dev.kobj, &lm85_group_in4))) goto err_remove_files; @@ -1457,11 +1462,8 @@ static struct lm85_data *lm85_update_device(struct device *dev) lm85_read_value(client, LM85_REG_FAN(i)); } - if (!((data->type == adt7463 || data->type == adt7468) && - (data->vid & 0x80))) { - data->in[4] = lm85_read_value(client, - LM85_REG_IN(4)); - } + if (!data->has_vid5) + data->in[4] = lm85_read_value(client, LM85_REG_IN(4)); if (data->type == adt7468) data->cfg5 = lm85_read_value(client, ADT7468_REG_CFG5); @@ -1528,8 +1530,7 @@ static struct lm85_data *lm85_update_device(struct device *dev) lm85_read_value(client, LM85_REG_FAN_MIN(i)); } - if (!((data->type == adt7463 || data->type == adt7468) && - (data->vid & 0x80))) { + if (!data->has_vid5) { data->in_min[4] = lm85_read_value(client, LM85_REG_IN_MIN(4)); data->in_max[4] = lm85_read_value(client, -- cgit v1.2.3-70-g09d2 From bc6db2b5ce79b1d5c4ba2763948fa6e731d60295 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 25 Feb 2011 08:26:47 -0800 Subject: hwmon: (lm85) Use function to remove sysfs attribute files Signed-off-by: Guenter Roeck Acked-by: Jean Delvare --- drivers/hwmon/lm85.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/lm85.c b/drivers/hwmon/lm85.c index 22536761a7f..590226a7c5a 100644 --- a/drivers/hwmon/lm85.c +++ b/drivers/hwmon/lm85.c @@ -1281,6 +1281,15 @@ static int lm85_detect(struct i2c_client *client, struct i2c_board_info *info) return 0; } +static void lm85_remove_files(struct i2c_client *client, struct lm85_data *data) +{ + sysfs_remove_group(&client->dev.kobj, &lm85_group); + if (!data->has_vid5) + sysfs_remove_group(&client->dev.kobj, &lm85_group_in4); + if (data->type == emc6d100) + sysfs_remove_group(&client->dev.kobj, &lm85_group_in567); +} + static int lm85_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -1349,10 +1358,7 @@ static int lm85_probe(struct i2c_client *client, /* Error out and cleanup code */ err_remove_files: - sysfs_remove_group(&client->dev.kobj, &lm85_group); - sysfs_remove_group(&client->dev.kobj, &lm85_group_in4); - if (data->type == emc6d100) - sysfs_remove_group(&client->dev.kobj, &lm85_group_in567); + lm85_remove_files(client, data); err_kfree: kfree(data); return err; @@ -1362,10 +1368,7 @@ static int lm85_remove(struct i2c_client *client) { struct lm85_data *data = i2c_get_clientdata(client); hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&client->dev.kobj, &lm85_group); - sysfs_remove_group(&client->dev.kobj, &lm85_group_in4); - if (data->type == emc6d100) - sysfs_remove_group(&client->dev.kobj, &lm85_group_in567); + lm85_remove_files(client, data); kfree(data); return 0; } -- cgit v1.2.3-70-g09d2 From 4f8ab430fa5dc338ce3012c91a3c7a2685d708de Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 25 Feb 2011 08:29:51 -0800 Subject: hwmon: (lm85) Do not update hysteresis and temp_auto_off if auto_temp_min is changed So far, the driver updates temp_auto_hyst and temp_auto_off if temp_auto_min changes. This may cause problems if temp_auto_off was not configured before configuring temp_auto_min, because one of the variables used (off_desired) is not initialized in this case. To simplify the code, and for consistency with other drivers, no longer update the delta between "min" and "off" until the user sets the "off" value again. Signed-off-by: Guenter Roeck Acked-by: Jean Delvare --- drivers/hwmon/lm85.c | 17 ----------------- 1 file changed, 17 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/lm85.c b/drivers/hwmon/lm85.c index 590226a7c5a..fbb5189141b 100644 --- a/drivers/hwmon/lm85.c +++ b/drivers/hwmon/lm85.c @@ -283,10 +283,6 @@ struct lm85_zone { u8 hyst; /* Low limit hysteresis. (0-15) */ u8 range; /* Temp range, encoded */ s8 critical; /* "All fans ON" temp limit */ - u8 off_desired; /* Actual "off" temperature specified. Preserved - * to prevent "drift" as other autofan control - * values change. - */ u8 max_desired; /* Actual "max" temperature specified. Preserved * to prevent "drift" as other autofan control * values change. @@ -892,7 +888,6 @@ static ssize_t set_temp_auto_temp_off(struct device *dev, mutex_lock(&data->update_lock); min = TEMP_FROM_REG(data->zone[nr].limit); - data->zone[nr].off_desired = TEMP_TO_REG(val); data->zone[nr].hyst = HYST_TO_REG(min - val); if (nr == 0 || nr == 1) { lm85_write_value(client, LM85_REG_AFAN_HYST1, @@ -935,18 +930,6 @@ static ssize_t set_temp_auto_temp_min(struct device *dev, ((data->zone[nr].range & 0x0f) << 4) | (data->pwm_freq[nr] & 0x07)); -/* Update temp_auto_hyst and temp_auto_off */ - data->zone[nr].hyst = HYST_TO_REG(TEMP_FROM_REG( - data->zone[nr].limit) - TEMP_FROM_REG( - data->zone[nr].off_desired)); - if (nr == 0 || nr == 1) { - lm85_write_value(client, LM85_REG_AFAN_HYST1, - (data->zone[0].hyst << 4) - | data->zone[1].hyst); - } else { - lm85_write_value(client, LM85_REG_AFAN_HYST2, - (data->zone[2].hyst << 4)); - } mutex_unlock(&data->update_lock); return count; } -- cgit v1.2.3-70-g09d2 From 06923f84422371a6fb10b3efcd05b80ab48715c0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 19 Feb 2011 08:27:47 -0800 Subject: hwmon: (lm85) Add support for EMC6D103S EMC6D103S is similar to EMC6D103, only it does not support registers 62[5:7], 6D[0:7], and 6E[0:7]. Register respective sysfs attributes and update affected registers for all other chips only. Signed-off-by: Guenter Roeck Acked-by: Jean Delvare --- Documentation/hwmon/lm85 | 12 +++++++- drivers/hwmon/lm85.c | 75 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 62 insertions(+), 25 deletions(-) (limited to 'drivers/hwmon') diff --git a/Documentation/hwmon/lm85 b/Documentation/hwmon/lm85 index 239258a63c8..7c49feaa79d 100644 --- a/Documentation/hwmon/lm85 +++ b/Documentation/hwmon/lm85 @@ -26,6 +26,14 @@ Supported chips: Prefix: 'emc6d102' Addresses scanned: I2C 0x2c, 0x2d, 0x2e Datasheet: http://www.smsc.com/main/catalog/emc6d102.html + * SMSC EMC6D103 + Prefix: 'emc6d103' + Addresses scanned: I2C 0x2c, 0x2d, 0x2e + Datasheet: http://www.smsc.com/main/catalog/emc6d103.html + * SMSC EMC6D103S + Prefix: 'emc6d103s' + Addresses scanned: I2C 0x2c, 0x2d, 0x2e + Datasheet: http://www.smsc.com/main/catalog/emc6d103s.html Authors: Philip Pokorny , @@ -122,9 +130,11 @@ to be register compatible. The EMC6D100 offers all the features of the EMC6D101 plus additional voltage monitoring and system control features. Unfortunately it is not possible to distinguish between the package versions on register level so these additional voltage inputs may read -zero. The EMC6D102 features addtional ADC bits thus extending precision +zero. EMC6D102 and EMC6D103 feature additional ADC bits thus extending precision of voltage and temperature channels. +SMSC EMC6D103S is similar to EMC6D103, but does not support pwm#_auto_pwm_minctl +and temp#_auto_temp_off. Hardware Configurations ----------------------- diff --git a/drivers/hwmon/lm85.c b/drivers/hwmon/lm85.c index fbb5189141b..cf47e6e476e 100644 --- a/drivers/hwmon/lm85.c +++ b/drivers/hwmon/lm85.c @@ -41,7 +41,7 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END }; enum chips { any_chip, lm85b, lm85c, adm1027, adt7463, adt7468, - emc6d100, emc6d102, emc6d103 + emc6d100, emc6d102, emc6d103, emc6d103s }; /* The LM85 registers */ @@ -350,6 +350,7 @@ static const struct i2c_device_id lm85_id[] = { { "emc6d101", emc6d100 }, { "emc6d102", emc6d102 }, { "emc6d103", emc6d103 }, + { "emc6d103s", emc6d103s }, { } }; MODULE_DEVICE_TABLE(i2c, lm85_id); @@ -1068,13 +1069,7 @@ static struct attribute *lm85_attributes[] = { &sensor_dev_attr_pwm1_auto_pwm_min.dev_attr.attr, &sensor_dev_attr_pwm2_auto_pwm_min.dev_attr.attr, &sensor_dev_attr_pwm3_auto_pwm_min.dev_attr.attr, - &sensor_dev_attr_pwm1_auto_pwm_minctl.dev_attr.attr, - &sensor_dev_attr_pwm2_auto_pwm_minctl.dev_attr.attr, - &sensor_dev_attr_pwm3_auto_pwm_minctl.dev_attr.attr, - &sensor_dev_attr_temp1_auto_temp_off.dev_attr.attr, - &sensor_dev_attr_temp2_auto_temp_off.dev_attr.attr, - &sensor_dev_attr_temp3_auto_temp_off.dev_attr.attr, &sensor_dev_attr_temp1_auto_temp_min.dev_attr.attr, &sensor_dev_attr_temp2_auto_temp_min.dev_attr.attr, &sensor_dev_attr_temp3_auto_temp_min.dev_attr.attr, @@ -1095,6 +1090,26 @@ static const struct attribute_group lm85_group = { .attrs = lm85_attributes, }; +static struct attribute *lm85_attributes_minctl[] = { + &sensor_dev_attr_pwm1_auto_pwm_minctl.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_pwm_minctl.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_pwm_minctl.dev_attr.attr, +}; + +static const struct attribute_group lm85_group_minctl = { + .attrs = lm85_attributes_minctl, +}; + +static struct attribute *lm85_attributes_temp_off[] = { + &sensor_dev_attr_temp1_auto_temp_off.dev_attr.attr, + &sensor_dev_attr_temp2_auto_temp_off.dev_attr.attr, + &sensor_dev_attr_temp3_auto_temp_off.dev_attr.attr, +}; + +static const struct attribute_group lm85_group_temp_off = { + .attrs = lm85_attributes_temp_off, +}; + static struct attribute *lm85_attributes_in4[] = { &sensor_dev_attr_in4_input.dev_attr.attr, &sensor_dev_attr_in4_min.dev_attr.attr, @@ -1242,16 +1257,9 @@ static int lm85_detect(struct i2c_client *client, struct i2c_board_info *info) case LM85_VERSTEP_EMC6D103_A1: type_name = "emc6d103"; break; - /* - * Registers apparently missing in EMC6D103S/EMC6D103:A2 - * compared to EMC6D103:A0, EMC6D103:A1, and EMC6D102 - * (according to the data sheets), but used unconditionally - * in the driver: 62[5:7], 6D[0:7], and 6E[0:7]. - * So skip EMC6D103S for now. case LM85_VERSTEP_EMC6D103S: type_name = "emc6d103s"; break; - */ } } else { dev_dbg(&adapter->dev, @@ -1267,6 +1275,10 @@ static int lm85_detect(struct i2c_client *client, struct i2c_board_info *info) static void lm85_remove_files(struct i2c_client *client, struct lm85_data *data) { sysfs_remove_group(&client->dev.kobj, &lm85_group); + if (data->type != emc6d103s) { + sysfs_remove_group(&client->dev.kobj, &lm85_group_minctl); + sysfs_remove_group(&client->dev.kobj, &lm85_group_temp_off); + } if (!data->has_vid5) sysfs_remove_group(&client->dev.kobj, &lm85_group_in4); if (data->type == emc6d100) @@ -1295,6 +1307,7 @@ static int lm85_probe(struct i2c_client *client, case emc6d100: case emc6d102: case emc6d103: + case emc6d103s: data->freq_map = adm1027_freq_map; break; default: @@ -1312,6 +1325,17 @@ static int lm85_probe(struct i2c_client *client, if (err) goto err_kfree; + /* minctl and temp_off exist on all chips except emc6d103s */ + if (data->type != emc6d103s) { + err = sysfs_create_group(&client->dev.kobj, &lm85_group_minctl); + if (err) + goto err_kfree; + err = sysfs_create_group(&client->dev.kobj, + &lm85_group_temp_off); + if (err) + goto err_kfree; + } + /* The ADT7463/68 have an optional VRM 10 mode where pin 21 is used as a sixth digital VID input rather than an analog input. */ if (data->type == adt7463 || data->type == adt7468) { @@ -1475,7 +1499,8 @@ static struct lm85_data *lm85_update_device(struct device *dev) /* More alarm bits */ data->alarms |= lm85_read_value(client, EMC6D100_REG_ALARM3) << 16; - } else if (data->type == emc6d102 || data->type == emc6d103) { + } else if (data->type == emc6d102 || data->type == emc6d103 || + data->type == emc6d103s) { /* Have to read LSB bits after the MSB ones because the reading of the MSB bits has frozen the LSBs (backward from the ADM1027). @@ -1560,17 +1585,19 @@ static struct lm85_data *lm85_update_device(struct device *dev) } } - i = lm85_read_value(client, LM85_REG_AFAN_SPIKE1); - data->autofan[0].min_off = (i & 0x20) != 0; - data->autofan[1].min_off = (i & 0x40) != 0; - data->autofan[2].min_off = (i & 0x80) != 0; + if (data->type != emc6d103s) { + i = lm85_read_value(client, LM85_REG_AFAN_SPIKE1); + data->autofan[0].min_off = (i & 0x20) != 0; + data->autofan[1].min_off = (i & 0x40) != 0; + data->autofan[2].min_off = (i & 0x80) != 0; - i = lm85_read_value(client, LM85_REG_AFAN_HYST1); - data->zone[0].hyst = i >> 4; - data->zone[1].hyst = i & 0x0f; + i = lm85_read_value(client, LM85_REG_AFAN_HYST1); + data->zone[0].hyst = i >> 4; + data->zone[1].hyst = i & 0x0f; - i = lm85_read_value(client, LM85_REG_AFAN_HYST2); - data->zone[2].hyst = i >> 4; + i = lm85_read_value(client, LM85_REG_AFAN_HYST2); + data->zone[2].hyst = i >> 4; + } data->last_config = jiffies; } /* last_config */ -- cgit v1.2.3-70-g09d2 From 442aba78728e77e03172ba83e905b9aff96febcf Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 26 Jan 2011 20:09:02 -0800 Subject: hwmon: PMBus device driver This driver adds support for hardware monitoring features of various PMBus devices. Signed-off-by: Guenter Roeck Acked-by: Jonathan Cameron --- drivers/hwmon/Kconfig | 25 + drivers/hwmon/Makefile | 4 + drivers/hwmon/pmbus.c | 203 ++++++ drivers/hwmon/pmbus.h | 313 +++++++++ drivers/hwmon/pmbus_core.c | 1628 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/i2c/pmbus.h | 45 ++ 6 files changed, 2218 insertions(+) create mode 100644 drivers/hwmon/pmbus.c create mode 100644 drivers/hwmon/pmbus.h create mode 100644 drivers/hwmon/pmbus_core.c create mode 100644 include/linux/i2c/pmbus.h (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index d4ffc605208..ac116693e35 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -756,6 +756,31 @@ config SENSORS_PCF8591 These devices are hard to detect and rarely found on mainstream hardware. If unsure, say N. +config PMBUS + tristate "PMBus support" + depends on I2C && EXPERIMENTAL + default n + help + Say yes here if you want to enable PMBus support. + + This driver can also be built as a module. If so, the module will + be called pmbus_core. + +if PMBUS + +config SENSORS_PMBUS + tristate "Generic PMBus devices" + default n + help + If you say yes here you get hardware monitoring support for generic + PMBus devices, including but not limited to BMR450, BMR451, BMR453, + BMR454, and LTC2978. + + This driver can also be built as a module. If so, the module will + be called pmbus. + +endif # PMBUS + config SENSORS_SHT15 tristate "Sensiron humidity and temperature sensors. SHT15 and compat." depends on GENERIC_GPIO diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 46819818ef5..09e67955fd5 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -114,6 +114,10 @@ obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o +# PMBus drivers +obj-$(CONFIG_PMBUS) += pmbus_core.o +obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o + ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG endif diff --git a/drivers/hwmon/pmbus.c b/drivers/hwmon/pmbus.c new file mode 100644 index 00000000000..98e2e28899e --- /dev/null +++ b/drivers/hwmon/pmbus.c @@ -0,0 +1,203 @@ +/* + * Hardware monitoring driver for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +/* + * Find sensor groups and status registers on each page. + */ +static void pmbus_find_sensor_groups(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int page; + + /* Sensors detected on page 0 only */ + if (pmbus_check_word_register(client, 0, PMBUS_READ_VIN)) + info->func[0] |= PMBUS_HAVE_VIN; + if (pmbus_check_word_register(client, 0, PMBUS_READ_VCAP)) + info->func[0] |= PMBUS_HAVE_VCAP; + if (pmbus_check_word_register(client, 0, PMBUS_READ_IIN)) + info->func[0] |= PMBUS_HAVE_IIN; + if (pmbus_check_word_register(client, 0, PMBUS_READ_PIN)) + info->func[0] |= PMBUS_HAVE_PIN; + if (info->func[0] + && pmbus_check_byte_register(client, 0, PMBUS_STATUS_INPUT)) + info->func[0] |= PMBUS_HAVE_STATUS_INPUT; + if (pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_1)) { + info->func[0] |= PMBUS_HAVE_FAN12; + if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_12)) + info->func[0] |= PMBUS_HAVE_STATUS_FAN12; + } + if (pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_3)) { + info->func[0] |= PMBUS_HAVE_FAN34; + if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_34)) + info->func[0] |= PMBUS_HAVE_STATUS_FAN34; + } + if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_1)) { + info->func[0] |= PMBUS_HAVE_TEMP; + if (pmbus_check_byte_register(client, 0, + PMBUS_STATUS_TEMPERATURE)) + info->func[0] |= PMBUS_HAVE_STATUS_TEMP; + } + + /* Sensors detected on all pages */ + for (page = 0; page < info->pages; page++) { + if (pmbus_check_word_register(client, page, PMBUS_READ_VOUT)) { + info->func[page] |= PMBUS_HAVE_VOUT; + if (pmbus_check_byte_register(client, page, + PMBUS_STATUS_VOUT)) + info->func[page] |= PMBUS_HAVE_STATUS_VOUT; + } + if (pmbus_check_word_register(client, page, PMBUS_READ_IOUT)) { + info->func[page] |= PMBUS_HAVE_IOUT; + if (pmbus_check_byte_register(client, 0, + PMBUS_STATUS_IOUT)) + info->func[page] |= PMBUS_HAVE_STATUS_IOUT; + } + if (pmbus_check_word_register(client, page, PMBUS_READ_POUT)) + info->func[page] |= PMBUS_HAVE_POUT; + } +} + +/* + * Identify chip parameters. + */ +static int pmbus_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + if (!info->pages) { + /* + * Check if the PAGE command is supported. If it is, + * keep setting the page number until it fails or until the + * maximum number of pages has been reached. Assume that + * this is the number of pages supported by the chip. + */ + if (pmbus_check_byte_register(client, 0, PMBUS_PAGE)) { + int page; + + for (page = 1; page < PMBUS_PAGES; page++) { + if (pmbus_set_page(client, page) < 0) + break; + } + pmbus_set_page(client, 0); + info->pages = page; + } else { + info->pages = 1; + } + } + + /* + * We should check if the COEFFICIENTS register is supported. + * If it is, and the chip is configured for direct mode, we can read + * the coefficients from the chip, one set per group of sensor + * registers. + * + * To do this, we will need access to a chip which actually supports the + * COEFFICIENTS command, since the command is too complex to implement + * without testing it. + */ + + /* Try to find sensor groups */ + pmbus_find_sensor_groups(client, info); + + return 0; +} + +static int pmbus_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pmbus_driver_info *info; + int ret; + + info = kzalloc(sizeof(struct pmbus_driver_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pages = id->driver_data; + info->identify = pmbus_identify; + + ret = pmbus_do_probe(client, id, info); + if (ret < 0) + goto out; + return 0; + +out: + kfree(info); + return ret; +} + +static int pmbus_remove(struct i2c_client *client) +{ + int ret; + const struct pmbus_driver_info *info; + + info = pmbus_get_driver_info(client); + ret = pmbus_do_remove(client); + kfree(info); + return ret; +} + +/* + * Use driver_data to set the number of pages supported by the chip. + */ +static const struct i2c_device_id pmbus_id[] = { + {"bmr450", 1}, + {"bmr451", 1}, + {"bmr453", 1}, + {"bmr454", 1}, + {"ltc2978", 8}, + {"pmbus", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, pmbus_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver pmbus_driver = { + .driver = { + .name = "pmbus", + }, + .probe = pmbus_probe, + .remove = pmbus_remove, + .id_table = pmbus_id, +}; + +static int __init pmbus_init(void) +{ + return i2c_add_driver(&pmbus_driver); +} + +static void __exit pmbus_exit(void) +{ + i2c_del_driver(&pmbus_driver); +} + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("Generic PMBus driver"); +MODULE_LICENSE("GPL"); +module_init(pmbus_init); +module_exit(pmbus_exit); diff --git a/drivers/hwmon/pmbus.h b/drivers/hwmon/pmbus.h new file mode 100644 index 00000000000..a81f7f22876 --- /dev/null +++ b/drivers/hwmon/pmbus.h @@ -0,0 +1,313 @@ +/* + * pmbus.h - Common defines and structures for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef PMBUS_H +#define PMBUS_H + +/* + * Registers + */ +#define PMBUS_PAGE 0x00 +#define PMBUS_OPERATION 0x01 +#define PMBUS_ON_OFF_CONFIG 0x02 +#define PMBUS_CLEAR_FAULTS 0x03 +#define PMBUS_PHASE 0x04 + +#define PMBUS_CAPABILITY 0x19 +#define PMBUS_QUERY 0x1A + +#define PMBUS_VOUT_MODE 0x20 +#define PMBUS_VOUT_COMMAND 0x21 +#define PMBUS_VOUT_TRIM 0x22 +#define PMBUS_VOUT_CAL_OFFSET 0x23 +#define PMBUS_VOUT_MAX 0x24 +#define PMBUS_VOUT_MARGIN_HIGH 0x25 +#define PMBUS_VOUT_MARGIN_LOW 0x26 +#define PMBUS_VOUT_TRANSITION_RATE 0x27 +#define PMBUS_VOUT_DROOP 0x28 +#define PMBUS_VOUT_SCALE_LOOP 0x29 +#define PMBUS_VOUT_SCALE_MONITOR 0x2A + +#define PMBUS_COEFFICIENTS 0x30 +#define PMBUS_POUT_MAX 0x31 + +#define PMBUS_FAN_CONFIG_12 0x3A +#define PMBUS_FAN_COMMAND_1 0x3B +#define PMBUS_FAN_COMMAND_2 0x3C +#define PMBUS_FAN_CONFIG_34 0x3D +#define PMBUS_FAN_COMMAND_3 0x3E +#define PMBUS_FAN_COMMAND_4 0x3F + +#define PMBUS_VOUT_OV_FAULT_LIMIT 0x40 +#define PMBUS_VOUT_OV_FAULT_RESPONSE 0x41 +#define PMBUS_VOUT_OV_WARN_LIMIT 0x42 +#define PMBUS_VOUT_UV_WARN_LIMIT 0x43 +#define PMBUS_VOUT_UV_FAULT_LIMIT 0x44 +#define PMBUS_VOUT_UV_FAULT_RESPONSE 0x45 +#define PMBUS_IOUT_OC_FAULT_LIMIT 0x46 +#define PMBUS_IOUT_OC_FAULT_RESPONSE 0x47 +#define PMBUS_IOUT_OC_LV_FAULT_LIMIT 0x48 +#define PMBUS_IOUT_OC_LV_FAULT_RESPONSE 0x49 +#define PMBUS_IOUT_OC_WARN_LIMIT 0x4A +#define PMBUS_IOUT_UC_FAULT_LIMIT 0x4B +#define PMBUS_IOUT_UC_FAULT_RESPONSE 0x4C + +#define PMBUS_OT_FAULT_LIMIT 0x4F +#define PMBUS_OT_FAULT_RESPONSE 0x50 +#define PMBUS_OT_WARN_LIMIT 0x51 +#define PMBUS_UT_WARN_LIMIT 0x52 +#define PMBUS_UT_FAULT_LIMIT 0x53 +#define PMBUS_UT_FAULT_RESPONSE 0x54 +#define PMBUS_VIN_OV_FAULT_LIMIT 0x55 +#define PMBUS_VIN_OV_FAULT_RESPONSE 0x56 +#define PMBUS_VIN_OV_WARN_LIMIT 0x57 +#define PMBUS_VIN_UV_WARN_LIMIT 0x58 +#define PMBUS_VIN_UV_FAULT_LIMIT 0x59 + +#define PMBUS_IIN_OC_FAULT_LIMIT 0x5B +#define PMBUS_IIN_OC_WARN_LIMIT 0x5D + +#define PMBUS_POUT_OP_FAULT_LIMIT 0x68 +#define PMBUS_POUT_OP_WARN_LIMIT 0x6A +#define PMBUS_PIN_OP_WARN_LIMIT 0x6B + +#define PMBUS_STATUS_BYTE 0x78 +#define PMBUS_STATUS_WORD 0x79 +#define PMBUS_STATUS_VOUT 0x7A +#define PMBUS_STATUS_IOUT 0x7B +#define PMBUS_STATUS_INPUT 0x7C +#define PMBUS_STATUS_TEMPERATURE 0x7D +#define PMBUS_STATUS_CML 0x7E +#define PMBUS_STATUS_OTHER 0x7F +#define PMBUS_STATUS_MFR_SPECIFIC 0x80 +#define PMBUS_STATUS_FAN_12 0x81 +#define PMBUS_STATUS_FAN_34 0x82 + +#define PMBUS_READ_VIN 0x88 +#define PMBUS_READ_IIN 0x89 +#define PMBUS_READ_VCAP 0x8A +#define PMBUS_READ_VOUT 0x8B +#define PMBUS_READ_IOUT 0x8C +#define PMBUS_READ_TEMPERATURE_1 0x8D +#define PMBUS_READ_TEMPERATURE_2 0x8E +#define PMBUS_READ_TEMPERATURE_3 0x8F +#define PMBUS_READ_FAN_SPEED_1 0x90 +#define PMBUS_READ_FAN_SPEED_2 0x91 +#define PMBUS_READ_FAN_SPEED_3 0x92 +#define PMBUS_READ_FAN_SPEED_4 0x93 +#define PMBUS_READ_DUTY_CYCLE 0x94 +#define PMBUS_READ_FREQUENCY 0x95 +#define PMBUS_READ_POUT 0x96 +#define PMBUS_READ_PIN 0x97 + +#define PMBUS_REVISION 0x98 +#define PMBUS_MFR_ID 0x99 +#define PMBUS_MFR_MODEL 0x9A +#define PMBUS_MFR_REVISION 0x9B +#define PMBUS_MFR_LOCATION 0x9C +#define PMBUS_MFR_DATE 0x9D +#define PMBUS_MFR_SERIAL 0x9E + +/* + * CAPABILITY + */ +#define PB_CAPABILITY_SMBALERT (1<<4) +#define PB_CAPABILITY_ERROR_CHECK (1<<7) + +/* + * VOUT_MODE + */ +#define PB_VOUT_MODE_MODE_MASK 0xe0 +#define PB_VOUT_MODE_PARAM_MASK 0x1f + +#define PB_VOUT_MODE_LINEAR 0x00 +#define PB_VOUT_MODE_VID 0x20 +#define PB_VOUT_MODE_DIRECT 0x40 + +/* + * Fan configuration + */ +#define PB_FAN_2_PULSE_MASK ((1 << 0) | (1 << 1)) +#define PB_FAN_2_RPM (1 << 2) +#define PB_FAN_2_INSTALLED (1 << 3) +#define PB_FAN_1_PULSE_MASK ((1 << 4) | (1 << 5)) +#define PB_FAN_1_RPM (1 << 6) +#define PB_FAN_1_INSTALLED (1 << 7) + +/* + * STATUS_BYTE, STATUS_WORD (lower) + */ +#define PB_STATUS_NONE_ABOVE (1<<0) +#define PB_STATUS_CML (1<<1) +#define PB_STATUS_TEMPERATURE (1<<2) +#define PB_STATUS_VIN_UV (1<<3) +#define PB_STATUS_IOUT_OC (1<<4) +#define PB_STATUS_VOUT_OV (1<<5) +#define PB_STATUS_OFF (1<<6) +#define PB_STATUS_BUSY (1<<7) + +/* + * STATUS_WORD (upper) + */ +#define PB_STATUS_UNKNOWN (1<<8) +#define PB_STATUS_OTHER (1<<9) +#define PB_STATUS_FANS (1<<10) +#define PB_STATUS_POWER_GOOD_N (1<<11) +#define PB_STATUS_WORD_MFR (1<<12) +#define PB_STATUS_INPUT (1<<13) +#define PB_STATUS_IOUT_POUT (1<<14) +#define PB_STATUS_VOUT (1<<15) + +/* + * STATUS_IOUT + */ +#define PB_POUT_OP_WARNING (1<<0) +#define PB_POUT_OP_FAULT (1<<1) +#define PB_POWER_LIMITING (1<<2) +#define PB_CURRENT_SHARE_FAULT (1<<3) +#define PB_IOUT_UC_FAULT (1<<4) +#define PB_IOUT_OC_WARNING (1<<5) +#define PB_IOUT_OC_LV_FAULT (1<<6) +#define PB_IOUT_OC_FAULT (1<<7) + +/* + * STATUS_VOUT, STATUS_INPUT + */ +#define PB_VOLTAGE_UV_FAULT (1<<4) +#define PB_VOLTAGE_UV_WARNING (1<<5) +#define PB_VOLTAGE_OV_WARNING (1<<6) +#define PB_VOLTAGE_OV_FAULT (1<<7) + +/* + * STATUS_INPUT + */ +#define PB_PIN_OP_WARNING (1<<0) +#define PB_IIN_OC_WARNING (1<<1) +#define PB_IIN_OC_FAULT (1<<2) + +/* + * STATUS_TEMPERATURE + */ +#define PB_TEMP_UT_FAULT (1<<4) +#define PB_TEMP_UT_WARNING (1<<5) +#define PB_TEMP_OT_WARNING (1<<6) +#define PB_TEMP_OT_FAULT (1<<7) + +/* + * STATUS_FAN + */ +#define PB_FAN_AIRFLOW_WARNING (1<<0) +#define PB_FAN_AIRFLOW_FAULT (1<<1) +#define PB_FAN_FAN2_SPEED_OVERRIDE (1<<2) +#define PB_FAN_FAN1_SPEED_OVERRIDE (1<<3) +#define PB_FAN_FAN2_WARNING (1<<4) +#define PB_FAN_FAN1_WARNING (1<<5) +#define PB_FAN_FAN2_FAULT (1<<6) +#define PB_FAN_FAN1_FAULT (1<<7) + +/* + * CML_FAULT_STATUS + */ +#define PB_CML_FAULT_OTHER_MEM_LOGIC (1<<0) +#define PB_CML_FAULT_OTHER_COMM (1<<1) +#define PB_CML_FAULT_PROCESSOR (1<<3) +#define PB_CML_FAULT_MEMORY (1<<4) +#define PB_CML_FAULT_PACKET_ERROR (1<<5) +#define PB_CML_FAULT_INVALID_DATA (1<<6) +#define PB_CML_FAULT_INVALID_COMMAND (1<<7) + +enum pmbus_sensor_classes { + PSC_VOLTAGE_IN = 0, + PSC_VOLTAGE_OUT, + PSC_CURRENT_IN, + PSC_CURRENT_OUT, + PSC_POWER, + PSC_TEMPERATURE, + PSC_FAN, + PSC_NUM_CLASSES /* Number of power sensor classes */ +}; + +#define PMBUS_PAGES 32 /* Per PMBus specification */ + +/* Functionality bit mask */ +#define PMBUS_HAVE_VIN (1 << 0) +#define PMBUS_HAVE_VCAP (1 << 1) +#define PMBUS_HAVE_VOUT (1 << 2) +#define PMBUS_HAVE_IIN (1 << 3) +#define PMBUS_HAVE_IOUT (1 << 4) +#define PMBUS_HAVE_PIN (1 << 5) +#define PMBUS_HAVE_POUT (1 << 6) +#define PMBUS_HAVE_FAN12 (1 << 7) +#define PMBUS_HAVE_FAN34 (1 << 8) +#define PMBUS_HAVE_TEMP (1 << 9) +#define PMBUS_HAVE_TEMP2 (1 << 10) +#define PMBUS_HAVE_TEMP3 (1 << 11) +#define PMBUS_HAVE_STATUS_VOUT (1 << 12) +#define PMBUS_HAVE_STATUS_IOUT (1 << 13) +#define PMBUS_HAVE_STATUS_INPUT (1 << 14) +#define PMBUS_HAVE_STATUS_TEMP (1 << 15) +#define PMBUS_HAVE_STATUS_FAN12 (1 << 16) +#define PMBUS_HAVE_STATUS_FAN34 (1 << 17) + +struct pmbus_driver_info { + int pages; /* Total number of pages */ + bool direct[PSC_NUM_CLASSES]; + /* true if device uses direct data format + for the given sensor class */ + /* + * Support one set of coefficients for each sensor type + * Used for chips providing data in direct mode. + */ + int m[PSC_NUM_CLASSES]; /* mantissa for direct data format */ + int b[PSC_NUM_CLASSES]; /* offset */ + int R[PSC_NUM_CLASSES]; /* exponent */ + + u32 func[PMBUS_PAGES]; /* Functionality, per page */ + /* + * The get_status function maps manufacturing specific status values + * into PMBus standard status values. + * This function is optional and only necessary if chip specific status + * register values have to be mapped into standard PMBus status register + * values. + */ + int (*get_status)(struct i2c_client *client, int page, int reg); + /* + * The identify function determines supported PMBus functionality. + * This function is only necessary if a chip driver supports multiple + * chips, and the chip functionality is not pre-determined. + */ + int (*identify)(struct i2c_client *client, + struct pmbus_driver_info *info); +}; + +/* Function declarations */ + +int pmbus_set_page(struct i2c_client *client, u8 page); +int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 reg); +void pmbus_clear_faults(struct i2c_client *client); +bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); +bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); +int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, + struct pmbus_driver_info *info); +int pmbus_do_remove(struct i2c_client *client); +const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client + *client); + +#endif /* PMBUS_H */ diff --git a/drivers/hwmon/pmbus_core.c b/drivers/hwmon/pmbus_core.c new file mode 100644 index 00000000000..0edab1c9553 --- /dev/null +++ b/drivers/hwmon/pmbus_core.c @@ -0,0 +1,1628 @@ +/* + * Hardware monitoring driver for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +/* + * Constants needed to determine number of sensors, booleans, and labels. + */ +#define PMBUS_MAX_INPUT_SENSORS 11 /* 6*volt, 3*curr, 2*power */ +#define PMBUS_VOUT_SENSORS_PER_PAGE 5 /* input, min, max, lcrit, + crit */ +#define PMBUS_IOUT_SENSORS_PER_PAGE 4 /* input, min, max, crit */ +#define PMBUS_POUT_SENSORS_PER_PAGE 4 /* input, cap, max, crit */ +#define PMBUS_MAX_SENSORS_PER_FAN 1 /* input */ +#define PMBUS_MAX_SENSORS_PER_TEMP 5 /* input, min, max, lcrit, + crit */ + +#define PMBUS_MAX_INPUT_BOOLEANS 7 /* v: min_alarm, max_alarm, + lcrit_alarm, crit_alarm; + c: alarm, crit_alarm; + p: crit_alarm */ +#define PMBUS_VOUT_BOOLEANS_PER_PAGE 4 /* min_alarm, max_alarm, + lcrit_alarm, crit_alarm */ +#define PMBUS_IOUT_BOOLEANS_PER_PAGE 3 /* alarm, lcrit_alarm, + crit_alarm */ +#define PMBUS_POUT_BOOLEANS_PER_PAGE 2 /* alarm, crit_alarm */ +#define PMBUS_MAX_BOOLEANS_PER_FAN 2 /* alarm, fault */ +#define PMBUS_MAX_BOOLEANS_PER_TEMP 4 /* min_alarm, max_alarm, + lcrit_alarm, crit_alarm */ + +#define PMBUS_MAX_INPUT_LABELS 4 /* vin, vcap, iin, pin */ + +/* + * status, status_vout, status_iout, status_fans, and status_temp + * are paged. status_input and status_fan34 are unpaged. + * status_fan34 is a special case to handle a second set of fans + * on page 0. + */ +#define PB_NUM_STATUS_REG (PMBUS_PAGES * 5 + 2) + +/* + * Index into status register array, per status register group + */ +#define PB_STATUS_BASE 0 +#define PB_STATUS_VOUT_BASE (PB_STATUS_BASE + PMBUS_PAGES) +#define PB_STATUS_IOUT_BASE (PB_STATUS_VOUT_BASE + PMBUS_PAGES) +#define PB_STATUS_FAN_BASE (PB_STATUS_IOUT_BASE + PMBUS_PAGES) +#define PB_STATUS_FAN34_BASE (PB_STATUS_FAN_BASE + PMBUS_PAGES) +#define PB_STATUS_INPUT_BASE (PB_STATUS_FAN34_BASE + 1) +#define PB_STATUS_TEMP_BASE (PB_STATUS_INPUT_BASE + 1) + +struct pmbus_sensor { + char name[I2C_NAME_SIZE]; /* sysfs sensor name */ + struct sensor_device_attribute attribute; + u8 page; /* page number */ + u8 reg; /* register */ + enum pmbus_sensor_classes class; /* sensor class */ + bool update; /* runtime sensor update needed */ + int data; /* Sensor data. + Negative if there was a read error */ +}; + +struct pmbus_boolean { + char name[I2C_NAME_SIZE]; /* sysfs boolean name */ + struct sensor_device_attribute attribute; +}; + +struct pmbus_label { + char name[I2C_NAME_SIZE]; /* sysfs label name */ + struct sensor_device_attribute attribute; + char label[I2C_NAME_SIZE]; /* label */ +}; + +struct pmbus_data { + struct device *hwmon_dev; + + u32 flags; /* from platform data */ + + int exponent; /* linear mode: exponent for output voltages */ + + const struct pmbus_driver_info *info; + + int max_attributes; + int num_attributes; + struct attribute **attributes; + struct attribute_group group; + + /* + * Sensors cover both sensor and limit registers. + */ + int max_sensors; + int num_sensors; + struct pmbus_sensor *sensors; + /* + * Booleans are used for alarms. + * Values are determined from status registers. + */ + int max_booleans; + int num_booleans; + struct pmbus_boolean *booleans; + /* + * Labels are used to map generic names (e.g., "in1") + * to PMBus specific names (e.g., "vin" or "vout1"). + */ + int max_labels; + int num_labels; + struct pmbus_label *labels; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + /* + * A single status register covers multiple attributes, + * so we keep them all together. + */ + u8 status_bits; + u8 status[PB_NUM_STATUS_REG]; + + u8 currpage; +}; + +int pmbus_set_page(struct i2c_client *client, u8 page) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + int rv = 0; + int newpage; + + if (page != data->currpage) { + rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + newpage = i2c_smbus_read_byte_data(client, PMBUS_PAGE); + if (newpage != page) + rv = -EINVAL; + else + data->currpage = page; + } + return rv; +} +EXPORT_SYMBOL_GPL(pmbus_set_page); + +static int pmbus_write_byte(struct i2c_client *client, u8 page, u8 value) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_write_byte(client, value); +} + +static int pmbus_write_word_data(struct i2c_client *client, u8 page, u8 reg, + u16 word) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_write_word_data(client, reg, word); +} + +int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 reg) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_read_word_data(client, reg); +} +EXPORT_SYMBOL_GPL(pmbus_read_word_data); + +static int pmbus_read_byte_data(struct i2c_client *client, u8 page, u8 reg) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_read_byte_data(client, reg); +} + +static void pmbus_clear_fault_page(struct i2c_client *client, int page) +{ + pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); +} + +void pmbus_clear_faults(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + int i; + + for (i = 0; i < data->info->pages; i++) + pmbus_clear_fault_page(client, i); +} +EXPORT_SYMBOL_GPL(pmbus_clear_faults); + +static int pmbus_check_status_cml(struct i2c_client *client, int page) +{ + int status, status2; + + status = pmbus_read_byte_data(client, page, PMBUS_STATUS_BYTE); + if (status < 0 || (status & PB_STATUS_CML)) { + status2 = pmbus_read_byte_data(client, page, PMBUS_STATUS_CML); + if (status2 < 0 || (status2 & PB_CML_FAULT_INVALID_COMMAND)) + return -EINVAL; + } + return 0; +} + +bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg) +{ + int rv; + struct pmbus_data *data = i2c_get_clientdata(client); + + rv = pmbus_read_byte_data(client, page, reg); + if (rv >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK)) + rv = pmbus_check_status_cml(client, page); + pmbus_clear_fault_page(client, page); + return rv >= 0; +} +EXPORT_SYMBOL_GPL(pmbus_check_byte_register); + +bool pmbus_check_word_register(struct i2c_client *client, int page, int reg) +{ + int rv; + struct pmbus_data *data = i2c_get_clientdata(client); + + rv = pmbus_read_word_data(client, page, reg); + if (rv >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK)) + rv = pmbus_check_status_cml(client, page); + pmbus_clear_fault_page(client, page); + return rv >= 0; +} +EXPORT_SYMBOL_GPL(pmbus_check_word_register); + +const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + + return data->info; +} +EXPORT_SYMBOL_GPL(pmbus_get_driver_info); + +static int pmbus_get_status(struct i2c_client *client, int page, int reg) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->get_status) { + status = info->get_status(client, page, reg); + if (status != -ENODATA) + return status; + } + return pmbus_read_byte_data(client, page, reg); +} + +static struct pmbus_data *pmbus_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + + mutex_lock(&data->update_lock); + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + int i; + + for (i = 0; i < info->pages; i++) + data->status[PB_STATUS_BASE + i] + = pmbus_read_byte_data(client, i, + PMBUS_STATUS_BYTE); + for (i = 0; i < info->pages; i++) { + if (!(info->func[i] & PMBUS_HAVE_STATUS_VOUT)) + continue; + data->status[PB_STATUS_VOUT_BASE + i] + = pmbus_get_status(client, i, PMBUS_STATUS_VOUT); + } + for (i = 0; i < info->pages; i++) { + if (!(info->func[i] & PMBUS_HAVE_STATUS_IOUT)) + continue; + data->status[PB_STATUS_IOUT_BASE + i] + = pmbus_get_status(client, i, PMBUS_STATUS_IOUT); + } + for (i = 0; i < info->pages; i++) { + if (!(info->func[i] & PMBUS_HAVE_STATUS_TEMP)) + continue; + data->status[PB_STATUS_TEMP_BASE + i] + = pmbus_get_status(client, i, + PMBUS_STATUS_TEMPERATURE); + } + for (i = 0; i < info->pages; i++) { + if (!(info->func[i] & PMBUS_HAVE_STATUS_FAN12)) + continue; + data->status[PB_STATUS_FAN_BASE + i] + = pmbus_get_status(client, i, PMBUS_STATUS_FAN_12); + } + + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) + data->status[PB_STATUS_INPUT_BASE] + = pmbus_get_status(client, 0, PMBUS_STATUS_INPUT); + + if (info->func[0] & PMBUS_HAVE_STATUS_FAN34) + data->status[PB_STATUS_FAN34_BASE] + = pmbus_get_status(client, 0, PMBUS_STATUS_FAN_34); + + for (i = 0; i < data->num_sensors; i++) { + struct pmbus_sensor *sensor = &data->sensors[i]; + + if (!data->valid || sensor->update) + sensor->data + = pmbus_read_word_data(client, sensor->page, + sensor->reg); + } + pmbus_clear_faults(client); + data->last_updated = jiffies; + data->valid = 1; + } + mutex_unlock(&data->update_lock); + return data; +} + +/* + * Convert linear sensor values to milli- or micro-units + * depending on sensor type. + */ +static int pmbus_reg2data_linear(struct pmbus_data *data, + struct pmbus_sensor *sensor) +{ + s16 exponent, mantissa; + long val; + + if (sensor->class == PSC_VOLTAGE_OUT) { + exponent = data->exponent; + mantissa = (s16) sensor->data; + } else { + exponent = (sensor->data >> 11) & 0x001f; + mantissa = sensor->data & 0x07ff; + + if (exponent > 0x0f) + exponent |= 0xffe0; /* sign extend exponent */ + if (mantissa > 0x03ff) + mantissa |= 0xf800; /* sign extend mantissa */ + } + + val = mantissa; + + /* scale result to milli-units for all sensors except fans */ + if (sensor->class != PSC_FAN) + val = val * 1000L; + + /* scale result to micro-units for power sensors */ + if (sensor->class == PSC_POWER) + val = val * 1000L; + + if (exponent >= 0) + val <<= exponent; + else + val >>= -exponent; + + return (int)val; +} + +/* + * Convert direct sensor values to milli- or micro-units + * depending on sensor type. + */ +static int pmbus_reg2data_direct(struct pmbus_data *data, + struct pmbus_sensor *sensor) +{ + long val = (s16) sensor->data; + long m, b, R; + + m = data->info->m[sensor->class]; + b = data->info->b[sensor->class]; + R = data->info->R[sensor->class]; + + if (m == 0) + return 0; + + /* X = 1/m * (Y * 10^-R - b) */ + R = -R; + /* scale result to milli-units for everything but fans */ + if (sensor->class != PSC_FAN) { + R += 3; + b *= 1000; + } + + /* scale result to micro-units for power sensors */ + if (sensor->class == PSC_POWER) { + R += 3; + b *= 1000; + } + + while (R > 0) { + val *= 10; + R--; + } + while (R < 0) { + val = DIV_ROUND_CLOSEST(val, 10); + R++; + } + + return (int)((val - b) / m); +} + +static int pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor) +{ + int val; + + if (data->info->direct[sensor->class]) + val = pmbus_reg2data_direct(data, sensor); + else + val = pmbus_reg2data_linear(data, sensor); + + return val; +} + +#define MAX_MANTISSA (1023 * 1000) +#define MIN_MANTISSA (511 * 1000) + +static u16 pmbus_data2reg_linear(struct pmbus_data *data, + enum pmbus_sensor_classes class, long val) +{ + s16 exponent = 0, mantissa = 0; + bool negative = false; + + /* simple case */ + if (val == 0) + return 0; + + if (val < 0) { + negative = true; + val = -val; + } + + if (class == PSC_VOLTAGE_OUT) { + /* + * For a static exponents, we don't have a choice + * but to adjust the value to it. + */ + if (data->exponent < 0) + val <<= -data->exponent; + else + val >>= data->exponent; + val = DIV_ROUND_CLOSEST(val, 1000); + if (val > 0x7fff) + val = 0x7fff; + return negative ? -val : val; + } + + /* Power is in uW. Convert to mW before converting. */ + if (class == PSC_POWER) + val = DIV_ROUND_CLOSEST(val, 1000L); + + /* + * For simplicity, convert fan data to milli-units + * before calculating the exponent. + */ + if (class == PSC_FAN) + val = val * 1000; + + /* Reduce large mantissa until it fits into 10 bit */ + while (val >= MAX_MANTISSA && exponent < 15) { + exponent++; + val >>= 1; + } + /* Increase small mantissa to improve precision */ + while (val < MIN_MANTISSA && exponent > -15) { + exponent--; + val <<= 1; + } + + /* Convert mantissa from milli-units to units */ + mantissa = DIV_ROUND_CLOSEST(val, 1000); + + /* Ensure that resulting number is within range */ + if (mantissa > 0x3ff) + mantissa = 0x3ff; + + /* restore sign */ + if (negative) + mantissa = -mantissa; + + /* Convert to 5 bit exponent, 11 bit mantissa */ + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); +} + +static u16 pmbus_data2reg_direct(struct pmbus_data *data, + enum pmbus_sensor_classes class, long val) +{ + long m, b, R; + + m = data->info->m[class]; + b = data->info->b[class]; + R = data->info->R[class]; + + /* Power is in uW. Adjust R and b. */ + if (class == PSC_POWER) { + R -= 3; + b *= 1000; + } + + /* Calculate Y = (m * X + b) * 10^R */ + if (class != PSC_FAN) { + R -= 3; /* Adjust R and b for data in milli-units */ + b *= 1000; + } + val = val * m + b; + + while (R > 0) { + val *= 10; + R--; + } + while (R < 0) { + val = DIV_ROUND_CLOSEST(val, 10); + R++; + } + + return val; +} + +static u16 pmbus_data2reg(struct pmbus_data *data, + enum pmbus_sensor_classes class, long val) +{ + u16 regval; + + if (data->info->direct[class]) + regval = pmbus_data2reg_direct(data, class, val); + else + regval = pmbus_data2reg_linear(data, class, val); + + return regval; +} + +/* + * Return boolean calculated from converted data. + * defines a status register index and mask, and optionally + * two sensor indexes. + * The upper half-word references the two sensors, + * two sensor indices. + * The upper half-word references the two optional sensors, + * the lower half word references status register and mask. + * The function returns true if (status[reg] & mask) is true and, + * if specified, if v1 >= v2. + * To determine if an object exceeds upper limits, specify . + * To determine if an object exceeds lower limits, specify . + * + * For booleans created with pmbus_add_boolean_reg(), only the lower 16 bits of + * index are set. s1 and s2 (the sensor index values) are zero in this case. + * The function returns true if (status[reg] & mask) is true. + * + * If the boolean was created with pmbus_add_boolean_cmp(), a comparison against + * a specified limit has to be performed to determine the boolean result. + * In this case, the function returns true if v1 >= v2 (where v1 and v2 are + * sensor values referenced by sensor indices s1 and s2). + * + * To determine if an object exceeds upper limits, specify = . + * To determine if an object exceeds lower limits, specify = . + * + * If a negative value is stored in any of the referenced registers, this value + * reflects an error code which will be returned. + */ +static int pmbus_get_boolean(struct pmbus_data *data, int index, int *val) +{ + u8 s1 = (index >> 24) & 0xff; + u8 s2 = (index >> 16) & 0xff; + u8 reg = (index >> 8) & 0xff; + u8 mask = index & 0xff; + int status; + u8 regval; + + status = data->status[reg]; + if (status < 0) + return status; + + regval = status & mask; + if (!s1 && !s2) + *val = !!regval; + else { + int v1, v2; + struct pmbus_sensor *sensor1, *sensor2; + + sensor1 = &data->sensors[s1]; + if (sensor1->data < 0) + return sensor1->data; + sensor2 = &data->sensors[s2]; + if (sensor2->data < 0) + return sensor2->data; + + v1 = pmbus_reg2data(data, sensor1); + v2 = pmbus_reg2data(data, sensor2); + *val = !!(regval && v1 >= v2); + } + return 0; +} + +static ssize_t pmbus_show_boolean(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pmbus_data *data = pmbus_update_device(dev); + int val; + int err; + + err = pmbus_get_boolean(data, attr->index, &val); + if (err) + return err; + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t pmbus_show_sensor(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pmbus_data *data = pmbus_update_device(dev); + struct pmbus_sensor *sensor; + + sensor = &data->sensors[attr->index]; + if (sensor->data < 0) + return sensor->data; + + return snprintf(buf, PAGE_SIZE, "%d\n", pmbus_reg2data(data, sensor)); +} + +static ssize_t pmbus_set_sensor(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i2c_client *client = to_i2c_client(dev); + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor *sensor = &data->sensors[attr->index]; + ssize_t rv = count; + long val = 0; + int ret; + u16 regval; + + if (strict_strtol(buf, 10, &val) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + regval = pmbus_data2reg(data, sensor->class, val); + ret = pmbus_write_word_data(client, sensor->page, sensor->reg, regval); + if (ret < 0) + rv = ret; + else + data->sensors[attr->index].data = regval; + mutex_unlock(&data->update_lock); + return rv; +} + +static ssize_t pmbus_show_label(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pmbus_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + + return snprintf(buf, PAGE_SIZE, "%s\n", + data->labels[attr->index].label); +} + +#define PMBUS_ADD_ATTR(data, _name, _idx, _mode, _type, _show, _set) \ +do { \ + struct sensor_device_attribute *a \ + = &data->_type##s[data->num_##_type##s].attribute; \ + BUG_ON(data->num_attributes >= data->max_attributes); \ + a->dev_attr.attr.name = _name; \ + a->dev_attr.attr.mode = _mode; \ + a->dev_attr.show = _show; \ + a->dev_attr.store = _set; \ + a->index = _idx; \ + data->attributes[data->num_attributes] = &a->dev_attr.attr; \ + data->num_attributes++; \ +} while (0) + +#define PMBUS_ADD_GET_ATTR(data, _name, _type, _idx) \ + PMBUS_ADD_ATTR(data, _name, _idx, S_IRUGO, _type, \ + pmbus_show_##_type, NULL) + +#define PMBUS_ADD_SET_ATTR(data, _name, _type, _idx) \ + PMBUS_ADD_ATTR(data, _name, _idx, S_IWUSR | S_IRUGO, _type, \ + pmbus_show_##_type, pmbus_set_##_type) + +static void pmbus_add_boolean(struct pmbus_data *data, + const char *name, const char *type, int seq, + int idx) +{ + struct pmbus_boolean *boolean; + + BUG_ON(data->num_booleans >= data->max_booleans); + + boolean = &data->booleans[data->num_booleans]; + + snprintf(boolean->name, sizeof(boolean->name), "%s%d_%s", + name, seq, type); + PMBUS_ADD_GET_ATTR(data, boolean->name, boolean, idx); + data->num_booleans++; +} + +static void pmbus_add_boolean_reg(struct pmbus_data *data, + const char *name, const char *type, + int seq, int reg, int bit) +{ + pmbus_add_boolean(data, name, type, seq, (reg << 8) | bit); +} + +static void pmbus_add_boolean_cmp(struct pmbus_data *data, + const char *name, const char *type, + int seq, int i1, int i2, int reg, int mask) +{ + pmbus_add_boolean(data, name, type, seq, + (i1 << 24) | (i2 << 16) | (reg << 8) | mask); +} + +static void pmbus_add_sensor(struct pmbus_data *data, + const char *name, const char *type, int seq, + int page, int reg, enum pmbus_sensor_classes class, + bool update) +{ + struct pmbus_sensor *sensor; + + BUG_ON(data->num_sensors >= data->max_sensors); + + sensor = &data->sensors[data->num_sensors]; + snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s", + name, seq, type); + sensor->page = page; + sensor->reg = reg; + sensor->class = class; + sensor->update = update; + if (update) + PMBUS_ADD_GET_ATTR(data, sensor->name, sensor, + data->num_sensors); + else + PMBUS_ADD_SET_ATTR(data, sensor->name, sensor, + data->num_sensors); + data->num_sensors++; +} + +static void pmbus_add_label(struct pmbus_data *data, + const char *name, int seq, + const char *lstring, int index) +{ + struct pmbus_label *label; + + BUG_ON(data->num_labels >= data->max_labels); + + label = &data->labels[data->num_labels]; + snprintf(label->name, sizeof(label->name), "%s%d_label", name, seq); + if (!index) + strncpy(label->label, lstring, sizeof(label->label) - 1); + else + snprintf(label->label, sizeof(label->label), "%s%d", lstring, + index); + + PMBUS_ADD_GET_ATTR(data, label->name, label, data->num_labels); + data->num_labels++; +} + +static const int pmbus_temp_registers[] = { + PMBUS_READ_TEMPERATURE_1, + PMBUS_READ_TEMPERATURE_2, + PMBUS_READ_TEMPERATURE_3 +}; + +static const int pmbus_fan_registers[] = { + PMBUS_READ_FAN_SPEED_1, + PMBUS_READ_FAN_SPEED_2, + PMBUS_READ_FAN_SPEED_3, + PMBUS_READ_FAN_SPEED_4 +}; + +static const int pmbus_fan_config_registers[] = { + PMBUS_FAN_CONFIG_12, + PMBUS_FAN_CONFIG_12, + PMBUS_FAN_CONFIG_34, + PMBUS_FAN_CONFIG_34 +}; + +static const int pmbus_fan_status_registers[] = { + PMBUS_STATUS_FAN_12, + PMBUS_STATUS_FAN_12, + PMBUS_STATUS_FAN_34, + PMBUS_STATUS_FAN_34 +}; + +/* + * Determine maximum number of sensors, booleans, and labels. + * To keep things simple, only make a rough high estimate. + */ +static void pmbus_find_max_attr(struct i2c_client *client, + struct pmbus_data *data) +{ + const struct pmbus_driver_info *info = data->info; + int page, max_sensors, max_booleans, max_labels; + + max_sensors = PMBUS_MAX_INPUT_SENSORS; + max_booleans = PMBUS_MAX_INPUT_BOOLEANS; + max_labels = PMBUS_MAX_INPUT_LABELS; + + for (page = 0; page < info->pages; page++) { + if (info->func[page] & PMBUS_HAVE_VOUT) { + max_sensors += PMBUS_VOUT_SENSORS_PER_PAGE; + max_booleans += PMBUS_VOUT_BOOLEANS_PER_PAGE; + max_labels++; + } + if (info->func[page] & PMBUS_HAVE_IOUT) { + max_sensors += PMBUS_IOUT_SENSORS_PER_PAGE; + max_booleans += PMBUS_IOUT_BOOLEANS_PER_PAGE; + max_labels++; + } + if (info->func[page] & PMBUS_HAVE_POUT) { + max_sensors += PMBUS_POUT_SENSORS_PER_PAGE; + max_booleans += PMBUS_POUT_BOOLEANS_PER_PAGE; + max_labels++; + } + if (info->func[page] & PMBUS_HAVE_FAN12) { + if (page == 0) { + max_sensors += + ARRAY_SIZE(pmbus_fan_registers) * + PMBUS_MAX_SENSORS_PER_FAN; + max_booleans += + ARRAY_SIZE(pmbus_fan_registers) * + PMBUS_MAX_BOOLEANS_PER_FAN; + } else { + max_sensors += PMBUS_MAX_SENSORS_PER_FAN; + max_booleans += PMBUS_MAX_BOOLEANS_PER_FAN; + } + } + if (info->func[page] & PMBUS_HAVE_TEMP) { + if (page == 0) { + max_sensors += + ARRAY_SIZE(pmbus_temp_registers) * + PMBUS_MAX_SENSORS_PER_TEMP; + max_booleans += + ARRAY_SIZE(pmbus_temp_registers) * + PMBUS_MAX_BOOLEANS_PER_TEMP; + } else { + max_sensors += PMBUS_MAX_SENSORS_PER_TEMP; + max_booleans += PMBUS_MAX_BOOLEANS_PER_TEMP; + } + } + } + data->max_sensors = max_sensors; + data->max_booleans = max_booleans; + data->max_labels = max_labels; + data->max_attributes = max_sensors + max_booleans + max_labels; +} + +/* + * Search for attributes. Allocate sensors, booleans, and labels as needed. + */ +static void pmbus_find_attributes(struct i2c_client *client, + struct pmbus_data *data) +{ + const struct pmbus_driver_info *info = data->info; + int page, i0, i1, in_index; + + /* + * Input voltage sensors + */ + in_index = 1; + if (info->func[0] & PMBUS_HAVE_VIN) { + bool have_alarm = false; + + i0 = data->num_sensors; + pmbus_add_label(data, "in", in_index, "vin", 0); + pmbus_add_sensor(data, "in", "input", in_index, + 0, PMBUS_READ_VIN, PSC_VOLTAGE_IN, true); + if (pmbus_check_word_register(client, 0, + PMBUS_VIN_UV_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "min", in_index, + 0, PMBUS_VIN_UV_WARN_LIMIT, + PSC_VOLTAGE_IN, false); + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) { + pmbus_add_boolean_reg(data, "in", "min_alarm", + in_index, + PB_STATUS_INPUT_BASE, + PB_VOLTAGE_UV_WARNING); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, 0, + PMBUS_VIN_UV_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "lcrit", in_index, + 0, PMBUS_VIN_UV_FAULT_LIMIT, + PSC_VOLTAGE_IN, false); + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) { + pmbus_add_boolean_reg(data, "in", "lcrit_alarm", + in_index, + PB_STATUS_INPUT_BASE, + PB_VOLTAGE_UV_FAULT); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, 0, + PMBUS_VIN_OV_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "max", in_index, + 0, PMBUS_VIN_OV_WARN_LIMIT, + PSC_VOLTAGE_IN, false); + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) { + pmbus_add_boolean_reg(data, "in", "max_alarm", + in_index, + PB_STATUS_INPUT_BASE, + PB_VOLTAGE_OV_WARNING); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, 0, + PMBUS_VIN_OV_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "crit", in_index, + 0, PMBUS_VIN_OV_FAULT_LIMIT, + PSC_VOLTAGE_IN, false); + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) { + pmbus_add_boolean_reg(data, "in", "crit_alarm", + in_index, + PB_STATUS_INPUT_BASE, + PB_VOLTAGE_OV_FAULT); + have_alarm = true; + } + } + /* + * Add generic alarm attribute only if there are no individual + * attributes. + */ + if (!have_alarm) + pmbus_add_boolean_reg(data, "in", "alarm", + in_index, + PB_STATUS_BASE, + PB_STATUS_VIN_UV); + in_index++; + } + if (info->func[0] & PMBUS_HAVE_VCAP) { + pmbus_add_label(data, "in", in_index, "vcap", 0); + pmbus_add_sensor(data, "in", "input", in_index, 0, + PMBUS_READ_VCAP, PSC_VOLTAGE_IN, true); + in_index++; + } + + /* + * Output voltage sensors + */ + for (page = 0; page < info->pages; page++) { + bool have_alarm = false; + + if (!(info->func[page] & PMBUS_HAVE_VOUT)) + continue; + + i0 = data->num_sensors; + pmbus_add_label(data, "in", in_index, "vout", page + 1); + pmbus_add_sensor(data, "in", "input", in_index, page, + PMBUS_READ_VOUT, PSC_VOLTAGE_OUT, true); + if (pmbus_check_word_register(client, page, + PMBUS_VOUT_UV_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "min", in_index, page, + PMBUS_VOUT_UV_WARN_LIMIT, + PSC_VOLTAGE_OUT, false); + if (info->func[page] & PMBUS_HAVE_STATUS_VOUT) { + pmbus_add_boolean_reg(data, "in", "min_alarm", + in_index, + PB_STATUS_VOUT_BASE + + page, + PB_VOLTAGE_UV_WARNING); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, page, + PMBUS_VOUT_UV_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "lcrit", in_index, page, + PMBUS_VOUT_UV_FAULT_LIMIT, + PSC_VOLTAGE_OUT, false); + if (info->func[page] & PMBUS_HAVE_STATUS_VOUT) { + pmbus_add_boolean_reg(data, "in", "lcrit_alarm", + in_index, + PB_STATUS_VOUT_BASE + + page, + PB_VOLTAGE_UV_FAULT); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, page, + PMBUS_VOUT_OV_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "max", in_index, page, + PMBUS_VOUT_OV_WARN_LIMIT, + PSC_VOLTAGE_OUT, false); + if (info->func[page] & PMBUS_HAVE_STATUS_VOUT) { + pmbus_add_boolean_reg(data, "in", "max_alarm", + in_index, + PB_STATUS_VOUT_BASE + + page, + PB_VOLTAGE_OV_WARNING); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, page, + PMBUS_VOUT_OV_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "crit", in_index, page, + PMBUS_VOUT_OV_FAULT_LIMIT, + PSC_VOLTAGE_OUT, false); + if (info->func[page] & PMBUS_HAVE_STATUS_VOUT) { + pmbus_add_boolean_reg(data, "in", "crit_alarm", + in_index, + PB_STATUS_VOUT_BASE + + page, + PB_VOLTAGE_OV_FAULT); + have_alarm = true; + } + } + /* + * Add generic alarm attribute only if there are no individual + * attributes. + */ + if (!have_alarm) + pmbus_add_boolean_reg(data, "in", "alarm", + in_index, + PB_STATUS_BASE + page, + PB_STATUS_VOUT_OV); + in_index++; + } + + /* + * Current sensors + */ + + /* + * Input current sensors + */ + in_index = 1; + if (info->func[0] & PMBUS_HAVE_IIN) { + i0 = data->num_sensors; + pmbus_add_label(data, "curr", in_index, "iin", 0); + pmbus_add_sensor(data, "curr", "input", in_index, + 0, PMBUS_READ_IIN, PSC_CURRENT_IN, true); + if (pmbus_check_word_register(client, 0, + PMBUS_IIN_OC_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "curr", "max", in_index, + 0, PMBUS_IIN_OC_WARN_LIMIT, + PSC_CURRENT_IN, false); + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) { + pmbus_add_boolean_reg(data, "curr", "max_alarm", + in_index, + PB_STATUS_INPUT_BASE, + PB_IIN_OC_WARNING); + } + } + if (pmbus_check_word_register(client, 0, + PMBUS_IIN_OC_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "curr", "crit", in_index, + 0, PMBUS_IIN_OC_FAULT_LIMIT, + PSC_CURRENT_IN, false); + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) + pmbus_add_boolean_reg(data, "curr", + "crit_alarm", + in_index, + PB_STATUS_INPUT_BASE, + PB_IIN_OC_FAULT); + } + in_index++; + } + + /* + * Output current sensors + */ + for (page = 0; page < info->pages; page++) { + bool have_alarm = false; + + if (!(info->func[page] & PMBUS_HAVE_IOUT)) + continue; + + i0 = data->num_sensors; + pmbus_add_label(data, "curr", in_index, "iout", page + 1); + pmbus_add_sensor(data, "curr", "input", in_index, page, + PMBUS_READ_IOUT, PSC_CURRENT_OUT, true); + if (pmbus_check_word_register(client, page, + PMBUS_IOUT_OC_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "curr", "max", in_index, page, + PMBUS_IOUT_OC_WARN_LIMIT, + PSC_CURRENT_OUT, false); + if (info->func[page] & PMBUS_HAVE_STATUS_IOUT) { + pmbus_add_boolean_reg(data, "curr", "max_alarm", + in_index, + PB_STATUS_IOUT_BASE + + page, PB_IOUT_OC_WARNING); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, page, + PMBUS_IOUT_UC_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "curr", "lcrit", in_index, page, + PMBUS_IOUT_UC_FAULT_LIMIT, + PSC_CURRENT_OUT, false); + if (info->func[page] & PMBUS_HAVE_STATUS_IOUT) { + pmbus_add_boolean_reg(data, "curr", + "lcrit_alarm", + in_index, + PB_STATUS_IOUT_BASE + + page, PB_IOUT_UC_FAULT); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, page, + PMBUS_IOUT_OC_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "curr", "crit", in_index, page, + PMBUS_IOUT_OC_FAULT_LIMIT, + PSC_CURRENT_OUT, false); + if (info->func[page] & PMBUS_HAVE_STATUS_IOUT) { + pmbus_add_boolean_reg(data, "curr", + "crit_alarm", + in_index, + PB_STATUS_IOUT_BASE + + page, PB_IOUT_OC_FAULT); + have_alarm = true; + } + } + /* + * Add generic alarm attribute only if there are no individual + * attributes. + */ + if (!have_alarm) + pmbus_add_boolean_reg(data, "curr", "alarm", + in_index, + PB_STATUS_BASE + page, + PB_STATUS_IOUT_OC); + in_index++; + } + + /* + * Power sensors + */ + /* + * Input Power sensors + */ + in_index = 1; + if (info->func[0] & PMBUS_HAVE_PIN) { + i0 = data->num_sensors; + pmbus_add_label(data, "power", in_index, "pin", 0); + pmbus_add_sensor(data, "power", "input", in_index, + 0, PMBUS_READ_PIN, PSC_POWER, true); + if (pmbus_check_word_register(client, 0, + PMBUS_PIN_OP_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "power", "max", in_index, + 0, PMBUS_PIN_OP_WARN_LIMIT, PSC_POWER, + false); + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) + pmbus_add_boolean_reg(data, "power", + "alarm", + in_index, + PB_STATUS_INPUT_BASE, + PB_PIN_OP_WARNING); + } + in_index++; + } + + /* + * Output Power sensors + */ + for (page = 0; page < info->pages; page++) { + bool need_alarm = false; + + if (!(info->func[page] & PMBUS_HAVE_POUT)) + continue; + + i0 = data->num_sensors; + pmbus_add_label(data, "power", in_index, "pout", page + 1); + pmbus_add_sensor(data, "power", "input", in_index, page, + PMBUS_READ_POUT, PSC_POWER, true); + /* + * Per hwmon sysfs API, power_cap is to be used to limit output + * power. + * We have two registers related to maximum output power, + * PMBUS_POUT_MAX and PMBUS_POUT_OP_WARN_LIMIT. + * PMBUS_POUT_MAX matches the powerX_cap attribute definition. + * There is no attribute in the API to match + * PMBUS_POUT_OP_WARN_LIMIT. We use powerX_max for now. + */ + if (pmbus_check_word_register(client, page, PMBUS_POUT_MAX)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "power", "cap", in_index, page, + PMBUS_POUT_MAX, PSC_POWER, false); + need_alarm = true; + } + if (pmbus_check_word_register(client, page, + PMBUS_POUT_OP_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "power", "max", in_index, page, + PMBUS_POUT_OP_WARN_LIMIT, PSC_POWER, + false); + need_alarm = true; + } + if (need_alarm && (info->func[page] & PMBUS_HAVE_STATUS_IOUT)) + pmbus_add_boolean_reg(data, "power", "alarm", + in_index, + PB_STATUS_IOUT_BASE + page, + PB_POUT_OP_WARNING + | PB_POWER_LIMITING); + + if (pmbus_check_word_register(client, page, + PMBUS_POUT_OP_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "power", "crit", in_index, page, + PMBUS_POUT_OP_FAULT_LIMIT, PSC_POWER, + false); + if (info->func[page] & PMBUS_HAVE_STATUS_IOUT) + pmbus_add_boolean_reg(data, "power", + "crit_alarm", + in_index, + PB_STATUS_IOUT_BASE + + page, + PB_POUT_OP_FAULT); + } + in_index++; + } + + /* + * Temperature sensors + */ + in_index = 1; + for (page = 0; page < info->pages; page++) { + int t, temps; + + if (!(info->func[page] & PMBUS_HAVE_TEMP)) + continue; + + temps = page ? 1 : ARRAY_SIZE(pmbus_temp_registers); + for (t = 0; t < temps; t++) { + bool have_alarm = false; + + if (!pmbus_check_word_register + (client, page, pmbus_temp_registers[t])) + break; + + i0 = data->num_sensors; + pmbus_add_sensor(data, "temp", "input", in_index, page, + pmbus_temp_registers[t], + PSC_TEMPERATURE, true); + + /* + * PMBus provides only one status register for TEMP1-3. + * Thus, we can not use the status register to determine + * which of the three sensors actually caused an alarm. + * Always compare current temperature against the limit + * registers to determine alarm conditions for a + * specific sensor. + */ + if (pmbus_check_word_register + (client, page, PMBUS_UT_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "temp", "min", in_index, + page, PMBUS_UT_WARN_LIMIT, + PSC_TEMPERATURE, false); + if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) { + pmbus_add_boolean_cmp(data, "temp", + "min_alarm", in_index, i1, i0, + PB_STATUS_TEMP_BASE + page, + PB_TEMP_UT_WARNING); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, page, + PMBUS_UT_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "temp", "lcrit", + in_index, page, + PMBUS_UT_FAULT_LIMIT, + PSC_TEMPERATURE, false); + if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) { + pmbus_add_boolean_cmp(data, "temp", + "lcrit_alarm", in_index, i1, i0, + PB_STATUS_TEMP_BASE + page, + PB_TEMP_UT_FAULT); + have_alarm = true; + } + } + if (pmbus_check_word_register + (client, page, PMBUS_OT_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "temp", "max", in_index, + page, PMBUS_OT_WARN_LIMIT, + PSC_TEMPERATURE, false); + if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) { + pmbus_add_boolean_cmp(data, "temp", + "max_alarm", in_index, i0, i1, + PB_STATUS_TEMP_BASE + page, + PB_TEMP_OT_WARNING); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, page, + PMBUS_OT_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "temp", "crit", in_index, + page, PMBUS_OT_FAULT_LIMIT, + PSC_TEMPERATURE, false); + if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) { + pmbus_add_boolean_cmp(data, "temp", + "crit_alarm", in_index, i0, i1, + PB_STATUS_TEMP_BASE + page, + PB_TEMP_OT_FAULT); + have_alarm = true; + } + } + /* + * Last resort - we were not able to create any alarm + * registers. Report alarm for all sensors using the + * status register temperature alarm bit. + */ + if (!have_alarm) + pmbus_add_boolean_reg(data, "temp", "alarm", + in_index, + PB_STATUS_BASE + page, + PB_STATUS_TEMPERATURE); + in_index++; + } + } + + /* + * Fans + */ + in_index = 1; + for (page = 0; page < info->pages; page++) { + int fans, f; + + if (!(info->func[page] & PMBUS_HAVE_FAN12)) + continue; + + fans = page ? 1 : ARRAY_SIZE(pmbus_fan_registers); + for (f = 0; f < fans; f++) { + int regval; + + if (!pmbus_check_word_register(client, page, + pmbus_fan_registers[f]) + || !pmbus_check_byte_register(client, page, + pmbus_fan_config_registers[f])) + break; + + /* + * Skip fan if not installed. + * Each fan configuration register covers multiple fans, + * so we have to do some magic. + */ + regval = pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[f]); + if (regval < 0 || + (!(regval & (PB_FAN_1_INSTALLED >> ((f & 1) * 4))))) + continue; + + i0 = data->num_sensors; + pmbus_add_sensor(data, "fan", "input", in_index, page, + pmbus_fan_registers[f], PSC_FAN, true); + + /* + * Each fan status register covers multiple fans, + * so we have to do some magic. + */ + if (pmbus_check_byte_register + (client, page, pmbus_fan_status_registers[f])) { + int base; + + if (f > 1) /* fan 3, 4 */ + base = PB_STATUS_FAN34_BASE; + else + base = PB_STATUS_FAN_BASE + page; + pmbus_add_boolean_reg(data, "fan", "alarm", + in_index, base, + PB_FAN_FAN1_WARNING >> (f & 1)); + pmbus_add_boolean_reg(data, "fan", "fault", + in_index, base, + PB_FAN_FAN1_FAULT >> (f & 1)); + } + in_index++; + } + } +} + +/* + * Identify chip parameters. + * This function is called for all chips. + */ +static int pmbus_identify_common(struct i2c_client *client, + struct pmbus_data *data) +{ + int vout_mode, exponent; + + vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode >= 0) { + /* + * Not all chips support the VOUT_MODE command, + * so a failure to read it is not an error. + */ + switch (vout_mode >> 5) { + case 0: /* linear mode */ + if (data->info->direct[PSC_VOLTAGE_OUT]) + return -ENODEV; + + exponent = vout_mode & 0x1f; + /* and sign-extend it */ + if (exponent & 0x10) + exponent |= ~0x1f; + data->exponent = exponent; + break; + case 2: /* direct mode */ + if (!data->info->direct[PSC_VOLTAGE_OUT]) + return -ENODEV; + break; + default: + return -ENODEV; + } + } + + /* Determine maximum number of sensors, booleans, and labels */ + pmbus_find_max_attr(client, data); + pmbus_clear_fault_page(client, 0); + return 0; +} + +int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, + struct pmbus_driver_info *info) +{ + const struct pmbus_platform_data *pdata = client->dev.platform_data; + struct pmbus_data *data; + int ret; + + if (!info) { + dev_err(&client->dev, "Missing chip information"); + return -ENODEV; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_BYTE + | I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(&client->dev, "No memory to allocate driver data\n"); + return -ENOMEM; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* + * Bail out if status register or PMBus revision register + * does not exist. + */ + if (i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE) < 0 + || i2c_smbus_read_byte_data(client, PMBUS_REVISION) < 0) { + dev_err(&client->dev, + "Status or revision register not found\n"); + ret = -ENODEV; + goto out_data; + } + + if (pdata) + data->flags = pdata->flags; + data->info = info; + + pmbus_clear_faults(client); + + if (info->identify) { + ret = (*info->identify)(client, info); + if (ret < 0) { + dev_err(&client->dev, "Chip identification failed\n"); + goto out_data; + } + } + + if (info->pages <= 0 || info->pages > PMBUS_PAGES) { + dev_err(&client->dev, "Bad number of PMBus pages: %d\n", + info->pages); + ret = -EINVAL; + goto out_data; + } + /* + * Bail out if more than one page was configured, but we can not + * select the highest page. This is an indication that the wrong + * chip type was selected. Better bail out now than keep + * returning errors later on. + */ + if (info->pages > 1 && pmbus_set_page(client, info->pages - 1) < 0) { + dev_err(&client->dev, "Failed to select page %d\n", + info->pages - 1); + ret = -EINVAL; + goto out_data; + } + + ret = pmbus_identify_common(client, data); + if (ret < 0) { + dev_err(&client->dev, "Failed to identify chip capabilities\n"); + goto out_data; + } + + ret = -ENOMEM; + data->sensors = kzalloc(sizeof(struct pmbus_sensor) * data->max_sensors, + GFP_KERNEL); + if (!data->sensors) { + dev_err(&client->dev, "No memory to allocate sensor data\n"); + goto out_data; + } + + data->booleans = kzalloc(sizeof(struct pmbus_boolean) + * data->max_booleans, GFP_KERNEL); + if (!data->booleans) { + dev_err(&client->dev, "No memory to allocate boolean data\n"); + goto out_sensors; + } + + data->labels = kzalloc(sizeof(struct pmbus_label) * data->max_labels, + GFP_KERNEL); + if (!data->labels) { + dev_err(&client->dev, "No memory to allocate label data\n"); + goto out_booleans; + } + + data->attributes = kzalloc(sizeof(struct attribute *) + * data->max_attributes, GFP_KERNEL); + if (!data->attributes) { + dev_err(&client->dev, "No memory to allocate attribute data\n"); + goto out_labels; + } + + pmbus_find_attributes(client, data); + + /* + * If there are no attributes, something is wrong. + * Bail out instead of trying to register nothing. + */ + if (!data->num_attributes) { + dev_err(&client->dev, "No attributes found\n"); + ret = -ENODEV; + goto out_attributes; + } + + /* Register sysfs hooks */ + data->group.attrs = data->attributes; + ret = sysfs_create_group(&client->dev.kobj, &data->group); + if (ret) { + dev_err(&client->dev, "Failed to create sysfs entries\n"); + goto out_attributes; + } + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + dev_err(&client->dev, "Failed to register hwmon device\n"); + goto out_hwmon_device_register; + } + return 0; + +out_hwmon_device_register: + sysfs_remove_group(&client->dev.kobj, &data->group); +out_attributes: + kfree(data->attributes); +out_labels: + kfree(data->labels); +out_booleans: + kfree(data->booleans); +out_sensors: + kfree(data->sensors); +out_data: + kfree(data); + return ret; +} +EXPORT_SYMBOL_GPL(pmbus_do_probe); + +int pmbus_do_remove(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &data->group); + kfree(data->attributes); + kfree(data->labels); + kfree(data->booleans); + kfree(data->sensors); + kfree(data); + return 0; +} +EXPORT_SYMBOL_GPL(pmbus_do_remove); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus core driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/i2c/pmbus.h b/include/linux/i2c/pmbus.h new file mode 100644 index 00000000000..69280db02c4 --- /dev/null +++ b/include/linux/i2c/pmbus.h @@ -0,0 +1,45 @@ +/* + * Hardware monitoring driver for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _PMBUS_H_ +#define _PMBUS_H_ + +/* flags */ + +/* + * PMBUS_SKIP_STATUS_CHECK + * + * During register detection, skip checking the status register for + * communication or command errors. + * + * Some PMBus chips respond with valid data when trying to read an unsupported + * register. For such chips, checking the status register is mandatory when + * trying to determine if a chip register exists or not. + * Other PMBus chips don't support the STATUS_CML register, or report + * communication errors for no explicable reason. For such chips, checking + * the status register must be disabled. + */ +#define PMBUS_SKIP_STATUS_CHECK (1 << 0) + +struct pmbus_platform_data { + u32 flags; /* Device specific flags */ +}; + +#endif /* _PMBUS_H_ */ -- cgit v1.2.3-70-g09d2 From 8ea3238ba16fbf06ff20b2979f894dc88383584a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 26 Jan 2011 20:11:04 -0800 Subject: hwmon: (pmbus) Add support for Maxim MAX8688 Signed-off-by: Guenter Roeck Acked-by: Jonathan Cameron --- drivers/hwmon/Kconfig | 10 +++ drivers/hwmon/Makefile | 1 + drivers/hwmon/max8688.c | 158 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 drivers/hwmon/max8688.c (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index ac116693e35..521f191fcbe 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -779,6 +779,16 @@ config SENSORS_PMBUS This driver can also be built as a module. If so, the module will be called pmbus. +config SENSORS_MAX8688 + tristate "Maxim MAX8688" + default n + help + If you say yes here you get hardware monitoring support for Maxim + MAX8688. + + This driver can also be built as a module. If so, the module will + be called max8688. + endif # PMBUS config SENSORS_SHT15 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 09e67955fd5..0e7cf92d1b5 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -117,6 +117,7 @@ obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o # PMBus drivers obj-$(CONFIG_PMBUS) += pmbus_core.o obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o +obj-$(CONFIG_SENSORS_MAX8688) += max8688.o ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/hwmon/max8688.c b/drivers/hwmon/max8688.c new file mode 100644 index 00000000000..8ebfef2ecf2 --- /dev/null +++ b/drivers/hwmon/max8688.c @@ -0,0 +1,158 @@ +/* + * Hardware monitoring driver for Maxim MAX8688 + * + * Copyright (c) 2011 Ericsson AB. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define MAX8688_MFG_STATUS 0xd8 + +#define MAX8688_STATUS_OC_FAULT (1 << 4) +#define MAX8688_STATUS_OV_FAULT (1 << 5) +#define MAX8688_STATUS_OV_WARNING (1 << 8) +#define MAX8688_STATUS_UV_FAULT (1 << 9) +#define MAX8688_STATUS_UV_WARNING (1 << 10) +#define MAX8688_STATUS_UC_FAULT (1 << 11) +#define MAX8688_STATUS_OC_WARNING (1 << 12) +#define MAX8688_STATUS_OT_FAULT (1 << 13) +#define MAX8688_STATUS_OT_WARNING (1 << 14) + +static int max8688_get_status(struct i2c_client *client, int page, int reg) +{ + int ret = 0; + int mfg_status; + + if (page) + return -EINVAL; + + switch (reg) { + case PMBUS_STATUS_VOUT: + mfg_status = pmbus_read_word_data(client, 0, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_UV_WARNING) + ret |= PB_VOLTAGE_UV_WARNING; + if (mfg_status & MAX8688_STATUS_UV_FAULT) + ret |= PB_VOLTAGE_UV_FAULT; + if (mfg_status & MAX8688_STATUS_OV_WARNING) + ret |= PB_VOLTAGE_OV_WARNING; + if (mfg_status & MAX8688_STATUS_OV_FAULT) + ret |= PB_VOLTAGE_OV_FAULT; + break; + case PMBUS_STATUS_IOUT: + mfg_status = pmbus_read_word_data(client, 0, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_UC_FAULT) + ret |= PB_IOUT_UC_FAULT; + if (mfg_status & MAX8688_STATUS_OC_WARNING) + ret |= PB_IOUT_OC_WARNING; + if (mfg_status & MAX8688_STATUS_OC_FAULT) + ret |= PB_IOUT_OC_FAULT; + break; + case PMBUS_STATUS_TEMPERATURE: + mfg_status = pmbus_read_word_data(client, 0, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_OT_WARNING) + ret |= PB_TEMP_OT_WARNING; + if (mfg_status & MAX8688_STATUS_OT_FAULT) + ret |= PB_TEMP_OT_FAULT; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static struct pmbus_driver_info max8688_info = { + .pages = 1, + .direct[PSC_VOLTAGE_IN] = true, + .direct[PSC_VOLTAGE_OUT] = true, + .direct[PSC_TEMPERATURE] = true, + .direct[PSC_CURRENT_OUT] = true, + .m[PSC_VOLTAGE_IN] = 19995, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -1, + .m[PSC_VOLTAGE_OUT] = 19995, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -1, + .m[PSC_CURRENT_OUT] = 23109, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = -2, + .m[PSC_TEMPERATURE] = -7612, + .b[PSC_TEMPERATURE] = 335, + .R[PSC_TEMPERATURE] = -3, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_STATUS_TEMP, + .get_status = max8688_get_status, +}; + +static int max8688_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &max8688_info); +} + +static int max8688_remove(struct i2c_client *client) +{ + return pmbus_do_remove(client); +} + +static const struct i2c_device_id max8688_id[] = { + {"max8688", 0}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, max8688_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max8688_driver = { + .driver = { + .name = "max8688", + }, + .probe = max8688_probe, + .remove = max8688_remove, + .id_table = max8688_id, +}; + +static int __init max8688_init(void) +{ + return i2c_add_driver(&max8688_driver); +} + +static void __exit max8688_exit(void) +{ + i2c_del_driver(&max8688_driver); +} + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX8688"); +MODULE_LICENSE("GPL"); +module_init(max8688_init); +module_exit(max8688_exit); -- cgit v1.2.3-70-g09d2 From dcb7d0668b178deec81db128b9fbab29190063c4 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 26 Jan 2011 20:14:26 -0800 Subject: hwmon: (pmbus) Add support for Maxim MAX16064 Signed-off-by: Guenter Roeck Acked-by: Jonathan Cameron --- drivers/hwmon/Kconfig | 10 ++++++ drivers/hwmon/Makefile | 1 + drivers/hwmon/max16064.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 drivers/hwmon/max16064.c (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 521f191fcbe..de91d7d6a38 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -779,6 +779,16 @@ config SENSORS_PMBUS This driver can also be built as a module. If so, the module will be called pmbus. +config SENSORS_MAX16064 + tristate "Maxim MAX16064" + default n + help + If you say yes here you get hardware monitoring support for Maxim + MAX16064. + + This driver can also be built as a module. If so, the module will + be called max16064. + config SENSORS_MAX8688 tristate "Maxim MAX8688" default n diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 0e7cf92d1b5..9788fe248c6 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -117,6 +117,7 @@ obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o # PMBus drivers obj-$(CONFIG_PMBUS) += pmbus_core.o obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o +obj-$(CONFIG_SENSORS_MAX16064) += max16064.o obj-$(CONFIG_SENSORS_MAX8688) += max8688.o ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y) diff --git a/drivers/hwmon/max16064.c b/drivers/hwmon/max16064.c new file mode 100644 index 00000000000..1d6d717060d --- /dev/null +++ b/drivers/hwmon/max16064.c @@ -0,0 +1,91 @@ +/* + * Hardware monitoring driver for Maxim MAX16064 + * + * Copyright (c) 2011 Ericsson AB. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include "pmbus.h" + +static struct pmbus_driver_info max16064_info = { + .pages = 4, + .direct[PSC_VOLTAGE_IN] = true, + .direct[PSC_VOLTAGE_OUT] = true, + .direct[PSC_TEMPERATURE] = true, + .m[PSC_VOLTAGE_IN] = 19995, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -1, + .m[PSC_VOLTAGE_OUT] = 19995, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -1, + .m[PSC_TEMPERATURE] = -7612, + .b[PSC_TEMPERATURE] = 335, + .R[PSC_TEMPERATURE] = -3, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_TEMP, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +}; + +static int max16064_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &max16064_info); +} + +static int max16064_remove(struct i2c_client *client) +{ + return pmbus_do_remove(client); +} + +static const struct i2c_device_id max16064_id[] = { + {"max16064", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max16064_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max16064_driver = { + .driver = { + .name = "max16064", + }, + .probe = max16064_probe, + .remove = max16064_remove, + .id_table = max16064_id, +}; + +static int __init max16064_init(void) +{ + return i2c_add_driver(&max16064_driver); +} + +static void __exit max16064_exit(void) +{ + i2c_del_driver(&max16064_driver); +} + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX16064"); +MODULE_LICENSE("GPL"); +module_init(max16064_init); +module_exit(max16064_exit); -- cgit v1.2.3-70-g09d2 From a3eeb453539d7ad9c0d8cced1e0883a6a8ed18f3 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 26 Jan 2011 20:15:27 -0800 Subject: hwmon: (pmbus) Add support for Maxim MAX34440/MAX34441 Signed-off-by: Guenter Roeck Acked-by: Jonathan Cameron --- drivers/hwmon/Kconfig | 10 +++ drivers/hwmon/Makefile | 1 + drivers/hwmon/max34440.c | 199 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 drivers/hwmon/max34440.c (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index de91d7d6a38..19b0af40c7a 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -789,6 +789,16 @@ config SENSORS_MAX16064 This driver can also be built as a module. If so, the module will be called max16064. +config SENSORS_MAX34440 + tristate "Maxim MAX34440/MAX34441" + default n + help + If you say yes here you get hardware monitoring support for Maxim + MAX34440 and MAX34441. + + This driver can also be built as a module. If so, the module will + be called max34440. + config SENSORS_MAX8688 tristate "Maxim MAX8688" default n diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 9788fe248c6..feacc7c243c 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -118,6 +118,7 @@ obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o obj-$(CONFIG_PMBUS) += pmbus_core.o obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o obj-$(CONFIG_SENSORS_MAX16064) += max16064.o +obj-$(CONFIG_SENSORS_MAX34440) += max34440.o obj-$(CONFIG_SENSORS_MAX8688) += max8688.o ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y) diff --git a/drivers/hwmon/max34440.c b/drivers/hwmon/max34440.c new file mode 100644 index 00000000000..992b701b4c5 --- /dev/null +++ b/drivers/hwmon/max34440.c @@ -0,0 +1,199 @@ +/* + * Hardware monitoring driver for Maxim MAX34440/MAX34441 + * + * Copyright (c) 2011 Ericsson AB. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { max34440, max34441 }; + +#define MAX34440_STATUS_OC_WARN (1 << 0) +#define MAX34440_STATUS_OC_FAULT (1 << 1) +#define MAX34440_STATUS_OT_FAULT (1 << 5) +#define MAX34440_STATUS_OT_WARN (1 << 6) + +static int max34440_get_status(struct i2c_client *client, int page, int reg) +{ + int ret; + int mfg_status; + + ret = pmbus_set_page(client, page); + if (ret < 0) + return ret; + + switch (reg) { + case PMBUS_STATUS_IOUT: + mfg_status = pmbus_read_word_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX34440_STATUS_OC_WARN) + ret |= PB_IOUT_OC_WARNING; + if (mfg_status & MAX34440_STATUS_OC_FAULT) + ret |= PB_IOUT_OC_FAULT; + break; + case PMBUS_STATUS_TEMPERATURE: + mfg_status = pmbus_read_word_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX34440_STATUS_OT_WARN) + ret |= PB_TEMP_OT_WARNING; + if (mfg_status & MAX34440_STATUS_OT_FAULT) + ret |= PB_TEMP_OT_FAULT; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static struct pmbus_driver_info max34440_info[] = { + [max34440] = { + .pages = 14, + .direct[PSC_VOLTAGE_IN] = true, + .direct[PSC_VOLTAGE_OUT] = true, + .direct[PSC_TEMPERATURE] = true, + .direct[PSC_CURRENT_OUT] = true, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, /* R = 0 in datasheet reflects mV */ + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, /* R = 0 in datasheet reflects mV */ + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3, /* R = 0 in datasheet reflects mA */ + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[7] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[8] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[9] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[10] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[11] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[12] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[13] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .get_status = max34440_get_status, + }, + [max34441] = { + .pages = 12, + .direct[PSC_VOLTAGE_IN] = true, + .direct[PSC_VOLTAGE_OUT] = true, + .direct[PSC_TEMPERATURE] = true, + .direct[PSC_CURRENT_OUT] = true, + .direct[PSC_FAN] = true, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .m[PSC_FAN] = 1, + .b[PSC_FAN] = 0, + .R[PSC_FAN] = 0, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[5] = PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12, + .func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[7] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[8] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[9] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[10] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[11] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .get_status = max34440_get_status, + }, +}; + +static int max34440_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &max34440_info[id->driver_data]); +} + +static int max34440_remove(struct i2c_client *client) +{ + return pmbus_do_remove(client); +} + +static const struct i2c_device_id max34440_id[] = { + {"max34440", max34440}, + {"max34441", max34441}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max34440_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max34440_driver = { + .driver = { + .name = "max34440", + }, + .probe = max34440_probe, + .remove = max34440_remove, + .id_table = max34440_id, +}; + +static int __init max34440_init(void) +{ + return i2c_add_driver(&max34440_driver); +} + +static void __exit max34440_exit(void) +{ + i2c_del_driver(&max34440_driver); +} + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX34440/MAX34441"); +MODULE_LICENSE("GPL"); +module_init(max34440_init); +module_exit(max34440_exit); -- cgit v1.2.3-70-g09d2 From 83274c68a34094bbb465589e61f91a3714f8d026 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 1 Mar 2011 13:49:18 -0800 Subject: hwmon: (pmbus) More stringent checking of VOUT_MODE register Some PMBus chips do not support the VOUT_MODE register. To make matters worse, such chips may not return an error when reading the register either, but instead return 0xff. Check if the register exists using pmbus_check_byte_register() before reading its value. In addition, validate the returned value and ignore it if it is 0xff (which is invalid). Reported-by: Greg Schnorr Tested-by: Greg Schnorr Signed-off-by: Guenter Roeck Acked-by: Greg Schnorr --- drivers/hwmon/pmbus_core.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/pmbus_core.c b/drivers/hwmon/pmbus_core.c index 0edab1c9553..b7c64ba31f4 100644 --- a/drivers/hwmon/pmbus_core.c +++ b/drivers/hwmon/pmbus_core.c @@ -1426,10 +1426,11 @@ static void pmbus_find_attributes(struct i2c_client *client, static int pmbus_identify_common(struct i2c_client *client, struct pmbus_data *data) { - int vout_mode, exponent; + int vout_mode = -1, exponent; - vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); - if (vout_mode >= 0) { + if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE)) + vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode >= 0 && vout_mode != 0xff) { /* * Not all chips support the VOUT_MODE command, * so a failure to read it is not an error. -- cgit v1.2.3-70-g09d2 From 954df6763cd84551a565a0c341d3401cf30b9d2d Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 5 Mar 2011 07:55:10 -0800 Subject: hwmon: (pmbus) Improve support for paged fans So far, it seemed like fans would either all be in page 0, or that there would be one page per fan. Turns out this was a wrong assumption. There is at least one PMBus fan controller which supports three pages with four fans each. Update code to handle this situation. Reported-by: Greg Schnorr Tested-by: Greg Schnorr Signed-off-by: Guenter Roeck Acked-by: Greg Schnorr --- drivers/hwmon/pmbus_core.c | 70 ++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 30 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/pmbus_core.c b/drivers/hwmon/pmbus_core.c index b7c64ba31f4..d025a118bf9 100644 --- a/drivers/hwmon/pmbus_core.c +++ b/drivers/hwmon/pmbus_core.c @@ -58,12 +58,10 @@ #define PMBUS_MAX_INPUT_LABELS 4 /* vin, vcap, iin, pin */ /* - * status, status_vout, status_iout, status_fans, and status_temp - * are paged. status_input and status_fan34 are unpaged. - * status_fan34 is a special case to handle a second set of fans - * on page 0. + * status, status_vout, status_iout, status_fans, status_fan34, and status_temp + * are paged. status_input is unpaged. */ -#define PB_NUM_STATUS_REG (PMBUS_PAGES * 5 + 2) +#define PB_NUM_STATUS_REG (PMBUS_PAGES * 6 + 1) /* * Index into status register array, per status register group @@ -73,7 +71,7 @@ #define PB_STATUS_IOUT_BASE (PB_STATUS_VOUT_BASE + PMBUS_PAGES) #define PB_STATUS_FAN_BASE (PB_STATUS_IOUT_BASE + PMBUS_PAGES) #define PB_STATUS_FAN34_BASE (PB_STATUS_FAN_BASE + PMBUS_PAGES) -#define PB_STATUS_INPUT_BASE (PB_STATUS_FAN34_BASE + 1) +#define PB_STATUS_INPUT_BASE (PB_STATUS_FAN34_BASE + PMBUS_PAGES) #define PB_STATUS_TEMP_BASE (PB_STATUS_INPUT_BASE + 1) struct pmbus_sensor { @@ -327,14 +325,17 @@ static struct pmbus_data *pmbus_update_device(struct device *dev) = pmbus_get_status(client, i, PMBUS_STATUS_FAN_12); } + for (i = 0; i < info->pages; i++) { + if (!(info->func[i] & PMBUS_HAVE_STATUS_FAN34)) + continue; + data->status[PB_STATUS_FAN34_BASE + i] + = pmbus_get_status(client, i, PMBUS_STATUS_FAN_34); + } + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) data->status[PB_STATUS_INPUT_BASE] = pmbus_get_status(client, 0, PMBUS_STATUS_INPUT); - if (info->func[0] & PMBUS_HAVE_STATUS_FAN34) - data->status[PB_STATUS_FAN34_BASE] - = pmbus_get_status(client, 0, PMBUS_STATUS_FAN_34); - for (i = 0; i < data->num_sensors; i++) { struct pmbus_sensor *sensor = &data->sensors[i]; @@ -817,6 +818,20 @@ static const int pmbus_fan_status_registers[] = { PMBUS_STATUS_FAN_34 }; +static const u32 pmbus_fan_flags[] = { + PMBUS_HAVE_FAN12, + PMBUS_HAVE_FAN12, + PMBUS_HAVE_FAN34, + PMBUS_HAVE_FAN34 +}; + +static const u32 pmbus_fan_status_flags[] = { + PMBUS_HAVE_STATUS_FAN12, + PMBUS_HAVE_STATUS_FAN12, + PMBUS_HAVE_STATUS_FAN34, + PMBUS_HAVE_STATUS_FAN34 +}; + /* * Determine maximum number of sensors, booleans, and labels. * To keep things simple, only make a rough high estimate. @@ -848,17 +863,12 @@ static void pmbus_find_max_attr(struct i2c_client *client, max_labels++; } if (info->func[page] & PMBUS_HAVE_FAN12) { - if (page == 0) { - max_sensors += - ARRAY_SIZE(pmbus_fan_registers) * - PMBUS_MAX_SENSORS_PER_FAN; - max_booleans += - ARRAY_SIZE(pmbus_fan_registers) * - PMBUS_MAX_BOOLEANS_PER_FAN; - } else { - max_sensors += PMBUS_MAX_SENSORS_PER_FAN; - max_booleans += PMBUS_MAX_BOOLEANS_PER_FAN; - } + max_sensors += 2 * PMBUS_MAX_SENSORS_PER_FAN; + max_booleans += 2 * PMBUS_MAX_BOOLEANS_PER_FAN; + } + if (info->func[page] & PMBUS_HAVE_FAN34) { + max_sensors += 2 * PMBUS_MAX_SENSORS_PER_FAN; + max_booleans += 2 * PMBUS_MAX_BOOLEANS_PER_FAN; } if (info->func[page] & PMBUS_HAVE_TEMP) { if (page == 0) { @@ -1365,15 +1375,14 @@ static void pmbus_find_attributes(struct i2c_client *client, */ in_index = 1; for (page = 0; page < info->pages; page++) { - int fans, f; + int f; - if (!(info->func[page] & PMBUS_HAVE_FAN12)) - continue; - - fans = page ? 1 : ARRAY_SIZE(pmbus_fan_registers); - for (f = 0; f < fans; f++) { + for (f = 0; f < ARRAY_SIZE(pmbus_fan_registers); f++) { int regval; + if (!(info->func[page] & pmbus_fan_flags[f])) + break; + if (!pmbus_check_word_register(client, page, pmbus_fan_registers[f]) || !pmbus_check_byte_register(client, page, @@ -1399,12 +1408,13 @@ static void pmbus_find_attributes(struct i2c_client *client, * Each fan status register covers multiple fans, * so we have to do some magic. */ - if (pmbus_check_byte_register - (client, page, pmbus_fan_status_registers[f])) { + if ((info->func[page] & pmbus_fan_status_flags[f]) && + pmbus_check_byte_register(client, + page, pmbus_fan_status_registers[f])) { int base; if (f > 1) /* fan 3, 4 */ - base = PB_STATUS_FAN34_BASE; + base = PB_STATUS_FAN34_BASE + page; else base = PB_STATUS_FAN_BASE + page; pmbus_add_boolean_reg(data, "fan", "alarm", -- cgit v1.2.3-70-g09d2 From b49547a5df96bc755d6f96abb8503678af49d91c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 6 Mar 2011 10:56:52 -0800 Subject: hwmon: (pmbus) Improve support for paged temperature sensors Assumption so far was that PMBus devices would support TEMP2 and TEMP3 registers only on page 0, and that only the TEMP1 register would be used/supported on other pages. Turns out that is not correct. UCD92xx devices support TEMP1 and TEMP2 on page 0, and TEMP2 on other pages. So it is necessary to change the core code such that it does not make a page based assumptions about temperature register support. Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus_core.c | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/pmbus_core.c b/drivers/hwmon/pmbus_core.c index d025a118bf9..e9dda58a5b9 100644 --- a/drivers/hwmon/pmbus_core.c +++ b/drivers/hwmon/pmbus_core.c @@ -797,6 +797,12 @@ static const int pmbus_temp_registers[] = { PMBUS_READ_TEMPERATURE_3 }; +static const int pmbus_temp_flags[] = { + PMBUS_HAVE_TEMP, + PMBUS_HAVE_TEMP2, + PMBUS_HAVE_TEMP3 +}; + static const int pmbus_fan_registers[] = { PMBUS_READ_FAN_SPEED_1, PMBUS_READ_FAN_SPEED_2, @@ -871,17 +877,16 @@ static void pmbus_find_max_attr(struct i2c_client *client, max_booleans += 2 * PMBUS_MAX_BOOLEANS_PER_FAN; } if (info->func[page] & PMBUS_HAVE_TEMP) { - if (page == 0) { - max_sensors += - ARRAY_SIZE(pmbus_temp_registers) * - PMBUS_MAX_SENSORS_PER_TEMP; - max_booleans += - ARRAY_SIZE(pmbus_temp_registers) * - PMBUS_MAX_BOOLEANS_PER_TEMP; - } else { - max_sensors += PMBUS_MAX_SENSORS_PER_TEMP; - max_booleans += PMBUS_MAX_BOOLEANS_PER_TEMP; - } + max_sensors += PMBUS_MAX_SENSORS_PER_TEMP; + max_booleans += PMBUS_MAX_BOOLEANS_PER_TEMP; + } + if (info->func[page] & PMBUS_HAVE_TEMP2) { + max_sensors += PMBUS_MAX_SENSORS_PER_TEMP; + max_booleans += PMBUS_MAX_BOOLEANS_PER_TEMP; + } + if (info->func[page] & PMBUS_HAVE_TEMP3) { + max_sensors += PMBUS_MAX_SENSORS_PER_TEMP; + max_booleans += PMBUS_MAX_BOOLEANS_PER_TEMP; } } data->max_sensors = max_sensors; @@ -1273,18 +1278,23 @@ static void pmbus_find_attributes(struct i2c_client *client, */ in_index = 1; for (page = 0; page < info->pages; page++) { - int t, temps; - - if (!(info->func[page] & PMBUS_HAVE_TEMP)) - continue; + int t; - temps = page ? 1 : ARRAY_SIZE(pmbus_temp_registers); - for (t = 0; t < temps; t++) { + for (t = 0; t < ARRAY_SIZE(pmbus_temp_registers); t++) { bool have_alarm = false; + /* + * A PMBus chip may support any combination of + * temperature registers on any page. So we can not + * abort after a failure to detect a register, but have + * to continue checking for all registers on all pages. + */ + if (!(info->func[page] & pmbus_temp_flags[t])) + continue; + if (!pmbus_check_word_register (client, page, pmbus_temp_registers[t])) - break; + continue; i0 = data->num_sensors; pmbus_add_sensor(data, "temp", "input", in_index, page, -- cgit v1.2.3-70-g09d2 From 8677011a5d8e0358ce5ae26d82dfcddcad073c47 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 6 Mar 2011 12:28:24 -0800 Subject: hwmon: (pmbus) Continuously update temperature limit registers PMBus only has one set of limit registers for up to three temperature sensors. Thus, changing a limit for one of the temperature sensors affects limits for other temperature sensors in the same page (and potentially multiple pages depending on the chip implementation). To handle this situation, re-read all temperature limit registers when updating sensor data. This way, all affected temperature limits are updated whenever the limit for a single sensor is changed. Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus_core.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/pmbus_core.c b/drivers/hwmon/pmbus_core.c index e9dda58a5b9..92540c9cd34 100644 --- a/drivers/hwmon/pmbus_core.c +++ b/drivers/hwmon/pmbus_core.c @@ -1308,13 +1308,19 @@ static void pmbus_find_attributes(struct i2c_client *client, * Always compare current temperature against the limit * registers to determine alarm conditions for a * specific sensor. + * + * Since there is only one set of limit registers for + * up to three temperature sensors, we need to update + * all limit registers after the limit was changed for + * one of the sensors. This ensures that correct limits + * are reported for all temperature sensors. */ if (pmbus_check_word_register (client, page, PMBUS_UT_WARN_LIMIT)) { i1 = data->num_sensors; pmbus_add_sensor(data, "temp", "min", in_index, page, PMBUS_UT_WARN_LIMIT, - PSC_TEMPERATURE, false); + PSC_TEMPERATURE, true); if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) { pmbus_add_boolean_cmp(data, "temp", "min_alarm", in_index, i1, i0, @@ -1329,7 +1335,7 @@ static void pmbus_find_attributes(struct i2c_client *client, pmbus_add_sensor(data, "temp", "lcrit", in_index, page, PMBUS_UT_FAULT_LIMIT, - PSC_TEMPERATURE, false); + PSC_TEMPERATURE, true); if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) { pmbus_add_boolean_cmp(data, "temp", "lcrit_alarm", in_index, i1, i0, @@ -1343,7 +1349,7 @@ static void pmbus_find_attributes(struct i2c_client *client, i1 = data->num_sensors; pmbus_add_sensor(data, "temp", "max", in_index, page, PMBUS_OT_WARN_LIMIT, - PSC_TEMPERATURE, false); + PSC_TEMPERATURE, true); if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) { pmbus_add_boolean_cmp(data, "temp", "max_alarm", in_index, i0, i1, @@ -1357,7 +1363,7 @@ static void pmbus_find_attributes(struct i2c_client *client, i1 = data->num_sensors; pmbus_add_sensor(data, "temp", "crit", in_index, page, PMBUS_OT_FAULT_LIMIT, - PSC_TEMPERATURE, false); + PSC_TEMPERATURE, true); if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) { pmbus_add_boolean_cmp(data, "temp", "crit_alarm", in_index, i0, i1, -- cgit v1.2.3-70-g09d2 From 9f6ad1ce6484a92ef864e00611a8ef3daf9c986d Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 7 Mar 2011 18:34:50 -0800 Subject: hwmon: (pmbus) Fix LINEAR16 data format LINEAR16 data format is unsigned, not signed. Impact is that affected attributes report negative values in the upper half of the supported value range. Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus_core.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/pmbus_core.c b/drivers/hwmon/pmbus_core.c index 92540c9cd34..6474512f49b 100644 --- a/drivers/hwmon/pmbus_core.c +++ b/drivers/hwmon/pmbus_core.c @@ -359,20 +359,21 @@ static struct pmbus_data *pmbus_update_device(struct device *dev) static int pmbus_reg2data_linear(struct pmbus_data *data, struct pmbus_sensor *sensor) { - s16 exponent, mantissa; + s16 exponent; + s32 mantissa; long val; - if (sensor->class == PSC_VOLTAGE_OUT) { + if (sensor->class == PSC_VOLTAGE_OUT) { /* LINEAR16 */ exponent = data->exponent; - mantissa = (s16) sensor->data; - } else { + mantissa = (u16) sensor->data; + } else { /* LINEAR11 */ exponent = (sensor->data >> 11) & 0x001f; mantissa = sensor->data & 0x07ff; if (exponent > 0x0f) exponent |= 0xffe0; /* sign extend exponent */ if (mantissa > 0x03ff) - mantissa |= 0xf800; /* sign extend mantissa */ + mantissa |= 0xfffff800; /* sign extend mantissa */ } val = mantissa; @@ -454,19 +455,18 @@ static int pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor) static u16 pmbus_data2reg_linear(struct pmbus_data *data, enum pmbus_sensor_classes class, long val) { - s16 exponent = 0, mantissa = 0; + s16 exponent = 0, mantissa; bool negative = false; /* simple case */ if (val == 0) return 0; - if (val < 0) { - negative = true; - val = -val; - } - if (class == PSC_VOLTAGE_OUT) { + /* LINEAR16 does not support negative voltages */ + if (val < 0) + return 0; + /* * For a static exponents, we don't have a choice * but to adjust the value to it. @@ -476,9 +476,12 @@ static u16 pmbus_data2reg_linear(struct pmbus_data *data, else val >>= data->exponent; val = DIV_ROUND_CLOSEST(val, 1000); - if (val > 0x7fff) - val = 0x7fff; - return negative ? -val : val; + return val & 0xffff; + } + + if (val < 0) { + negative = true; + val = -val; } /* Power is in uW. Convert to mW before converting. */ -- cgit v1.2.3-70-g09d2 From de7790155f745d30d58ed131ed112b8735413ab7 Mon Sep 17 00:00:00 2001 From: "per.dalen@appeartv.com" Date: Thu, 3 Mar 2011 14:13:21 -0500 Subject: hwmon: Add support for LTC4151 LTC4151 is High Voltage I2C Current and Voltage Monitor from Linear Technology. Signed-off-by: Per Dalen Signed-off-by: Guenter Roeck --- Documentation/hwmon/ltc4151 | 47 ++++++++ drivers/hwmon/Kconfig | 11 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/ltc4151.c | 256 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 315 insertions(+) create mode 100644 Documentation/hwmon/ltc4151 create mode 100644 drivers/hwmon/ltc4151.c (limited to 'drivers/hwmon') diff --git a/Documentation/hwmon/ltc4151 b/Documentation/hwmon/ltc4151 new file mode 100644 index 00000000000..43c667e6677 --- /dev/null +++ b/Documentation/hwmon/ltc4151 @@ -0,0 +1,47 @@ +Kernel driver ltc4151 +===================== + +Supported chips: + * Linear Technology LTC4151 + Prefix: 'ltc4151' + Addresses scanned: - + Datasheet: + http://www.linear.com/docs/Datasheet/4151fc.pdf + +Author: Per Dalen + + +Description +----------- + +The LTC4151 is a High Voltage I2C Current and Voltage Monitor. + + +Usage Notes +----------- + +This driver does not probe for LTC4151 devices, since there is no register +which can be safely used to identify the chip. You will have to instantiate +the devices explicitly. + +Example: the following will load the driver for an LTC4151 at address 0x6f +on I2C bus #0: +# modprobe ltc4151 +# echo ltc4151 0x6f > /sys/bus/i2c/devices/i2c-0/new_device + + +Sysfs entries +------------- + +Voltage readings provided by this driver are reported as obtained from the ADIN +and VIN registers. + +Current reading provided by this driver is reported as obtained from the Current +Sense register. The reported value assumes that a 1 mOhm sense resistor is +installed. + +in1_input VDIN voltage (mV) + +in2_input ADIN voltage (mV) + +curr1_input SENSE current (mA) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 19b0af40c7a..7bda6226425 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -636,6 +636,17 @@ config SENSORS_LM93 This driver can also be built as a module. If so, the module will be called lm93. +config SENSORS_LTC4151 + tristate "Linear Technology LTC4151" + depends on I2C + default n + help + If you say yes here you get support for Linear Technology LTC4151 + High Voltage I2C Current and Voltage Monitor interface. + + This driver can also be built as a module. If so, the module will + be called ltc4151. + config SENSORS_LTC4215 tristate "Linear Technology LTC4215" depends on I2C && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index feacc7c243c..bd0410e4b44 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -80,6 +80,7 @@ obj-$(CONFIG_SENSORS_LM90) += lm90.o obj-$(CONFIG_SENSORS_LM92) += lm92.o obj-$(CONFIG_SENSORS_LM93) += lm93.o obj-$(CONFIG_SENSORS_LM95241) += lm95241.o +obj-$(CONFIG_SENSORS_LTC4151) += ltc4151.o obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o diff --git a/drivers/hwmon/ltc4151.c b/drivers/hwmon/ltc4151.c new file mode 100644 index 00000000000..4ac06b75aa6 --- /dev/null +++ b/drivers/hwmon/ltc4151.c @@ -0,0 +1,256 @@ +/* + * Driver for Linear Technology LTC4151 High Voltage I2C Current + * and Voltage Monitor + * + * Copyright (C) 2011 AppearTV AS + * + * Derived from: + * + * Driver for Linear Technology LTC4261 I2C Negative Voltage Hot + * Swap Controller + * Copyright (C) 2010 Ericsson AB. + * + * Datasheet: http://www.linear.com/docs/Datasheet/4151fc.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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* chip registers */ +#define LTC4151_SENSE_H 0x00 +#define LTC4151_SENSE_L 0x01 +#define LTC4151_VIN_H 0x02 +#define LTC4151_VIN_L 0x03 +#define LTC4151_ADIN_H 0x04 +#define LTC4151_ADIN_L 0x05 + +struct ltc4151_data { + struct device *hwmon_dev; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + /* Registers */ + u8 regs[6]; +}; + +static struct ltc4151_data *ltc4151_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ltc4151_data *data = i2c_get_clientdata(client); + struct ltc4151_data *ret = data; + + mutex_lock(&data->update_lock); + + /* + * The chip's A/D updates 6 times per second + * (Conversion Rate 6 - 9 Hz) + */ + if (time_after(jiffies, data->last_updated + HZ / 6) || !data->valid) { + int i; + + dev_dbg(&client->dev, "Starting ltc4151 update\n"); + + /* Read all registers */ + for (i = 0; i < ARRAY_SIZE(data->regs); i++) { + int val; + + val = i2c_smbus_read_byte_data(client, i); + if (unlikely(val < 0)) { + dev_dbg(dev, + "Failed to read ADC value: error %d\n", + val); + ret = ERR_PTR(val); + goto abort; + } + data->regs[i] = val; + } + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +/* Return the voltage from the given register in mV */ +static int ltc4151_get_value(struct ltc4151_data *data, u8 reg) +{ + u32 val; + + val = (data->regs[reg] << 4) + (data->regs[reg + 1] >> 4); + + switch (reg) { + case LTC4151_ADIN_H: + /* 500uV resolution. Convert to mV. */ + val = val * 500 / 1000; + break; + case LTC4151_SENSE_H: + /* + * 20uV resolution. Convert to current as measured with + * an 1 mOhm sense resistor, in mA. + */ + val = val * 20; + break; + case LTC4151_VIN_H: + /* 25 mV per increment */ + val = val * 25; + break; + default: + /* If we get here, the developer messed up */ + WARN_ON_ONCE(1); + val = 0; + break; + } + + return val; +} + +static ssize_t ltc4151_show_value(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ltc4151_data *data = ltc4151_update_device(dev); + int value; + + if (IS_ERR(data)) + return PTR_ERR(data); + + value = ltc4151_get_value(data, attr->index); + return snprintf(buf, PAGE_SIZE, "%d\n", value); +} + +/* + * Input voltages. + */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, \ + ltc4151_show_value, NULL, LTC4151_VIN_H); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, \ + ltc4151_show_value, NULL, LTC4151_ADIN_H); + +/* Currents (via sense resistor) */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, \ + ltc4151_show_value, NULL, LTC4151_SENSE_H); + +/* Finally, construct an array of pointers to members of the above objects, + * as required for sysfs_create_group() + */ +static struct attribute *ltc4151_attributes[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + + &sensor_dev_attr_curr1_input.dev_attr.attr, + + NULL, +}; + +static const struct attribute_group ltc4151_group = { + .attrs = ltc4151_attributes, +}; + +static int ltc4151_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct ltc4151_data *data; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto out_kzalloc; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Register sysfs hooks */ + ret = sysfs_create_group(&client->dev.kobj, <c4151_group); + if (ret) + goto out_sysfs_create_group; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto out_hwmon_device_register; + } + + return 0; + +out_hwmon_device_register: + sysfs_remove_group(&client->dev.kobj, <c4151_group); +out_sysfs_create_group: + kfree(data); +out_kzalloc: + return ret; +} + +static int ltc4151_remove(struct i2c_client *client) +{ + struct ltc4151_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, <c4151_group); + + kfree(data); + + return 0; +} + +static const struct i2c_device_id ltc4151_id[] = { + { "ltc4151", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc4151_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver ltc4151_driver = { + .driver = { + .name = "ltc4151", + }, + .probe = ltc4151_probe, + .remove = ltc4151_remove, + .id_table = ltc4151_id, +}; + +static int __init ltc4151_init(void) +{ + return i2c_add_driver(<c4151_driver); +} + +static void __exit ltc4151_exit(void) +{ + i2c_del_driver(<c4151_driver); +} + +MODULE_AUTHOR("Per Dalen "); +MODULE_DESCRIPTION("LTC4151 driver"); +MODULE_LICENSE("GPL"); + +module_init(ltc4151_init); +module_exit(ltc4151_exit); -- cgit v1.2.3-70-g09d2 From bce26c58df86599c9570cee83eac58bdaae760e4 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 4 Feb 2011 12:54:14 -0800 Subject: hwmon: (w83627ehf) Unify temperature register access, and use strict string conversions This patch unifies temperature register access, and replaces simple_strtoXXX with strict_strtoXXX throughout the driver. Signed-off-by: Guenter Roeck Acked-by: Ian Dobson --- drivers/hwmon/w83627ehf.c | 219 ++++++++++++++++++++++++---------------------- 1 file changed, 113 insertions(+), 106 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 073eabedc43..66e6855f939 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -164,13 +164,10 @@ static const u16 W83627EHF_REG_FAN_MIN[] = { 0x3b, 0x3c, 0x3d, 0x3e, 0x55c }; #define W83627EHF_REG_IN(nr) ((nr < 7) ? (0x20 + (nr)) : \ (0x550 + (nr) - 7)) -#define W83627EHF_REG_TEMP1 0x27 -#define W83627EHF_REG_TEMP1_HYST 0x3a -#define W83627EHF_REG_TEMP1_OVER 0x39 -static const u16 W83627EHF_REG_TEMP[] = { 0x150, 0x250 }; -static const u16 W83627EHF_REG_TEMP_HYST[] = { 0x153, 0x253 }; -static const u16 W83627EHF_REG_TEMP_OVER[] = { 0x155, 0x255 }; -static const u16 W83627EHF_REG_TEMP_CONFIG[] = { 0x152, 0x252 }; +static const u16 W83627EHF_REG_TEMP[] = { 0x27, 0x150, 0x250 }; +static const u16 W83627EHF_REG_TEMP_HYST[] = { 0x3a, 0x153, 0x253 }; +static const u16 W83627EHF_REG_TEMP_OVER[] = { 0x39, 0x155, 0x255 }; +static const u16 W83627EHF_REG_TEMP_CONFIG[] = { 0, 0x152, 0x252 }; /* Fan clock dividers are spread over the following five registers */ #define W83627EHF_REG_FANDIV1 0x47 @@ -216,6 +213,15 @@ static const u8 W83627EHF_REG_FAN_STEP_OUTPUT_COMMON[] static const u8 W83627EHF_REG_FAN_MAX_OUTPUT_W83667_B[] = { 0x67, 0x69, 0x6b }; static const u8 W83627EHF_REG_FAN_STEP_OUTPUT_W83667_B[] = { 0x68, 0x6a, 0x6c }; +static inline int is_word_sized(u16 reg) +{ + return (((reg & 0xff00) == 0x100 + || (reg & 0xff00) == 0x200) + && ((reg & 0x00ff) == 0x50 + || (reg & 0x00ff) == 0x53 + || (reg & 0x00ff) == 0x55)); +} + /* * Conversions */ @@ -247,21 +253,19 @@ div_from_reg(u8 reg) } static inline int -temp1_from_reg(s8 reg) +temp_from_reg(u16 reg, s16 regval) { - return reg * 1000; + if (is_word_sized(reg)) + return LM75_TEMP_FROM_REG(regval); + return regval * 1000; } -static inline s8 -temp1_to_reg(long temp, int min, int max) +static inline s16 +temp_to_reg(u16 reg, long temp) { - if (temp <= min) - return min / 1000; - if (temp >= max) - return max / 1000; - if (temp < 0) - return (temp - 500) / 1000; - return (temp + 500) / 1000; + if (is_word_sized(reg)) + return LM75_TEMP_TO_REG(temp); + return DIV_ROUND_CLOSEST(SENSORS_LIMIT(temp, -127000, 128000), 1000); } /* Some of analog inputs have internal scaling (2x), 8mV is ADC LSB */ @@ -308,12 +312,9 @@ struct w83627ehf_data { u8 fan_div[5]; u8 has_fan; /* some fan inputs can be disabled */ u8 temp_type[3]; - s8 temp1; - s8 temp1_max; - s8 temp1_max_hyst; - s16 temp[2]; - s16 temp_max[2]; - s16 temp_max_hyst[2]; + s16 temp[3]; + s16 temp_max[3]; + s16 temp_max_hyst[3]; u32 alarms; u8 pwm_mode[4]; /* 0->DC variable voltage, 1->PWM variable duty cycle */ @@ -344,15 +345,6 @@ struct w83627ehf_sio_data { enum kinds kind; }; -static inline int is_word_sized(u16 reg) -{ - return (((reg & 0xff00) == 0x100 - || (reg & 0xff00) == 0x200) - && ((reg & 0x00ff) == 0x50 - || (reg & 0x00ff) == 0x53 - || (reg & 0x00ff) == 0x55)); -} - /* Registers 0x50-0x5f are banked */ static inline void w83627ehf_set_bank(struct w83627ehf_data *data, u16 reg) { @@ -586,13 +578,7 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) } /* Measured temperatures and limits */ - data->temp1 = w83627ehf_read_value(data, - W83627EHF_REG_TEMP1); - data->temp1_max = w83627ehf_read_value(data, - W83627EHF_REG_TEMP1_OVER); - data->temp1_max_hyst = w83627ehf_read_value(data, - W83627EHF_REG_TEMP1_HYST); - for (i = 0; i < 2; i++) { + for (i = 0; i < 3; i++) { data->temp[i] = w83627ehf_read_value(data, W83627EHF_REG_TEMP[i]); data->temp_max[i] = w83627ehf_read_value(data, @@ -641,8 +627,11 @@ store_in_##reg (struct device *dev, struct device_attribute *attr, \ struct w83627ehf_data *data = dev_get_drvdata(dev); \ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ - u32 val = simple_strtoul(buf, NULL, 10); \ - \ + unsigned long val; \ + int err; \ + err = strict_strtoul(buf, 10, &val); \ + if (err < 0) \ + return err; \ mutex_lock(&data->update_lock); \ data->in_##reg[nr] = in_to_reg(val, nr); \ w83627ehf_write_value(data, W83627EHF_REG_IN_##REG(nr), \ @@ -746,10 +735,15 @@ store_fan_min(struct device *dev, struct device_attribute *attr, struct w83627ehf_data *data = dev_get_drvdata(dev); struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - unsigned int val = simple_strtoul(buf, NULL, 10); + unsigned long val; + int err; unsigned int reg; u8 new_div; + err = strict_strtoul(buf, 10, &val); + if (err < 0) + return err; + mutex_lock(&data->update_lock); if (!val) { /* No min limit, alarm disabled */ @@ -761,14 +755,14 @@ store_fan_min(struct device *dev, struct device_attribute *attr, even with the highest divider (128) */ data->fan_min[nr] = 254; new_div = 7; /* 128 == (1 << 7) */ - dev_warn(dev, "fan%u low limit %u below minimum %u, set to " + dev_warn(dev, "fan%u low limit %lu below minimum %u, set to " "minimum\n", nr + 1, val, fan_from_reg(254, 128)); } else if (!reg) { /* Speed above this value cannot possibly be represented, even with the lowest divider (1) */ data->fan_min[nr] = 1; new_div = 0; /* 1 == (1 << 0) */ - dev_warn(dev, "fan%u low limit %u above maximum %u, set to " + dev_warn(dev, "fan%u low limit %lu above maximum %u, set to " "maximum\n", nr + 1, val, fan_from_reg(1, 1)); } else { /* Automatically pick the best divider, i.e. the one such @@ -847,37 +841,7 @@ static struct sensor_device_attribute sda_fan_div[] = { SENSOR_ATTR(fan5_div, S_IRUGO, show_fan_div, NULL, 4), }; -#define show_temp1_reg(reg) \ -static ssize_t \ -show_##reg(struct device *dev, struct device_attribute *attr, \ - char *buf) \ -{ \ - struct w83627ehf_data *data = w83627ehf_update_device(dev); \ - return sprintf(buf, "%d\n", temp1_from_reg(data->reg)); \ -} -show_temp1_reg(temp1); -show_temp1_reg(temp1_max); -show_temp1_reg(temp1_max_hyst); - -#define store_temp1_reg(REG, reg) \ -static ssize_t \ -store_temp1_##reg(struct device *dev, struct device_attribute *attr, \ - const char *buf, size_t count) \ -{ \ - struct w83627ehf_data *data = dev_get_drvdata(dev); \ - long val = simple_strtol(buf, NULL, 10); \ - \ - mutex_lock(&data->update_lock); \ - data->temp1_##reg = temp1_to_reg(val, -128000, 127000); \ - w83627ehf_write_value(data, W83627EHF_REG_TEMP1_##REG, \ - data->temp1_##reg); \ - mutex_unlock(&data->update_lock); \ - return count; \ -} -store_temp1_reg(OVER, max); -store_temp1_reg(HYST, max_hyst); - -#define show_temp_reg(reg) \ +#define show_temp_reg(REG, reg) \ static ssize_t \ show_##reg(struct device *dev, struct device_attribute *attr, \ char *buf) \ @@ -886,11 +850,11 @@ show_##reg(struct device *dev, struct device_attribute *attr, \ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ return sprintf(buf, "%d\n", \ - LM75_TEMP_FROM_REG(data->reg[nr])); \ + temp_from_reg(W83627EHF_REG_##REG[nr], data->reg[nr])); \ } -show_temp_reg(temp); -show_temp_reg(temp_max); -show_temp_reg(temp_max_hyst); +show_temp_reg(TEMP, temp); +show_temp_reg(TEMP_OVER, temp_max); +show_temp_reg(TEMP_HYST, temp_max_hyst); #define store_temp_reg(REG, reg) \ static ssize_t \ @@ -900,10 +864,13 @@ store_##reg(struct device *dev, struct device_attribute *attr, \ struct w83627ehf_data *data = dev_get_drvdata(dev); \ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ - long val = simple_strtol(buf, NULL, 10); \ - \ + int err; \ + long val; \ + err = strict_strtol(buf, 10, &val); \ + if (err < 0) \ + return err; \ mutex_lock(&data->update_lock); \ - data->reg[nr] = LM75_TEMP_TO_REG(val); \ + data->reg[nr] = temp_to_reg(W83627EHF_REG_TEMP_##REG[nr], val); \ w83627ehf_write_value(data, W83627EHF_REG_TEMP_##REG[nr], \ data->reg[nr]); \ mutex_unlock(&data->update_lock); \ @@ -922,27 +889,27 @@ show_temp_type(struct device *dev, struct device_attribute *attr, char *buf) } static struct sensor_device_attribute sda_temp_input[] = { - SENSOR_ATTR(temp1_input, S_IRUGO, show_temp1, NULL, 0), - SENSOR_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 0), - SENSOR_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 1), + SENSOR_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0), + SENSOR_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1), + SENSOR_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2), }; static struct sensor_device_attribute sda_temp_max[] = { - SENSOR_ATTR(temp1_max, S_IRUGO | S_IWUSR, show_temp1_max, - store_temp1_max, 0), - SENSOR_ATTR(temp2_max, S_IRUGO | S_IWUSR, show_temp_max, + SENSOR_ATTR(temp1_max, S_IRUGO | S_IWUSR, show_temp_max, store_temp_max, 0), - SENSOR_ATTR(temp3_max, S_IRUGO | S_IWUSR, show_temp_max, + SENSOR_ATTR(temp2_max, S_IRUGO | S_IWUSR, show_temp_max, store_temp_max, 1), + SENSOR_ATTR(temp3_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 2), }; static struct sensor_device_attribute sda_temp_max_hyst[] = { - SENSOR_ATTR(temp1_max_hyst, S_IRUGO | S_IWUSR, show_temp1_max_hyst, - store_temp1_max_hyst, 0), - SENSOR_ATTR(temp2_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + SENSOR_ATTR(temp1_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, store_temp_max_hyst, 0), - SENSOR_ATTR(temp3_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + SENSOR_ATTR(temp2_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, store_temp_max_hyst, 1), + SENSOR_ATTR(temp3_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 2), }; static struct sensor_device_attribute sda_temp_alarm[] = { @@ -978,9 +945,14 @@ store_pwm_mode(struct device *dev, struct device_attribute *attr, struct w83627ehf_data *data = dev_get_drvdata(dev); struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - u32 val = simple_strtoul(buf, NULL, 10); + unsigned long val; + int err; u16 reg; + err = strict_strtoul(buf, 10, &val); + if (err < 0) + return err; + if (val > 1) return -EINVAL; mutex_lock(&data->update_lock); @@ -1001,7 +973,14 @@ store_pwm(struct device *dev, struct device_attribute *attr, struct w83627ehf_data *data = dev_get_drvdata(dev); struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - u32 val = SENSORS_LIMIT(simple_strtoul(buf, NULL, 10), 0, 255); + unsigned long val; + int err; + + err = strict_strtoul(buf, 10, &val); + if (err < 0) + return err; + + val = SENSORS_LIMIT(val, 0, 255); mutex_lock(&data->update_lock); data->pwm[nr] = val; @@ -1017,9 +996,14 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr, struct w83627ehf_data *data = dev_get_drvdata(dev); struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - u32 val = simple_strtoul(buf, NULL, 10); + unsigned long val; + int err; u16 reg; + err = strict_strtoul(buf, 10, &val); + if (err < 0) + return err; + if (!val || (val > 4)) return -EINVAL; mutex_lock(&data->update_lock); @@ -1040,7 +1024,7 @@ static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ struct w83627ehf_data *data = w83627ehf_update_device(dev); \ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ - return sprintf(buf, "%d\n", temp1_from_reg(data->reg[nr])); \ + return sprintf(buf, "%d\n", data->reg[nr] * 1000); \ } show_tol_temp(tolerance) @@ -1053,7 +1037,14 @@ store_target_temp(struct device *dev, struct device_attribute *attr, struct w83627ehf_data *data = dev_get_drvdata(dev); struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - u8 val = temp1_to_reg(simple_strtoul(buf, NULL, 10), 0, 127000); + long val; + int err; + + err = strict_strtol(buf, 10, &val); + if (err < 0) + return err; + + val = SENSORS_LIMIT(DIV_ROUND_CLOSEST(val, 1000), 0, 127); mutex_lock(&data->update_lock); data->target_temp[nr] = val; @@ -1070,8 +1061,15 @@ store_tolerance(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; u16 reg; + long val; + int err; + + err = strict_strtol(buf, 10, &val); + if (err < 0) + return err; + /* Limit the temp to 0C - 15C */ - u8 val = temp1_to_reg(simple_strtoul(buf, NULL, 10), 0, 15000); + val = SENSORS_LIMIT(DIV_ROUND_CLOSEST(val, 1000), 0, 15); mutex_lock(&data->update_lock); reg = w83627ehf_read_value(data, W83627EHF_REG_TOLERANCE[nr]); @@ -1154,7 +1152,12 @@ store_##reg(struct device *dev, struct device_attribute *attr, \ struct w83627ehf_data *data = dev_get_drvdata(dev); \ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ - u32 val = SENSORS_LIMIT(simple_strtoul(buf, NULL, 10), 1, 255); \ + unsigned long val; \ + int err; \ + err = strict_strtoul(buf, 10, &val); \ + if (err < 0) \ + return err; \ + val = SENSORS_LIMIT(val, 1, 255); \ mutex_lock(&data->update_lock); \ data->reg[nr] = val; \ w83627ehf_write_value(data, data->REG_##REG[nr], val); \ @@ -1185,8 +1188,12 @@ store_##reg(struct device *dev, struct device_attribute *attr, \ struct w83627ehf_data *data = dev_get_drvdata(dev); \ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ - u8 val = step_time_to_reg(simple_strtoul(buf, NULL, 10), \ - data->pwm_mode[nr]); \ + unsigned long val; \ + int err; \ + err = strict_strtoul(buf, 10, &val); \ + if (err < 0) \ + return err; \ + val = step_time_to_reg(val, data->pwm_mode[nr]); \ mutex_lock(&data->update_lock); \ data->reg[nr] = val; \ w83627ehf_write_value(data, W83627EHF_REG_##REG[nr], val); \ @@ -1336,10 +1343,10 @@ static inline void __devinit w83627ehf_init_device(struct w83627ehf_data *data) tmp | 0x01); /* Enable temp2 and temp3 if needed */ - for (i = 0; i < 2; i++) { + for (i = 1; i < 3; i++) { tmp = w83627ehf_read_value(data, W83627EHF_REG_TEMP_CONFIG[i]); - if ((i == 1) && data->temp3_disable) + if ((i == 2) && data->temp3_disable) continue; if (tmp & 0x01) w83627ehf_write_value(data, @@ -1400,7 +1407,7 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) /* Check temp3 configuration bit for 667HG */ if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) { data->temp3_disable = w83627ehf_read_value(data, - W83627EHF_REG_TEMP_CONFIG[1]) & 0x01; + W83627EHF_REG_TEMP_CONFIG[2]) & 0x01; data->in6_skip = !data->temp3_disable; } -- cgit v1.2.3-70-g09d2 From e7e1ca6ef4f331c66392d3548cead7f1205f9278 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 4 Feb 2011 13:24:30 -0800 Subject: hwmon: (w83627ehf) Fixed most checkpatch warnings and errors This cleanup fixes most of the checkpatch warnings and errors in the w83627ehf driver. Remaining warnings and errors are left untouched on purpose to avoid making the code less readable. Signed-off-by: Guenter Roeck Acked-by: Ian Dobson --- drivers/hwmon/w83627ehf.c | 149 ++++++++++++++++++++++++++-------------------- 1 file changed, 85 insertions(+), 64 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 66e6855f939..1128eac7023 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -1,10 +1,10 @@ /* w83627ehf - Driver for the hardware monitoring functionality of - the Winbond W83627EHF Super-I/O chip + the Winbond W83627EHF Super-I/O chip Copyright (C) 2005 Jean Delvare Copyright (C) 2006 Yuan Mu (Winbond), - Rudolf Marek - David Hubbard + Rudolf Marek + David Hubbard Daniel J Blueman Shamelessly ripped from the w83627hf driver @@ -35,7 +35,7 @@ Chip #vin #fan #pwm #temp chip IDs man ID w83627ehf 10 5 4 3 0x8850 0x88 0x5ca3 - 0x8860 0xa1 + 0x8860 0xa1 w83627dhg 9 5 4 3 0xa020 0xc1 0x5ca3 w83627dhg-p 9 5 4 3 0xb070 0xc1 0x5ca3 w83667hg 9 5 3 3 0xa510 0xc1 0x5ca3 @@ -61,7 +61,7 @@ enum kinds { w83627ehf, w83627dhg, w83627dhg_p, w83667hg, w83667hg_b }; /* used to set data->name = w83627ehf_device_names[data->sio_kind] */ -static const char * w83627ehf_device_names[] = { +static const char * const w83627ehf_device_names[] = { "w83627ehf", "w83627dhg", "w83627dhg", @@ -80,7 +80,7 @@ MODULE_PARM_DESC(force_id, "Override the detected device ID"); */ #define W83627EHF_LD_HWM 0x0b -#define W83667HG_LD_VID 0x0d +#define W83667HG_LD_VID 0x0d #define SIO_REG_LDSEL 0x07 /* Logical device select */ #define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ @@ -94,7 +94,7 @@ MODULE_PARM_DESC(force_id, "Override the detected device ID"); #define SIO_W83627EHG_ID 0x8860 #define SIO_W83627DHG_ID 0xa020 #define SIO_W83627DHG_P_ID 0xb070 -#define SIO_W83667HG_ID 0xa510 +#define SIO_W83667HG_ID 0xa510 #define SIO_W83667HG_B_ID 0xb350 #define SIO_ID_MASK 0xFFF0 @@ -138,7 +138,7 @@ superio_exit(int ioreg) * ISA constants */ -#define IOREGION_ALIGNMENT ~7 +#define IOREGION_ALIGNMENT (~7) #define IOREGION_OFFSET 5 #define IOREGION_LENGTH 2 #define ADDR_REG_OFFSET 0 @@ -279,7 +279,8 @@ static inline long in_from_reg(u8 reg, u8 nr) static inline u8 in_to_reg(u32 val, u8 nr) { - return SENSORS_LIMIT(((val + (scale_in[nr] / 2)) / scale_in[nr]), 0, 255); + return SENSORS_LIMIT(((val + (scale_in[nr] / 2)) / scale_in[nr]), 0, + 255); } /* @@ -321,7 +322,8 @@ struct w83627ehf_data { u8 pwm_enable[4]; /* 1->manual 2->thermal cruise mode (also called SmartFan I) 3->fan speed cruise mode - 4->variable thermal cruise (also called SmartFan III) */ + 4->variable thermal cruise (also called + SmartFan III) */ u8 pwm_num; /* number of pwm */ u8 pwm[4]; u8 target_temp[4]; @@ -384,7 +386,8 @@ static u16 w83627ehf_read_value(struct w83627ehf_data *data, u16 reg) return res; } -static int w83627ehf_write_value(struct w83627ehf_data *data, u16 reg, u16 value) +static int w83627ehf_write_value(struct w83627ehf_data *data, u16 reg, + u16 value) { int word_sized = is_word_sized(reg); @@ -518,7 +521,7 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) time */ if (data->fan[i] == 0xff && data->fan_div[i] < 0x07) { - dev_dbg(dev, "Increasing fan%d " + dev_dbg(dev, "Increasing fan%d " "clock divider from %u to %u\n", i + 1, div_from_reg(data->fan_div[i]), div_from_reg(data->fan_div[i] + 1)); @@ -548,16 +551,16 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) ((pwmcfg >> W83627EHF_PWM_MODE_SHIFT[i]) & 1) ? 0 : 1; data->pwm_enable[i] = - ((pwmcfg >> W83627EHF_PWM_ENABLE_SHIFT[i]) - & 3) + 1; + ((pwmcfg >> W83627EHF_PWM_ENABLE_SHIFT[i]) + & 3) + 1; data->pwm[i] = w83627ehf_read_value(data, W83627EHF_REG_PWM[i]); data->fan_start_output[i] = w83627ehf_read_value(data, - W83627EHF_REG_FAN_START_OUTPUT[i]); + W83627EHF_REG_FAN_START_OUTPUT[i]); data->fan_stop_output[i] = w83627ehf_read_value(data, - W83627EHF_REG_FAN_STOP_OUTPUT[i]); + W83627EHF_REG_FAN_STOP_OUTPUT[i]); data->fan_stop_time[i] = w83627ehf_read_value(data, - W83627EHF_REG_FAN_STOP_TIME[i]); + W83627EHF_REG_FAN_STOP_TIME[i]); if (data->REG_FAN_MAX_OUTPUT[i] != 0xff) data->fan_max_output[i] = @@ -611,7 +614,8 @@ show_##reg(struct device *dev, struct device_attribute *attr, \ char *buf) \ { \ struct w83627ehf_data *data = w83627ehf_update_device(dev); \ - struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ return sprintf(buf, "%ld\n", in_from_reg(data->reg[nr], nr)); \ } @@ -621,11 +625,12 @@ show_in_reg(in_max) #define store_in_reg(REG, reg) \ static ssize_t \ -store_in_##reg (struct device *dev, struct device_attribute *attr, \ - const char *buf, size_t count) \ +store_in_##reg(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ { \ struct w83627ehf_data *data = dev_get_drvdata(dev); \ - struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ unsigned long val; \ int err; \ @@ -643,7 +648,8 @@ store_in_##reg (struct device *dev, struct device_attribute *attr, \ store_in_reg(MIN, min) store_in_reg(MAX, max) -static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, char *buf) +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) { struct w83627ehf_data *data = w83627ehf_update_device(dev); struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); @@ -678,29 +684,29 @@ static struct sensor_device_attribute sda_in_alarm[] = { }; static struct sensor_device_attribute sda_in_min[] = { - SENSOR_ATTR(in0_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 0), - SENSOR_ATTR(in1_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 1), - SENSOR_ATTR(in2_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 2), - SENSOR_ATTR(in3_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 3), - SENSOR_ATTR(in4_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 4), - SENSOR_ATTR(in5_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 5), - SENSOR_ATTR(in6_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 6), - SENSOR_ATTR(in7_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 7), - SENSOR_ATTR(in8_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 8), - SENSOR_ATTR(in9_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 9), + SENSOR_ATTR(in0_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 0), + SENSOR_ATTR(in1_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 1), + SENSOR_ATTR(in2_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 2), + SENSOR_ATTR(in3_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 3), + SENSOR_ATTR(in4_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 4), + SENSOR_ATTR(in5_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 5), + SENSOR_ATTR(in6_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 6), + SENSOR_ATTR(in7_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 7), + SENSOR_ATTR(in8_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 8), + SENSOR_ATTR(in9_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 9), }; static struct sensor_device_attribute sda_in_max[] = { - SENSOR_ATTR(in0_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 0), - SENSOR_ATTR(in1_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 1), - SENSOR_ATTR(in2_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 2), - SENSOR_ATTR(in3_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 3), - SENSOR_ATTR(in4_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 4), - SENSOR_ATTR(in5_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 5), - SENSOR_ATTR(in6_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 6), - SENSOR_ATTR(in7_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 7), - SENSOR_ATTR(in8_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 8), - SENSOR_ATTR(in9_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 9), + SENSOR_ATTR(in0_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 0), + SENSOR_ATTR(in1_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 1), + SENSOR_ATTR(in2_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 2), + SENSOR_ATTR(in3_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 3), + SENSOR_ATTR(in4_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 4), + SENSOR_ATTR(in5_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 5), + SENSOR_ATTR(in6_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 6), + SENSOR_ATTR(in7_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 7), + SENSOR_ATTR(in8_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 8), + SENSOR_ATTR(in9_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 9), }; #define show_fan_reg(reg) \ @@ -709,7 +715,8 @@ show_##reg(struct device *dev, struct device_attribute *attr, \ char *buf) \ { \ struct w83627ehf_data *data = w83627ehf_update_device(dev); \ - struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ return sprintf(buf, "%d\n", \ fan_from_reg(data->reg[nr], \ @@ -847,7 +854,8 @@ show_##reg(struct device *dev, struct device_attribute *attr, \ char *buf) \ { \ struct w83627ehf_data *data = w83627ehf_update_device(dev); \ - struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ return sprintf(buf, "%d\n", \ temp_from_reg(W83627EHF_REG_##REG[nr], data->reg[nr])); \ @@ -862,7 +870,8 @@ store_##reg(struct device *dev, struct device_attribute *attr, \ const char *buf, size_t count) \ { \ struct w83627ehf_data *data = dev_get_drvdata(dev); \ - struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ int err; \ long val; \ @@ -925,11 +934,12 @@ static struct sensor_device_attribute sda_temp_type[] = { }; #define show_pwm_reg(reg) \ -static ssize_t show_##reg (struct device *dev, struct device_attribute *attr, \ - char *buf) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ { \ struct w83627ehf_data *data = w83627ehf_update_device(dev); \ - struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ return sprintf(buf, "%d\n", data->reg[nr]); \ } @@ -1022,7 +1032,8 @@ static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ char *buf) \ { \ struct w83627ehf_data *data = w83627ehf_update_device(dev); \ - struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ return sprintf(buf, "%d\n", data->reg[nr] * 1000); \ } @@ -1141,16 +1152,18 @@ static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ char *buf) \ { \ struct w83627ehf_data *data = w83627ehf_update_device(dev); \ - struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ return sprintf(buf, "%d\n", data->reg[nr]); \ -}\ +} \ static ssize_t \ store_##reg(struct device *dev, struct device_attribute *attr, \ const char *buf, size_t count) \ -{\ +{ \ struct w83627ehf_data *data = dev_get_drvdata(dev); \ - struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ unsigned long val; \ int err; \ @@ -1175,10 +1188,12 @@ static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ char *buf) \ { \ struct w83627ehf_data *data = w83627ehf_update_device(dev); \ - struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ return sprintf(buf, "%d\n", \ - step_time_from_reg(data->reg[nr], data->pwm_mode[nr])); \ + step_time_from_reg(data->reg[nr], \ + data->pwm_mode[nr])); \ } \ \ static ssize_t \ @@ -1186,7 +1201,8 @@ store_##reg(struct device *dev, struct device_attribute *attr, \ const char *buf, size_t count) \ { \ struct w83627ehf_data *data = dev_get_drvdata(dev); \ - struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ unsigned long val; \ int err; \ @@ -1387,7 +1403,8 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) goto exit; } - if (!(data = kzalloc(sizeof(struct w83627ehf_data), GFP_KERNEL))) { + data = kzalloc(sizeof(struct w83627ehf_data), GFP_KERNEL); + if (!data) { err = -ENOMEM; goto exit_release; } @@ -1508,10 +1525,11 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) w83627ehf_update_fan_div(data); /* Register sysfs hooks */ - for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays); i++) - if ((err = device_create_file(dev, - &sda_sf3_arrays[i].dev_attr))) + for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays); i++) { + err = device_create_file(dev, &sda_sf3_arrays[i].dev_attr); + if (err) goto exit_remove; + } for (i = 0; i < ARRAY_SIZE(sda_sf3_max_step_arrays); i++) { struct sensor_device_attribute *attr = @@ -1525,8 +1543,9 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) /* if fan4 is enabled create the sf3 files for it */ if ((data->has_fan & (1 << 3)) && data->pwm_num >= 4) for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays_fan4); i++) { - if ((err = device_create_file(dev, - &sda_sf3_arrays_fan4[i].dev_attr))) + err = device_create_file(dev, + &sda_sf3_arrays_fan4[i].dev_attr); + if (err) goto exit_remove; } @@ -1696,7 +1715,8 @@ static int __init w83627ehf_find(int sioaddr, unsigned short *addr, /* Activate logical device if needed */ val = superio_inb(sioaddr, SIO_REG_ENABLE); if (!(val & 0x01)) { - pr_warn("Forcibly enabling Super-I/O. Sensor is probably unusable.\n"); + pr_warn("Forcibly enabling Super-I/O. " + "Sensor is probably unusable.\n"); superio_outb(sioaddr, SIO_REG_ENABLE, val | 0x01); } @@ -1733,7 +1753,8 @@ static int __init sensors_w83627ehf_init(void) if (err) goto exit; - if (!(pdev = platform_device_alloc(DRVNAME, address))) { + pdev = platform_device_alloc(DRVNAME, address); + if (!pdev) { err = -ENOMEM; pr_err("Device allocation failed\n"); goto exit_unregister; -- cgit v1.2.3-70-g09d2 From 83cc8985b82dff2ef85987a8481a92aab1b33323 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 6 Feb 2011 08:10:15 -0800 Subject: hwmon: (w83627ehf) Optimize multi-bank register access Assume that each register is banked, and set the bank for each access. Cache the bank number so it only needs to be set if it changes. Signed-off-by: Guenter Roeck Acked-by: Ian Dobson --- drivers/hwmon/w83627ehf.c | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 1128eac7023..5a627b9db3e 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -304,6 +304,7 @@ struct w83627ehf_data { unsigned long last_updated; /* In jiffies */ /* Register values */ + u8 bank; /* current register bank */ u8 in_num; /* number of in inputs we have */ u8 in[10]; /* Register value */ u8 in_max[10]; /* Register value */ @@ -347,21 +348,19 @@ struct w83627ehf_sio_data { enum kinds kind; }; -/* Registers 0x50-0x5f are banked */ +/* + * On older chips, only registers 0x50-0x5f are banked. + * On more recent chips, all registers are banked. + * Assume that is the case and set the bank number for each access. + * Cache the bank number so it only needs to be set if it changes. + */ static inline void w83627ehf_set_bank(struct w83627ehf_data *data, u16 reg) { - if ((reg & 0x00f0) == 0x50) { - outb_p(W83627EHF_REG_BANK, data->addr + ADDR_REG_OFFSET); - outb_p(reg >> 8, data->addr + DATA_REG_OFFSET); - } -} - -/* Not strictly necessary, but play it safe for now */ -static inline void w83627ehf_reset_bank(struct w83627ehf_data *data, u16 reg) -{ - if (reg & 0xff00) { + u8 bank = reg >> 8; + if (data->bank != bank) { outb_p(W83627EHF_REG_BANK, data->addr + ADDR_REG_OFFSET); - outb_p(0, data->addr + DATA_REG_OFFSET); + outb_p(bank, data->addr + DATA_REG_OFFSET); + data->bank = bank; } } @@ -379,10 +378,8 @@ static u16 w83627ehf_read_value(struct w83627ehf_data *data, u16 reg) data->addr + ADDR_REG_OFFSET); res = (res << 8) + inb_p(data->addr + DATA_REG_OFFSET); } - w83627ehf_reset_bank(data, reg); mutex_unlock(&data->lock); - return res; } @@ -401,7 +398,6 @@ static int w83627ehf_write_value(struct w83627ehf_data *data, u16 reg, data->addr + ADDR_REG_OFFSET); } outb_p(value & 0xff, data->addr + DATA_REG_OFFSET); - w83627ehf_reset_bank(data, reg); mutex_unlock(&data->lock); return 0; -- cgit v1.2.3-70-g09d2 From d36cf32c9a6c4ffea8f2108a05defb55800f9215 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 7 Feb 2011 15:08:54 -0800 Subject: hwmon: (w83627ehf) Improve support for W83667HG-B Add support for 4th temperature sensor on W83677HG-B. Display temperature labels on W83677HG-B to report temperature sources. Signed-off-by: Guenter Roeck Acked-by: Ian Dobson --- Documentation/hwmon/w83627ehf | 19 ++++-- drivers/hwmon/w83627ehf.c | 155 +++++++++++++++++++++++++++++++++--------- 2 files changed, 136 insertions(+), 38 deletions(-) (limited to 'drivers/hwmon') diff --git a/Documentation/hwmon/w83627ehf b/Documentation/hwmon/w83627ehf index 13d556112fc..064301944d4 100644 --- a/Documentation/hwmon/w83627ehf +++ b/Documentation/hwmon/w83627ehf @@ -39,16 +39,21 @@ This driver implements support for the Winbond W83627EHF, W83627EHG, W83627DHG, W83627DHG-P, W83667HG and W83667HG-B super I/O chips. We will refer to them collectively as Winbond chips. -The chips implement three temperature sensors, five fan rotation -speed sensors, ten analog voltage sensors (only nine for the 627DHG), one -VID (6 pins for the 627EHF/EHG, 8 pins for the 627DHG and 667HG), alarms -with beep warnings (control unimplemented), and some automatic fan +The chips implement three temperature sensors (up to four for 667HG-B), +five fan rotation speed sensors, ten analog voltage sensors (only nine for the +627DHG), one VID (6 pins for the 627EHF/EHG, 8 pins for the 627DHG and 667HG), +alarms with beep warnings (control unimplemented), and some automatic fan regulation strategies (plus manual fan control mode). +The temperature sensor sources on W82677HG-B are configurable. temp4 is only +reported if its temperature source differs from the temperature sources of the +other three temperature sensors. The configured source for each of the +temperature sensors is reported in tempX_label. + Temperatures are measured in degrees Celsius and measurement resolution is 1 -degC for temp1 and 0.5 degC for temp2 and temp3. An alarm is triggered when -the temperature gets higher than high limit; it stays on until the temperature -falls below the hysteresis value. +degC for temp1 and temp4, and 0.5 degC for temp2 and temp3. An alarm is +triggered when the temperature gets higher than high limit; it stays on until +the temperature falls below the hysteresis value. Fan rotation speeds are reported in RPM (rotations per minute). An alarm is triggered if the rotation speed has dropped below a programmable limit. Fan diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 5a627b9db3e..d10fe706c85 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -39,7 +39,7 @@ w83627dhg 9 5 4 3 0xa020 0xc1 0x5ca3 w83627dhg-p 9 5 4 3 0xb070 0xc1 0x5ca3 w83667hg 9 5 3 3 0xa510 0xc1 0x5ca3 - w83667hg-b 9 5 3 3 0xb350 0xc1 0x5ca3 + w83667hg-b 9 5 3 4 0xb350 0xc1 0x5ca3 */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -164,10 +164,10 @@ static const u16 W83627EHF_REG_FAN_MIN[] = { 0x3b, 0x3c, 0x3d, 0x3e, 0x55c }; #define W83627EHF_REG_IN(nr) ((nr < 7) ? (0x20 + (nr)) : \ (0x550 + (nr) - 7)) -static const u16 W83627EHF_REG_TEMP[] = { 0x27, 0x150, 0x250 }; -static const u16 W83627EHF_REG_TEMP_HYST[] = { 0x3a, 0x153, 0x253 }; -static const u16 W83627EHF_REG_TEMP_OVER[] = { 0x39, 0x155, 0x255 }; -static const u16 W83627EHF_REG_TEMP_CONFIG[] = { 0, 0x152, 0x252 }; +static const u16 W83627EHF_REG_TEMP[] = { 0x27, 0x150, 0x250, 0x7e }; +static const u16 W83627EHF_REG_TEMP_HYST[] = { 0x3a, 0x153, 0x253, 0 }; +static const u16 W83627EHF_REG_TEMP_OVER[] = { 0x39, 0x155, 0x255, 0 }; +static const u16 W83627EHF_REG_TEMP_CONFIG[] = { 0, 0x152, 0x252, 0 }; /* Fan clock dividers are spread over the following five registers */ #define W83627EHF_REG_FANDIV1 0x47 @@ -213,6 +213,19 @@ static const u8 W83627EHF_REG_FAN_STEP_OUTPUT_COMMON[] static const u8 W83627EHF_REG_FAN_MAX_OUTPUT_W83667_B[] = { 0x67, 0x69, 0x6b }; static const u8 W83627EHF_REG_FAN_STEP_OUTPUT_W83667_B[] = { 0x68, 0x6a, 0x6c }; +static const char *const w83667hg_b_temp_label[] = { + "SYSTIN", + "CPUTIN", + "AUXTIN", + "AMDTSI", + "PECI Agent 1", + "PECI Agent 2", + "PECI Agent 3", + "PECI Agent 4" +}; + +#define NUM_REG_TEMP 4 + static inline int is_word_sized(u16 reg) { return (((reg & 0xff00) == 0x100 @@ -294,6 +307,9 @@ struct w83627ehf_data { struct device *hwmon_dev; struct mutex lock; + u8 temp_src[NUM_REG_TEMP]; + const char * const *temp_label; + const u8 *REG_FAN_START_OUTPUT; const u8 *REG_FAN_STOP_OUTPUT; const u8 *REG_FAN_MAX_OUTPUT; @@ -314,9 +330,9 @@ struct w83627ehf_data { u8 fan_div[5]; u8 has_fan; /* some fan inputs can be disabled */ u8 temp_type[3]; - s16 temp[3]; - s16 temp_max[3]; - s16 temp_max_hyst[3]; + s16 temp[4]; + s16 temp_max[4]; + s16 temp_max_hyst[4]; u32 alarms; u8 pwm_mode[4]; /* 0->DC variable voltage, 1->PWM variable duty cycle */ @@ -339,7 +355,7 @@ struct w83627ehf_data { u8 vid; u8 vrm; - u8 temp3_disable; + u8 have_temp; u8 in6_skip; }; @@ -577,12 +593,18 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) } /* Measured temperatures and limits */ - for (i = 0; i < 3; i++) { - data->temp[i] = w83627ehf_read_value(data, - W83627EHF_REG_TEMP[i]); - data->temp_max[i] = w83627ehf_read_value(data, - W83627EHF_REG_TEMP_OVER[i]); - data->temp_max_hyst[i] = w83627ehf_read_value(data, + for (i = 0; i < NUM_REG_TEMP; i++) { + if (!(data->have_temp & (1 << i))) + continue; + data->temp[i] + = w83627ehf_read_value(data, W83627EHF_REG_TEMP[i]); + if (i > 2) + break; + data->temp_max[i] + = w83627ehf_read_value(data, + W83627EHF_REG_TEMP_OVER[i]); + data->temp_max_hyst[i] + = w83627ehf_read_value(data, W83627EHF_REG_TEMP_HYST[i]); } @@ -844,6 +866,15 @@ static struct sensor_device_attribute sda_fan_div[] = { SENSOR_ATTR(fan5_div, S_IRUGO, show_fan_div, NULL, 4), }; +static ssize_t +show_temp_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627ehf_data *data = w83627ehf_update_device(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + return sprintf(buf, "%s\n", data->temp_label[data->temp_src[nr]]); +} + #define show_temp_reg(REG, reg) \ static ssize_t \ show_##reg(struct device *dev, struct device_attribute *attr, \ @@ -897,6 +928,14 @@ static struct sensor_device_attribute sda_temp_input[] = { SENSOR_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0), SENSOR_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1), SENSOR_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2), + SENSOR_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3), +}; + +static struct sensor_device_attribute sda_temp_label[] = { + SENSOR_ATTR(temp1_label, S_IRUGO, show_temp_label, NULL, 0), + SENSOR_ATTR(temp2_label, S_IRUGO, show_temp_label, NULL, 1), + SENSOR_ATTR(temp3_label, S_IRUGO, show_temp_label, NULL, 2), + SENSOR_ATTR(temp4_label, S_IRUGO, show_temp_label, NULL, 3), }; static struct sensor_device_attribute sda_temp_max[] = { @@ -1328,10 +1367,13 @@ static void w83627ehf_device_remove_files(struct device *dev) device_remove_file(dev, &sda_target_temp[i].dev_attr); device_remove_file(dev, &sda_tolerance[i].dev_attr); } - for (i = 0; i < 3; i++) { - if ((i == 2) && data->temp3_disable) + for (i = 0; i < NUM_REG_TEMP; i++) { + if (!(data->have_temp & (1 << i))) continue; device_remove_file(dev, &sda_temp_input[i].dev_attr); + device_remove_file(dev, &sda_temp_label[i].dev_attr); + if (i > 2) + break; device_remove_file(dev, &sda_temp_max[i].dev_attr); device_remove_file(dev, &sda_temp_max_hyst[i].dev_attr); device_remove_file(dev, &sda_temp_alarm[i].dev_attr); @@ -1354,12 +1396,14 @@ static inline void __devinit w83627ehf_init_device(struct w83627ehf_data *data) w83627ehf_write_value(data, W83627EHF_REG_CONFIG, tmp | 0x01); - /* Enable temp2 and temp3 if needed */ - for (i = 1; i < 3; i++) { + /* Enable temperature sensors if needed */ + for (i = 0; i < NUM_REG_TEMP; i++) { + if (!(data->have_temp & (1 << i))) + continue; + if (!W83627EHF_REG_TEMP_CONFIG[i]) + continue; tmp = w83627ehf_read_value(data, W83627EHF_REG_TEMP_CONFIG[i]); - if ((i == 2) && data->temp3_disable) - continue; if (tmp & 0x01) w83627ehf_write_value(data, W83627EHF_REG_TEMP_CONFIG[i], @@ -1417,11 +1461,52 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) data->pwm_num = (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) ? 3 : 4; + data->have_temp = 0x07; /* Check temp3 configuration bit for 667HG */ - if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) { - data->temp3_disable = w83627ehf_read_value(data, - W83627EHF_REG_TEMP_CONFIG[2]) & 0x01; - data->in6_skip = !data->temp3_disable; + if (sio_data->kind == w83667hg) { + u8 reg; + + reg = w83627ehf_read_value(data, W83627EHF_REG_TEMP_CONFIG[2]); + if (reg & 0x01) + data->have_temp &= ~(1 << 2); + else + data->in6_skip = 1; /* Either temp3 or in6 */ + } else if (sio_data->kind == w83667hg_b) { + u8 reg; + + reg = w83627ehf_read_value(data, 0x4a); + data->temp_src[0] = reg >> 5; + reg = w83627ehf_read_value(data, 0x49); + data->temp_src[1] = reg & 0x07; + data->temp_src[2] = (reg >> 4) & 0x07; + + /* + * W83667HG-B has another temperature register at 0x7e. + * The temperature source is selected with register 0x7d. + * Support it if the source differs from already reported + * sources. + */ + reg = w83627ehf_read_value(data, 0x7d); + reg &= 0x07; + if (reg != data->temp_src[0] && reg != data->temp_src[1] + && reg != data->temp_src[2]) { + data->temp_src[3] = reg; + data->have_temp |= 1 << 3; + } + + /* + * Chip supports either AUXTIN or VIN3. Try to find out which + * one. + */ + reg = w83627ehf_read_value(data, W83627EHF_REG_TEMP_CONFIG[2]); + if (data->temp_src[2] == 2 && (reg & 0x01)) + data->have_temp &= ~(1 << 2); + + if ((data->temp_src[2] == 2 && (data->have_temp & (1 << 2))) + || (data->temp_src[3] == 2 && (data->have_temp & (1 << 3)))) + data->in6_skip = 1; + + data->temp_label = w83667hg_b_temp_label; } data->REG_FAN_START_OUTPUT = W83627EHF_REG_FAN_START_OUTPUT; @@ -1584,13 +1669,21 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) } } - for (i = 0; i < 3; i++) { - if ((i == 2) && data->temp3_disable) + for (i = 0; i < NUM_REG_TEMP; i++) { + if (!(data->have_temp & (1 << i))) continue; - if ((err = device_create_file(dev, - &sda_temp_input[i].dev_attr)) - || (err = device_create_file(dev, - &sda_temp_max[i].dev_attr)) + err = device_create_file(dev, &sda_temp_input[i].dev_attr); + if (err) + goto exit_remove; + if (data->temp_label) { + err = device_create_file(dev, + &sda_temp_label[i].dev_attr); + if (err) + goto exit_remove; + } + if (i > 2) + break; + if ((err = device_create_file(dev, &sda_temp_max[i].dev_attr)) || (err = device_create_file(dev, &sda_temp_max_hyst[i].dev_attr)) || (err = device_create_file(dev, -- cgit v1.2.3-70-g09d2 From 279af1a91a041070e17cb41900151cb724e1b32a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 13 Feb 2011 22:34:47 -0800 Subject: hwmon: (w83627ehf) Convert register arrays to 16 bit, and convert access to pointers For newer chips, several registers are banked and thus need to be 16 bit. Also, register addresses change. To prepare for those chips, convert affected register arrays to 16 bit, and change access to those registers to array pointers in struct w83627ehf_data. Signed-off-by: Guenter Roeck Acked-by: Ian Dobson --- drivers/hwmon/w83627ehf.c | 60 ++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 24 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index d10fe706c85..2420f1c6f4e 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -196,22 +196,23 @@ static const u8 W83627EHF_PWM_MODE_SHIFT[] = { 0, 1, 0, 6 }; static const u8 W83627EHF_PWM_ENABLE_SHIFT[] = { 2, 4, 1, 4 }; /* FAN Duty Cycle, be used to control */ -static const u8 W83627EHF_REG_PWM[] = { 0x01, 0x03, 0x11, 0x61 }; -static const u8 W83627EHF_REG_TARGET[] = { 0x05, 0x06, 0x13, 0x63 }; +static const u16 W83627EHF_REG_PWM[] = { 0x01, 0x03, 0x11, 0x61 }; +static const u16 W83627EHF_REG_TARGET[] = { 0x05, 0x06, 0x13, 0x63 }; static const u8 W83627EHF_REG_TOLERANCE[] = { 0x07, 0x07, 0x14, 0x62 }; /* Advanced Fan control, some values are common for all fans */ -static const u8 W83627EHF_REG_FAN_START_OUTPUT[] = { 0x0a, 0x0b, 0x16, 0x65 }; -static const u8 W83627EHF_REG_FAN_STOP_OUTPUT[] = { 0x08, 0x09, 0x15, 0x64 }; -static const u8 W83627EHF_REG_FAN_STOP_TIME[] = { 0x0c, 0x0d, 0x17, 0x66 }; +static const u16 W83627EHF_REG_FAN_START_OUTPUT[] = { 0x0a, 0x0b, 0x16, 0x65 }; +static const u16 W83627EHF_REG_FAN_STOP_OUTPUT[] = { 0x08, 0x09, 0x15, 0x64 }; +static const u16 W83627EHF_REG_FAN_STOP_TIME[] = { 0x0c, 0x0d, 0x17, 0x66 }; -static const u8 W83627EHF_REG_FAN_MAX_OUTPUT_COMMON[] +static const u16 W83627EHF_REG_FAN_MAX_OUTPUT_COMMON[] = { 0xff, 0x67, 0xff, 0x69 }; -static const u8 W83627EHF_REG_FAN_STEP_OUTPUT_COMMON[] +static const u16 W83627EHF_REG_FAN_STEP_OUTPUT_COMMON[] = { 0xff, 0x68, 0xff, 0x6a }; -static const u8 W83627EHF_REG_FAN_MAX_OUTPUT_W83667_B[] = { 0x67, 0x69, 0x6b }; -static const u8 W83627EHF_REG_FAN_STEP_OUTPUT_W83667_B[] = { 0x68, 0x6a, 0x6c }; +static const u16 W83627EHF_REG_FAN_MAX_OUTPUT_W83667_B[] = { 0x67, 0x69, 0x6b }; +static const u16 W83627EHF_REG_FAN_STEP_OUTPUT_W83667_B[] + = { 0x68, 0x6a, 0x6c }; static const char *const w83667hg_b_temp_label[] = { "SYSTIN", @@ -310,10 +311,15 @@ struct w83627ehf_data { u8 temp_src[NUM_REG_TEMP]; const char * const *temp_label; - const u8 *REG_FAN_START_OUTPUT; - const u8 *REG_FAN_STOP_OUTPUT; - const u8 *REG_FAN_MAX_OUTPUT; - const u8 *REG_FAN_STEP_OUTPUT; + const u16 *REG_PWM; + const u16 *REG_TARGET; + const u16 *REG_FAN; + const u16 *REG_FAN_MIN; + const u16 *REG_FAN_START_OUTPUT; + const u16 *REG_FAN_STOP_OUTPUT; + const u16 *REG_FAN_STOP_TIME; + const u16 *REG_FAN_MAX_OUTPUT; + const u16 *REG_FAN_STEP_OUTPUT; struct mutex update_lock; char valid; /* !=0 if following fields are valid */ @@ -524,9 +530,9 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) continue; data->fan[i] = w83627ehf_read_value(data, - W83627EHF_REG_FAN[i]); + data->REG_FAN[i]); data->fan_min[i] = w83627ehf_read_value(data, - W83627EHF_REG_FAN_MIN[i]); + data->REG_FAN_MIN[i]); /* If we failed to measure the fan speed and clock divider can be increased, let's try that for next @@ -543,7 +549,7 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) if (data->fan_min[i] >= 2 && data->fan_min[i] != 255) w83627ehf_write_value(data, - W83627EHF_REG_FAN_MIN[i], + data->REG_FAN_MIN[i], (data->fan_min[i] /= 2)); } } @@ -566,13 +572,13 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) ((pwmcfg >> W83627EHF_PWM_ENABLE_SHIFT[i]) & 3) + 1; data->pwm[i] = w83627ehf_read_value(data, - W83627EHF_REG_PWM[i]); + data->REG_PWM[i]); data->fan_start_output[i] = w83627ehf_read_value(data, - W83627EHF_REG_FAN_START_OUTPUT[i]); + data->REG_FAN_START_OUTPUT[i]); data->fan_stop_output[i] = w83627ehf_read_value(data, - W83627EHF_REG_FAN_STOP_OUTPUT[i]); + data->REG_FAN_STOP_OUTPUT[i]); data->fan_stop_time[i] = w83627ehf_read_value(data, - W83627EHF_REG_FAN_STOP_TIME[i]); + data->REG_FAN_STOP_TIME[i]); if (data->REG_FAN_MAX_OUTPUT[i] != 0xff) data->fan_max_output[i] = @@ -586,7 +592,7 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) data->target_temp[i] = w83627ehf_read_value(data, - W83627EHF_REG_TARGET[i]) & + data->REG_TARGET[i]) & (data->pwm_mode[i] == 1 ? 0x7f : 0xff); data->tolerance[i] = (tolerance >> (i == 1 ? 4 : 0)) & 0x0f; @@ -822,7 +828,7 @@ store_fan_min(struct device *dev, struct device_attribute *attr, /* Give the chip time to sample a new speed value */ data->last_updated = jiffies; } - w83627ehf_write_value(data, W83627EHF_REG_FAN_MIN[nr], + w83627ehf_write_value(data, data->REG_FAN_MIN[nr], data->fan_min[nr]); mutex_unlock(&data->update_lock); @@ -1029,7 +1035,7 @@ store_pwm(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->pwm[nr] = val; - w83627ehf_write_value(data, W83627EHF_REG_PWM[nr], val); + w83627ehf_write_value(data, data->REG_PWM[nr], val); mutex_unlock(&data->update_lock); return count; } @@ -1094,7 +1100,7 @@ store_target_temp(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->target_temp[nr] = val; - w83627ehf_write_value(data, W83627EHF_REG_TARGET[nr], val); + w83627ehf_write_value(data, data->REG_TARGET[nr], val); mutex_unlock(&data->update_lock); return count; } @@ -1509,8 +1515,14 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) data->temp_label = w83667hg_b_temp_label; } + data->REG_PWM = W83627EHF_REG_PWM; + data->REG_TARGET = W83627EHF_REG_TARGET; + data->REG_FAN = W83627EHF_REG_FAN; + data->REG_FAN_MIN = W83627EHF_REG_FAN_MIN; data->REG_FAN_START_OUTPUT = W83627EHF_REG_FAN_START_OUTPUT; data->REG_FAN_STOP_OUTPUT = W83627EHF_REG_FAN_STOP_OUTPUT; + data->REG_FAN_STOP_TIME = W83627EHF_REG_FAN_STOP_TIME; + data->REG_FAN_START_OUTPUT = W83627EHF_REG_FAN_START_OUTPUT; if (sio_data->kind == w83667hg_b) { data->REG_FAN_MAX_OUTPUT = W83627EHF_REG_FAN_MAX_OUTPUT_W83667_B; -- cgit v1.2.3-70-g09d2 From b84bb5186297d181075d917e9049da3a3b78d10d Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 13 Feb 2011 23:01:25 -0800 Subject: hwmon: (w83627ehf) Permit enabling SmartFan IV mode if configured at startup If SmartFan IV mode was configured at startup, it should be possible to re-enable it later on. Signed-off-by: Guenter Roeck Acked-by: Ian Dobson --- drivers/hwmon/w83627ehf.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 2420f1c6f4e..2f17f99e0ae 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -346,7 +346,10 @@ struct w83627ehf_data { 2->thermal cruise mode (also called SmartFan I) 3->fan speed cruise mode 4->variable thermal cruise (also called - SmartFan III) */ + SmartFan III) + 5->enhanced variable thermal cruise (also called + SmartFan IV) */ + u8 pwm_enable_orig[4]; /* original value of pwm_enable */ u8 pwm_num; /* number of pwm */ u8 pwm[4]; u8 target_temp[4]; @@ -1055,7 +1058,7 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr, if (err < 0) return err; - if (!val || (val > 4)) + if (!val || (val > 4 && val != data->pwm_enable_orig[nr])) return -EINVAL; mutex_lock(&data->update_lock); reg = w83627ehf_read_value(data, W83627EHF_REG_PWM_ENABLE[nr]); @@ -1617,6 +1620,11 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) /* Read fan clock dividers immediately */ w83627ehf_update_fan_div(data); + /* Read pwm data to save original values */ + w83627ehf_update_pwm_common(dev, data); + for (i = 0; i < data->pwm_num; i++) + data->pwm_enable_orig[i] = data->pwm_enable[i]; + /* Register sysfs hooks */ for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays); i++) { err = device_create_file(dev, &sda_sf3_arrays[i].dev_attr); -- cgit v1.2.3-70-g09d2 From ec3e5a16446fef1891611fe3bdfa5954d1ddf5e4 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 2 Feb 2011 08:46:49 -0800 Subject: hwmon: (w83627ehf) Add support for Nuvoton NCT6775F and NCT6776F This patch adds support for NCT6775F and NCT6776F to the w83627ehf driver. Signed-off-by: Guenter Roeck Tested-by: Ian Dobson (NCT6776F) Tested-by: Zachary Marzec (ASUS P8P67 PRO/NCT6776F) Acked-by: Ian Dobson --- Documentation/hwmon/w83627ehf | 59 +++- drivers/hwmon/w83627ehf.c | 791 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 694 insertions(+), 156 deletions(-) (limited to 'drivers/hwmon') diff --git a/Documentation/hwmon/w83627ehf b/Documentation/hwmon/w83627ehf index b634e980dc6..76ffef94ed7 100644 --- a/Documentation/hwmon/w83627ehf +++ b/Documentation/hwmon/w83627ehf @@ -22,6 +22,14 @@ Supported chips: Prefix: 'w83667hg' Addresses scanned: ISA address retrieved from Super I/O registers Datasheet: Available from Nuvoton upon request + * Nuvoton NCT6775F/W83667HG-I + Prefix: 'nct6775' + Addresses scanned: ISA address retrieved from Super I/O registers + Datasheet: Available from Nuvoton upon request + * Nuvoton NCT6776F + Prefix: 'nct6776' + Addresses scanned: ISA address retrieved from Super I/O registers + Datasheet: Available from Nuvoton upon request Authors: Jean Delvare @@ -34,24 +42,28 @@ Description ----------- This driver implements support for the Winbond W83627EHF, W83627EHG, -W83627DHG, W83627DHG-P, W83667HG and W83667HG-B super I/O chips. -We will refer to them collectively as Winbond chips. - -The chips implement three temperature sensors (up to four for 667HG-B), -five fan rotation speed sensors, ten analog voltage sensors (only nine for the -627DHG), one VID (6 pins for the 627EHF/EHG, 8 pins for the 627DHG and 667HG), -alarms with beep warnings (control unimplemented), and some automatic fan -regulation strategies (plus manual fan control mode). - -The temperature sensor sources on W82677HG-B are configurable. temp4 is only -reported if its temperature source differs from the temperature sources of the -other three temperature sensors. The configured source for each of the -temperature sensors is reported in tempX_label. +W83627DHG, W83627DHG-P, W83667HG, W83667HG-B, W83667HG-I (NCT6775F), +and NCT6776F super I/O chips. We will refer to them collectively as +Winbond chips. + +The chips implement three temperature sensors (up to four for 667HG-B, and nine +for NCT6775F and NCT6776F), five fan rotation speed sensors, ten analog voltage +sensors (only nine for the 627DHG), one VID (6 pins for the 627EHF/EHG, 8 pins +for the 627DHG and 667HG), alarms with beep warnings (control unimplemented), +and some automatic fan regulation strategies (plus manual fan control mode). + +The temperature sensor sources on W82677HG-B, NCT6775F, and NCT6776F are +configurable. temp4 and higher attributes are only reported if its temperature +source differs from the temperature sources of the already reported temperature +sensors. The configured source for each of the temperature sensors is provided +in tempX_label. Temperatures are measured in degrees Celsius and measurement resolution is 1 -degC for temp1 and temp4, and 0.5 degC for temp2 and temp3. An alarm is -triggered when the temperature gets higher than high limit; it stays on until -the temperature falls below the hysteresis value. +degC for temp1 and and 0.5 degC for temp2 and temp3. For temp4 and higher, +resolution is 1 degC for W83667HG-B and 0.0 degC for NCT6775F and NCT6776F. +An alarm is triggered when the temperature gets higher than high limit; +it stays on until the temperature falls below the hysteresis value. +Alarms are only supported for temp1, temp2, and temp3. Fan rotation speeds are reported in RPM (rotations per minute). An alarm is triggered if the rotation speed has dropped below a programmable limit. Fan @@ -83,7 +95,8 @@ prog -> pwm4 (not on 667HG and 667HG-B; the programmable setting is not name - this is a standard hwmon device entry. For the W83627EHF and W83627EHG, it is set to "w83627ehf", for the W83627DHG it is set to "w83627dhg", - and for the W83667HG it is set to "w83667hg". + for the W83667HG and W83667HG-B it is set to "w83667hg", for NCT6775F it + is set to "nct6775", and for NCT6776F it is set to "nct6776". pwm[1-4] - this file stores PWM duty cycle or DC value (fan speed) in range: 0 (stop) to 255 (full) @@ -93,6 +106,18 @@ pwm[1-4]_enable - this file controls mode of fan/temperature control: * 2 "Thermal Cruise" mode * 3 "Fan Speed Cruise" mode * 4 "Smart Fan III" mode + * 5 "Smart Fan IV" mode + + SmartFan III mode is not supported on NCT6776F. + + SmartFan IV mode is configurable only if it was configured at system + startup, and is only supported for W83677HG-B, NCT6775F, and NCT6776F. + SmartFan IV operational parameters can not be configured at this time, + and the various pwm attributes are not used in SmartFan IV mode. + The attributes can be written to, which is useful if you plan to + configure the system for a different pwm mode. However, the information + returned when reading pwm attributes is unrelated to SmartFan IV + operation. pwm[1-4]_mode - controls if output is PWM or DC level * 0 DC output (0 - 12v) diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 2f17f99e0ae..b3b4f2b41dc 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -6,6 +6,7 @@ Rudolf Marek David Hubbard Daniel J Blueman + Copyright (C) 2010 Sheng-Yuan Huang (Nuvoton) (PS00) Shamelessly ripped from the w83627hf driver Copyright (C) 2003 Mark Studebaker @@ -40,6 +41,8 @@ w83627dhg-p 9 5 4 3 0xb070 0xc1 0x5ca3 w83667hg 9 5 3 3 0xa510 0xc1 0x5ca3 w83667hg-b 9 5 3 4 0xb350 0xc1 0x5ca3 + nct6775f 9 4 3 9 0xb470 0xc1 0x5ca3 + nct6776f 9 5 3 9 0xC330 0xc1 0x5ca3 */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -58,7 +61,8 @@ #include #include "lm75.h" -enum kinds { w83627ehf, w83627dhg, w83627dhg_p, w83667hg, w83667hg_b }; +enum kinds { w83627ehf, w83627dhg, w83627dhg_p, w83667hg, w83667hg_b, nct6775, + nct6776 }; /* used to set data->name = w83627ehf_device_names[data->sio_kind] */ static const char * const w83627ehf_device_names[] = { @@ -67,6 +71,8 @@ static const char * const w83627ehf_device_names[] = { "w83627dhg", "w83667hg", "w83667hg", + "nct6775", + "nct6776", }; static unsigned short force_id; @@ -96,6 +102,8 @@ MODULE_PARM_DESC(force_id, "Override the detected device ID"); #define SIO_W83627DHG_P_ID 0xb070 #define SIO_W83667HG_ID 0xa510 #define SIO_W83667HG_B_ID 0xb350 +#define SIO_NCT6775_ID 0xb470 +#define SIO_NCT6776_ID 0xc330 #define SIO_ID_MASK 0xFFF0 static inline void @@ -176,6 +184,10 @@ static const u16 W83627EHF_REG_TEMP_CONFIG[] = { 0, 0x152, 0x252, 0 }; #define W83627EHF_REG_DIODE 0x59 #define W83627EHF_REG_SMI_OVT 0x4C +/* NCT6775F has its own fan divider registers */ +#define NCT6775_REG_FANDIV1 0x506 +#define NCT6775_REG_FANDIV2 0x507 + #define W83627EHF_REG_ALARM1 0x459 #define W83627EHF_REG_ALARM2 0x45A #define W83627EHF_REG_ALARM3 0x45B @@ -214,6 +226,28 @@ static const u16 W83627EHF_REG_FAN_MAX_OUTPUT_W83667_B[] = { 0x67, 0x69, 0x6b }; static const u16 W83627EHF_REG_FAN_STEP_OUTPUT_W83667_B[] = { 0x68, 0x6a, 0x6c }; +static const u16 NCT6775_REG_TARGET[] = { 0x101, 0x201, 0x301 }; +static const u16 NCT6775_REG_FAN_MODE[] = { 0x102, 0x202, 0x302 }; +static const u16 NCT6775_REG_FAN_STOP_OUTPUT[] = { 0x105, 0x205, 0x305 }; +static const u16 NCT6775_REG_FAN_START_OUTPUT[] = { 0x106, 0x206, 0x306 }; +static const u16 NCT6775_REG_FAN_STOP_TIME[] = { 0x107, 0x207, 0x307 }; +static const u16 NCT6775_REG_PWM[] = { 0x109, 0x209, 0x309 }; +static const u16 NCT6775_REG_FAN_MAX_OUTPUT[] = { 0x10a, 0x20a, 0x30a }; +static const u16 NCT6775_REG_FAN_STEP_OUTPUT[] = { 0x10b, 0x20b, 0x30b }; +static const u16 NCT6776_REG_FAN[] = { 0x630, 0x632, 0x634, 0x636, 0x638 }; +static const u16 NCT6776_REG_FAN_MIN[] = { 0x63a, 0x63c, 0x63e, 0x640, 0x642}; + +static const u16 NCT6775_REG_TEMP[] + = { 0x27, 0x150, 0x250, 0x73, 0x75, 0x77, 0x62b, 0x62c, 0x62d }; +static const u16 NCT6775_REG_TEMP_CONFIG[] + = { 0, 0x152, 0x252, 0, 0, 0, 0x628, 0x629, 0x62A }; +static const u16 NCT6775_REG_TEMP_HYST[] + = { 0x3a, 0x153, 0x253, 0, 0, 0, 0x673, 0x678, 0x67D }; +static const u16 NCT6775_REG_TEMP_OVER[] + = { 0x39, 0x155, 0x255, 0, 0, 0, 0x672, 0x677, 0x67C }; +static const u16 NCT6775_REG_TEMP_SOURCE[] + = { 0x621, 0x622, 0x623, 0x100, 0x200, 0x300, 0x624, 0x625, 0x626 }; + static const char *const w83667hg_b_temp_label[] = { "SYSTIN", "CPUTIN", @@ -225,15 +259,71 @@ static const char *const w83667hg_b_temp_label[] = { "PECI Agent 4" }; -#define NUM_REG_TEMP 4 +static const char *const nct6775_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN", + "AMD SB-TSI", + "PECI Agent 0", + "PECI Agent 1", + "PECI Agent 2", + "PECI Agent 3", + "PECI Agent 4", + "PECI Agent 5", + "PECI Agent 6", + "PECI Agent 7", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "PCH_DIM0_TEMP", + "PCH_DIM1_TEMP", + "PCH_DIM2_TEMP", + "PCH_DIM3_TEMP" +}; + +static const char *const nct6776_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "SMBUSMASTER 2", + "SMBUSMASTER 3", + "SMBUSMASTER 4", + "SMBUSMASTER 5", + "SMBUSMASTER 6", + "SMBUSMASTER 7", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "PCH_DIM0_TEMP", + "PCH_DIM1_TEMP", + "PCH_DIM2_TEMP", + "PCH_DIM3_TEMP", + "BYTE_TEMP" +}; + +#define NUM_REG_TEMP ARRAY_SIZE(NCT6775_REG_TEMP) static inline int is_word_sized(u16 reg) { - return (((reg & 0xff00) == 0x100 + return ((((reg & 0xff00) == 0x100 || (reg & 0xff00) == 0x200) && ((reg & 0x00ff) == 0x50 || (reg & 0x00ff) == 0x53 - || (reg & 0x00ff) == 0x55)); + || (reg & 0x00ff) == 0x55)) + || (reg & 0xfff0) == 0x630 + || reg == 0x640 || reg == 0x642 + || ((reg & 0xfff0) == 0x650 + && (reg & 0x000f) >= 0x06) + || reg == 0x73 || reg == 0x75 || reg == 0x77 + ); } /* @@ -253,11 +343,20 @@ static inline u8 step_time_to_reg(unsigned int msec, u8 mode) } static inline unsigned int -fan_from_reg(u8 reg, unsigned int div) +fan_from_reg(int reg, u16 val, unsigned int div) { - if (reg == 0 || reg == 255) + if (val == 0) return 0; - return 1350000U / (reg * div); + if (is_word_sized(reg)) { + if ((val & 0xff1f) == 0xff1f) + return 0; + val = (val & 0x1f) | ((val & 0xff00) >> 3); + } else { + if (val == 255 || div == 0) + return 0; + val *= div; + } + return 1350000U / val; } static inline unsigned int @@ -274,7 +373,7 @@ temp_from_reg(u16 reg, s16 regval) return regval * 1000; } -static inline s16 +static inline u16 temp_to_reg(u16 reg, long temp) { if (is_word_sized(reg)) @@ -308,6 +407,10 @@ struct w83627ehf_data { struct device *hwmon_dev; struct mutex lock; + u16 reg_temp[NUM_REG_TEMP]; + u16 reg_temp_over[NUM_REG_TEMP]; + u16 reg_temp_hyst[NUM_REG_TEMP]; + u16 reg_temp_config[NUM_REG_TEMP]; u8 temp_src[NUM_REG_TEMP]; const char * const *temp_label; @@ -331,14 +434,15 @@ struct w83627ehf_data { u8 in[10]; /* Register value */ u8 in_max[10]; /* Register value */ u8 in_min[10]; /* Register value */ - u8 fan[5]; - u8 fan_min[5]; + u16 fan[5]; + u16 fan_min[5]; u8 fan_div[5]; u8 has_fan; /* some fan inputs can be disabled */ + u8 has_fan_min; /* some fans don't have min register */ u8 temp_type[3]; - s16 temp[4]; - s16 temp_max[4]; - s16 temp_max_hyst[4]; + s16 temp[9]; + s16 temp_max[9]; + s16 temp_max_hyst[9]; u32 alarms; u8 pwm_mode[4]; /* 0->DC variable voltage, 1->PWM variable duty cycle */ @@ -364,7 +468,7 @@ struct w83627ehf_data { u8 vid; u8 vrm; - u8 have_temp; + u16 have_temp; u8 in6_skip; }; @@ -428,6 +532,34 @@ static int w83627ehf_write_value(struct w83627ehf_data *data, u16 reg, return 0; } +/* This function assumes that the caller holds data->update_lock */ +static void nct6775_write_fan_div(struct w83627ehf_data *data, int nr) +{ + u8 reg; + + switch (nr) { + case 0: + reg = (w83627ehf_read_value(data, NCT6775_REG_FANDIV1) & 0x70) + | (data->fan_div[0] & 0x7); + w83627ehf_write_value(data, NCT6775_REG_FANDIV1, reg); + break; + case 1: + reg = (w83627ehf_read_value(data, NCT6775_REG_FANDIV1) & 0x7) + | ((data->fan_div[1] << 4) & 0x70); + w83627ehf_write_value(data, NCT6775_REG_FANDIV1, reg); + case 2: + reg = (w83627ehf_read_value(data, NCT6775_REG_FANDIV2) & 0x70) + | (data->fan_div[2] & 0x7); + w83627ehf_write_value(data, NCT6775_REG_FANDIV2, reg); + break; + case 3: + reg = (w83627ehf_read_value(data, NCT6775_REG_FANDIV2) & 0x7) + | ((data->fan_div[3] << 4) & 0x70); + w83627ehf_write_value(data, NCT6775_REG_FANDIV2, reg); + break; + } +} + /* This function assumes that the caller holds data->update_lock */ static void w83627ehf_write_fan_div(struct w83627ehf_data *data, int nr) { @@ -479,6 +611,32 @@ static void w83627ehf_write_fan_div(struct w83627ehf_data *data, int nr) } } +static void w83627ehf_write_fan_div_common(struct device *dev, + struct w83627ehf_data *data, int nr) +{ + struct w83627ehf_sio_data *sio_data = dev->platform_data; + + if (sio_data->kind == nct6776) + ; /* no dividers, do nothing */ + else if (sio_data->kind == nct6775) + nct6775_write_fan_div(data, nr); + else + w83627ehf_write_fan_div(data, nr); +} + +static void nct6775_update_fan_div(struct w83627ehf_data *data) +{ + u8 i; + + i = w83627ehf_read_value(data, NCT6775_REG_FANDIV1); + data->fan_div[0] = i & 0x7; + data->fan_div[1] = (i & 0x70) >> 4; + i = w83627ehf_read_value(data, NCT6775_REG_FANDIV2); + data->fan_div[2] = i & 0x7; + if (data->has_fan & (1<<3)) + data->fan_div[3] = (i & 0x70) >> 4; +} + static void w83627ehf_update_fan_div(struct w83627ehf_data *data) { int i; @@ -504,10 +662,79 @@ static void w83627ehf_update_fan_div(struct w83627ehf_data *data) } } +static void w83627ehf_update_fan_div_common(struct device *dev, + struct w83627ehf_data *data) +{ + struct w83627ehf_sio_data *sio_data = dev->platform_data; + + if (sio_data->kind == nct6776) + ; /* no dividers, do nothing */ + else if (sio_data->kind == nct6775) + nct6775_update_fan_div(data); + else + w83627ehf_update_fan_div(data); +} + +static void nct6775_update_pwm(struct w83627ehf_data *data) +{ + int i; + int pwmcfg, fanmodecfg; + + for (i = 0; i < data->pwm_num; i++) { + pwmcfg = w83627ehf_read_value(data, + W83627EHF_REG_PWM_ENABLE[i]); + fanmodecfg = w83627ehf_read_value(data, + NCT6775_REG_FAN_MODE[i]); + data->pwm_mode[i] = + ((pwmcfg >> W83627EHF_PWM_MODE_SHIFT[i]) & 1) ? 0 : 1; + data->pwm_enable[i] = ((fanmodecfg >> 4) & 7) + 1; + data->tolerance[i] = fanmodecfg & 0x0f; + data->pwm[i] = w83627ehf_read_value(data, data->REG_PWM[i]); + } +} + +static void w83627ehf_update_pwm(struct w83627ehf_data *data) +{ + int i; + int pwmcfg = 0, tolerance = 0; /* shut up the compiler */ + + for (i = 0; i < data->pwm_num; i++) { + if (!(data->has_fan & (1 << i))) + continue; + + /* pwmcfg, tolerance mapped for i=0, i=1 to same reg */ + if (i != 1) { + pwmcfg = w83627ehf_read_value(data, + W83627EHF_REG_PWM_ENABLE[i]); + tolerance = w83627ehf_read_value(data, + W83627EHF_REG_TOLERANCE[i]); + } + data->pwm_mode[i] = + ((pwmcfg >> W83627EHF_PWM_MODE_SHIFT[i]) & 1) ? 0 : 1; + data->pwm_enable[i] = ((pwmcfg >> W83627EHF_PWM_ENABLE_SHIFT[i]) + & 3) + 1; + data->pwm[i] = w83627ehf_read_value(data, data->REG_PWM[i]); + + data->tolerance[i] = (tolerance >> (i == 1 ? 4 : 0)) & 0x0f; + } +} + +static void w83627ehf_update_pwm_common(struct device *dev, + struct w83627ehf_data *data) +{ + struct w83627ehf_sio_data *sio_data = dev->platform_data; + + if (sio_data->kind == nct6775 || sio_data->kind == nct6776) + nct6775_update_pwm(data); + else + w83627ehf_update_pwm(data); +} + static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) { struct w83627ehf_data *data = dev_get_drvdata(dev); - int pwmcfg = 0, tolerance = 0; /* shut up the compiler */ + struct w83627ehf_sio_data *sio_data = dev->platform_data; + int i; mutex_lock(&data->update_lock); @@ -515,7 +742,7 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) if (time_after(jiffies, data->last_updated + HZ + HZ/2) || !data->valid) { /* Fan clock dividers */ - w83627ehf_update_fan_div(data); + w83627ehf_update_fan_div_common(dev, data); /* Measured voltages and limits */ for (i = 0; i < data->in_num; i++) { @@ -533,23 +760,29 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) continue; data->fan[i] = w83627ehf_read_value(data, - data->REG_FAN[i]); - data->fan_min[i] = w83627ehf_read_value(data, + data->REG_FAN[i]); + + if (data->has_fan_min & (1 << i)) + data->fan_min[i] = w83627ehf_read_value(data, data->REG_FAN_MIN[i]); /* If we failed to measure the fan speed and clock divider can be increased, let's try that for next time */ - if (data->fan[i] == 0xff - && data->fan_div[i] < 0x07) { + if (!is_word_sized(data->REG_FAN[i]) + && (data->fan[i] == 0xff + || (sio_data->kind == nct6775 + && data->fan[i] == 0x00)) + && data->fan_div[i] < 0x07) { dev_dbg(dev, "Increasing fan%d " "clock divider from %u to %u\n", i + 1, div_from_reg(data->fan_div[i]), div_from_reg(data->fan_div[i] + 1)); data->fan_div[i]++; - w83627ehf_write_fan_div(data, i); + w83627ehf_write_fan_div_common(dev, data, i); /* Preserve min limit if possible */ - if (data->fan_min[i] >= 2 + if ((data->has_fan_min & (1 << i)) + && data->fan_min[i] >= 2 && data->fan_min[i] != 255) w83627ehf_write_value(data, data->REG_FAN_MIN[i], @@ -557,64 +790,54 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) } } + w83627ehf_update_pwm_common(dev, data); + for (i = 0; i < data->pwm_num; i++) { if (!(data->has_fan & (1 << i))) continue; - /* pwmcfg, tolerance mapped for i=0, i=1 to same reg */ - if (i != 1) { - pwmcfg = w83627ehf_read_value(data, - W83627EHF_REG_PWM_ENABLE[i]); - tolerance = w83627ehf_read_value(data, - W83627EHF_REG_TOLERANCE[i]); - } - data->pwm_mode[i] = - ((pwmcfg >> W83627EHF_PWM_MODE_SHIFT[i]) & 1) - ? 0 : 1; - data->pwm_enable[i] = - ((pwmcfg >> W83627EHF_PWM_ENABLE_SHIFT[i]) - & 3) + 1; - data->pwm[i] = w83627ehf_read_value(data, - data->REG_PWM[i]); - data->fan_start_output[i] = w83627ehf_read_value(data, - data->REG_FAN_START_OUTPUT[i]); - data->fan_stop_output[i] = w83627ehf_read_value(data, - data->REG_FAN_STOP_OUTPUT[i]); - data->fan_stop_time[i] = w83627ehf_read_value(data, - data->REG_FAN_STOP_TIME[i]); - - if (data->REG_FAN_MAX_OUTPUT[i] != 0xff) + data->fan_start_output[i] = + w83627ehf_read_value(data, + data->REG_FAN_START_OUTPUT[i]); + data->fan_stop_output[i] = + w83627ehf_read_value(data, + data->REG_FAN_STOP_OUTPUT[i]); + data->fan_stop_time[i] = + w83627ehf_read_value(data, + data->REG_FAN_STOP_TIME[i]); + + if (data->REG_FAN_MAX_OUTPUT && + data->REG_FAN_MAX_OUTPUT[i] != 0xff) data->fan_max_output[i] = w83627ehf_read_value(data, - data->REG_FAN_MAX_OUTPUT[i]); + data->REG_FAN_MAX_OUTPUT[i]); - if (data->REG_FAN_STEP_OUTPUT[i] != 0xff) + if (data->REG_FAN_STEP_OUTPUT && + data->REG_FAN_STEP_OUTPUT[i] != 0xff) data->fan_step_output[i] = w83627ehf_read_value(data, - data->REG_FAN_STEP_OUTPUT[i]); + data->REG_FAN_STEP_OUTPUT[i]); data->target_temp[i] = w83627ehf_read_value(data, data->REG_TARGET[i]) & (data->pwm_mode[i] == 1 ? 0x7f : 0xff); - data->tolerance[i] = (tolerance >> (i == 1 ? 4 : 0)) - & 0x0f; } /* Measured temperatures and limits */ for (i = 0; i < NUM_REG_TEMP; i++) { if (!(data->have_temp & (1 << i))) continue; - data->temp[i] - = w83627ehf_read_value(data, W83627EHF_REG_TEMP[i]); - if (i > 2) - break; - data->temp_max[i] - = w83627ehf_read_value(data, - W83627EHF_REG_TEMP_OVER[i]); - data->temp_max_hyst[i] - = w83627ehf_read_value(data, - W83627EHF_REG_TEMP_HYST[i]); + data->temp[i] = w83627ehf_read_value(data, + data->reg_temp[i]); + if (data->reg_temp_over[i]) + data->temp_max[i] + = w83627ehf_read_value(data, + data->reg_temp_over[i]); + if (data->reg_temp_hyst[i]) + data->temp_max_hyst[i] + = w83627ehf_read_value(data, + data->reg_temp_hyst[i]); } data->alarms = w83627ehf_read_value(data, @@ -736,21 +959,29 @@ static struct sensor_device_attribute sda_in_max[] = { SENSOR_ATTR(in9_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 9), }; -#define show_fan_reg(reg) \ -static ssize_t \ -show_##reg(struct device *dev, struct device_attribute *attr, \ - char *buf) \ -{ \ - struct w83627ehf_data *data = w83627ehf_update_device(dev); \ - struct sensor_device_attribute *sensor_attr = \ - to_sensor_dev_attr(attr); \ - int nr = sensor_attr->index; \ - return sprintf(buf, "%d\n", \ - fan_from_reg(data->reg[nr], \ - div_from_reg(data->fan_div[nr]))); \ +static ssize_t +show_fan(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627ehf_data *data = w83627ehf_update_device(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + return sprintf(buf, "%d\n", + fan_from_reg(data->REG_FAN[nr], + data->fan[nr], + div_from_reg(data->fan_div[nr]))); +} + +static ssize_t +show_fan_min(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627ehf_data *data = w83627ehf_update_device(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + return sprintf(buf, "%d\n", + fan_from_reg(data->REG_FAN_MIN[nr], + data->fan_min[nr], + div_from_reg(data->fan_div[nr]))); } -show_fan_reg(fan); -show_fan_reg(fan_min); static ssize_t show_fan_div(struct device *dev, struct device_attribute *attr, @@ -779,6 +1010,18 @@ store_fan_min(struct device *dev, struct device_attribute *attr, return err; mutex_lock(&data->update_lock); + if (is_word_sized(data->REG_FAN_MIN[nr])) { + if (!val) { + val = 0xff1f; + } else { + if (val > 1350000U) + val = 135000U; + val = 1350000U / val; + val = (val & 0x1f) | ((val << 3) & 0xff00); + } + data->fan_min[nr] = val; + goto done; /* Leave fan divider alone */ + } if (!val) { /* No min limit, alarm disabled */ data->fan_min[nr] = 255; @@ -790,14 +1033,16 @@ store_fan_min(struct device *dev, struct device_attribute *attr, data->fan_min[nr] = 254; new_div = 7; /* 128 == (1 << 7) */ dev_warn(dev, "fan%u low limit %lu below minimum %u, set to " - "minimum\n", nr + 1, val, fan_from_reg(254, 128)); + "minimum\n", nr + 1, val, + fan_from_reg(data->REG_FAN_MIN[nr], 254, 128)); } else if (!reg) { /* Speed above this value cannot possibly be represented, even with the lowest divider (1) */ data->fan_min[nr] = 1; new_div = 0; /* 1 == (1 << 0) */ dev_warn(dev, "fan%u low limit %lu above maximum %u, set to " - "maximum\n", nr + 1, val, fan_from_reg(1, 1)); + "maximum\n", nr + 1, val, + fan_from_reg(data->REG_FAN_MIN[nr], 1, 1)); } else { /* Automatically pick the best divider, i.e. the one such that the min limit will correspond to a register value @@ -827,10 +1072,11 @@ store_fan_min(struct device *dev, struct device_attribute *attr, nr + 1, div_from_reg(data->fan_div[nr]), div_from_reg(new_div)); data->fan_div[nr] = new_div; - w83627ehf_write_fan_div(data, nr); + w83627ehf_write_fan_div_common(dev, data, nr); /* Give the chip time to sample a new speed value */ data->last_updated = jiffies; } +done: w83627ehf_write_value(data, data->REG_FAN_MIN[nr], data->fan_min[nr]); mutex_unlock(&data->update_lock); @@ -884,7 +1130,7 @@ show_temp_label(struct device *dev, struct device_attribute *attr, char *buf) return sprintf(buf, "%s\n", data->temp_label[data->temp_src[nr]]); } -#define show_temp_reg(REG, reg) \ +#define show_temp_reg(addr, reg) \ static ssize_t \ show_##reg(struct device *dev, struct device_attribute *attr, \ char *buf) \ @@ -894,13 +1140,13 @@ show_##reg(struct device *dev, struct device_attribute *attr, \ to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ return sprintf(buf, "%d\n", \ - temp_from_reg(W83627EHF_REG_##REG[nr], data->reg[nr])); \ + temp_from_reg(data->addr[nr], data->reg[nr])); \ } -show_temp_reg(TEMP, temp); -show_temp_reg(TEMP_OVER, temp_max); -show_temp_reg(TEMP_HYST, temp_max_hyst); +show_temp_reg(reg_temp, temp); +show_temp_reg(reg_temp_over, temp_max); +show_temp_reg(reg_temp_hyst, temp_max_hyst); -#define store_temp_reg(REG, reg) \ +#define store_temp_reg(addr, reg) \ static ssize_t \ store_##reg(struct device *dev, struct device_attribute *attr, \ const char *buf, size_t count) \ @@ -915,14 +1161,14 @@ store_##reg(struct device *dev, struct device_attribute *attr, \ if (err < 0) \ return err; \ mutex_lock(&data->update_lock); \ - data->reg[nr] = temp_to_reg(W83627EHF_REG_TEMP_##REG[nr], val); \ - w83627ehf_write_value(data, W83627EHF_REG_TEMP_##REG[nr], \ + data->reg[nr] = temp_to_reg(data->addr[nr], val); \ + w83627ehf_write_value(data, data->addr[nr], \ data->reg[nr]); \ mutex_unlock(&data->update_lock); \ return count; \ } -store_temp_reg(OVER, temp_max); -store_temp_reg(HYST, temp_max_hyst); +store_temp_reg(reg_temp_over, temp_max); +store_temp_reg(reg_temp_hyst, temp_max_hyst); static ssize_t show_temp_type(struct device *dev, struct device_attribute *attr, char *buf) @@ -938,6 +1184,11 @@ static struct sensor_device_attribute sda_temp_input[] = { SENSOR_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1), SENSOR_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2), SENSOR_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3), + SENSOR_ATTR(temp5_input, S_IRUGO, show_temp, NULL, 4), + SENSOR_ATTR(temp6_input, S_IRUGO, show_temp, NULL, 5), + SENSOR_ATTR(temp7_input, S_IRUGO, show_temp, NULL, 6), + SENSOR_ATTR(temp8_input, S_IRUGO, show_temp, NULL, 7), + SENSOR_ATTR(temp9_input, S_IRUGO, show_temp, NULL, 8), }; static struct sensor_device_attribute sda_temp_label[] = { @@ -945,6 +1196,11 @@ static struct sensor_device_attribute sda_temp_label[] = { SENSOR_ATTR(temp2_label, S_IRUGO, show_temp_label, NULL, 1), SENSOR_ATTR(temp3_label, S_IRUGO, show_temp_label, NULL, 2), SENSOR_ATTR(temp4_label, S_IRUGO, show_temp_label, NULL, 3), + SENSOR_ATTR(temp5_label, S_IRUGO, show_temp_label, NULL, 4), + SENSOR_ATTR(temp6_label, S_IRUGO, show_temp_label, NULL, 5), + SENSOR_ATTR(temp7_label, S_IRUGO, show_temp_label, NULL, 6), + SENSOR_ATTR(temp8_label, S_IRUGO, show_temp_label, NULL, 7), + SENSOR_ATTR(temp9_label, S_IRUGO, show_temp_label, NULL, 8), }; static struct sensor_device_attribute sda_temp_max[] = { @@ -954,6 +1210,18 @@ static struct sensor_device_attribute sda_temp_max[] = { store_temp_max, 1), SENSOR_ATTR(temp3_max, S_IRUGO | S_IWUSR, show_temp_max, store_temp_max, 2), + SENSOR_ATTR(temp4_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 3), + SENSOR_ATTR(temp5_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 4), + SENSOR_ATTR(temp6_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 5), + SENSOR_ATTR(temp7_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 6), + SENSOR_ATTR(temp8_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 7), + SENSOR_ATTR(temp9_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 8), }; static struct sensor_device_attribute sda_temp_max_hyst[] = { @@ -963,6 +1231,18 @@ static struct sensor_device_attribute sda_temp_max_hyst[] = { store_temp_max_hyst, 1), SENSOR_ATTR(temp3_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, store_temp_max_hyst, 2), + SENSOR_ATTR(temp4_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 3), + SENSOR_ATTR(temp5_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 4), + SENSOR_ATTR(temp6_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 5), + SENSOR_ATTR(temp7_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 6), + SENSOR_ATTR(temp8_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 7), + SENSOR_ATTR(temp9_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 8), }; static struct sensor_device_attribute sda_temp_alarm[] = { @@ -1048,6 +1328,7 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct w83627ehf_data *data = dev_get_drvdata(dev); + struct w83627ehf_sio_data *sio_data = dev->platform_data; struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; unsigned long val; @@ -1060,12 +1341,25 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr, if (!val || (val > 4 && val != data->pwm_enable_orig[nr])) return -EINVAL; + /* SmartFan III mode is not supported on NCT6776F */ + if (sio_data->kind == nct6776 && val == 4) + return -EINVAL; + mutex_lock(&data->update_lock); - reg = w83627ehf_read_value(data, W83627EHF_REG_PWM_ENABLE[nr]); data->pwm_enable[nr] = val; - reg &= ~(0x03 << W83627EHF_PWM_ENABLE_SHIFT[nr]); - reg |= (val - 1) << W83627EHF_PWM_ENABLE_SHIFT[nr]; - w83627ehf_write_value(data, W83627EHF_REG_PWM_ENABLE[nr], reg); + if (sio_data->kind == nct6775 || sio_data->kind == nct6776) { + reg = w83627ehf_read_value(data, + NCT6775_REG_FAN_MODE[nr]); + reg &= 0x0f; + reg |= (val - 1) << 4; + w83627ehf_write_value(data, + NCT6775_REG_FAN_MODE[nr], reg); + } else { + reg = w83627ehf_read_value(data, W83627EHF_REG_PWM_ENABLE[nr]); + reg &= ~(0x03 << W83627EHF_PWM_ENABLE_SHIFT[nr]); + reg |= (val - 1) << W83627EHF_PWM_ENABLE_SHIFT[nr]; + w83627ehf_write_value(data, W83627EHF_REG_PWM_ENABLE[nr], reg); + } mutex_unlock(&data->update_lock); return count; } @@ -1113,6 +1407,7 @@ store_tolerance(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct w83627ehf_data *data = dev_get_drvdata(dev); + struct w83627ehf_sio_data *sio_data = dev->platform_data; struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; u16 reg; @@ -1127,13 +1422,22 @@ store_tolerance(struct device *dev, struct device_attribute *attr, val = SENSORS_LIMIT(DIV_ROUND_CLOSEST(val, 1000), 0, 15); mutex_lock(&data->update_lock); - reg = w83627ehf_read_value(data, W83627EHF_REG_TOLERANCE[nr]); - data->tolerance[nr] = val; - if (nr == 1) - reg = (reg & 0x0f) | (val << 4); - else + if (sio_data->kind == nct6775 || sio_data->kind == nct6776) { + /* Limit tolerance further for NCT6776F */ + if (sio_data->kind == nct6776 && val > 7) + val = 7; + reg = w83627ehf_read_value(data, NCT6775_REG_FAN_MODE[nr]); reg = (reg & 0xf0) | val; - w83627ehf_write_value(data, W83627EHF_REG_TOLERANCE[nr], reg); + w83627ehf_write_value(data, NCT6775_REG_FAN_MODE[nr], reg); + } else { + reg = w83627ehf_read_value(data, W83627EHF_REG_TOLERANCE[nr]); + if (nr == 1) + reg = (reg & 0x0f) | (val << 4); + else + reg = (reg & 0xf0) | val; + w83627ehf_write_value(data, W83627EHF_REG_TOLERANCE[nr], reg); + } + data->tolerance[nr] = val; mutex_unlock(&data->update_lock); return count; } @@ -1350,7 +1654,8 @@ static void w83627ehf_device_remove_files(struct device *dev) for (i = 0; i < ARRAY_SIZE(sda_sf3_max_step_arrays); i++) { struct sensor_device_attribute *attr = &sda_sf3_max_step_arrays[i]; - if (data->REG_FAN_STEP_OUTPUT[attr->index] != 0xff) + if (data->REG_FAN_STEP_OUTPUT && + data->REG_FAN_STEP_OUTPUT[attr->index] != 0xff) device_remove_file(dev, &attr->dev_attr); } for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays_fan4); i++) @@ -1381,10 +1686,10 @@ static void w83627ehf_device_remove_files(struct device *dev) continue; device_remove_file(dev, &sda_temp_input[i].dev_attr); device_remove_file(dev, &sda_temp_label[i].dev_attr); - if (i > 2) - break; device_remove_file(dev, &sda_temp_max[i].dev_attr); device_remove_file(dev, &sda_temp_max_hyst[i].dev_attr); + if (i > 2) + continue; device_remove_file(dev, &sda_temp_alarm[i].dev_attr); device_remove_file(dev, &sda_temp_type[i].dev_attr); } @@ -1409,13 +1714,13 @@ static inline void __devinit w83627ehf_init_device(struct w83627ehf_data *data) for (i = 0; i < NUM_REG_TEMP; i++) { if (!(data->have_temp & (1 << i))) continue; - if (!W83627EHF_REG_TEMP_CONFIG[i]) + if (!data->reg_temp_config[i]) continue; tmp = w83627ehf_read_value(data, - W83627EHF_REG_TEMP_CONFIG[i]); + data->reg_temp_config[i]); if (tmp & 0x01) w83627ehf_write_value(data, - W83627EHF_REG_TEMP_CONFIG[i], + data->reg_temp_config[i], tmp & 0xfe); } @@ -1434,13 +1739,39 @@ static inline void __devinit w83627ehf_init_device(struct w83627ehf_data *data) } } +static void w82627ehf_swap_tempreg(struct w83627ehf_data *data, + int r1, int r2) +{ + u16 tmp; + + tmp = data->temp_src[r1]; + data->temp_src[r1] = data->temp_src[r2]; + data->temp_src[r2] = tmp; + + tmp = data->reg_temp[r1]; + data->reg_temp[r1] = data->reg_temp[r2]; + data->reg_temp[r2] = tmp; + + tmp = data->reg_temp_over[r1]; + data->reg_temp_over[r1] = data->reg_temp_over[r2]; + data->reg_temp_over[r2] = tmp; + + tmp = data->reg_temp_hyst[r1]; + data->reg_temp_hyst[r1] = data->reg_temp_hyst[r2]; + data->reg_temp_hyst[r2] = tmp; + + tmp = data->reg_temp_config[r1]; + data->reg_temp_config[r1] = data->reg_temp_config[r2]; + data->reg_temp_config[r2] = tmp; +} + static int __devinit w83627ehf_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct w83627ehf_sio_data *sio_data = dev->platform_data; struct w83627ehf_data *data; struct resource *res; - u8 fan4pin, fan5pin, en_vrm10; + u8 fan3pin, fan4pin, fan4min, fan5pin, en_vrm10; int i, err = 0; res = platform_get_resource(pdev, IORESOURCE_IO, 0); @@ -1466,9 +1797,11 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) /* 627EHG and 627EHF have 10 voltage inputs; 627DHG and 667HG have 9 */ data->in_num = (sio_data->kind == w83627ehf) ? 10 : 9; - /* 667HG has 3 pwms */ + /* 667HG, NCT6775F, and NCT6776F have 3 pwms */ data->pwm_num = (sio_data->kind == w83667hg - || sio_data->kind == w83667hg_b) ? 3 : 4; + || sio_data->kind == w83667hg_b + || sio_data->kind == nct6775 + || sio_data->kind == nct6776) ? 3 : 4; data->have_temp = 0x07; /* Check temp3 configuration bit for 667HG */ @@ -1479,15 +1812,98 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) if (reg & 0x01) data->have_temp &= ~(1 << 2); else - data->in6_skip = 1; /* Either temp3 or in6 */ + data->in6_skip = 1; /* either temp3 or in6 */ + } + + /* Deal with temperature register setup first. */ + if (sio_data->kind == nct6775 || sio_data->kind == nct6776) { + int mask = 0; + + /* + * Display temperature sensor output only if it monitors + * a source other than one already reported. Always display + * first three temperature registers, though. + */ + for (i = 0; i < NUM_REG_TEMP; i++) { + u8 src; + + data->reg_temp[i] = NCT6775_REG_TEMP[i]; + data->reg_temp_over[i] = NCT6775_REG_TEMP_OVER[i]; + data->reg_temp_hyst[i] = NCT6775_REG_TEMP_HYST[i]; + data->reg_temp_config[i] = NCT6775_REG_TEMP_CONFIG[i]; + + src = w83627ehf_read_value(data, + NCT6775_REG_TEMP_SOURCE[i]); + src &= 0x1f; + if (src && !(mask & (1 << src))) { + data->have_temp |= 1 << i; + mask |= 1 << src; + } + + data->temp_src[i] = src; + + /* + * Now do some register swapping if index 0..2 don't + * point to SYSTIN(1), CPUIN(2), and AUXIN(3). + * Idea is to have the first three attributes + * report SYSTIN, CPUIN, and AUXIN if possible + * without overriding the basic system configuration. + */ + if (i > 0 && data->temp_src[0] != 1 + && data->temp_src[i] == 1) + w82627ehf_swap_tempreg(data, 0, i); + if (i > 1 && data->temp_src[1] != 2 + && data->temp_src[i] == 2) + w82627ehf_swap_tempreg(data, 1, i); + if (i > 2 && data->temp_src[2] != 3 + && data->temp_src[i] == 3) + w82627ehf_swap_tempreg(data, 2, i); + } + if (sio_data->kind == nct6776) { + /* + * On NCT6776, AUXTIN and VIN3 pins are shared. + * Only way to detect it is to check if AUXTIN is used + * as a temperature source, and if that source is + * enabled. + * + * If that is the case, disable in6, which reports VIN3. + * Otherwise disable temp3. + */ + if (data->temp_src[2] == 3) { + u8 reg; + + if (data->reg_temp_config[2]) + reg = w83627ehf_read_value(data, + data->reg_temp_config[2]); + else + reg = 0; /* Assume AUXTIN is used */ + + if (reg & 0x01) + data->have_temp &= ~(1 << 2); + else + data->in6_skip = 1; + } + } + + data->temp_label = nct6776_temp_label; } else if (sio_data->kind == w83667hg_b) { u8 reg; + /* + * Temperature sources are selected with bank 0, registers 0x49 + * and 0x4a. + */ + for (i = 0; i < ARRAY_SIZE(W83627EHF_REG_TEMP); i++) { + data->reg_temp[i] = W83627EHF_REG_TEMP[i]; + data->reg_temp_over[i] = W83627EHF_REG_TEMP_OVER[i]; + data->reg_temp_hyst[i] = W83627EHF_REG_TEMP_HYST[i]; + data->reg_temp_config[i] = W83627EHF_REG_TEMP_CONFIG[i]; + } reg = w83627ehf_read_value(data, 0x4a); data->temp_src[0] = reg >> 5; reg = w83627ehf_read_value(data, 0x49); data->temp_src[1] = reg & 0x07; - data->temp_src[2] = (reg >> 4) & 0x07; + data->temp_src[2] = (reg >> 4) & 0x07; /* * W83667HG-B has another temperature register at 0x7e. @@ -1516,22 +1932,54 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) data->in6_skip = 1; data->temp_label = w83667hg_b_temp_label; + } else { + /* Temperature sources are fixed */ + for (i = 0; i < 3; i++) { + data->reg_temp[i] = W83627EHF_REG_TEMP[i]; + data->reg_temp_over[i] = W83627EHF_REG_TEMP_OVER[i]; + data->reg_temp_hyst[i] = W83627EHF_REG_TEMP_HYST[i]; + data->reg_temp_config[i] = W83627EHF_REG_TEMP_CONFIG[i]; + } } - data->REG_PWM = W83627EHF_REG_PWM; - data->REG_TARGET = W83627EHF_REG_TARGET; - data->REG_FAN = W83627EHF_REG_FAN; - data->REG_FAN_MIN = W83627EHF_REG_FAN_MIN; - data->REG_FAN_START_OUTPUT = W83627EHF_REG_FAN_START_OUTPUT; - data->REG_FAN_STOP_OUTPUT = W83627EHF_REG_FAN_STOP_OUTPUT; - data->REG_FAN_STOP_TIME = W83627EHF_REG_FAN_STOP_TIME; - data->REG_FAN_START_OUTPUT = W83627EHF_REG_FAN_START_OUTPUT; - if (sio_data->kind == w83667hg_b) { + if (sio_data->kind == nct6775) { + data->REG_PWM = NCT6775_REG_PWM; + data->REG_TARGET = NCT6775_REG_TARGET; + data->REG_FAN = W83627EHF_REG_FAN; + data->REG_FAN_MIN = W83627EHF_REG_FAN_MIN; + data->REG_FAN_START_OUTPUT = NCT6775_REG_FAN_START_OUTPUT; + data->REG_FAN_STOP_OUTPUT = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_FAN_STOP_TIME = NCT6775_REG_FAN_STOP_TIME; + data->REG_FAN_MAX_OUTPUT = NCT6775_REG_FAN_MAX_OUTPUT; + data->REG_FAN_STEP_OUTPUT = NCT6775_REG_FAN_STEP_OUTPUT; + } else if (sio_data->kind == nct6776) { + data->REG_PWM = NCT6775_REG_PWM; + data->REG_TARGET = NCT6775_REG_TARGET; + data->REG_FAN = NCT6776_REG_FAN; + data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; + data->REG_FAN_START_OUTPUT = NCT6775_REG_FAN_START_OUTPUT; + data->REG_FAN_STOP_OUTPUT = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_FAN_STOP_TIME = NCT6775_REG_FAN_STOP_TIME; + } else if (sio_data->kind == w83667hg_b) { + data->REG_PWM = W83627EHF_REG_PWM; + data->REG_TARGET = W83627EHF_REG_TARGET; + data->REG_FAN = W83627EHF_REG_FAN; + data->REG_FAN_MIN = W83627EHF_REG_FAN_MIN; + data->REG_FAN_START_OUTPUT = W83627EHF_REG_FAN_START_OUTPUT; + data->REG_FAN_STOP_OUTPUT = W83627EHF_REG_FAN_STOP_OUTPUT; + data->REG_FAN_STOP_TIME = W83627EHF_REG_FAN_STOP_TIME; data->REG_FAN_MAX_OUTPUT = W83627EHF_REG_FAN_MAX_OUTPUT_W83667_B; data->REG_FAN_STEP_OUTPUT = W83627EHF_REG_FAN_STEP_OUTPUT_W83667_B; } else { + data->REG_PWM = W83627EHF_REG_PWM; + data->REG_TARGET = W83627EHF_REG_TARGET; + data->REG_FAN = W83627EHF_REG_FAN; + data->REG_FAN_MIN = W83627EHF_REG_FAN_MIN; + data->REG_FAN_START_OUTPUT = W83627EHF_REG_FAN_START_OUTPUT; + data->REG_FAN_STOP_OUTPUT = W83627EHF_REG_FAN_STOP_OUTPUT; + data->REG_FAN_STOP_TIME = W83627EHF_REG_FAN_STOP_TIME; data->REG_FAN_MAX_OUTPUT = W83627EHF_REG_FAN_MAX_OUTPUT_COMMON; data->REG_FAN_STEP_OUTPUT = @@ -1544,7 +1992,8 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) data->vrm = vid_which_vrm(); superio_enter(sio_data->sioreg); /* Read VID value */ - if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) { + if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b || + sio_data->kind == nct6775 || sio_data->kind == nct6776) { /* W83667HG has different pins for VID input and output, so we can get the VID input values directly at logical device D 0xe3. */ @@ -1595,12 +2044,27 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) } /* fan4 and fan5 share some pins with the GPIO and serial flash */ - if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) { - fan5pin = superio_inb(sio_data->sioreg, 0x27) & 0x20; + if (sio_data->kind == nct6775) { + /* On NCT6775, fan4 shares pins with the fdc interface */ + fan3pin = 1; + fan4pin = !(superio_inb(sio_data->sioreg, 0x2A) & 0x80); + fan4min = 0; + fan5pin = 0; + } else if (sio_data->kind == nct6776) { + fan3pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x40); + fan4pin = !!(superio_inb(sio_data->sioreg, 0x1C) & 0x01); + fan5pin = !!(superio_inb(sio_data->sioreg, 0x1C) & 0x02); + fan4min = fan4pin; + } else if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) { + fan3pin = 1; fan4pin = superio_inb(sio_data->sioreg, 0x27) & 0x40; + fan5pin = superio_inb(sio_data->sioreg, 0x27) & 0x20; + fan4min = fan4pin; } else { - fan5pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x02); + fan3pin = 1; fan4pin = !(superio_inb(sio_data->sioreg, 0x29) & 0x06); + fan5pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x02); + fan4min = fan4pin; } superio_exit(sio_data->sioreg); @@ -1610,15 +2074,36 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) connected fan5 as input unless they are emitting log 1, which is not the default. */ - data->has_fan = 0x07; /* fan1, fan2 and fan3 */ - i = w83627ehf_read_value(data, W83627EHF_REG_FANDIV1); - if ((i & (1 << 2)) && fan4pin) - data->has_fan |= (1 << 3); - if (!(i & (1 << 1)) && fan5pin) - data->has_fan |= (1 << 4); + data->has_fan = data->has_fan_min = 0x03; /* fan1 and fan2 */ + + data->has_fan |= (fan3pin << 2); + data->has_fan_min |= (fan3pin << 2); + + /* + * NCT6775F and NCT6776F don't have the W83627EHF_REG_FANDIV1 register + */ + if (sio_data->kind == nct6775 || sio_data->kind == nct6776) { + data->has_fan |= (fan4pin << 3) | (fan5pin << 4); + data->has_fan_min |= (fan4min << 3) | (fan5pin << 4); + } else { + i = w83627ehf_read_value(data, W83627EHF_REG_FANDIV1); + if ((i & (1 << 2)) && fan4pin) { + data->has_fan |= (1 << 3); + data->has_fan_min |= (1 << 3); + } + if (!(i & (1 << 1)) && fan5pin) { + data->has_fan |= (1 << 4); + data->has_fan_min |= (1 << 4); + } + } /* Read fan clock dividers immediately */ - w83627ehf_update_fan_div(data); + w83627ehf_update_fan_div_common(dev, data); + + /* Read pwm data to save original values */ + w83627ehf_update_pwm_common(dev, data); + for (i = 0; i < data->pwm_num; i++) + data->pwm_enable_orig[i] = data->pwm_enable[i]; /* Read pwm data to save original values */ w83627ehf_update_pwm_common(dev, data); @@ -1635,7 +2120,8 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) for (i = 0; i < ARRAY_SIZE(sda_sf3_max_step_arrays); i++) { struct sensor_device_attribute *attr = &sda_sf3_max_step_arrays[i]; - if (data->REG_FAN_STEP_OUTPUT[attr->index] != 0xff) { + if (data->REG_FAN_STEP_OUTPUT && + data->REG_FAN_STEP_OUTPUT[attr->index] != 0xff) { err = device_create_file(dev, &attr->dev_attr); if (err) goto exit_remove; @@ -1668,12 +2154,20 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) if ((err = device_create_file(dev, &sda_fan_input[i].dev_attr)) || (err = device_create_file(dev, - &sda_fan_alarm[i].dev_attr)) - || (err = device_create_file(dev, - &sda_fan_div[i].dev_attr)) - || (err = device_create_file(dev, - &sda_fan_min[i].dev_attr))) + &sda_fan_alarm[i].dev_attr))) goto exit_remove; + if (sio_data->kind != nct6776) { + err = device_create_file(dev, + &sda_fan_div[i].dev_attr); + if (err) + goto exit_remove; + } + if (data->has_fan_min & (1 << i)) { + err = device_create_file(dev, + &sda_fan_min[i].dev_attr); + if (err) + goto exit_remove; + } if (i < data->pwm_num && ((err = device_create_file(dev, &sda_pwm[i].dev_attr)) @@ -1701,12 +2195,21 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) if (err) goto exit_remove; } + if (data->reg_temp_over[i]) { + err = device_create_file(dev, + &sda_temp_max[i].dev_attr); + if (err) + goto exit_remove; + } + if (data->reg_temp_hyst[i]) { + err = device_create_file(dev, + &sda_temp_max_hyst[i].dev_attr); + if (err) + goto exit_remove; + } if (i > 2) - break; - if ((err = device_create_file(dev, &sda_temp_max[i].dev_attr)) - || (err = device_create_file(dev, - &sda_temp_max_hyst[i].dev_attr)) - || (err = device_create_file(dev, + continue; + if ((err = device_create_file(dev, &sda_temp_alarm[i].dev_attr)) || (err = device_create_file(dev, &sda_temp_type[i].dev_attr))) @@ -1767,6 +2270,8 @@ static int __init w83627ehf_find(int sioaddr, unsigned short *addr, static const char __initdata sio_name_W83627DHG_P[] = "W83627DHG-P"; static const char __initdata sio_name_W83667HG[] = "W83667HG"; static const char __initdata sio_name_W83667HG_B[] = "W83667HG-B"; + static const char __initdata sio_name_NCT6775[] = "NCT6775F"; + static const char __initdata sio_name_NCT6776[] = "NCT6776F"; u16 val; const char *sio_name; @@ -1803,6 +2308,14 @@ static int __init w83627ehf_find(int sioaddr, unsigned short *addr, sio_data->kind = w83667hg_b; sio_name = sio_name_W83667HG_B; break; + case SIO_NCT6775_ID: + sio_data->kind = nct6775; + sio_name = sio_name_NCT6775; + break; + case SIO_NCT6776_ID: + sio_data->kind = nct6776; + sio_name = sio_name_NCT6776; + break; default: if (val != 0xffff) pr_debug("unsupported chip ID: 0x%04x\n", val); -- cgit v1.2.3-70-g09d2 From 26bc440e3f14df5daff4d1ff0f37de5e260cea18 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 11 Feb 2011 08:00:58 -0800 Subject: hwmon: (w83627ehf) Use 16 bit fan count registers if supported Some of the chips supported by this driver have 13 bit or 16 bit fan count registers. This patch improves support for those registers, specifically for NCT6775F. With the changes in this patch, fan speed is reported correctly even if the fan divider is set to a low value, which results in a fan speed reading above 0xff. With this patch, the width of fan count registers is no longer used to determine if the chip has fan divider register(s) or not. A dedicated flag is used instead to determine if this is the case. Signed-off-by: Guenter Roeck Acked-by: Ian Dobson --- drivers/hwmon/w83627ehf.c | 86 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 27 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index b3b4f2b41dc..df6e50260e0 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -234,7 +234,7 @@ static const u16 NCT6775_REG_FAN_STOP_TIME[] = { 0x107, 0x207, 0x307 }; static const u16 NCT6775_REG_PWM[] = { 0x109, 0x209, 0x309 }; static const u16 NCT6775_REG_FAN_MAX_OUTPUT[] = { 0x10a, 0x20a, 0x30a }; static const u16 NCT6775_REG_FAN_STEP_OUTPUT[] = { 0x10b, 0x20b, 0x30b }; -static const u16 NCT6776_REG_FAN[] = { 0x630, 0x632, 0x634, 0x636, 0x638 }; +static const u16 NCT6775_REG_FAN[] = { 0x630, 0x632, 0x634, 0x636, 0x638 }; static const u16 NCT6776_REG_FAN_MIN[] = { 0x63a, 0x63c, 0x63e, 0x640, 0x642}; static const u16 NCT6775_REG_TEMP[] @@ -342,21 +342,36 @@ static inline u8 step_time_to_reg(unsigned int msec, u8 mode) (msec + 200) / 400), 1, 255); } -static inline unsigned int -fan_from_reg(int reg, u16 val, unsigned int div) +static unsigned int fan_from_reg8(u16 reg, unsigned int divreg) { - if (val == 0) + if (reg == 0 || reg == 255) return 0; - if (is_word_sized(reg)) { - if ((val & 0xff1f) == 0xff1f) - return 0; - val = (val & 0x1f) | ((val & 0xff00) >> 3); - } else { - if (val == 255 || div == 0) - return 0; - val *= div; - } - return 1350000U / val; + return 1350000U / (reg << divreg); +} + +static unsigned int fan_from_reg13(u16 reg, unsigned int divreg) +{ + if ((reg & 0xff1f) == 0xff1f) + return 0; + + reg = (reg & 0x1f) | ((reg & 0xff00) >> 3); + + if (reg == 0) + return 0; + + return 1350000U / reg; +} + +static unsigned int fan_from_reg16(u16 reg, unsigned int divreg) +{ + if (reg == 0 || reg == 0xffff) + return 0; + + /* + * Even though the registers are 16 bit wide, the fan divisor + * still applies. + */ + return 1350000U / (reg << divreg); } static inline unsigned int @@ -424,6 +439,9 @@ struct w83627ehf_data { const u16 *REG_FAN_MAX_OUTPUT; const u16 *REG_FAN_STEP_OUTPUT; + unsigned int (*fan_from_reg)(u16 reg, unsigned int divreg); + unsigned int (*fan_from_reg_min)(u16 reg, unsigned int divreg); + struct mutex update_lock; char valid; /* !=0 if following fields are valid */ unsigned long last_updated; /* In jiffies */ @@ -439,6 +457,7 @@ struct w83627ehf_data { u8 fan_div[5]; u8 has_fan; /* some fan inputs can be disabled */ u8 has_fan_min; /* some fans don't have min register */ + bool has_fan_div; u8 temp_type[3]; s16 temp[9]; s16 temp_max[9]; @@ -769,8 +788,8 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) /* If we failed to measure the fan speed and clock divider can be increased, let's try that for next time */ - if (!is_word_sized(data->REG_FAN[i]) - && (data->fan[i] == 0xff + if (data->has_fan_div + && (data->fan[i] >= 0xff || (sio_data->kind == nct6775 && data->fan[i] == 0x00)) && data->fan_div[i] < 0x07) { @@ -966,9 +985,7 @@ show_fan(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; return sprintf(buf, "%d\n", - fan_from_reg(data->REG_FAN[nr], - data->fan[nr], - div_from_reg(data->fan_div[nr]))); + data->fan_from_reg(data->fan[nr], data->fan_div[nr])); } static ssize_t @@ -978,9 +995,8 @@ show_fan_min(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; return sprintf(buf, "%d\n", - fan_from_reg(data->REG_FAN_MIN[nr], - data->fan_min[nr], - div_from_reg(data->fan_div[nr]))); + data->fan_from_reg_min(data->fan_min[nr], + data->fan_div[nr])); } static ssize_t @@ -1010,7 +1026,11 @@ store_fan_min(struct device *dev, struct device_attribute *attr, return err; mutex_lock(&data->update_lock); - if (is_word_sized(data->REG_FAN_MIN[nr])) { + if (!data->has_fan_div) { + /* + * Only NCT6776F for now, so we know that this is a 13 bit + * register + */ if (!val) { val = 0xff1f; } else { @@ -1034,7 +1054,7 @@ store_fan_min(struct device *dev, struct device_attribute *attr, new_div = 7; /* 128 == (1 << 7) */ dev_warn(dev, "fan%u low limit %lu below minimum %u, set to " "minimum\n", nr + 1, val, - fan_from_reg(data->REG_FAN_MIN[nr], 254, 128)); + data->fan_from_reg_min(254, 7)); } else if (!reg) { /* Speed above this value cannot possibly be represented, even with the lowest divider (1) */ @@ -1042,7 +1062,7 @@ store_fan_min(struct device *dev, struct device_attribute *attr, new_div = 0; /* 1 == (1 << 0) */ dev_warn(dev, "fan%u low limit %lu above maximum %u, set to " "maximum\n", nr + 1, val, - fan_from_reg(data->REG_FAN_MIN[nr], 1, 1)); + data->fan_from_reg_min(1, 0)); } else { /* Automatically pick the best divider, i.e. the one such that the min limit will correspond to a register value @@ -1943,9 +1963,12 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) } if (sio_data->kind == nct6775) { + data->has_fan_div = true; + data->fan_from_reg = fan_from_reg16; + data->fan_from_reg_min = fan_from_reg8; data->REG_PWM = NCT6775_REG_PWM; data->REG_TARGET = NCT6775_REG_TARGET; - data->REG_FAN = W83627EHF_REG_FAN; + data->REG_FAN = NCT6775_REG_FAN; data->REG_FAN_MIN = W83627EHF_REG_FAN_MIN; data->REG_FAN_START_OUTPUT = NCT6775_REG_FAN_START_OUTPUT; data->REG_FAN_STOP_OUTPUT = NCT6775_REG_FAN_STOP_OUTPUT; @@ -1953,14 +1976,20 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) data->REG_FAN_MAX_OUTPUT = NCT6775_REG_FAN_MAX_OUTPUT; data->REG_FAN_STEP_OUTPUT = NCT6775_REG_FAN_STEP_OUTPUT; } else if (sio_data->kind == nct6776) { + data->has_fan_div = false; + data->fan_from_reg = fan_from_reg13; + data->fan_from_reg_min = fan_from_reg13; data->REG_PWM = NCT6775_REG_PWM; data->REG_TARGET = NCT6775_REG_TARGET; - data->REG_FAN = NCT6776_REG_FAN; + data->REG_FAN = NCT6775_REG_FAN; data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; data->REG_FAN_START_OUTPUT = NCT6775_REG_FAN_START_OUTPUT; data->REG_FAN_STOP_OUTPUT = NCT6775_REG_FAN_STOP_OUTPUT; data->REG_FAN_STOP_TIME = NCT6775_REG_FAN_STOP_TIME; } else if (sio_data->kind == w83667hg_b) { + data->has_fan_div = true; + data->fan_from_reg = fan_from_reg8; + data->fan_from_reg_min = fan_from_reg8; data->REG_PWM = W83627EHF_REG_PWM; data->REG_TARGET = W83627EHF_REG_TARGET; data->REG_FAN = W83627EHF_REG_FAN; @@ -1973,6 +2002,9 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) data->REG_FAN_STEP_OUTPUT = W83627EHF_REG_FAN_STEP_OUTPUT_W83667_B; } else { + data->has_fan_div = true; + data->fan_from_reg = fan_from_reg8; + data->fan_from_reg_min = fan_from_reg8; data->REG_PWM = W83627EHF_REG_PWM; data->REG_TARGET = W83627EHF_REG_TARGET; data->REG_FAN = W83627EHF_REG_FAN; -- cgit v1.2.3-70-g09d2 From 3382a918da968318a4284a14b3dee4a38f8a6749 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 13 Feb 2011 13:08:23 -0800 Subject: hwmon: (w83627ehf) Store rpm instead of raw fan speed data Since the fan speed value can be above 0xff, we can no longer use that value to determine if the fan speed reading is valid. This makes it difficult to manipulate the stored fan speed register value. If we store rpm instead of the fan speed register value, we do not need to correct it if the fan divisor value is changed, and the above mentioned problem no longer exists. Signed-off-by: Guenter Roeck Acked-by: Ian Dobson --- drivers/hwmon/w83627ehf.c | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index df6e50260e0..460292bad74 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -452,7 +452,7 @@ struct w83627ehf_data { u8 in[10]; /* Register value */ u8 in_max[10]; /* Register value */ u8 in_min[10]; /* Register value */ - u16 fan[5]; + unsigned int rpm[5]; u16 fan_min[5]; u8 fan_div[5]; u8 has_fan; /* some fan inputs can be disabled */ @@ -775,11 +775,14 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) /* Measured fan speeds and limits */ for (i = 0; i < 5; i++) { + u16 reg; + if (!(data->has_fan & (1 << i))) continue; - data->fan[i] = w83627ehf_read_value(data, - data->REG_FAN[i]); + reg = w83627ehf_read_value(data, data->REG_FAN[i]); + data->rpm[i] = data->fan_from_reg(reg, + data->fan_div[i]); if (data->has_fan_min & (1 << i)) data->fan_min[i] = w83627ehf_read_value(data, @@ -789,9 +792,8 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) divider can be increased, let's try that for next time */ if (data->has_fan_div - && (data->fan[i] >= 0xff - || (sio_data->kind == nct6775 - && data->fan[i] == 0x00)) + && (reg >= 0xff || (sio_data->kind == nct6775 + && reg == 0x00)) && data->fan_div[i] < 0x07) { dev_dbg(dev, "Increasing fan%d " "clock divider from %u to %u\n", @@ -984,8 +986,7 @@ show_fan(struct device *dev, struct device_attribute *attr, char *buf) struct w83627ehf_data *data = w83627ehf_update_device(dev); struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - return sprintf(buf, "%d\n", - data->fan_from_reg(data->fan[nr], data->fan_div[nr])); + return sprintf(buf, "%d\n", data->rpm[nr]); } static ssize_t @@ -1078,16 +1079,6 @@ store_fan_min(struct device *dev, struct device_attribute *attr, /* Write both the fan clock divider (if it changed) and the new fan min (unconditionally) */ if (new_div != data->fan_div[nr]) { - /* Preserve the fan speed reading */ - if (data->fan[nr] != 0xff) { - if (new_div > data->fan_div[nr]) - data->fan[nr] >>= new_div - data->fan_div[nr]; - else if (data->fan[nr] & 0x80) - data->fan[nr] = 0xff; - else - data->fan[nr] <<= data->fan_div[nr] - new_div; - } - dev_dbg(dev, "fan%u clock divider changed from %u to %u\n", nr + 1, div_from_reg(data->fan_div[nr]), div_from_reg(new_div)); -- cgit v1.2.3-70-g09d2 From 2dd59c26e9336db676956589320f1bc71dc1b2e7 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 22 Feb 2011 08:20:51 -0800 Subject: hwmon: (w83627ehf) Update Kconfig for W83677HG-B, NCT6775F and NCT6776F Signed-off-by: Guenter Roeck Acked-by: Ian Dobson --- drivers/hwmon/Kconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 7bda6226425..1bfb4439e4e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1170,7 +1170,7 @@ config SENSORS_W83627HF will be called w83627hf. config SENSORS_W83627EHF - tristate "Winbond W83627EHF/EHG/DHG, W83667HG" + tristate "Winbond W83627EHF/EHG/DHG, W83667HG, NCT6775F, NCT6776F" select HWMON_VID help If you say yes here you get support for the hardware @@ -1181,7 +1181,8 @@ config SENSORS_W83627EHF chip suited for specific Intel processors that use PECI such as the Core 2 Duo. - This driver also supports the W83667HG chip. + This driver also supports Nuvoton W83667HG, W83667HG-B, NCT6775F + (also known as W83667HG-I), and NCT6776F. This driver can also be built as a module. If so, the module will be called w83627ehf. -- cgit v1.2.3-70-g09d2 From d42e869acf0da4502c452b786dee35f0ecf4cbc8 Mon Sep 17 00:00:00 2001 From: Ian Dobson Date: Mon, 7 Mar 2011 14:21:12 -0800 Subject: hwmon: (w83627ehf) Add fan debounce support for NCT6775F and NCT6776F NCT6776F and NCT6775F support debouncing the fan RPM signal. This can help improve the stability of th RPM signal for some fans (Arctic cooling fans for example). This patch adds a module parameter fan_debounce, which when set to 1 enables debounce for all fans that the chip supports. Signed-off-by: Ian Dobson Signed-off-by: Guenter Roeck --- drivers/hwmon/w83627ehf.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 460292bad74..d1803194496 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -79,6 +79,10 @@ static unsigned short force_id; module_param(force_id, ushort, 0); MODULE_PARM_DESC(force_id, "Override the detected device ID"); +static unsigned short fan_debounce; +module_param(fan_debounce, ushort, 0); +MODULE_PARM_DESC(fan_debounce, "Enable debouncing for fan RPM signal"); + #define DRVNAME "w83627ehf" /* @@ -187,6 +191,7 @@ static const u16 W83627EHF_REG_TEMP_CONFIG[] = { 0, 0x152, 0x252, 0 }; /* NCT6775F has its own fan divider registers */ #define NCT6775_REG_FANDIV1 0x506 #define NCT6775_REG_FANDIV2 0x507 +#define NCT6775_REG_FAN_DEBOUNCE 0xf0 #define W83627EHF_REG_ALARM1 0x459 #define W83627EHF_REG_ALARM2 0x45A @@ -2089,6 +2094,22 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) fan5pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x02); fan4min = fan4pin; } + + if (fan_debounce && + (sio_data->kind == nct6775 || sio_data->kind == nct6776)) { + u8 tmp; + + superio_select(sio_data->sioreg, W83627EHF_LD_HWM); + tmp = superio_inb(sio_data->sioreg, NCT6775_REG_FAN_DEBOUNCE); + if (sio_data->kind == nct6776) + superio_outb(sio_data->sioreg, NCT6775_REG_FAN_DEBOUNCE, + 0x3e | tmp); + else + superio_outb(sio_data->sioreg, NCT6775_REG_FAN_DEBOUNCE, + 0x1e | tmp); + pr_info("Enabled fan debounce for chip %s\n", data->name); + } + superio_exit(sio_data->sioreg); /* It looks like fan4 and fan5 pins can be alternatively used -- cgit v1.2.3-70-g09d2 From 02309ad2b8cbcf45260dd9bb5a527799a612c161 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 10 Mar 2011 15:47:14 -0800 Subject: hwmon: (w83627ehf) Display correct temperature sensor labels for systems with NCT6775F Systems with NCT6775F reported temperature sensor labels for systems with NCT6776F. This patch fixes the problem. Signed-off-by: Guenter Roeck --- drivers/hwmon/w83627ehf.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index d1803194496..f2b377c56a3 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -1899,9 +1899,10 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) else data->in6_skip = 1; } + data->temp_label = nct6776_temp_label; + } else { + data->temp_label = nct6775_temp_label; } - - data->temp_label = nct6776_temp_label; } else if (sio_data->kind == w83667hg_b) { u8 reg; -- cgit v1.2.3-70-g09d2 From 44c4dc521e99c0b2946973090b27c9bd4eb81b63 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Mar 2011 20:57:07 +0100 Subject: hwmon/f71882fg: Fix temp1 sensor type reporting The code for determining a sensort type for temp1 was based on me misreading the datasheet, the tested registers do not determine what kind of sensor is actually reported as temp1. Instead the enable / disable certain additional digital sensor readouts throug PECI / AMDSI, etc. Independent of these settings temp1 is still reading an external BJT or thermistor as configured by the Temperature Sensor Type Register (0x6b). Signed-off-by: Hans de Goede Signed-off-by: Guenter Roeck --- drivers/hwmon/f71882fg.c | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index 6e06019015a..6c3de065e04 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -1,6 +1,6 @@ /*************************************************************************** * Copyright (C) 2006 by Hans Edgington * - * Copyright (C) 2007-2009 Hans de Goede * + * Copyright (C) 2007-2011 Hans de Goede * * * * 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 * @@ -57,8 +57,6 @@ #define ADDR_REG_OFFSET 5 #define DATA_REG_OFFSET 6 -#define F71882FG_REG_PECI 0x0A - #define F71882FG_REG_IN_STATUS 0x12 /* f71882fg only */ #define F71882FG_REG_IN_BEEP 0x13 /* f71882fg only */ #define F71882FG_REG_IN(nr) (0x20 + (nr)) @@ -943,7 +941,7 @@ static u16 f71882fg_read_temp(struct f71882fg_data *data, int nr) static struct f71882fg_data *f71882fg_update_device(struct device *dev) { struct f71882fg_data *data = dev_get_drvdata(dev); - int nr, reg = 0, reg2; + int nr, reg; int nr_fans = (data->type == f71882fg) ? 4 : 3; int nr_ins = (data->type == f71858fg || data->type == f8000) ? 3 : 9; @@ -980,38 +978,11 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev) F71882FG_REG_FAN_BEEP); data->temp_beep = f71882fg_read8(data, F71882FG_REG_TEMP_BEEP); - /* Have to hardcode type, because temp1 is special */ reg = f71882fg_read8(data, F71882FG_REG_TEMP_TYPE); + data->temp_type[1] = (reg & 0x02) ? 2 : 4; data->temp_type[2] = (reg & 0x04) ? 2 : 4; data->temp_type[3] = (reg & 0x08) ? 2 : 4; } - /* Determine temp index 1 sensor type */ - if (data->type == f71889fg) { - reg2 = f71882fg_read8(data, F71882FG_REG_START); - switch ((reg2 & 0x60) >> 5) { - case 0x00: /* BJT / Thermistor */ - data->temp_type[1] = (reg & 0x02) ? 2 : 4; - break; - case 0x01: /* AMDSI */ - data->temp_type[1] = 5; - break; - case 0x02: /* PECI */ - case 0x03: /* Ibex Peak ?? Report as PECI for now */ - data->temp_type[1] = 6; - break; - } - } else { - reg2 = f71882fg_read8(data, F71882FG_REG_PECI); - if ((reg2 & 0x03) == 0x01) - data->temp_type[1] = 6; /* PECI */ - else if ((reg2 & 0x03) == 0x02) - data->temp_type[1] = 5; /* AMDSI */ - else if (data->type == f71862fg || - data->type == f71882fg) - data->temp_type[1] = (reg & 0x02) ? 2 : 4; - else /* f71858fg and f8000 only support BJT */ - data->temp_type[1] = 2; - } data->pwm_enable = f71882fg_read8(data, F71882FG_REG_PWM_ENABLE); -- cgit v1.2.3-70-g09d2 From 98f7ba197f9ef366856b5cc9ef4f46e852f3d1c8 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Mar 2011 20:57:09 +0100 Subject: hwmon/f71882fg: Only allow negative auto point temps if fan_neg_temp is enabled Signed-off-by: Hans de Goede Signed-off-by: Guenter Roeck --- drivers/hwmon/f71882fg.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index 6c3de065e04..67ac8587059 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -84,6 +84,9 @@ #define F71882FG_REG_FAN_HYST(nr) (0x98 + (nr)) +#define F71882FG_REG_FAN_FAULT_T 0x9F +#define F71882FG_FAN_NEG_TEMP_EN 0x20 + #define F71882FG_REG_POINT_PWM(pwm, point) (0xAA + (point) + (16 * (pwm))) #define F71882FG_REG_POINT_TEMP(pwm, point) (0xA6 + (point) + (16 * (pwm))) #define F71882FG_REG_POINT_MAPPING(nr) (0xAF + 16 * (nr)) @@ -127,6 +130,7 @@ struct f71882fg_data { struct mutex update_lock; int temp_start; /* temp numbering start (0 or 1) */ char valid; /* !=0 if following fields are valid */ + char auto_point_temp_signed; unsigned long last_updated; /* In jiffies */ unsigned long last_limits; /* In jiffies */ @@ -1853,7 +1857,7 @@ static ssize_t store_pwm_auto_point_temp(struct device *dev, val /= 1000; - if (data->type == f71889fg) + if (data->auto_point_temp_signed) val = SENSORS_LIMIT(val, -128, 127); else val = SENSORS_LIMIT(val, 0, 127); @@ -1900,7 +1904,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) struct f71882fg_data *data; struct f71882fg_sio_data *sio_data = pdev->dev.platform_data; int err, i, nr_fans = (sio_data->type == f71882fg) ? 4 : 3; - u8 start_reg; + u8 start_reg, reg; data = kzalloc(sizeof(struct f71882fg_data), GFP_KERNEL); if (!data) @@ -1970,6 +1974,16 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) } if (start_reg & 0x02) { + switch (data->type) { + case f71889fg: + reg = f71882fg_read8(data, F71882FG_REG_FAN_FAULT_T); + if (reg & F71882FG_FAN_NEG_TEMP_EN) + data->auto_point_temp_signed = 1; + break; + default: + break; + } + data->pwm_enable = f71882fg_read8(data, F71882FG_REG_PWM_ENABLE); -- cgit v1.2.3-70-g09d2 From 0bae640088b5bd6c7a2a691ad20543d69c2b9b2c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Mar 2011 20:57:10 +0100 Subject: hwmon/f71882fg: Make creation of in sysfs attributes more generic This is a preparation patch for adding support for more different models, some of which have a sparse list of used voltage pins. Signed-off-by: Hans de Goede Signed-off-by: Guenter Roeck --- drivers/hwmon/f71882fg.c | 180 +++++++++++++++++++++++++++-------------------- 1 file changed, 103 insertions(+), 77 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index 67ac8587059..ec29c611ed3 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -93,6 +93,8 @@ #define F71882FG_REG_START 0x01 +#define F71882FG_MAX_INS 9 + #define FAN_MIN_DETECT 366 /* Lowest detectable fanspeed */ static unsigned short force_id; @@ -109,6 +111,22 @@ static const char *f71882fg_names[] = { "f8000", }; +static const char f71882fg_has_in[5][F71882FG_MAX_INS] = { + { 1, 1, 1, 0, 0, 0, 0, 0, 0 }, /* f71858fg */ + { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* f71862fg */ + { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* f71882fg */ + { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* f71889fg */ + { 1, 1, 1, 0, 0, 0, 0, 0, 0 }, /* f8000 */ +}; + +static const char f71882fg_has_in1_alarm[5] = { + 0, /* f71858fg */ + 0, /* f71862fg */ + 1, /* f71882fg */ + 1, /* f71889fg */ + 0, /* f8000 */ +}; + static struct platform_device *f71882fg_pdev; /* Super-I/O Function prototypes */ @@ -135,7 +153,7 @@ struct f71882fg_data { unsigned long last_limits; /* In jiffies */ /* Register Values */ - u8 in[9]; + u8 in[F71882FG_MAX_INS]; u8 in1_max; u8 in_status; u8 in_beep; @@ -264,13 +282,9 @@ static struct platform_driver f71882fg_driver = { static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); -/* Temp and in attr for the f71858fg, the f71858fg is special as it - has its temperature indexes start at 0 (the others start at 1) and - it only has 3 voltage inputs */ -static struct sensor_device_attribute_2 f71858fg_in_temp_attr[] = { - SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0), - SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1), - SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2), +/* Temp attr for the f71858fg, the f71858fg is special as it has its + temperature indexes start at 0 (the others start at 1) */ +static struct sensor_device_attribute_2 f71858fg_temp_attr[] = { SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0), SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_max, store_temp_max, 0, 0), @@ -310,17 +324,8 @@ static struct sensor_device_attribute_2 f71858fg_in_temp_attr[] = { SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 2), }; -/* Temp and in attr common to the f71862fg, f71882fg and f71889fg */ -static struct sensor_device_attribute_2 fxxxx_in_temp_attr[] = { - SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0), - SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1), - SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2), - SENSOR_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 0, 3), - SENSOR_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 0, 4), - SENSOR_ATTR_2(in5_input, S_IRUGO, show_in, NULL, 0, 5), - SENSOR_ATTR_2(in6_input, S_IRUGO, show_in, NULL, 0, 6), - SENSOR_ATTR_2(in7_input, S_IRUGO, show_in, NULL, 0, 7), - SENSOR_ATTR_2(in8_input, S_IRUGO, show_in, NULL, 0, 8), +/* Temp attr for the standard models */ +static struct sensor_device_attribute_2 fxxxx_temp_attr[] = { SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 1), SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_max, store_temp_max, 0, 1), @@ -379,24 +384,12 @@ static struct sensor_device_attribute_2 fxxxx_in_temp_attr[] = { SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 3), }; -/* For models with in1 alarm capability */ -static struct sensor_device_attribute_2 fxxxx_in1_alarm_attr[] = { - SENSOR_ATTR_2(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, - 0, 1), - SENSOR_ATTR_2(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, - 0, 1), - SENSOR_ATTR_2(in1_alarm, S_IRUGO, show_in_alarm, NULL, 0, 1), -}; - -/* Temp and in attr for the f8000 +/* Temp attr for the f8000 Note on the f8000 temp_ovt (crit) is used as max, and temp_high (max) is used as hysteresis value to clear alarms Also like the f71858fg its temperature indexes start at 0 */ -static struct sensor_device_attribute_2 f8000_in_temp_attr[] = { - SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0), - SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1), - SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2), +static struct sensor_device_attribute_2 f8000_temp_attr[] = { SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0), SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_crit, store_temp_crit, 0, 0), @@ -421,6 +414,28 @@ static struct sensor_device_attribute_2 f8000_in_temp_attr[] = { SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 2), }; +/* in attr for all models */ +static struct sensor_device_attribute_2 fxxxx_in_attr[] = { + SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0), + SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1), + SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2), + SENSOR_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 0, 3), + SENSOR_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 0, 4), + SENSOR_ATTR_2(in5_input, S_IRUGO, show_in, NULL, 0, 5), + SENSOR_ATTR_2(in6_input, S_IRUGO, show_in, NULL, 0, 6), + SENSOR_ATTR_2(in7_input, S_IRUGO, show_in, NULL, 0, 7), + SENSOR_ATTR_2(in8_input, S_IRUGO, show_in, NULL, 0, 8), +}; + +/* For models with in1 alarm capability */ +static struct sensor_device_attribute_2 fxxxx_in1_alarm_attr[] = { + SENSOR_ATTR_2(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, + 0, 1), + SENSOR_ATTR_2(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, + 0, 1), + SENSOR_ATTR_2(in1_alarm, S_IRUGO, show_in_alarm, NULL, 0, 1), +}; + /* Fan / PWM attr common to all models */ static struct sensor_device_attribute_2 fxxxx_fan_attr[4][6] = { { SENSOR_ATTR_2(fan1_input, S_IRUGO, show_fan, NULL, 0, 0), @@ -947,14 +962,13 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev) struct f71882fg_data *data = dev_get_drvdata(dev); int nr, reg; int nr_fans = (data->type == f71882fg) ? 4 : 3; - int nr_ins = (data->type == f71858fg || data->type == f8000) ? 3 : 9; mutex_lock(&data->update_lock); /* Update once every 60 seconds */ if (time_after(jiffies, data->last_limits + 60 * HZ) || !data->valid) { - if (data->type == f71882fg || data->type == f71889fg) { + if (f71882fg_has_in1_alarm[data->type]) { data->in1_max = f71882fg_read8(data, F71882FG_REG_IN1_HIGH); data->in_beep = @@ -1058,17 +1072,18 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev) data->pwm[nr] = f71882fg_read8(data, F71882FG_REG_PWM(nr)); } - /* The f8000 can monitor 1 more fan, but has no pwm for it */ if (data->type == f8000) data->fan[3] = f71882fg_read16(data, F71882FG_REG_FAN(3)); - if (data->type == f71882fg || data->type == f71889fg) + + if (f71882fg_has_in1_alarm[data->type]) data->in_status = f71882fg_read8(data, F71882FG_REG_IN_STATUS); - for (nr = 0; nr < nr_ins; nr++) - data->in[nr] = f71882fg_read8(data, - F71882FG_REG_IN(nr)); + for (nr = 0; nr < F71882FG_MAX_INS; nr++) + if (f71882fg_has_in[data->type][nr]) + data->in[nr] = f71882fg_read8(data, + F71882FG_REG_IN(nr)); data->last_updated = jiffies; data->valid = 1; @@ -1943,34 +1958,41 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) /* The f71858fg temperature alarms behave as the f8000 alarms in this mode */ err = f71882fg_create_sysfs_files(pdev, - f8000_in_temp_attr, - ARRAY_SIZE(f8000_in_temp_attr)); + f8000_temp_attr, + ARRAY_SIZE(f8000_temp_attr)); else err = f71882fg_create_sysfs_files(pdev, - f71858fg_in_temp_attr, - ARRAY_SIZE(f71858fg_in_temp_attr)); - break; - case f71882fg: - case f71889fg: - err = f71882fg_create_sysfs_files(pdev, - fxxxx_in1_alarm_attr, - ARRAY_SIZE(fxxxx_in1_alarm_attr)); - if (err) - goto exit_unregister_sysfs; - /* fall through! */ - case f71862fg: - err = f71882fg_create_sysfs_files(pdev, - fxxxx_in_temp_attr, - ARRAY_SIZE(fxxxx_in_temp_attr)); + f71858fg_temp_attr, + ARRAY_SIZE(f71858fg_temp_attr)); break; case f8000: err = f71882fg_create_sysfs_files(pdev, - f8000_in_temp_attr, - ARRAY_SIZE(f8000_in_temp_attr)); + f8000_temp_attr, + ARRAY_SIZE(f8000_temp_attr)); break; + default: + err = f71882fg_create_sysfs_files(pdev, + fxxxx_temp_attr, + ARRAY_SIZE(fxxxx_temp_attr)); } if (err) goto exit_unregister_sysfs; + + for (i = 0; i < F71882FG_MAX_INS; i++) { + if (f71882fg_has_in[data->type][i]) { + err = device_create_file(&pdev->dev, + &fxxxx_in_attr[i].dev_attr); + if (err) + goto exit_unregister_sysfs; + } + } + if (f71882fg_has_in1_alarm[data->type]) { + err = f71882fg_create_sysfs_files(pdev, + fxxxx_in1_alarm_attr, + ARRAY_SIZE(fxxxx_in1_alarm_attr)); + if (err) + goto exit_unregister_sysfs; + } } if (start_reg & 0x02) { @@ -2093,7 +2115,7 @@ exit_free: static int f71882fg_remove(struct platform_device *pdev) { struct f71882fg_data *data = platform_get_drvdata(pdev); - int nr_fans = (data->type == f71882fg) ? 4 : 3; + int i, nr_fans = (data->type == f71882fg) ? 4 : 3; u8 start_reg = f71882fg_read8(data, F71882FG_REG_START); if (data->hwmon_dev) @@ -2106,29 +2128,33 @@ static int f71882fg_remove(struct platform_device *pdev) case f71858fg: if (data->temp_config & 0x10) f71882fg_remove_sysfs_files(pdev, - f8000_in_temp_attr, - ARRAY_SIZE(f8000_in_temp_attr)); + f8000_temp_attr, + ARRAY_SIZE(f8000_temp_attr)); else f71882fg_remove_sysfs_files(pdev, - f71858fg_in_temp_attr, - ARRAY_SIZE(f71858fg_in_temp_attr)); - break; - case f71882fg: - case f71889fg: - f71882fg_remove_sysfs_files(pdev, - fxxxx_in1_alarm_attr, - ARRAY_SIZE(fxxxx_in1_alarm_attr)); - /* fall through! */ - case f71862fg: - f71882fg_remove_sysfs_files(pdev, - fxxxx_in_temp_attr, - ARRAY_SIZE(fxxxx_in_temp_attr)); + f71858fg_temp_attr, + ARRAY_SIZE(f71858fg_temp_attr)); break; case f8000: f71882fg_remove_sysfs_files(pdev, - f8000_in_temp_attr, - ARRAY_SIZE(f8000_in_temp_attr)); + f8000_temp_attr, + ARRAY_SIZE(f8000_temp_attr)); break; + default: + f71882fg_remove_sysfs_files(pdev, + fxxxx_temp_attr, + ARRAY_SIZE(fxxxx_temp_attr)); + } + for (i = 0; i < F71882FG_MAX_INS; i++) { + if (f71882fg_has_in[data->type][i]) { + device_remove_file(&pdev->dev, + &fxxxx_in_attr[i].dev_attr); + } + } + if (f71882fg_has_in1_alarm[data->type]) { + f71882fg_remove_sysfs_files(pdev, + fxxxx_in1_alarm_attr, + ARRAY_SIZE(fxxxx_in1_alarm_attr)); } } -- cgit v1.2.3-70-g09d2 From 60d2b378bb7ad1624d1332821da750d1eec50c53 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Mar 2011 20:57:11 +0100 Subject: hwmon/f71882fg: Make number of temps configurable Make the temp sysattr array for the standard models a 2d array, which allows to use it for models with less temperatures too. This is a preparation patch for adding support for more different models. Signed-off-by: Hans de Goede Signed-off-by: Guenter Roeck --- drivers/hwmon/f71882fg.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index ec29c611ed3..bf939e1aa73 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -325,7 +325,7 @@ static struct sensor_device_attribute_2 f71858fg_temp_attr[] = { }; /* Temp attr for the standard models */ -static struct sensor_device_attribute_2 fxxxx_temp_attr[] = { +static struct sensor_device_attribute_2 fxxxx_temp_attr[3][11] = { { SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 1), SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_max, store_temp_max, 0, 1), @@ -346,6 +346,7 @@ static struct sensor_device_attribute_2 fxxxx_temp_attr[] = { store_temp_beep, 0, 5), SENSOR_ATTR_2(temp1_type, S_IRUGO, show_temp_type, NULL, 0, 1), SENSOR_ATTR_2(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0, 1), +}, { SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 2), SENSOR_ATTR_2(temp2_max, S_IRUGO|S_IWUSR, show_temp_max, store_temp_max, 0, 2), @@ -364,6 +365,7 @@ static struct sensor_device_attribute_2 fxxxx_temp_attr[] = { store_temp_beep, 0, 6), SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 0, 2), SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 0, 2), +}, { SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 3), SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_max, store_temp_max, 0, 3), @@ -382,7 +384,7 @@ static struct sensor_device_attribute_2 fxxxx_temp_attr[] = { store_temp_beep, 0, 7), SENSOR_ATTR_2(temp3_type, S_IRUGO, show_temp_type, NULL, 0, 3), SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 3), -}; +} }; /* Temp attr for the f8000 Note on the f8000 temp_ovt (crit) is used as max, and temp_high (max) @@ -1919,6 +1921,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) struct f71882fg_data *data; struct f71882fg_sio_data *sio_data = pdev->dev.platform_data; int err, i, nr_fans = (sio_data->type == f71882fg) ? 4 : 3; + int nr_temps = 3; u8 start_reg, reg; data = kzalloc(sizeof(struct f71882fg_data), GFP_KERNEL); @@ -1972,8 +1975,8 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) break; default: err = f71882fg_create_sysfs_files(pdev, - fxxxx_temp_attr, - ARRAY_SIZE(fxxxx_temp_attr)); + &fxxxx_temp_attr[0][0], + ARRAY_SIZE(fxxxx_temp_attr[0]) * nr_temps); } if (err) goto exit_unregister_sysfs; @@ -2116,6 +2119,7 @@ static int f71882fg_remove(struct platform_device *pdev) { struct f71882fg_data *data = platform_get_drvdata(pdev); int i, nr_fans = (data->type == f71882fg) ? 4 : 3; + int nr_temps = 3; u8 start_reg = f71882fg_read8(data, F71882FG_REG_START); if (data->hwmon_dev) @@ -2142,8 +2146,8 @@ static int f71882fg_remove(struct platform_device *pdev) break; default: f71882fg_remove_sysfs_files(pdev, - fxxxx_temp_attr, - ARRAY_SIZE(fxxxx_temp_attr)); + &fxxxx_temp_attr[0][0], + ARRAY_SIZE(fxxxx_temp_attr[0]) * nr_temps); } for (i = 0; i < F71882FG_MAX_INS; i++) { if (f71882fg_has_in[data->type][i]) { -- cgit v1.2.3-70-g09d2 From 2a91946ac94765d22ebdc4afdeb2bf7028bfe623 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Mar 2011 20:57:08 +0100 Subject: hwmon/f71882fg: Remove bogus temp2_type for certain models Remove the temp2_type sysfs files for f8000 / f71858fg models, this got in accidentally and is wrong as these models don't have a Temperature Sensor Type Register at all. Signed-off-by: Hans de Goede Signed-off-by: Guenter Roeck --- drivers/hwmon/f71882fg.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index bf939e1aa73..b8b4bc9db03 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -308,7 +308,6 @@ static struct sensor_device_attribute_2 f71858fg_temp_attr[] = { SENSOR_ATTR_2(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 1), SENSOR_ATTR_2(temp2_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 5), - SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 0, 1), SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 0, 1), SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 2), SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_max, @@ -405,7 +404,6 @@ static struct sensor_device_attribute_2 f8000_temp_attr[] = { SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max, store_temp_max, 0, 1), SENSOR_ATTR_2(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 5), - SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 0, 1), SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 0, 1), SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 2), SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_crit, -- cgit v1.2.3-70-g09d2 From 78aa4f72b30e4f2c631f6f550a911aad24769cfd Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Mar 2011 20:57:12 +0100 Subject: hwmon/f71882fg: Separate temp beep sysfs attr from the other temp sysfs attr This is a preparation patch for adding support for more different models. Signed-off-by: Hans de Goede Signed-off-by: Guenter Roeck --- drivers/hwmon/f71882fg.c | 76 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 23 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index b8b4bc9db03..62352fd397d 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -127,6 +127,14 @@ static const char f71882fg_has_in1_alarm[5] = { 0, /* f8000 */ }; +static const char f71882fg_has_beep[5] = { + 0, /* f71858fg */ + 1, /* f71862fg */ + 1, /* f71882fg */ + 1, /* f71889fg */ + 0, /* f8000 */ +}; + static struct platform_device *f71882fg_pdev; /* Super-I/O Function prototypes */ @@ -324,7 +332,7 @@ static struct sensor_device_attribute_2 f71858fg_temp_attr[] = { }; /* Temp attr for the standard models */ -static struct sensor_device_attribute_2 fxxxx_temp_attr[3][11] = { { +static struct sensor_device_attribute_2 fxxxx_temp_attr[3][9] = { { SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 1), SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_max, store_temp_max, 0, 1), @@ -334,15 +342,11 @@ static struct sensor_device_attribute_2 fxxxx_temp_attr[3][11] = { { the max and crit alarms separately and lm_sensors v2 depends on the presence of temp#_alarm files. The same goes for temp2/3 _alarm. */ SENSOR_ATTR_2(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 1), - SENSOR_ATTR_2(temp1_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, - store_temp_beep, 0, 1), SENSOR_ATTR_2(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit, store_temp_crit, 0, 1), SENSOR_ATTR_2(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 1), SENSOR_ATTR_2(temp1_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 5), - SENSOR_ATTR_2(temp1_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, - store_temp_beep, 0, 5), SENSOR_ATTR_2(temp1_type, S_IRUGO, show_temp_type, NULL, 0, 1), SENSOR_ATTR_2(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0, 1), }, { @@ -353,15 +357,11 @@ static struct sensor_device_attribute_2 fxxxx_temp_attr[3][11] = { { store_temp_max_hyst, 0, 2), /* Should be temp2_max_alarm, see temp1_alarm note */ SENSOR_ATTR_2(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 2), - SENSOR_ATTR_2(temp2_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, - store_temp_beep, 0, 2), SENSOR_ATTR_2(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit, store_temp_crit, 0, 2), SENSOR_ATTR_2(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 2), SENSOR_ATTR_2(temp2_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 6), - SENSOR_ATTR_2(temp2_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, - store_temp_beep, 0, 6), SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 0, 2), SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 0, 2), }, { @@ -372,19 +372,33 @@ static struct sensor_device_attribute_2 fxxxx_temp_attr[3][11] = { { store_temp_max_hyst, 0, 3), /* Should be temp3_max_alarm, see temp1_alarm note */ SENSOR_ATTR_2(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 3), - SENSOR_ATTR_2(temp3_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, - store_temp_beep, 0, 3), SENSOR_ATTR_2(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit, store_temp_crit, 0, 3), SENSOR_ATTR_2(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 3), SENSOR_ATTR_2(temp3_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 7), - SENSOR_ATTR_2(temp3_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, - store_temp_beep, 0, 7), SENSOR_ATTR_2(temp3_type, S_IRUGO, show_temp_type, NULL, 0, 3), SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 3), } }; +/* Temp attr for models which can beep on temp alarm */ +static struct sensor_device_attribute_2 fxxxx_temp_beep_attr[3][2] = { { + SENSOR_ATTR_2(temp1_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 1), + SENSOR_ATTR_2(temp1_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 5), +}, { + SENSOR_ATTR_2(temp2_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 2), + SENSOR_ATTR_2(temp2_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 6), +}, { + SENSOR_ATTR_2(temp3_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 3), + SENSOR_ATTR_2(temp3_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 7), +} }; + /* Temp attr for the f8000 Note on the f8000 temp_ovt (crit) is used as max, and temp_high (max) is used as hysteresis value to clear alarms @@ -989,17 +1003,19 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev) data->temp_hyst[1] = f71882fg_read8(data, F71882FG_REG_TEMP_HYST(1)); } + /* All but the f71858fg / f8000 have this register */ + if ((data->type != f71858fg) && (data->type != f8000)) { + reg = f71882fg_read8(data, F71882FG_REG_TEMP_TYPE); + data->temp_type[1] = (reg & 0x02) ? 2 : 4; + data->temp_type[2] = (reg & 0x04) ? 2 : 4; + data->temp_type[3] = (reg & 0x08) ? 2 : 4; + } - if (data->type == f71862fg || data->type == f71882fg || - data->type == f71889fg) { + if (f71882fg_has_beep[data->type]) { data->fan_beep = f71882fg_read8(data, F71882FG_REG_FAN_BEEP); data->temp_beep = f71882fg_read8(data, F71882FG_REG_TEMP_BEEP); - reg = f71882fg_read8(data, F71882FG_REG_TEMP_TYPE); - data->temp_type[1] = (reg & 0x02) ? 2 : 4; - data->temp_type[2] = (reg & 0x04) ? 2 : 4; - data->temp_type[3] = (reg & 0x08) ? 2 : 4; } data->pwm_enable = f71882fg_read8(data, @@ -1979,6 +1995,15 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) if (err) goto exit_unregister_sysfs; + if (f71882fg_has_beep[data->type]) { + err = f71882fg_create_sysfs_files(pdev, + &fxxxx_temp_beep_attr[0][0], + ARRAY_SIZE(fxxxx_temp_beep_attr[0]) + * nr_temps); + if (err) + goto exit_unregister_sysfs; + } + for (i = 0; i < F71882FG_MAX_INS; i++) { if (f71882fg_has_in[data->type][i]) { err = device_create_file(&pdev->dev, @@ -2042,8 +2067,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) if (err) goto exit_unregister_sysfs; - if (data->type == f71862fg || data->type == f71882fg || - data->type == f71889fg) { + if (f71882fg_has_beep[data->type]) { err = f71882fg_create_sysfs_files(pdev, fxxxx_fan_beep_attr, nr_fans); if (err) @@ -2147,6 +2171,12 @@ static int f71882fg_remove(struct platform_device *pdev) &fxxxx_temp_attr[0][0], ARRAY_SIZE(fxxxx_temp_attr[0]) * nr_temps); } + if (f71882fg_has_beep[data->type]) { + f71882fg_remove_sysfs_files(pdev, + &fxxxx_temp_beep_attr[0][0], + ARRAY_SIZE(fxxxx_temp_beep_attr[0]) * nr_temps); + } + for (i = 0; i < F71882FG_MAX_INS; i++) { if (f71882fg_has_in[data->type][i]) { device_remove_file(&pdev->dev, @@ -2164,10 +2194,10 @@ static int f71882fg_remove(struct platform_device *pdev) f71882fg_remove_sysfs_files(pdev, &fxxxx_fan_attr[0][0], ARRAY_SIZE(fxxxx_fan_attr[0]) * nr_fans); - if (data->type == f71862fg || data->type == f71882fg || - data->type == f71889fg) + if (f71882fg_has_beep[data->type]) { f71882fg_remove_sysfs_files(pdev, fxxxx_fan_beep_attr, nr_fans); + } switch (data->type) { case f71862fg: -- cgit v1.2.3-70-g09d2 From e48a7f1a719b258ee8bd2e809205d6d0f9d66aed Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Mar 2011 20:57:13 +0100 Subject: hwmon/f71882fg: Break out test for auto pwm's controlled by digital readings Putting this check in its own switch case rather then in the switch case for adding pwm auto point sysfs attr is cleaner. This is a preparation patch for adding support for more different models. Signed-off-by: Hans de Goede Signed-off-by: Guenter Roeck --- drivers/hwmon/f71882fg.c | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index 62352fd397d..4c17f12054a 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -2074,6 +2074,27 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) goto exit_unregister_sysfs; } + switch (data->type) { + case f71889fg: + for (i = 0; i < nr_fans; i++) { + data->pwm_auto_point_mapping[i] = + f71882fg_read8(data, + F71882FG_REG_POINT_MAPPING(i)); + if (data->pwm_auto_point_mapping[i] & 0x80) + break; + } + if (i != nr_fans) { + dev_warn(&pdev->dev, + "Auto pwm controlled by raw digital " + "data, disabling pwm auto_point " + "sysfs attributes\n"); + goto no_pwm_auto_point; + } + break; + default: + break; + } + switch (data->type) { case f71862fg: err = f71882fg_create_sysfs_files(pdev, @@ -2090,23 +2111,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) f8000_auto_pwm_attr, ARRAY_SIZE(f8000_auto_pwm_attr)); break; - case f71889fg: - for (i = 0; i < nr_fans; i++) { - data->pwm_auto_point_mapping[i] = - f71882fg_read8(data, - F71882FG_REG_POINT_MAPPING(i)); - if (data->pwm_auto_point_mapping[i] & 0x80) - break; - } - if (i != nr_fans) { - dev_warn(&pdev->dev, - "Auto pwm controlled by raw digital " - "data, disabling pwm auto_point " - "sysfs attributes\n"); - break; - } - /* fall through */ - default: /* f71858fg / f71882fg */ + default: err = f71882fg_create_sysfs_files(pdev, &fxxxx_auto_pwm_attr[0][0], ARRAY_SIZE(fxxxx_auto_pwm_attr[0]) * nr_fans); @@ -2114,6 +2119,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) if (err) goto exit_unregister_sysfs; +no_pwm_auto_point: for (i = 0; i < nr_fans; i++) dev_info(&pdev->dev, "Fan: %d is in %s mode\n", i + 1, (data->pwm_enable & (1 << 2 * i)) ? -- cgit v1.2.3-70-g09d2 From 3cad402281607d4db0d99d88fbd67cabb7c5b9f1 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Mar 2011 20:57:14 +0100 Subject: hwmon/f71882fg: Add support for f71889ed Note that this patch also makes 2 changes to the code paths for the f71889fg to keep the code unified between the 2 (for simplicities sake). Both of these are harmless for then f71889fg: 1) The first change is to always set the FAN_PROG_SEL bit to 0. This influences accesses to some banked fan / pwm registers. On the f71889fg no registers which we use are banked. On the f71889ed however some more fan registers have been banked including one which we use, by making the FAN_PROG_SEL bit 0, address 0x96 will point to the right register. 2) The second change is to see a FANx_TEMP_SEL value of 0 as pointing to a PECI / AMDSI value, and thus disable our pwm related sysfs attr. This is correct for the f71889ed and on the f71889fg 0 is a reserved value, so we should never see it and if we do, disabling the pwm related sysfs attr is a sane thing to do. Signed-off-by: Hans de Goede Tested-by: Thomas Greve Tested-by: Sander Eikelenboom Signed-off-by: Guenter Roeck --- Documentation/hwmon/f71882fg | 12 ++++++++---- drivers/hwmon/f71882fg.c | 38 +++++++++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 15 deletions(-) (limited to 'drivers/hwmon') diff --git a/Documentation/hwmon/f71882fg b/Documentation/hwmon/f71882fg index a7952c2bd95..9aa210cae69 100644 --- a/Documentation/hwmon/f71882fg +++ b/Documentation/hwmon/f71882fg @@ -17,6 +17,10 @@ Supported chips: * Fintek F71889FG Prefix: 'f71889fg' Addresses scanned: none, address read from Super I/O config space + Datasheet: Available from the Fintek website + * Fintek F71889ED + Prefix: 'f71889ed' + Addresses scanned: none, address read from Super I/O config space Datasheet: Should become available on the Fintek website soon * Fintek F8000 Prefix: 'f8000' @@ -29,9 +33,9 @@ Author: Hans de Goede Description ----------- -Fintek F718xxFG/F8000 Super I/O chips include complete hardware monitoring -capabilities. They can monitor up to 9 voltages (3 for the F8000), 4 fans and -3 temperature sensors. +Fintek F718xx/F8000 Super I/O chips include complete hardware monitoring +capabilities. They can monitor up to 9 voltages, 4 fans and 3 temperature +sensors. These chips also have fan controlling features, using either DC or PWM, in three different modes (one manual, two automatic). @@ -99,5 +103,5 @@ Writing an unsupported mode will result in an invalid parameter error. The fan speed is regulated to keep the temp the fan is mapped to between temp#_auto_point2_temp and temp#_auto_point3_temp. -Both of the automatic modes require that pwm1 corresponds to fan1, pwm2 to +All of the automatic modes require that pwm1 corresponds to fan1, pwm2 to fan2 and pwm3 to fan3. diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index 4c17f12054a..49cf19621c0 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -51,16 +51,17 @@ #define SIO_F71862_ID 0x0601 /* Chipset ID */ #define SIO_F71882_ID 0x0541 /* Chipset ID */ #define SIO_F71889_ID 0x0723 /* Chipset ID */ +#define SIO_F71889E_ID 0x0909 /* Chipset ID */ #define SIO_F8000_ID 0x0581 /* Chipset ID */ #define REGION_LENGTH 8 #define ADDR_REG_OFFSET 5 #define DATA_REG_OFFSET 6 -#define F71882FG_REG_IN_STATUS 0x12 /* f71882fg only */ -#define F71882FG_REG_IN_BEEP 0x13 /* f71882fg only */ +#define F71882FG_REG_IN_STATUS 0x12 /* f7188x only */ +#define F71882FG_REG_IN_BEEP 0x13 /* f7188x only */ #define F71882FG_REG_IN(nr) (0x20 + (nr)) -#define F71882FG_REG_IN1_HIGH 0x32 /* f71882fg only */ +#define F71882FG_REG_IN1_HIGH 0x32 /* f7188x only */ #define F71882FG_REG_FAN(nr) (0xA0 + (16 * (nr))) #define F71882FG_REG_FAN_TARGET(nr) (0xA2 + (16 * (nr))) @@ -86,6 +87,7 @@ #define F71882FG_REG_FAN_FAULT_T 0x9F #define F71882FG_FAN_NEG_TEMP_EN 0x20 +#define F71882FG_FAN_PROG_SEL 0x80 #define F71882FG_REG_POINT_PWM(pwm, point) (0xAA + (point) + (16 * (pwm))) #define F71882FG_REG_POINT_TEMP(pwm, point) (0xA6 + (point) + (16 * (pwm))) @@ -101,37 +103,41 @@ static unsigned short force_id; module_param(force_id, ushort, 0); MODULE_PARM_DESC(force_id, "Override the detected device ID"); -enum chips { f71858fg, f71862fg, f71882fg, f71889fg, f8000 }; +enum chips { f71858fg, f71862fg, f71882fg, f71889fg, f71889ed, f8000 }; static const char *f71882fg_names[] = { "f71858fg", "f71862fg", "f71882fg", "f71889fg", + "f71889ed", "f8000", }; -static const char f71882fg_has_in[5][F71882FG_MAX_INS] = { +static const char f71882fg_has_in[6][F71882FG_MAX_INS] = { { 1, 1, 1, 0, 0, 0, 0, 0, 0 }, /* f71858fg */ { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* f71862fg */ { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* f71882fg */ { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* f71889fg */ + { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* f71889ed */ { 1, 1, 1, 0, 0, 0, 0, 0, 0 }, /* f8000 */ }; -static const char f71882fg_has_in1_alarm[5] = { +static const char f71882fg_has_in1_alarm[6] = { 0, /* f71858fg */ 0, /* f71862fg */ 1, /* f71882fg */ 1, /* f71889fg */ + 1, /* f71889ed */ 0, /* f8000 */ }; -static const char f71882fg_has_beep[5] = { +static const char f71882fg_has_beep[6] = { 0, /* f71858fg */ 1, /* f71862fg */ 1, /* f71882fg */ 1, /* f71889fg */ + 1, /* f71889ed */ 0, /* f8000 */ }; @@ -510,7 +516,7 @@ static struct sensor_device_attribute_2 fxxxx_fan_beep_attr[] = { }; /* PWM attr for the f71862fg, fewer pwms and fewer zones per pwm than the - f71858fg / f71882fg / f71889fg */ + standard models */ static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[] = { SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR, show_pwm_auto_point_channel, @@ -579,7 +585,7 @@ static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[] = { show_pwm_auto_point_temp_hyst, NULL, 3, 2), }; -/* PWM attr common to the f71858fg, f71882fg and f71889fg */ +/* PWM attr for the standard models */ static struct sensor_device_attribute_2 fxxxx_auto_pwm_attr[4][14] = { { SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR, show_pwm_auto_point_channel, @@ -2024,9 +2030,13 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) if (start_reg & 0x02) { switch (data->type) { case f71889fg: + case f71889ed: reg = f71882fg_read8(data, F71882FG_REG_FAN_FAULT_T); if (reg & F71882FG_FAN_NEG_TEMP_EN) data->auto_point_temp_signed = 1; + /* Ensure banked pwm registers point to right bank */ + reg &= ~F71882FG_FAN_PROG_SEL; + f71882fg_write8(data, F71882FG_REG_FAN_FAULT_T, reg); break; default: break; @@ -2048,6 +2058,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) break; case f71882fg: case f71889fg: + case f71889ed: err = 0; break; case f8000: @@ -2076,11 +2087,13 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) switch (data->type) { case f71889fg: + case f71889ed: for (i = 0; i < nr_fans; i++) { data->pwm_auto_point_mapping[i] = f71882fg_read8(data, F71882FG_REG_POINT_MAPPING(i)); - if (data->pwm_auto_point_mapping[i] & 0x80) + if ((data->pwm_auto_point_mapping[i] & 0x80) || + (data->pwm_auto_point_mapping[i] & 3) == 0) break; } if (i != nr_fans) { @@ -2219,7 +2232,7 @@ static int f71882fg_remove(struct platform_device *pdev) f8000_auto_pwm_attr, ARRAY_SIZE(f8000_auto_pwm_attr)); break; - default: /* f71858fg / f71882fg / f71889fg */ + default: f71882fg_remove_sysfs_files(pdev, &fxxxx_auto_pwm_attr[0][0], ARRAY_SIZE(fxxxx_auto_pwm_attr[0]) * nr_fans); @@ -2261,6 +2274,9 @@ static int __init f71882fg_find(int sioaddr, unsigned short *address, case SIO_F71889_ID: sio_data->type = f71889fg; break; + case SIO_F71889E_ID: + sio_data->type = f71889ed; + break; case SIO_F8000_ID: sio_data->type = f8000; break; -- cgit v1.2.3-70-g09d2 From c11bb993629078264f72f8de8d48d7c3286c4c3e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Mar 2011 20:57:15 +0100 Subject: hwmon/f71882fg: Add support for f71869f and f71869e Note that these 2 are register compatible and report the same superio id, but they are 2 distinct chips / models! Signed-off-by: Hans de Goede Tested-by: Thomas Faber Tested-by: Alexey Sychev Tested-by: Dieter Bloms Signed-off-by: Guenter Roeck --- Documentation/hwmon/f71882fg | 4 ++ drivers/hwmon/f71882fg.c | 120 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 119 insertions(+), 5 deletions(-) (limited to 'drivers/hwmon') diff --git a/Documentation/hwmon/f71882fg b/Documentation/hwmon/f71882fg index 9aa210cae69..4d0bc70f185 100644 --- a/Documentation/hwmon/f71882fg +++ b/Documentation/hwmon/f71882fg @@ -10,6 +10,10 @@ Supported chips: Prefix: 'f71862fg' Addresses scanned: none, address read from Super I/O config space Datasheet: Available from the Fintek website + * Fintek F71869F and F71869E + Prefix: 'f71869' + Addresses scanned: none, address read from Super I/O config space + Datasheet: Available from the Fintek website * Fintek F71882FG and F71883FG Prefix: 'f71882fg' Addresses scanned: none, address read from Super I/O config space diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index 49cf19621c0..eea69775789 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -49,6 +49,7 @@ #define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */ #define SIO_F71858_ID 0x0507 /* Chipset ID */ #define SIO_F71862_ID 0x0601 /* Chipset ID */ +#define SIO_F71869_ID 0x0814 /* Chipset ID */ #define SIO_F71882_ID 0x0541 /* Chipset ID */ #define SIO_F71889_ID 0x0723 /* Chipset ID */ #define SIO_F71889E_ID 0x0909 /* Chipset ID */ @@ -103,38 +104,42 @@ static unsigned short force_id; module_param(force_id, ushort, 0); MODULE_PARM_DESC(force_id, "Override the detected device ID"); -enum chips { f71858fg, f71862fg, f71882fg, f71889fg, f71889ed, f8000 }; +enum chips { f71858fg, f71862fg, f71869, f71882fg, f71889fg, f71889ed, f8000 }; static const char *f71882fg_names[] = { "f71858fg", "f71862fg", + "f71869", /* Both f71869f and f71869e, reg. compatible and same id */ "f71882fg", "f71889fg", "f71889ed", "f8000", }; -static const char f71882fg_has_in[6][F71882FG_MAX_INS] = { +static const char f71882fg_has_in[7][F71882FG_MAX_INS] = { { 1, 1, 1, 0, 0, 0, 0, 0, 0 }, /* f71858fg */ { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* f71862fg */ + { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* f71869 */ { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* f71882fg */ { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* f71889fg */ { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* f71889ed */ { 1, 1, 1, 0, 0, 0, 0, 0, 0 }, /* f8000 */ }; -static const char f71882fg_has_in1_alarm[6] = { +static const char f71882fg_has_in1_alarm[7] = { 0, /* f71858fg */ 0, /* f71862fg */ + 0, /* f71869 */ 1, /* f71882fg */ 1, /* f71889fg */ 1, /* f71889ed */ 0, /* f8000 */ }; -static const char f71882fg_has_beep[6] = { +static const char f71882fg_has_beep[7] = { 0, /* f71858fg */ 1, /* f71862fg */ + 1, /* f71869 */ 1, /* f71882fg */ 1, /* f71889fg */ 1, /* f71889ed */ @@ -585,6 +590,86 @@ static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[] = { show_pwm_auto_point_temp_hyst, NULL, 3, 2), }; +/* PWM attr for the f71869, almost identical to the f71862fg, but the + pwm setting when the temperature is above the pwmX_auto_point1_temp can be + programmed instead of being hardcoded to 0xff */ +static struct sensor_device_attribute_2 f71869_auto_pwm_attr[] = { + SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 0), + SENSOR_ATTR_2(pwm1_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 0), + SENSOR_ATTR_2(pwm1_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 0), + SENSOR_ATTR_2(pwm1_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 0), + SENSOR_ATTR_2(pwm1_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 0), + + SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 1), + SENSOR_ATTR_2(pwm2_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 1), + SENSOR_ATTR_2(pwm2_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 1), + SENSOR_ATTR_2(pwm2_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 1), + SENSOR_ATTR_2(pwm2_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 1), + + SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 2), + SENSOR_ATTR_2(pwm3_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 2), + SENSOR_ATTR_2(pwm3_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 2), + SENSOR_ATTR_2(pwm3_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 2), + SENSOR_ATTR_2(pwm3_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 2), +}; + /* PWM attr for the standard models */ static struct sensor_device_attribute_2 fxxxx_auto_pwm_attr[4][14] = { { SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR, @@ -1036,7 +1121,7 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev) f71882fg_read8(data, F71882FG_REG_POINT_MAPPING(nr)); - if (data->type != f71862fg) { + if (data->type != f71862fg && data->type != f71869) { int point; for (point = 0; point < 5; point++) { data->pwm_auto_point_pwm[nr][point] = @@ -1051,6 +1136,12 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev) (nr, point)); } } else { + if (data->type == f71869) { + data->pwm_auto_point_pwm[nr][0] = + f71882fg_read8(data, + F71882FG_REG_POINT_PWM + (nr, 0)); + } data->pwm_auto_point_pwm[nr][1] = f71882fg_read8(data, F71882FG_REG_POINT_PWM @@ -2029,6 +2120,10 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) if (start_reg & 0x02) { switch (data->type) { + case f71869: + /* The f71869 always has signed auto point temps */ + data->auto_point_temp_signed = 1; + /* Fall through to select correct fan/pwm reg bank! */ case f71889fg: case f71889ed: reg = f71882fg_read8(data, F71882FG_REG_FAN_FAULT_T); @@ -2056,6 +2151,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) case f71862fg: err = (data->pwm_enable & 0x15) != 0x15; break; + case f71869: case f71882fg: case f71889fg: case f71889ed: @@ -2086,6 +2182,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) } switch (data->type) { + case f71869: case f71889fg: case f71889ed: for (i = 0; i < nr_fans; i++) { @@ -2114,6 +2211,11 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) f71862fg_auto_pwm_attr, ARRAY_SIZE(f71862fg_auto_pwm_attr)); break; + case f71869: + err = f71882fg_create_sysfs_files(pdev, + f71869_auto_pwm_attr, + ARRAY_SIZE(f71869_auto_pwm_attr)); + break; case f8000: err = f71882fg_create_sysfs_files(pdev, f8000_fan_attr, @@ -2224,6 +2326,11 @@ static int f71882fg_remove(struct platform_device *pdev) f71862fg_auto_pwm_attr, ARRAY_SIZE(f71862fg_auto_pwm_attr)); break; + case f71869: + f71882fg_remove_sysfs_files(pdev, + f71869_auto_pwm_attr, + ARRAY_SIZE(f71869_auto_pwm_attr)); + break; case f8000: f71882fg_remove_sysfs_files(pdev, f8000_fan_attr, @@ -2268,6 +2375,9 @@ static int __init f71882fg_find(int sioaddr, unsigned short *address, case SIO_F71862_ID: sio_data->type = f71862fg; break; + case SIO_F71869_ID: + sio_data->type = f71869; + break; case SIO_F71882_ID: sio_data->type = f71882fg; break; -- cgit v1.2.3-70-g09d2 From e5e713cbe812e8b9ef2099694be7c587c51438c4 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 10 Mar 2011 08:54:02 +0100 Subject: hwmon/f71882fg: Add support for f71808e Signed-off-by: Hans de Goede Tested-by: Jonathan Michalon Signed-off-by: Guenter Roeck --- drivers/hwmon/f71882fg.c | 61 +++++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 21 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index eea69775789..a4d430ee7e2 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -47,6 +47,7 @@ #define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ #define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */ +#define SIO_F71808E_ID 0x0901 /* Chipset ID */ #define SIO_F71858_ID 0x0507 /* Chipset ID */ #define SIO_F71862_ID 0x0601 /* Chipset ID */ #define SIO_F71869_ID 0x0814 /* Chipset ID */ @@ -104,9 +105,11 @@ static unsigned short force_id; module_param(force_id, ushort, 0); MODULE_PARM_DESC(force_id, "Override the detected device ID"); -enum chips { f71858fg, f71862fg, f71869, f71882fg, f71889fg, f71889ed, f8000 }; +enum chips { f71808e, f71858fg, f71862fg, f71869, f71882fg, f71889fg, + f71889ed, f8000 }; static const char *f71882fg_names[] = { + "f71808e", "f71858fg", "f71862fg", "f71869", /* Both f71869f and f71869e, reg. compatible and same id */ @@ -116,7 +119,8 @@ static const char *f71882fg_names[] = { "f8000", }; -static const char f71882fg_has_in[7][F71882FG_MAX_INS] = { +static const char f71882fg_has_in[8][F71882FG_MAX_INS] = { + { 1, 1, 1, 1, 1, 1, 0, 1, 1 }, /* f71808e */ { 1, 1, 1, 0, 0, 0, 0, 0, 0 }, /* f71858fg */ { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* f71862fg */ { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* f71869 */ @@ -126,7 +130,8 @@ static const char f71882fg_has_in[7][F71882FG_MAX_INS] = { { 1, 1, 1, 0, 0, 0, 0, 0, 0 }, /* f8000 */ }; -static const char f71882fg_has_in1_alarm[7] = { +static const char f71882fg_has_in1_alarm[8] = { + 0, /* f71808e */ 0, /* f71858fg */ 0, /* f71862fg */ 0, /* f71869 */ @@ -136,7 +141,8 @@ static const char f71882fg_has_in1_alarm[7] = { 0, /* f8000 */ }; -static const char f71882fg_has_beep[7] = { +static const char f71882fg_has_beep[8] = { + 0, /* f71808e */ 0, /* f71858fg */ 1, /* f71862fg */ 1, /* f71869 */ @@ -181,7 +187,7 @@ struct f71882fg_data { u16 fan_full_speed[4]; u8 fan_status; u8 fan_beep; - /* Note: all models have only 3 temperature channels, but on some + /* Note: all models have max 3 temperature channels, but on some they are addressed as 0-2 and on others as 1-3, so for coding convenience we reserve space for 4 channels */ u16 temp[4]; @@ -590,7 +596,7 @@ static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[] = { show_pwm_auto_point_temp_hyst, NULL, 3, 2), }; -/* PWM attr for the f71869, almost identical to the f71862fg, but the +/* PWM attr for the f71808e/f71869, almost identical to the f71862fg, but the pwm setting when the temperature is above the pwmX_auto_point1_temp can be programmed instead of being hardcoded to 0xff */ static struct sensor_device_attribute_2 f71869_auto_pwm_attr[] = { @@ -1065,8 +1071,9 @@ static u16 f71882fg_read_temp(struct f71882fg_data *data, int nr) static struct f71882fg_data *f71882fg_update_device(struct device *dev) { struct f71882fg_data *data = dev_get_drvdata(dev); - int nr, reg; + int nr, reg, point; int nr_fans = (data->type == f71882fg) ? 4 : 3; + int nr_temps = (data->type == f71808e) ? 2 : 3; mutex_lock(&data->update_lock); @@ -1081,7 +1088,8 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev) } /* Get High & boundary temps*/ - for (nr = data->temp_start; nr < 3 + data->temp_start; nr++) { + for (nr = data->temp_start; nr < nr_temps + data->temp_start; + nr++) { data->temp_ovt[nr] = f71882fg_read8(data, F71882FG_REG_TEMP_OVT(nr)); data->temp_high[nr] = f71882fg_read8(data, @@ -1121,8 +1129,8 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev) f71882fg_read8(data, F71882FG_REG_POINT_MAPPING(nr)); - if (data->type != f71862fg && data->type != f71869) { - int point; + switch (data->type) { + default: for (point = 0; point < 5; point++) { data->pwm_auto_point_pwm[nr][point] = f71882fg_read8(data, @@ -1135,13 +1143,14 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev) F71882FG_REG_POINT_TEMP (nr, point)); } - } else { - if (data->type == f71869) { - data->pwm_auto_point_pwm[nr][0] = - f71882fg_read8(data, - F71882FG_REG_POINT_PWM - (nr, 0)); - } + break; + case f71808e: + case f71869: + data->pwm_auto_point_pwm[nr][0] = + f71882fg_read8(data, + F71882FG_REG_POINT_PWM(nr, 0)); + /* Fall through */ + case f71862fg: data->pwm_auto_point_pwm[nr][1] = f71882fg_read8(data, F71882FG_REG_POINT_PWM @@ -1158,6 +1167,7 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev) f71882fg_read8(data, F71882FG_REG_POINT_TEMP (nr, 3)); + break; } } data->last_limits = jiffies; @@ -1169,7 +1179,8 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev) F71882FG_REG_TEMP_STATUS); data->temp_diode_open = f71882fg_read8(data, F71882FG_REG_TEMP_DIODE_OPEN); - for (nr = data->temp_start; nr < 3 + data->temp_start; nr++) + for (nr = data->temp_start; nr < nr_temps + data->temp_start; + nr++) data->temp[nr] = f71882fg_read_temp(data, nr); data->fan_status = f71882fg_read8(data, @@ -2032,7 +2043,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) struct f71882fg_data *data; struct f71882fg_sio_data *sio_data = pdev->dev.platform_data; int err, i, nr_fans = (sio_data->type == f71882fg) ? 4 : 3; - int nr_temps = 3; + int nr_temps = (sio_data->type == f71808e) ? 2 : 3; u8 start_reg, reg; data = kzalloc(sizeof(struct f71882fg_data), GFP_KERNEL); @@ -2120,8 +2131,9 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) if (start_reg & 0x02) { switch (data->type) { + case f71808e: case f71869: - /* The f71869 always has signed auto point temps */ + /* These always have signed auto point temps */ data->auto_point_temp_signed = 1; /* Fall through to select correct fan/pwm reg bank! */ case f71889fg: @@ -2151,6 +2163,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) case f71862fg: err = (data->pwm_enable & 0x15) != 0x15; break; + case f71808e: case f71869: case f71882fg: case f71889fg: @@ -2182,6 +2195,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) } switch (data->type) { + case f71808e: case f71869: case f71889fg: case f71889ed: @@ -2211,6 +2225,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) f71862fg_auto_pwm_attr, ARRAY_SIZE(f71862fg_auto_pwm_attr)); break; + case f71808e: case f71869: err = f71882fg_create_sysfs_files(pdev, f71869_auto_pwm_attr, @@ -2262,7 +2277,7 @@ static int f71882fg_remove(struct platform_device *pdev) { struct f71882fg_data *data = platform_get_drvdata(pdev); int i, nr_fans = (data->type == f71882fg) ? 4 : 3; - int nr_temps = 3; + int nr_temps = (data->type == f71808e) ? 2 : 3; u8 start_reg = f71882fg_read8(data, F71882FG_REG_START); if (data->hwmon_dev) @@ -2326,6 +2341,7 @@ static int f71882fg_remove(struct platform_device *pdev) f71862fg_auto_pwm_attr, ARRAY_SIZE(f71862fg_auto_pwm_attr)); break; + case f71808e: case f71869: f71882fg_remove_sysfs_files(pdev, f71869_auto_pwm_attr, @@ -2369,6 +2385,9 @@ static int __init f71882fg_find(int sioaddr, unsigned short *address, devid = force_id ? force_id : superio_inw(sioaddr, SIO_REG_DEVID); switch (devid) { + case SIO_F71808E_ID: + sio_data->type = f71808e; + break; case SIO_F71858_ID: sio_data->type = f71858fg; break; -- cgit v1.2.3-70-g09d2 From d668a8b022a201e65ec5e301a9e6dff78987550c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 15 Mar 2011 16:34:07 -0700 Subject: hwmon: (lineage-pem): Fix in1 voltage alarm sysfs attributes The alarm bit assumed to be a low voltage alarm bit is not set for low voltage alarms, and the alarm bit assumed to be a high voltage alarm turns out to be a general alarm bit which is set for both low and high voltage alarms. Remove the in1_min_alarm sysfs attribute and rename in1_max_alarm to in1_alarm to reflect the situation. Signed-off-by: Guenter Roeck Acked-by: Jean Delvare --- drivers/hwmon/lineage-pem.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/lineage-pem.c b/drivers/hwmon/lineage-pem.c index d39ee24e52f..58eded27f38 100644 --- a/drivers/hwmon/lineage-pem.c +++ b/drivers/hwmon/lineage-pem.c @@ -345,9 +345,7 @@ static ssize_t pem_show_fan(struct device *dev, struct device_attribute *da, /* Voltages */ static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, pem_show_data, NULL, PEM_DATA_VOUT_LSB); -static SENSOR_DEVICE_ATTR_2(in1_min_alarm, S_IRUGO, pem_show_bool, NULL, - PEM_DATA_ALARM_2, ALRM2_OV_LOW); -static SENSOR_DEVICE_ATTR_2(in1_max_alarm, S_IRUGO, pem_show_bool, NULL, +static SENSOR_DEVICE_ATTR_2(in1_alarm, S_IRUGO, pem_show_bool, NULL, PEM_DATA_ALARM_1, ALRM1_VOUT_OUT_LIMIT); static SENSOR_DEVICE_ATTR_2(in1_crit_alarm, S_IRUGO, pem_show_bool, NULL, PEM_DATA_ALARM_1, ALRM1_OV_VOLT_SHUTDOWN); @@ -395,8 +393,7 @@ static SENSOR_DEVICE_ATTR_2(temp1_fault, S_IRUGO, pem_show_bool, NULL, static struct attribute *pem_attributes[] = { &sensor_dev_attr_in1_input.dev_attr.attr, - &sensor_dev_attr_in1_min_alarm.dev_attr.attr, - &sensor_dev_attr_in1_max_alarm.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, &sensor_dev_attr_in1_crit_alarm.dev_attr.attr, &sensor_dev_attr_in2_alarm.dev_attr.attr, -- cgit v1.2.3-70-g09d2 From 5c39e31195f49f6725f04b22921eca2388a3f909 Mon Sep 17 00:00:00 2001 From: matt mooney Date: Fri, 14 Jan 2011 06:12:38 -0800 Subject: hwmon: change to new flag variable Replace EXTRA_CFLAGS with ccflags-y. Signed-off-by: matt mooney Acked-by: WANG Cong Acked-by: Guenter Roeck Signed-off-by: Michal Marek --- drivers/hwmon/Makefile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index dde02d99c23..974f9cc766b 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -112,7 +112,5 @@ obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o -ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y) -EXTRA_CFLAGS += -DDEBUG -endif +ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG -- cgit v1.2.3-70-g09d2 From 396bd766a604b823369962ee0203c603b9c0bdec Mon Sep 17 00:00:00 2001 From: "Justin P. Mattock" Date: Mon, 21 Mar 2011 17:59:35 +0100 Subject: hwmon: (sht15) Spelling fix Remove one too many "n" in a word. Signed-off-by: Justin P. Mattock Acked-by: Jean Delvare --- drivers/hwmon/sht15.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c index a610e7880fb..3ba7dd82589 100644 --- a/drivers/hwmon/sht15.c +++ b/drivers/hwmon/sht15.c @@ -610,7 +610,7 @@ 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 */ + * prevent new ones from beginning */ mutex_lock(&data->read_lock); hwmon_device_unregister(data->hwmon_dev); sysfs_remove_group(&pdev->dev.kobj, &sht15_attr_group); -- cgit v1.2.3-70-g09d2 From ccd32e735de7a941906e093f8dca924bb05c5794 Mon Sep 17 00:00:00 2001 From: Vivien Didelot Date: Mon, 21 Mar 2011 17:59:35 +0100 Subject: hwmon: (sht15) Fix integer overflow in humidity calculation An integer overflow occurs in the calculation of RHlinear when the relative humidity is greater than around 30%. The consequence is a subtle (but noticeable) error in the resulting humidity measurement. Signed-off-by: Vivien Didelot Signed-off-by: Jean Delvare Cc: stable@kernel.org Cc: Jonathan Cameron --- drivers/hwmon/sht15.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c index 3ba7dd82589..1a9c32d6893 100644 --- a/drivers/hwmon/sht15.c +++ b/drivers/hwmon/sht15.c @@ -333,11 +333,11 @@ static inline int sht15_calc_humid(struct sht15_data *data) const int c1 = -4; const int c2 = 40500; /* x 10 ^ -6 */ - const int c3 = -2800; /* x10 ^ -9 */ + const int c3 = -28; /* x 10 ^ -7 */ RHlinear = c1*1000 + c2 * data->val_humid/1000 - + (data->val_humid * data->val_humid * c3)/1000000; + + (data->val_humid * data->val_humid * c3) / 10000; return (temp - 25000) * (10000 + 80 * data->val_humid) / 1000000 + RHlinear; } -- cgit v1.2.3-70-g09d2 From 39c3e721d65793373e7bc6f5dad0591ef6d09268 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:35 +0100 Subject: Let Kconfig handle lis3lv02d dependencies The dependencies between the various lis3lv02d drivers make it impossible to split them to different directories, while we really want to do this. Move handling of dependencies from Makefile to Kconfig, to make the move possible at all. Signed-off-by: Jean Delvare Acked-by: Guenter Roeck Acked-by: Eric Piel Acked-by: Jonathan Cameron Tested-by: Eric Piel Tested-by: Takashi Iwai --- drivers/hwmon/Kconfig | 43 +++++++++++++++++++------------------------ drivers/hwmon/Makefile | 9 ++++++--- 2 files changed, 25 insertions(+), 27 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 1bfb4439e4e..c48101cf58f 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2,6 +2,12 @@ # Hardware monitoring chip drivers configuration # +config SENSORS_LIS3LV02D + tristate + depends on INPUT + select INPUT_POLLDEV + default n + menuconfig HWMON tristate "Hardware Monitoring support" depends on HAS_IOMEM @@ -1218,7 +1224,7 @@ config SENSORS_ULTRA45 config SENSORS_LIS3_SPI tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (SPI)" depends on !ACPI && SPI_MASTER && INPUT - select INPUT_POLLDEV + select SENSORS_LIS3LV02D default n help This driver provides support for the LIS3LV02Dx accelerometer connected @@ -1235,7 +1241,7 @@ config SENSORS_LIS3_SPI config SENSORS_LIS3_I2C tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (I2C)" depends on I2C && INPUT - select INPUT_POLLDEV + select SENSORS_LIS3LV02D default n help This driver provides support for the LIS3LV02Dx accelerometer connected @@ -1296,35 +1302,24 @@ config SENSORS_ATK0110 This driver can also be built as a module. If so, the module will be called asus_atk0110. -config SENSORS_LIS3LV02D - tristate "STMicroeletronics LIS3* three-axis digital accelerometer" +config HP_ACCEL + tristate "HP laptop accelerometer" depends on INPUT - select INPUT_POLLDEV + select SENSORS_LIS3LV02D select NEW_LEDS select LEDS_CLASS default n help - This driver provides support for the LIS3* accelerometers, such as the - LIS3LV02DL or the LIS331DL. In particular, it can be found in a number - of HP laptops, which have the "Mobile Data Protection System 3D" or - "3D DriveGuard" feature. On such systems the driver should load - automatically (via ACPI alias). The accelerometer might also be found - in other systems, connected via SPI or I2C. The accelerometer data is - readable via /sys/devices/platform/lis3lv02d. + This driver provides support for the "Mobile Data Protection System 3D" + or "3D DriveGuard" feature of HP laptops. On such systems the driver + should load automatically (via ACPI alias). - This driver also provides an absolute input class device, allowing - a laptop to act as a pinball machine-esque joystick. It provides also - a misc device which can be used to detect free-fall. On HP laptops, - if the led infrastructure is activated, support for a led indicating - disk protection will be provided as hp::hddprotect. For more - information on the feature, refer to Documentation/hwmon/lis3lv02d. + Support for a led indicating disk protection will be provided as + hp::hddprotect. For more information on the feature, refer to + Documentation/hwmon/lis3lv02d. - This driver can also be built as modules. If so, the core module - will be called lis3lv02d and a specific module for HP laptops will be - called hp_accel. - - Say Y here if you have an applicable laptop and want to experience - the awesome power of lis3lv02d. + To compile this driver as a module, choose M here: the module will + be called hp_accel. endif # ACPI diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 8a238dec569..94bf2748297 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -5,8 +5,12 @@ obj-$(CONFIG_HWMON) += hwmon.o obj-$(CONFIG_HWMON_VID) += hwmon-vid.o +# Helper drivers +obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o + # APCI drivers obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o +obj-$(CONFIG_HP_ACCEL) += hp_accel.o # Native drivers # asb100, then w83781d go first, as they can override other drivers' addresses. @@ -63,9 +67,8 @@ obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o -obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o hp_accel.o -obj-$(CONFIG_SENSORS_LIS3_SPI) += lis3lv02d.o lis3lv02d_spi.o -obj-$(CONFIG_SENSORS_LIS3_I2C) += lis3lv02d.o lis3lv02d_i2c.o +obj-$(CONFIG_SENSORS_LIS3_SPI) += lis3lv02d_spi.o +obj-$(CONFIG_SENSORS_LIS3_I2C) += lis3lv02d_i2c.o obj-$(CONFIG_SENSORS_LM63) += lm63.o obj-$(CONFIG_SENSORS_LM70) += lm70.o obj-$(CONFIG_SENSORS_LM73) += lm73.o -- cgit v1.2.3-70-g09d2 From efcfed9bad88be8193ee6a1b8e72d7381e7b0e0e Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: Move hp_accel to drivers/platform/x86 The hp_accel driver isn't a hardware monitoring driver, so it doesn't belong to drivers/hwmon. Move it to drivers/platform/x86, assuming HP doesn't ship non-x86 laptops. Signed-off-by: Jean Delvare Acked-by: Guenter Roeck Acked-by: Eric Piel Acked-by: Jonathan Cameron Tested-by: Eric Piel Tested-by: Takashi Iwai --- Documentation/hwmon/hpfall.c | 146 --------------- Documentation/hwmon/lis3lv02d | 4 +- Documentation/laptops/hpfall.c | 146 +++++++++++++++ drivers/hwmon/Kconfig | 19 -- drivers/hwmon/Makefile | 1 - drivers/hwmon/hp_accel.c | 405 ---------------------------------------- drivers/platform/x86/Kconfig | 18 ++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/hp_accel.c | 404 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 571 insertions(+), 573 deletions(-) delete mode 100644 Documentation/hwmon/hpfall.c create mode 100644 Documentation/laptops/hpfall.c delete mode 100644 drivers/hwmon/hp_accel.c create mode 100644 drivers/platform/x86/hp_accel.c (limited to 'drivers/hwmon') diff --git a/Documentation/hwmon/hpfall.c b/Documentation/hwmon/hpfall.c deleted file mode 100644 index a4a8fc5d05d..00000000000 --- a/Documentation/hwmon/hpfall.c +++ /dev/null @@ -1,146 +0,0 @@ -/* Disk protection for HP machines. - * - * Copyright 2008 Eric Piel - * Copyright 2009 Pavel Machek - * - * GPLv2. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -char unload_heads_path[64]; - -int set_unload_heads_path(char *device) -{ - char devname[64]; - - if (strlen(device) <= 5 || strncmp(device, "/dev/", 5) != 0) - return -EINVAL; - strncpy(devname, device + 5, sizeof(devname)); - - snprintf(unload_heads_path, sizeof(unload_heads_path), - "/sys/block/%s/device/unload_heads", devname); - return 0; -} -int valid_disk(void) -{ - int fd = open(unload_heads_path, O_RDONLY); - if (fd < 0) { - perror(unload_heads_path); - return 0; - } - - close(fd); - return 1; -} - -void write_int(char *path, int i) -{ - char buf[1024]; - int fd = open(path, O_RDWR); - if (fd < 0) { - perror("open"); - exit(1); - } - sprintf(buf, "%d", i); - if (write(fd, buf, strlen(buf)) != strlen(buf)) { - perror("write"); - exit(1); - } - close(fd); -} - -void set_led(int on) -{ - write_int("/sys/class/leds/hp::hddprotect/brightness", on); -} - -void protect(int seconds) -{ - write_int(unload_heads_path, seconds*1000); -} - -int on_ac(void) -{ -// /sys/class/power_supply/AC0/online -} - -int lid_open(void) -{ -// /proc/acpi/button/lid/LID/state -} - -void ignore_me(void) -{ - protect(0); - set_led(0); -} - -int main(int argc, char **argv) -{ - int fd, ret; - struct sched_param param; - - if (argc == 1) - ret = set_unload_heads_path("/dev/sda"); - else if (argc == 2) - ret = set_unload_heads_path(argv[1]); - else - ret = -EINVAL; - - if (ret || !valid_disk()) { - fprintf(stderr, "usage: %s (default: /dev/sda)\n", - argv[0]); - exit(1); - } - - fd = open("/dev/freefall", O_RDONLY); - if (fd < 0) { - perror("/dev/freefall"); - return EXIT_FAILURE; - } - - daemon(0, 0); - param.sched_priority = sched_get_priority_max(SCHED_FIFO); - sched_setscheduler(0, SCHED_FIFO, ¶m); - mlockall(MCL_CURRENT|MCL_FUTURE); - - signal(SIGALRM, ignore_me); - - for (;;) { - unsigned char count; - - ret = read(fd, &count, sizeof(count)); - alarm(0); - if ((ret == -1) && (errno == EINTR)) { - /* Alarm expired, time to unpark the heads */ - continue; - } - - if (ret != sizeof(count)) { - perror("read"); - break; - } - - protect(21); - set_led(1); - if (1 || on_ac() || lid_open()) - alarm(2); - else - alarm(20); - } - - close(fd); - return EXIT_SUCCESS; -} diff --git a/Documentation/hwmon/lis3lv02d b/Documentation/hwmon/lis3lv02d index 06534f25e64..f1a4ec840f8 100644 --- a/Documentation/hwmon/lis3lv02d +++ b/Documentation/hwmon/lis3lv02d @@ -17,8 +17,8 @@ Description This driver provides support for the accelerometer found in various HP laptops sporting the feature officially called "HP Mobile Data Protection System 3D" or "HP 3D DriveGuard". It detects automatically laptops with this sensor. Known -models (full list can be found in drivers/hwmon/hp_accel.c) will have their -axis automatically oriented on standard way (eg: you can directly play +models (full list can be found in drivers/platform/x86/hp_accel.c) will have +their axis automatically oriented on standard way (eg: you can directly play neverball). The accelerometer data is readable via /sys/devices/platform/lis3lv02d. Reported values are scaled to mg values (1/1000th of earth gravity). diff --git a/Documentation/laptops/hpfall.c b/Documentation/laptops/hpfall.c new file mode 100644 index 00000000000..a4a8fc5d05d --- /dev/null +++ b/Documentation/laptops/hpfall.c @@ -0,0 +1,146 @@ +/* Disk protection for HP machines. + * + * Copyright 2008 Eric Piel + * Copyright 2009 Pavel Machek + * + * GPLv2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char unload_heads_path[64]; + +int set_unload_heads_path(char *device) +{ + char devname[64]; + + if (strlen(device) <= 5 || strncmp(device, "/dev/", 5) != 0) + return -EINVAL; + strncpy(devname, device + 5, sizeof(devname)); + + snprintf(unload_heads_path, sizeof(unload_heads_path), + "/sys/block/%s/device/unload_heads", devname); + return 0; +} +int valid_disk(void) +{ + int fd = open(unload_heads_path, O_RDONLY); + if (fd < 0) { + perror(unload_heads_path); + return 0; + } + + close(fd); + return 1; +} + +void write_int(char *path, int i) +{ + char buf[1024]; + int fd = open(path, O_RDWR); + if (fd < 0) { + perror("open"); + exit(1); + } + sprintf(buf, "%d", i); + if (write(fd, buf, strlen(buf)) != strlen(buf)) { + perror("write"); + exit(1); + } + close(fd); +} + +void set_led(int on) +{ + write_int("/sys/class/leds/hp::hddprotect/brightness", on); +} + +void protect(int seconds) +{ + write_int(unload_heads_path, seconds*1000); +} + +int on_ac(void) +{ +// /sys/class/power_supply/AC0/online +} + +int lid_open(void) +{ +// /proc/acpi/button/lid/LID/state +} + +void ignore_me(void) +{ + protect(0); + set_led(0); +} + +int main(int argc, char **argv) +{ + int fd, ret; + struct sched_param param; + + if (argc == 1) + ret = set_unload_heads_path("/dev/sda"); + else if (argc == 2) + ret = set_unload_heads_path(argv[1]); + else + ret = -EINVAL; + + if (ret || !valid_disk()) { + fprintf(stderr, "usage: %s (default: /dev/sda)\n", + argv[0]); + exit(1); + } + + fd = open("/dev/freefall", O_RDONLY); + if (fd < 0) { + perror("/dev/freefall"); + return EXIT_FAILURE; + } + + daemon(0, 0); + param.sched_priority = sched_get_priority_max(SCHED_FIFO); + sched_setscheduler(0, SCHED_FIFO, ¶m); + mlockall(MCL_CURRENT|MCL_FUTURE); + + signal(SIGALRM, ignore_me); + + for (;;) { + unsigned char count; + + ret = read(fd, &count, sizeof(count)); + alarm(0); + if ((ret == -1) && (errno == EINTR)) { + /* Alarm expired, time to unpark the heads */ + continue; + } + + if (ret != sizeof(count)) { + perror("read"); + break; + } + + protect(21); + set_led(1); + if (1 || on_ac() || lid_open()) + alarm(2); + else + alarm(20); + } + + close(fd); + return EXIT_SUCCESS; +} diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index c48101cf58f..3f8c895417d 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1302,25 +1302,6 @@ config SENSORS_ATK0110 This driver can also be built as a module. If so, the module will be called asus_atk0110. -config HP_ACCEL - tristate "HP laptop accelerometer" - depends on INPUT - select SENSORS_LIS3LV02D - select NEW_LEDS - select LEDS_CLASS - default n - help - This driver provides support for the "Mobile Data Protection System 3D" - or "3D DriveGuard" feature of HP laptops. On such systems the driver - should load automatically (via ACPI alias). - - Support for a led indicating disk protection will be provided as - hp::hddprotect. For more information on the feature, refer to - Documentation/hwmon/lis3lv02d. - - To compile this driver as a module, choose M here: the module will - be called hp_accel. - endif # ACPI endif # HWMON diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 94bf2748297..55ba906def7 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -10,7 +10,6 @@ obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o # APCI drivers obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o -obj-$(CONFIG_HP_ACCEL) += hp_accel.o # Native drivers # asb100, then w83781d go first, as they can override other drivers' addresses. diff --git a/drivers/hwmon/hp_accel.c b/drivers/hwmon/hp_accel.c deleted file mode 100644 index 3d21fa2b97c..00000000000 --- a/drivers/hwmon/hp_accel.c +++ /dev/null @@ -1,405 +0,0 @@ -/* - * hp_accel.c - Interface between LIS3LV02DL driver and HP ACPI BIOS - * - * Copyright (C) 2007-2008 Yan Burman - * Copyright (C) 2008 Eric Piel - * Copyright (C) 2008-2009 Pavel Machek - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "lis3lv02d.h" - -#define DRIVER_NAME "lis3lv02d" -#define ACPI_MDPS_CLASS "accelerometer" - -/* Delayed LEDs infrastructure ------------------------------------ */ - -/* Special LED class that can defer work */ -struct delayed_led_classdev { - struct led_classdev led_classdev; - struct work_struct work; - enum led_brightness new_brightness; - - unsigned int led; /* For driver */ - void (*set_brightness)(struct delayed_led_classdev *data, enum led_brightness value); -}; - -static inline void delayed_set_status_worker(struct work_struct *work) -{ - struct delayed_led_classdev *data = - container_of(work, struct delayed_led_classdev, work); - - data->set_brightness(data, data->new_brightness); -} - -static inline void delayed_sysfs_set(struct led_classdev *led_cdev, - enum led_brightness brightness) -{ - struct delayed_led_classdev *data = container_of(led_cdev, - struct delayed_led_classdev, led_classdev); - data->new_brightness = brightness; - schedule_work(&data->work); -} - -/* HP-specific accelerometer driver ------------------------------------ */ - -/* For automatic insertion of the module */ -static struct acpi_device_id lis3lv02d_device_ids[] = { - {"HPQ0004", 0}, /* HP Mobile Data Protection System PNP */ - {"", 0}, -}; -MODULE_DEVICE_TABLE(acpi, lis3lv02d_device_ids); - - -/** - * lis3lv02d_acpi_init - ACPI _INI method: initialize the device. - * @lis3: pointer to the device struct - * - * Returns 0 on success. - */ -int lis3lv02d_acpi_init(struct lis3lv02d *lis3) -{ - struct acpi_device *dev = lis3->bus_priv; - if (acpi_evaluate_object(dev->handle, METHOD_NAME__INI, - NULL, NULL) != AE_OK) - return -EINVAL; - - return 0; -} - -/** - * lis3lv02d_acpi_read - ACPI ALRD method: read a register - * @lis3: pointer to the device struct - * @reg: the register to read - * @ret: result of the operation - * - * Returns 0 on success. - */ -int lis3lv02d_acpi_read(struct lis3lv02d *lis3, int reg, u8 *ret) -{ - struct acpi_device *dev = lis3->bus_priv; - union acpi_object arg0 = { ACPI_TYPE_INTEGER }; - struct acpi_object_list args = { 1, &arg0 }; - unsigned long long lret; - acpi_status status; - - arg0.integer.value = reg; - - status = acpi_evaluate_integer(dev->handle, "ALRD", &args, &lret); - *ret = lret; - return (status != AE_OK) ? -EINVAL : 0; -} - -/** - * lis3lv02d_acpi_write - ACPI ALWR method: write to a register - * @lis3: pointer to the device struct - * @reg: the register to write to - * @val: the value to write - * - * Returns 0 on success. - */ -int lis3lv02d_acpi_write(struct lis3lv02d *lis3, int reg, u8 val) -{ - struct acpi_device *dev = lis3->bus_priv; - unsigned long long ret; /* Not used when writting */ - union acpi_object in_obj[2]; - struct acpi_object_list args = { 2, in_obj }; - - in_obj[0].type = ACPI_TYPE_INTEGER; - in_obj[0].integer.value = reg; - in_obj[1].type = ACPI_TYPE_INTEGER; - in_obj[1].integer.value = val; - - if (acpi_evaluate_integer(dev->handle, "ALWR", &args, &ret) != AE_OK) - return -EINVAL; - - return 0; -} - -static int lis3lv02d_dmi_matched(const struct dmi_system_id *dmi) -{ - lis3_dev.ac = *((union axis_conversion *)dmi->driver_data); - pr_info("hardware type %s found\n", dmi->ident); - - return 1; -} - -/* Represents, for each axis seen by userspace, the corresponding hw axis (+1). - * If the value is negative, the opposite of the hw value is used. */ -#define DEFINE_CONV(name, x, y, z) \ - static union axis_conversion lis3lv02d_axis_##name = \ - { .as_array = { x, y, z } } -DEFINE_CONV(normal, 1, 2, 3); -DEFINE_CONV(y_inverted, 1, -2, 3); -DEFINE_CONV(x_inverted, -1, 2, 3); -DEFINE_CONV(z_inverted, 1, 2, -3); -DEFINE_CONV(xy_swap, 2, 1, 3); -DEFINE_CONV(xy_rotated_left, -2, 1, 3); -DEFINE_CONV(xy_rotated_left_usd, -2, 1, -3); -DEFINE_CONV(xy_swap_inverted, -2, -1, 3); -DEFINE_CONV(xy_rotated_right, 2, -1, 3); -DEFINE_CONV(xy_swap_yz_inverted, 2, -1, -3); - -#define AXIS_DMI_MATCH(_ident, _name, _axis) { \ - .ident = _ident, \ - .callback = lis3lv02d_dmi_matched, \ - .matches = { \ - DMI_MATCH(DMI_PRODUCT_NAME, _name) \ - }, \ - .driver_data = &lis3lv02d_axis_##_axis \ -} - -#define AXIS_DMI_MATCH2(_ident, _class1, _name1, \ - _class2, _name2, \ - _axis) { \ - .ident = _ident, \ - .callback = lis3lv02d_dmi_matched, \ - .matches = { \ - DMI_MATCH(DMI_##_class1, _name1), \ - DMI_MATCH(DMI_##_class2, _name2), \ - }, \ - .driver_data = &lis3lv02d_axis_##_axis \ -} -static struct dmi_system_id lis3lv02d_dmi_ids[] = { - /* product names are truncated to match all kinds of a same model */ - AXIS_DMI_MATCH("NC64x0", "HP Compaq nc64", x_inverted), - AXIS_DMI_MATCH("NC84x0", "HP Compaq nc84", z_inverted), - AXIS_DMI_MATCH("NX9420", "HP Compaq nx9420", x_inverted), - AXIS_DMI_MATCH("NW9440", "HP Compaq nw9440", x_inverted), - AXIS_DMI_MATCH("NC2510", "HP Compaq 2510", y_inverted), - AXIS_DMI_MATCH("NC2710", "HP Compaq 2710", xy_swap), - AXIS_DMI_MATCH("NC8510", "HP Compaq 8510", xy_swap_inverted), - AXIS_DMI_MATCH("HP2133", "HP 2133", xy_rotated_left), - AXIS_DMI_MATCH("HP2140", "HP 2140", xy_swap_inverted), - AXIS_DMI_MATCH("NC653x", "HP Compaq 653", xy_rotated_left_usd), - AXIS_DMI_MATCH("NC6730b", "HP Compaq 6730b", xy_rotated_left_usd), - AXIS_DMI_MATCH("NC6730s", "HP Compaq 6730s", xy_swap), - AXIS_DMI_MATCH("NC651xx", "HP Compaq 651", xy_rotated_right), - AXIS_DMI_MATCH("NC6710x", "HP Compaq 6710", xy_swap_yz_inverted), - AXIS_DMI_MATCH("NC6715x", "HP Compaq 6715", y_inverted), - AXIS_DMI_MATCH("NC693xx", "HP EliteBook 693", xy_rotated_right), - AXIS_DMI_MATCH("NC693xx", "HP EliteBook 853", xy_swap), - /* Intel-based HP Pavilion dv5 */ - AXIS_DMI_MATCH2("HPDV5_I", - PRODUCT_NAME, "HP Pavilion dv5", - BOARD_NAME, "3603", - x_inverted), - /* AMD-based HP Pavilion dv5 */ - AXIS_DMI_MATCH2("HPDV5_A", - PRODUCT_NAME, "HP Pavilion dv5", - BOARD_NAME, "3600", - y_inverted), - AXIS_DMI_MATCH("DV7", "HP Pavilion dv7", x_inverted), - AXIS_DMI_MATCH("HP8710", "HP Compaq 8710", y_inverted), - AXIS_DMI_MATCH("HDX18", "HP HDX 18", x_inverted), - AXIS_DMI_MATCH("HPB432x", "HP ProBook 432", xy_rotated_left), - AXIS_DMI_MATCH("HPB442x", "HP ProBook 442", xy_rotated_left), - AXIS_DMI_MATCH("HPB452x", "HP ProBook 452", y_inverted), - AXIS_DMI_MATCH("HPB522x", "HP ProBook 522", xy_swap), - AXIS_DMI_MATCH("HPB532x", "HP ProBook 532", y_inverted), - AXIS_DMI_MATCH("Mini510x", "HP Mini 510", xy_rotated_left_usd), - { NULL, } -/* Laptop models without axis info (yet): - * "NC6910" "HP Compaq 6910" - * "NC2400" "HP Compaq nc2400" - * "NX74x0" "HP Compaq nx74" - * "NX6325" "HP Compaq nx6325" - * "NC4400" "HP Compaq nc4400" - */ -}; - -static void hpled_set(struct delayed_led_classdev *led_cdev, enum led_brightness value) -{ - struct acpi_device *dev = lis3_dev.bus_priv; - unsigned long long ret; /* Not used when writing */ - union acpi_object in_obj[1]; - struct acpi_object_list args = { 1, in_obj }; - - in_obj[0].type = ACPI_TYPE_INTEGER; - in_obj[0].integer.value = !!value; - - acpi_evaluate_integer(dev->handle, "ALED", &args, &ret); -} - -static struct delayed_led_classdev hpled_led = { - .led_classdev = { - .name = "hp::hddprotect", - .default_trigger = "none", - .brightness_set = delayed_sysfs_set, - .flags = LED_CORE_SUSPENDRESUME, - }, - .set_brightness = hpled_set, -}; - -static acpi_status -lis3lv02d_get_resource(struct acpi_resource *resource, void *context) -{ - if (resource->type == ACPI_RESOURCE_TYPE_EXTENDED_IRQ) { - struct acpi_resource_extended_irq *irq; - u32 *device_irq = context; - - irq = &resource->data.extended_irq; - *device_irq = irq->interrupts[0]; - } - - return AE_OK; -} - -static void lis3lv02d_enum_resources(struct acpi_device *device) -{ - acpi_status status; - - status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, - lis3lv02d_get_resource, &lis3_dev.irq); - if (ACPI_FAILURE(status)) - printk(KERN_DEBUG DRIVER_NAME ": Error getting resources\n"); -} - -static int lis3lv02d_add(struct acpi_device *device) -{ - int ret; - - if (!device) - return -EINVAL; - - lis3_dev.bus_priv = device; - lis3_dev.init = lis3lv02d_acpi_init; - lis3_dev.read = lis3lv02d_acpi_read; - lis3_dev.write = lis3lv02d_acpi_write; - strcpy(acpi_device_name(device), DRIVER_NAME); - strcpy(acpi_device_class(device), ACPI_MDPS_CLASS); - device->driver_data = &lis3_dev; - - /* obtain IRQ number of our device from ACPI */ - lis3lv02d_enum_resources(device); - - /* If possible use a "standard" axes order */ - if (lis3_dev.ac.x && lis3_dev.ac.y && lis3_dev.ac.z) { - pr_info("Using custom axes %d,%d,%d\n", - lis3_dev.ac.x, lis3_dev.ac.y, lis3_dev.ac.z); - } else if (dmi_check_system(lis3lv02d_dmi_ids) == 0) { - pr_info("laptop model unknown, using default axes configuration\n"); - lis3_dev.ac = lis3lv02d_axis_normal; - } - - /* call the core layer do its init */ - ret = lis3lv02d_init_device(&lis3_dev); - if (ret) - return ret; - - INIT_WORK(&hpled_led.work, delayed_set_status_worker); - ret = led_classdev_register(NULL, &hpled_led.led_classdev); - if (ret) { - lis3lv02d_joystick_disable(); - lis3lv02d_poweroff(&lis3_dev); - flush_work(&hpled_led.work); - return ret; - } - - return ret; -} - -static int lis3lv02d_remove(struct acpi_device *device, int type) -{ - if (!device) - return -EINVAL; - - lis3lv02d_joystick_disable(); - lis3lv02d_poweroff(&lis3_dev); - - led_classdev_unregister(&hpled_led.led_classdev); - flush_work(&hpled_led.work); - - return lis3lv02d_remove_fs(&lis3_dev); -} - - -#ifdef CONFIG_PM -static int lis3lv02d_suspend(struct acpi_device *device, pm_message_t state) -{ - /* make sure the device is off when we suspend */ - lis3lv02d_poweroff(&lis3_dev); - return 0; -} - -static int lis3lv02d_resume(struct acpi_device *device) -{ - lis3lv02d_poweron(&lis3_dev); - return 0; -} -#else -#define lis3lv02d_suspend NULL -#define lis3lv02d_resume NULL -#endif - -/* For the HP MDPS aka 3D Driveguard */ -static struct acpi_driver lis3lv02d_driver = { - .name = DRIVER_NAME, - .class = ACPI_MDPS_CLASS, - .ids = lis3lv02d_device_ids, - .ops = { - .add = lis3lv02d_add, - .remove = lis3lv02d_remove, - .suspend = lis3lv02d_suspend, - .resume = lis3lv02d_resume, - } -}; - -static int __init lis3lv02d_init_module(void) -{ - int ret; - - if (acpi_disabled) - return -ENODEV; - - ret = acpi_bus_register_driver(&lis3lv02d_driver); - if (ret < 0) - return ret; - - pr_info("driver loaded\n"); - - return 0; -} - -static void __exit lis3lv02d_exit_module(void) -{ - acpi_bus_unregister_driver(&lis3lv02d_driver); -} - -MODULE_DESCRIPTION("Glue between LIS3LV02Dx and HP ACPI BIOS and support for disk protection LED."); -MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek"); -MODULE_LICENSE("GPL"); - -module_init(lis3lv02d_init_module); -module_exit(lis3lv02d_exit_module); - diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index a59af5b24f0..222dfb737b1 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -138,6 +138,24 @@ config TC1100_WMI This is a driver for the WMI extensions (wireless and bluetooth power control) of the HP Compaq TC1100 tablet. +config HP_ACCEL + tristate "HP laptop accelerometer" + depends on INPUT && ACPI + select SENSORS_LIS3LV02D + select NEW_LEDS + select LEDS_CLASS + help + This driver provides support for the "Mobile Data Protection System 3D" + or "3D DriveGuard" feature of HP laptops. On such systems the driver + should load automatically (via ACPI alias). + + Support for a led indicating disk protection will be provided as + hp::hddprotect. For more information on the feature, refer to + Documentation/hwmon/lis3lv02d. + + To compile this driver as a module, choose M here: the module will + be called hp_accel. + config HP_WMI tristate "HP WMI extras" depends on ACPI_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 4ec4ff8f918..299aefb3e74 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o obj-$(CONFIG_DELL_WMI) += dell-wmi.o obj-$(CONFIG_ACER_WMI) += acer-wmi.o obj-$(CONFIG_ACERHDF) += acerhdf.o +obj-$(CONFIG_HP_ACCEL) += hp_accel.o obj-$(CONFIG_HP_WMI) += hp-wmi.o obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c new file mode 100644 index 00000000000..23f09c1b234 --- /dev/null +++ b/drivers/platform/x86/hp_accel.c @@ -0,0 +1,404 @@ +/* + * hp_accel.c - Interface between LIS3LV02DL driver and HP ACPI BIOS + * + * Copyright (C) 2007-2008 Yan Burman + * Copyright (C) 2008 Eric Piel + * Copyright (C) 2008-2009 Pavel Machek + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../hwmon/lis3lv02d.h" + +#define DRIVER_NAME "lis3lv02d" +#define ACPI_MDPS_CLASS "accelerometer" + +/* Delayed LEDs infrastructure ------------------------------------ */ + +/* Special LED class that can defer work */ +struct delayed_led_classdev { + struct led_classdev led_classdev; + struct work_struct work; + enum led_brightness new_brightness; + + unsigned int led; /* For driver */ + void (*set_brightness)(struct delayed_led_classdev *data, enum led_brightness value); +}; + +static inline void delayed_set_status_worker(struct work_struct *work) +{ + struct delayed_led_classdev *data = + container_of(work, struct delayed_led_classdev, work); + + data->set_brightness(data, data->new_brightness); +} + +static inline void delayed_sysfs_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct delayed_led_classdev *data = container_of(led_cdev, + struct delayed_led_classdev, led_classdev); + data->new_brightness = brightness; + schedule_work(&data->work); +} + +/* HP-specific accelerometer driver ------------------------------------ */ + +/* For automatic insertion of the module */ +static struct acpi_device_id lis3lv02d_device_ids[] = { + {"HPQ0004", 0}, /* HP Mobile Data Protection System PNP */ + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, lis3lv02d_device_ids); + + +/** + * lis3lv02d_acpi_init - ACPI _INI method: initialize the device. + * @lis3: pointer to the device struct + * + * Returns 0 on success. + */ +int lis3lv02d_acpi_init(struct lis3lv02d *lis3) +{ + struct acpi_device *dev = lis3->bus_priv; + if (acpi_evaluate_object(dev->handle, METHOD_NAME__INI, + NULL, NULL) != AE_OK) + return -EINVAL; + + return 0; +} + +/** + * lis3lv02d_acpi_read - ACPI ALRD method: read a register + * @lis3: pointer to the device struct + * @reg: the register to read + * @ret: result of the operation + * + * Returns 0 on success. + */ +int lis3lv02d_acpi_read(struct lis3lv02d *lis3, int reg, u8 *ret) +{ + struct acpi_device *dev = lis3->bus_priv; + union acpi_object arg0 = { ACPI_TYPE_INTEGER }; + struct acpi_object_list args = { 1, &arg0 }; + unsigned long long lret; + acpi_status status; + + arg0.integer.value = reg; + + status = acpi_evaluate_integer(dev->handle, "ALRD", &args, &lret); + *ret = lret; + return (status != AE_OK) ? -EINVAL : 0; +} + +/** + * lis3lv02d_acpi_write - ACPI ALWR method: write to a register + * @lis3: pointer to the device struct + * @reg: the register to write to + * @val: the value to write + * + * Returns 0 on success. + */ +int lis3lv02d_acpi_write(struct lis3lv02d *lis3, int reg, u8 val) +{ + struct acpi_device *dev = lis3->bus_priv; + unsigned long long ret; /* Not used when writting */ + union acpi_object in_obj[2]; + struct acpi_object_list args = { 2, in_obj }; + + in_obj[0].type = ACPI_TYPE_INTEGER; + in_obj[0].integer.value = reg; + in_obj[1].type = ACPI_TYPE_INTEGER; + in_obj[1].integer.value = val; + + if (acpi_evaluate_integer(dev->handle, "ALWR", &args, &ret) != AE_OK) + return -EINVAL; + + return 0; +} + +static int lis3lv02d_dmi_matched(const struct dmi_system_id *dmi) +{ + lis3_dev.ac = *((union axis_conversion *)dmi->driver_data); + pr_info("hardware type %s found\n", dmi->ident); + + return 1; +} + +/* Represents, for each axis seen by userspace, the corresponding hw axis (+1). + * If the value is negative, the opposite of the hw value is used. */ +#define DEFINE_CONV(name, x, y, z) \ + static union axis_conversion lis3lv02d_axis_##name = \ + { .as_array = { x, y, z } } +DEFINE_CONV(normal, 1, 2, 3); +DEFINE_CONV(y_inverted, 1, -2, 3); +DEFINE_CONV(x_inverted, -1, 2, 3); +DEFINE_CONV(z_inverted, 1, 2, -3); +DEFINE_CONV(xy_swap, 2, 1, 3); +DEFINE_CONV(xy_rotated_left, -2, 1, 3); +DEFINE_CONV(xy_rotated_left_usd, -2, 1, -3); +DEFINE_CONV(xy_swap_inverted, -2, -1, 3); +DEFINE_CONV(xy_rotated_right, 2, -1, 3); +DEFINE_CONV(xy_swap_yz_inverted, 2, -1, -3); + +#define AXIS_DMI_MATCH(_ident, _name, _axis) { \ + .ident = _ident, \ + .callback = lis3lv02d_dmi_matched, \ + .matches = { \ + DMI_MATCH(DMI_PRODUCT_NAME, _name) \ + }, \ + .driver_data = &lis3lv02d_axis_##_axis \ +} + +#define AXIS_DMI_MATCH2(_ident, _class1, _name1, \ + _class2, _name2, \ + _axis) { \ + .ident = _ident, \ + .callback = lis3lv02d_dmi_matched, \ + .matches = { \ + DMI_MATCH(DMI_##_class1, _name1), \ + DMI_MATCH(DMI_##_class2, _name2), \ + }, \ + .driver_data = &lis3lv02d_axis_##_axis \ +} +static struct dmi_system_id lis3lv02d_dmi_ids[] = { + /* product names are truncated to match all kinds of a same model */ + AXIS_DMI_MATCH("NC64x0", "HP Compaq nc64", x_inverted), + AXIS_DMI_MATCH("NC84x0", "HP Compaq nc84", z_inverted), + AXIS_DMI_MATCH("NX9420", "HP Compaq nx9420", x_inverted), + AXIS_DMI_MATCH("NW9440", "HP Compaq nw9440", x_inverted), + AXIS_DMI_MATCH("NC2510", "HP Compaq 2510", y_inverted), + AXIS_DMI_MATCH("NC2710", "HP Compaq 2710", xy_swap), + AXIS_DMI_MATCH("NC8510", "HP Compaq 8510", xy_swap_inverted), + AXIS_DMI_MATCH("HP2133", "HP 2133", xy_rotated_left), + AXIS_DMI_MATCH("HP2140", "HP 2140", xy_swap_inverted), + AXIS_DMI_MATCH("NC653x", "HP Compaq 653", xy_rotated_left_usd), + AXIS_DMI_MATCH("NC6730b", "HP Compaq 6730b", xy_rotated_left_usd), + AXIS_DMI_MATCH("NC6730s", "HP Compaq 6730s", xy_swap), + AXIS_DMI_MATCH("NC651xx", "HP Compaq 651", xy_rotated_right), + AXIS_DMI_MATCH("NC6710x", "HP Compaq 6710", xy_swap_yz_inverted), + AXIS_DMI_MATCH("NC6715x", "HP Compaq 6715", y_inverted), + AXIS_DMI_MATCH("NC693xx", "HP EliteBook 693", xy_rotated_right), + AXIS_DMI_MATCH("NC693xx", "HP EliteBook 853", xy_swap), + /* Intel-based HP Pavilion dv5 */ + AXIS_DMI_MATCH2("HPDV5_I", + PRODUCT_NAME, "HP Pavilion dv5", + BOARD_NAME, "3603", + x_inverted), + /* AMD-based HP Pavilion dv5 */ + AXIS_DMI_MATCH2("HPDV5_A", + PRODUCT_NAME, "HP Pavilion dv5", + BOARD_NAME, "3600", + y_inverted), + AXIS_DMI_MATCH("DV7", "HP Pavilion dv7", x_inverted), + AXIS_DMI_MATCH("HP8710", "HP Compaq 8710", y_inverted), + AXIS_DMI_MATCH("HDX18", "HP HDX 18", x_inverted), + AXIS_DMI_MATCH("HPB432x", "HP ProBook 432", xy_rotated_left), + AXIS_DMI_MATCH("HPB442x", "HP ProBook 442", xy_rotated_left), + AXIS_DMI_MATCH("HPB452x", "HP ProBook 452", y_inverted), + AXIS_DMI_MATCH("HPB522x", "HP ProBook 522", xy_swap), + AXIS_DMI_MATCH("HPB532x", "HP ProBook 532", y_inverted), + AXIS_DMI_MATCH("Mini510x", "HP Mini 510", xy_rotated_left_usd), + { NULL, } +/* Laptop models without axis info (yet): + * "NC6910" "HP Compaq 6910" + * "NC2400" "HP Compaq nc2400" + * "NX74x0" "HP Compaq nx74" + * "NX6325" "HP Compaq nx6325" + * "NC4400" "HP Compaq nc4400" + */ +}; + +static void hpled_set(struct delayed_led_classdev *led_cdev, enum led_brightness value) +{ + struct acpi_device *dev = lis3_dev.bus_priv; + unsigned long long ret; /* Not used when writing */ + union acpi_object in_obj[1]; + struct acpi_object_list args = { 1, in_obj }; + + in_obj[0].type = ACPI_TYPE_INTEGER; + in_obj[0].integer.value = !!value; + + acpi_evaluate_integer(dev->handle, "ALED", &args, &ret); +} + +static struct delayed_led_classdev hpled_led = { + .led_classdev = { + .name = "hp::hddprotect", + .default_trigger = "none", + .brightness_set = delayed_sysfs_set, + .flags = LED_CORE_SUSPENDRESUME, + }, + .set_brightness = hpled_set, +}; + +static acpi_status +lis3lv02d_get_resource(struct acpi_resource *resource, void *context) +{ + if (resource->type == ACPI_RESOURCE_TYPE_EXTENDED_IRQ) { + struct acpi_resource_extended_irq *irq; + u32 *device_irq = context; + + irq = &resource->data.extended_irq; + *device_irq = irq->interrupts[0]; + } + + return AE_OK; +} + +static void lis3lv02d_enum_resources(struct acpi_device *device) +{ + acpi_status status; + + status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, + lis3lv02d_get_resource, &lis3_dev.irq); + if (ACPI_FAILURE(status)) + printk(KERN_DEBUG DRIVER_NAME ": Error getting resources\n"); +} + +static int lis3lv02d_add(struct acpi_device *device) +{ + int ret; + + if (!device) + return -EINVAL; + + lis3_dev.bus_priv = device; + lis3_dev.init = lis3lv02d_acpi_init; + lis3_dev.read = lis3lv02d_acpi_read; + lis3_dev.write = lis3lv02d_acpi_write; + strcpy(acpi_device_name(device), DRIVER_NAME); + strcpy(acpi_device_class(device), ACPI_MDPS_CLASS); + device->driver_data = &lis3_dev; + + /* obtain IRQ number of our device from ACPI */ + lis3lv02d_enum_resources(device); + + /* If possible use a "standard" axes order */ + if (lis3_dev.ac.x && lis3_dev.ac.y && lis3_dev.ac.z) { + pr_info("Using custom axes %d,%d,%d\n", + lis3_dev.ac.x, lis3_dev.ac.y, lis3_dev.ac.z); + } else if (dmi_check_system(lis3lv02d_dmi_ids) == 0) { + pr_info("laptop model unknown, using default axes configuration\n"); + lis3_dev.ac = lis3lv02d_axis_normal; + } + + /* call the core layer do its init */ + ret = lis3lv02d_init_device(&lis3_dev); + if (ret) + return ret; + + INIT_WORK(&hpled_led.work, delayed_set_status_worker); + ret = led_classdev_register(NULL, &hpled_led.led_classdev); + if (ret) { + lis3lv02d_joystick_disable(); + lis3lv02d_poweroff(&lis3_dev); + flush_work(&hpled_led.work); + return ret; + } + + return ret; +} + +static int lis3lv02d_remove(struct acpi_device *device, int type) +{ + if (!device) + return -EINVAL; + + lis3lv02d_joystick_disable(); + lis3lv02d_poweroff(&lis3_dev); + + led_classdev_unregister(&hpled_led.led_classdev); + flush_work(&hpled_led.work); + + return lis3lv02d_remove_fs(&lis3_dev); +} + + +#ifdef CONFIG_PM +static int lis3lv02d_suspend(struct acpi_device *device, pm_message_t state) +{ + /* make sure the device is off when we suspend */ + lis3lv02d_poweroff(&lis3_dev); + return 0; +} + +static int lis3lv02d_resume(struct acpi_device *device) +{ + lis3lv02d_poweron(&lis3_dev); + return 0; +} +#else +#define lis3lv02d_suspend NULL +#define lis3lv02d_resume NULL +#endif + +/* For the HP MDPS aka 3D Driveguard */ +static struct acpi_driver lis3lv02d_driver = { + .name = DRIVER_NAME, + .class = ACPI_MDPS_CLASS, + .ids = lis3lv02d_device_ids, + .ops = { + .add = lis3lv02d_add, + .remove = lis3lv02d_remove, + .suspend = lis3lv02d_suspend, + .resume = lis3lv02d_resume, + } +}; + +static int __init lis3lv02d_init_module(void) +{ + int ret; + + if (acpi_disabled) + return -ENODEV; + + ret = acpi_bus_register_driver(&lis3lv02d_driver); + if (ret < 0) + return ret; + + pr_info("driver loaded\n"); + + return 0; +} + +static void __exit lis3lv02d_exit_module(void) +{ + acpi_bus_unregister_driver(&lis3lv02d_driver); +} + +MODULE_DESCRIPTION("Glue between LIS3LV02Dx and HP ACPI BIOS and support for disk protection LED."); +MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek"); +MODULE_LICENSE("GPL"); + +module_init(lis3lv02d_init_module); +module_exit(lis3lv02d_exit_module); -- cgit v1.2.3-70-g09d2 From ff606677f6a47c63329cf8e6c7cf978c29f2d736 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: Move lis3lv02d drivers to drivers/misc The lis3lv02d drivers aren't hardware monitoring drivers, so the don't belong to drivers/hwmon. Move them to drivers/misc, short of a better home. Signed-off-by: Jean Delvare Acked-by: Guenter Roeck Acked-by: Eric Piel Acked-by: Jonathan Cameron Tested-by: Eric Piel Tested-by: Takashi Iwai --- Documentation/hwmon/lis3lv02d | 92 --- Documentation/misc-devices/lis3lv02d | 92 +++ MAINTAINERS | 4 +- drivers/hwmon/Kconfig | 40 -- drivers/hwmon/Makefile | 5 - drivers/hwmon/lis3lv02d.c | 1000 -------------------------------- drivers/hwmon/lis3lv02d.h | 291 ---------- drivers/hwmon/lis3lv02d_i2c.c | 279 --------- drivers/hwmon/lis3lv02d_spi.c | 145 ----- drivers/misc/Kconfig | 9 + drivers/misc/Makefile | 1 + drivers/misc/lis3lv02d/Kconfig | 37 ++ drivers/misc/lis3lv02d/Makefile | 7 + drivers/misc/lis3lv02d/lis3lv02d.c | 999 +++++++++++++++++++++++++++++++ drivers/misc/lis3lv02d/lis3lv02d.h | 291 ++++++++++ drivers/misc/lis3lv02d/lis3lv02d_i2c.c | 279 +++++++++ drivers/misc/lis3lv02d/lis3lv02d_spi.c | 145 +++++ drivers/platform/x86/hp_accel.c | 2 +- 18 files changed, 1863 insertions(+), 1855 deletions(-) delete mode 100644 Documentation/hwmon/lis3lv02d create mode 100644 Documentation/misc-devices/lis3lv02d delete mode 100644 drivers/hwmon/lis3lv02d.c delete mode 100644 drivers/hwmon/lis3lv02d.h delete mode 100644 drivers/hwmon/lis3lv02d_i2c.c delete mode 100644 drivers/hwmon/lis3lv02d_spi.c create mode 100644 drivers/misc/lis3lv02d/Kconfig create mode 100644 drivers/misc/lis3lv02d/Makefile create mode 100644 drivers/misc/lis3lv02d/lis3lv02d.c create mode 100644 drivers/misc/lis3lv02d/lis3lv02d.h create mode 100644 drivers/misc/lis3lv02d/lis3lv02d_i2c.c create mode 100644 drivers/misc/lis3lv02d/lis3lv02d_spi.c (limited to 'drivers/hwmon') diff --git a/Documentation/hwmon/lis3lv02d b/Documentation/hwmon/lis3lv02d deleted file mode 100644 index f1a4ec840f8..00000000000 --- a/Documentation/hwmon/lis3lv02d +++ /dev/null @@ -1,92 +0,0 @@ -Kernel driver lis3lv02d -======================= - -Supported chips: - - * STMicroelectronics LIS3LV02DL, LIS3LV02DQ (12 bits precision) - * STMicroelectronics LIS302DL, LIS3L02DQ, LIS331DL (8 bits) - -Authors: - Yan Burman - Eric Piel - - -Description ------------ - -This driver provides support for the accelerometer found in various HP laptops -sporting the feature officially called "HP Mobile Data Protection System 3D" or -"HP 3D DriveGuard". It detects automatically laptops with this sensor. Known -models (full list can be found in drivers/platform/x86/hp_accel.c) will have -their axis automatically oriented on standard way (eg: you can directly play -neverball). The accelerometer data is readable via -/sys/devices/platform/lis3lv02d. Reported values are scaled -to mg values (1/1000th of earth gravity). - -Sysfs attributes under /sys/devices/platform/lis3lv02d/: -position - 3D position that the accelerometer reports. Format: "(x,y,z)" -rate - read reports the sampling rate of the accelerometer device in HZ. - write changes sampling rate of the accelerometer device. - Only values which are supported by HW are accepted. -selftest - performs selftest for the chip as specified by chip manufacturer. - -This driver also provides an absolute input class device, allowing -the laptop to act as a pinball machine-esque joystick. Joystick device can be -calibrated. Joystick device can be in two different modes. -By default output values are scaled between -32768 .. 32767. In joystick raw -mode, joystick and sysfs position entry have the same scale. There can be -small difference due to input system fuzziness feature. -Events are also available as input event device. - -Selftest is meant only for hardware diagnostic purposes. It is not meant to be -used during normal operations. Position data is not corrupted during selftest -but interrupt behaviour is not guaranteed to work reliably. In test mode, the -sensing element is internally moved little bit. Selftest measures difference -between normal mode and test mode. Chip specifications tell the acceptance -limit for each type of the chip. Limits are provided via platform data -to allow adjustment of the limits without a change to the actual driver. -Seltest returns either "OK x y z" or "FAIL x y z" where x, y and z are -measured difference between modes. Axes are not remapped in selftest mode. -Measurement values are provided to help HW diagnostic applications to make -final decision. - -On HP laptops, if the led infrastructure is activated, support for a led -indicating disk protection will be provided as /sys/class/leds/hp::hddprotect. - -Another feature of the driver is misc device called "freefall" that -acts similar to /dev/rtc and reacts on free-fall interrupts received -from the device. It supports blocking operations, poll/select and -fasync operation modes. You must read 1 bytes from the device. The -result is number of free-fall interrupts since the last successful -read (or 255 if number of interrupts would not fit). See the hpfall.c -file for an example on using the device. - - -Axes orientation ----------------- - -For better compatibility between the various laptops. The values reported by -the accelerometer are converted into a "standard" organisation of the axes -(aka "can play neverball out of the box"): - * When the laptop is horizontal the position reported is about 0 for X and Y - and a positive value for Z - * If the left side is elevated, X increases (becomes positive) - * If the front side (where the touchpad is) is elevated, Y decreases - (becomes negative) - * If the laptop is put upside-down, Z becomes negative - -If your laptop model is not recognized (cf "dmesg"), you can send an -email to the maintainer to add it to the database. When reporting a new -laptop, please include the output of "dmidecode" plus the value of -/sys/devices/platform/lis3lv02d/position in these four cases. - -Q&A ---- - -Q: How do I safely simulate freefall? I have an HP "portable -workstation" which has about 3.5kg and a plastic case, so letting it -fall to the ground is out of question... - -A: The sensor is pretty sensitive, so your hands can do it. Lift it -into free space, follow the fall with your hands for like 10 -centimeters. That should be enough to trigger the detection. diff --git a/Documentation/misc-devices/lis3lv02d b/Documentation/misc-devices/lis3lv02d new file mode 100644 index 00000000000..f1a4ec840f8 --- /dev/null +++ b/Documentation/misc-devices/lis3lv02d @@ -0,0 +1,92 @@ +Kernel driver lis3lv02d +======================= + +Supported chips: + + * STMicroelectronics LIS3LV02DL, LIS3LV02DQ (12 bits precision) + * STMicroelectronics LIS302DL, LIS3L02DQ, LIS331DL (8 bits) + +Authors: + Yan Burman + Eric Piel + + +Description +----------- + +This driver provides support for the accelerometer found in various HP laptops +sporting the feature officially called "HP Mobile Data Protection System 3D" or +"HP 3D DriveGuard". It detects automatically laptops with this sensor. Known +models (full list can be found in drivers/platform/x86/hp_accel.c) will have +their axis automatically oriented on standard way (eg: you can directly play +neverball). The accelerometer data is readable via +/sys/devices/platform/lis3lv02d. Reported values are scaled +to mg values (1/1000th of earth gravity). + +Sysfs attributes under /sys/devices/platform/lis3lv02d/: +position - 3D position that the accelerometer reports. Format: "(x,y,z)" +rate - read reports the sampling rate of the accelerometer device in HZ. + write changes sampling rate of the accelerometer device. + Only values which are supported by HW are accepted. +selftest - performs selftest for the chip as specified by chip manufacturer. + +This driver also provides an absolute input class device, allowing +the laptop to act as a pinball machine-esque joystick. Joystick device can be +calibrated. Joystick device can be in two different modes. +By default output values are scaled between -32768 .. 32767. In joystick raw +mode, joystick and sysfs position entry have the same scale. There can be +small difference due to input system fuzziness feature. +Events are also available as input event device. + +Selftest is meant only for hardware diagnostic purposes. It is not meant to be +used during normal operations. Position data is not corrupted during selftest +but interrupt behaviour is not guaranteed to work reliably. In test mode, the +sensing element is internally moved little bit. Selftest measures difference +between normal mode and test mode. Chip specifications tell the acceptance +limit for each type of the chip. Limits are provided via platform data +to allow adjustment of the limits without a change to the actual driver. +Seltest returns either "OK x y z" or "FAIL x y z" where x, y and z are +measured difference between modes. Axes are not remapped in selftest mode. +Measurement values are provided to help HW diagnostic applications to make +final decision. + +On HP laptops, if the led infrastructure is activated, support for a led +indicating disk protection will be provided as /sys/class/leds/hp::hddprotect. + +Another feature of the driver is misc device called "freefall" that +acts similar to /dev/rtc and reacts on free-fall interrupts received +from the device. It supports blocking operations, poll/select and +fasync operation modes. You must read 1 bytes from the device. The +result is number of free-fall interrupts since the last successful +read (or 255 if number of interrupts would not fit). See the hpfall.c +file for an example on using the device. + + +Axes orientation +---------------- + +For better compatibility between the various laptops. The values reported by +the accelerometer are converted into a "standard" organisation of the axes +(aka "can play neverball out of the box"): + * When the laptop is horizontal the position reported is about 0 for X and Y + and a positive value for Z + * If the left side is elevated, X increases (becomes positive) + * If the front side (where the touchpad is) is elevated, Y decreases + (becomes negative) + * If the laptop is put upside-down, Z becomes negative + +If your laptop model is not recognized (cf "dmesg"), you can send an +email to the maintainer to add it to the database. When reporting a new +laptop, please include the output of "dmidecode" plus the value of +/sys/devices/platform/lis3lv02d/position in these four cases. + +Q&A +--- + +Q: How do I safely simulate freefall? I have an HP "portable +workstation" which has about 3.5kg and a plastic case, so letting it +fall to the ground is out of question... + +A: The sensor is pretty sensitive, so your hands can do it. Lift it +into free space, follow the fall with your hands for like 10 +centimeters. That should be enough to trigger the detection. diff --git a/MAINTAINERS b/MAINTAINERS index c7a41b1fe45..4edb2a800b5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3913,8 +3913,8 @@ S: Supported LIS3LV02D ACCELEROMETER DRIVER M: Eric Piel S: Maintained -F: Documentation/hwmon/lis3lv02d -F: drivers/hwmon/lis3lv02d.* +F: Documentation/misc-devices/lis3lv02d +F: drivers/misc/lis3lv02d/ LLC (802.2) M: Arnaldo Carvalho de Melo diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 3f8c895417d..47621abb9a0 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2,12 +2,6 @@ # Hardware monitoring chip drivers configuration # -config SENSORS_LIS3LV02D - tristate - depends on INPUT - select INPUT_POLLDEV - default n - menuconfig HWMON tristate "Hardware Monitoring support" depends on HAS_IOMEM @@ -1221,40 +1215,6 @@ config SENSORS_ULTRA45 This driver provides support for the Ultra45 workstation environmental sensors. -config SENSORS_LIS3_SPI - tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (SPI)" - depends on !ACPI && SPI_MASTER && INPUT - select SENSORS_LIS3LV02D - default n - help - This driver provides support for the LIS3LV02Dx accelerometer connected - via SPI. The accelerometer data is readable via - /sys/devices/platform/lis3lv02d. - - This driver also provides an absolute input class device, allowing - the laptop to act as a pinball machine-esque joystick. - - This driver can also be built as modules. If so, the core module - will be called lis3lv02d and a specific module for the SPI transport - is called lis3lv02d_spi. - -config SENSORS_LIS3_I2C - tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (I2C)" - depends on I2C && INPUT - select SENSORS_LIS3LV02D - default n - help - This driver provides support for the LIS3LV02Dx accelerometer connected - via I2C. The accelerometer data is readable via - /sys/devices/platform/lis3lv02d. - - This driver also provides an absolute input class device, allowing - the device to act as a pinball machine-esque joystick. - - This driver can also be built as modules. If so, the core module - will be called lis3lv02d and a specific module for the I2C transport - is called lis3lv02d_i2c. - config SENSORS_APPLESMC tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)" depends on INPUT && X86 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 55ba906def7..c068f82082c 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -5,9 +5,6 @@ obj-$(CONFIG_HWMON) += hwmon.o obj-$(CONFIG_HWMON_VID) += hwmon-vid.o -# Helper drivers -obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o - # APCI drivers obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o @@ -66,8 +63,6 @@ obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o -obj-$(CONFIG_SENSORS_LIS3_SPI) += lis3lv02d_spi.o -obj-$(CONFIG_SENSORS_LIS3_I2C) += lis3lv02d_i2c.o obj-$(CONFIG_SENSORS_LM63) += lm63.o obj-$(CONFIG_SENSORS_LM70) += lm70.o obj-$(CONFIG_SENSORS_LM73) += lm73.o diff --git a/drivers/hwmon/lis3lv02d.c b/drivers/hwmon/lis3lv02d.c deleted file mode 100644 index d805e8e5796..00000000000 --- a/drivers/hwmon/lis3lv02d.c +++ /dev/null @@ -1,1000 +0,0 @@ -/* - * lis3lv02d.c - ST LIS3LV02DL accelerometer driver - * - * Copyright (C) 2007-2008 Yan Burman - * Copyright (C) 2008 Eric Piel - * Copyright (C) 2008-2009 Pavel Machek - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "lis3lv02d.h" - -#define DRIVER_NAME "lis3lv02d" - -/* joystick device poll interval in milliseconds */ -#define MDPS_POLL_INTERVAL 50 -#define MDPS_POLL_MIN 0 -#define MDPS_POLL_MAX 2000 - -#define LIS3_SYSFS_POWERDOWN_DELAY 5000 /* In milliseconds */ - -#define SELFTEST_OK 0 -#define SELFTEST_FAIL -1 -#define SELFTEST_IRQ -2 - -#define IRQ_LINE0 0 -#define IRQ_LINE1 1 - -/* - * The sensor can also generate interrupts (DRDY) but it's pretty pointless - * because they are generated even if the data do not change. So it's better - * to keep the interrupt for the free-fall event. The values are updated at - * 40Hz (at the lowest frequency), but as it can be pretty time consuming on - * some low processor, we poll the sensor only at 20Hz... enough for the - * joystick. - */ - -#define LIS3_PWRON_DELAY_WAI_12B (5000) -#define LIS3_PWRON_DELAY_WAI_8B (3000) - -/* - * LIS3LV02D spec says 1024 LSBs corresponds 1 G -> 1LSB is 1000/1024 mG - * LIS302D spec says: 18 mG / digit - * LIS3_ACCURACY is used to increase accuracy of the intermediate - * calculation results. - */ -#define LIS3_ACCURACY 1024 -/* Sensitivity values for -2G +2G scale */ -#define LIS3_SENSITIVITY_12B ((LIS3_ACCURACY * 1000) / 1024) -#define LIS3_SENSITIVITY_8B (18 * LIS3_ACCURACY) - -#define LIS3_DEFAULT_FUZZ_12B 3 -#define LIS3_DEFAULT_FLAT_12B 3 -#define LIS3_DEFAULT_FUZZ_8B 1 -#define LIS3_DEFAULT_FLAT_8B 1 - -struct lis3lv02d lis3_dev = { - .misc_wait = __WAIT_QUEUE_HEAD_INITIALIZER(lis3_dev.misc_wait), -}; - -EXPORT_SYMBOL_GPL(lis3_dev); - -/* just like param_set_int() but does sanity-check so that it won't point - * over the axis array size - */ -static int param_set_axis(const char *val, const struct kernel_param *kp) -{ - int ret = param_set_int(val, kp); - if (!ret) { - int val = *(int *)kp->arg; - if (val < 0) - val = -val; - if (!val || val > 3) - return -EINVAL; - } - return ret; -} - -static struct kernel_param_ops param_ops_axis = { - .set = param_set_axis, - .get = param_get_int, -}; - -module_param_array_named(axes, lis3_dev.ac.as_array, axis, NULL, 0644); -MODULE_PARM_DESC(axes, "Axis-mapping for x,y,z directions"); - -static s16 lis3lv02d_read_8(struct lis3lv02d *lis3, int reg) -{ - s8 lo; - if (lis3->read(lis3, reg, &lo) < 0) - return 0; - - return lo; -} - -static s16 lis3lv02d_read_12(struct lis3lv02d *lis3, int reg) -{ - u8 lo, hi; - - lis3->read(lis3, reg - 1, &lo); - lis3->read(lis3, reg, &hi); - /* In "12 bit right justified" mode, bit 6, bit 7, bit 8 = bit 5 */ - return (s16)((hi << 8) | lo); -} - -/** - * lis3lv02d_get_axis - For the given axis, give the value converted - * @axis: 1,2,3 - can also be negative - * @hw_values: raw values returned by the hardware - * - * Returns the converted value. - */ -static inline int lis3lv02d_get_axis(s8 axis, int hw_values[3]) -{ - if (axis > 0) - return hw_values[axis - 1]; - else - return -hw_values[-axis - 1]; -} - -/** - * lis3lv02d_get_xyz - Get X, Y and Z axis values from the accelerometer - * @lis3: pointer to the device struct - * @x: where to store the X axis value - * @y: where to store the Y axis value - * @z: where to store the Z axis value - * - * Note that 40Hz input device can eat up about 10% CPU at 800MHZ - */ -static void lis3lv02d_get_xyz(struct lis3lv02d *lis3, int *x, int *y, int *z) -{ - int position[3]; - int i; - - if (lis3->blkread) { - if (lis3_dev.whoami == WAI_12B) { - u16 data[3]; - lis3->blkread(lis3, OUTX_L, 6, (u8 *)data); - for (i = 0; i < 3; i++) - position[i] = (s16)le16_to_cpu(data[i]); - } else { - u8 data[5]; - /* Data: x, dummy, y, dummy, z */ - lis3->blkread(lis3, OUTX, 5, data); - for (i = 0; i < 3; i++) - position[i] = (s8)data[i * 2]; - } - } else { - position[0] = lis3->read_data(lis3, OUTX); - position[1] = lis3->read_data(lis3, OUTY); - position[2] = lis3->read_data(lis3, OUTZ); - } - - for (i = 0; i < 3; i++) - position[i] = (position[i] * lis3->scale) / LIS3_ACCURACY; - - *x = lis3lv02d_get_axis(lis3->ac.x, position); - *y = lis3lv02d_get_axis(lis3->ac.y, position); - *z = lis3lv02d_get_axis(lis3->ac.z, position); -} - -/* conversion btw sampling rate and the register values */ -static int lis3_12_rates[4] = {40, 160, 640, 2560}; -static int lis3_8_rates[2] = {100, 400}; -static int lis3_3dc_rates[16] = {0, 1, 10, 25, 50, 100, 200, 400, 1600, 5000}; - -/* ODR is Output Data Rate */ -static int lis3lv02d_get_odr(void) -{ - u8 ctrl; - int shift; - - lis3_dev.read(&lis3_dev, CTRL_REG1, &ctrl); - ctrl &= lis3_dev.odr_mask; - shift = ffs(lis3_dev.odr_mask) - 1; - return lis3_dev.odrs[(ctrl >> shift)]; -} - -static int lis3lv02d_set_odr(int rate) -{ - u8 ctrl; - int i, len, shift; - - if (!rate) - return -EINVAL; - - lis3_dev.read(&lis3_dev, CTRL_REG1, &ctrl); - ctrl &= ~lis3_dev.odr_mask; - len = 1 << hweight_long(lis3_dev.odr_mask); /* # of possible values */ - shift = ffs(lis3_dev.odr_mask) - 1; - - for (i = 0; i < len; i++) - if (lis3_dev.odrs[i] == rate) { - lis3_dev.write(&lis3_dev, CTRL_REG1, - ctrl | (i << shift)); - return 0; - } - return -EINVAL; -} - -static int lis3lv02d_selftest(struct lis3lv02d *lis3, s16 results[3]) -{ - u8 ctlreg, reg; - s16 x, y, z; - u8 selftest; - int ret; - u8 ctrl_reg_data; - unsigned char irq_cfg; - - mutex_lock(&lis3->mutex); - - irq_cfg = lis3->irq_cfg; - if (lis3_dev.whoami == WAI_8B) { - lis3->data_ready_count[IRQ_LINE0] = 0; - lis3->data_ready_count[IRQ_LINE1] = 0; - - /* Change interrupt cfg to data ready for selftest */ - atomic_inc(&lis3_dev.wake_thread); - lis3->irq_cfg = LIS3_IRQ1_DATA_READY | LIS3_IRQ2_DATA_READY; - lis3->read(lis3, CTRL_REG3, &ctrl_reg_data); - lis3->write(lis3, CTRL_REG3, (ctrl_reg_data & - ~(LIS3_IRQ1_MASK | LIS3_IRQ2_MASK)) | - (LIS3_IRQ1_DATA_READY | LIS3_IRQ2_DATA_READY)); - } - - if (lis3_dev.whoami == WAI_3DC) { - ctlreg = CTRL_REG4; - selftest = CTRL4_ST0; - } else { - ctlreg = CTRL_REG1; - if (lis3_dev.whoami == WAI_12B) - selftest = CTRL1_ST; - else - selftest = CTRL1_STP; - } - - lis3->read(lis3, ctlreg, ®); - lis3->write(lis3, ctlreg, (reg | selftest)); - msleep(lis3->pwron_delay / lis3lv02d_get_odr()); - - /* Read directly to avoid axis remap */ - x = lis3->read_data(lis3, OUTX); - y = lis3->read_data(lis3, OUTY); - z = lis3->read_data(lis3, OUTZ); - - /* back to normal settings */ - lis3->write(lis3, ctlreg, reg); - msleep(lis3->pwron_delay / lis3lv02d_get_odr()); - - results[0] = x - lis3->read_data(lis3, OUTX); - results[1] = y - lis3->read_data(lis3, OUTY); - results[2] = z - lis3->read_data(lis3, OUTZ); - - ret = 0; - - if (lis3_dev.whoami == WAI_8B) { - /* Restore original interrupt configuration */ - atomic_dec(&lis3_dev.wake_thread); - lis3->write(lis3, CTRL_REG3, ctrl_reg_data); - lis3->irq_cfg = irq_cfg; - - if ((irq_cfg & LIS3_IRQ1_MASK) && - lis3->data_ready_count[IRQ_LINE0] < 2) { - ret = SELFTEST_IRQ; - goto fail; - } - - if ((irq_cfg & LIS3_IRQ2_MASK) && - lis3->data_ready_count[IRQ_LINE1] < 2) { - ret = SELFTEST_IRQ; - goto fail; - } - } - - if (lis3->pdata) { - int i; - for (i = 0; i < 3; i++) { - /* Check against selftest acceptance limits */ - if ((results[i] < lis3->pdata->st_min_limits[i]) || - (results[i] > lis3->pdata->st_max_limits[i])) { - ret = SELFTEST_FAIL; - goto fail; - } - } - } - - /* test passed */ -fail: - mutex_unlock(&lis3->mutex); - return ret; -} - -/* - * Order of registers in the list affects to order of the restore process. - * Perhaps it is a good idea to set interrupt enable register as a last one - * after all other configurations - */ -static u8 lis3_wai8_regs[] = { FF_WU_CFG_1, FF_WU_THS_1, FF_WU_DURATION_1, - FF_WU_CFG_2, FF_WU_THS_2, FF_WU_DURATION_2, - CLICK_CFG, CLICK_SRC, CLICK_THSY_X, CLICK_THSZ, - CLICK_TIMELIMIT, CLICK_LATENCY, CLICK_WINDOW, - CTRL_REG1, CTRL_REG2, CTRL_REG3}; - -static u8 lis3_wai12_regs[] = {FF_WU_CFG, FF_WU_THS_L, FF_WU_THS_H, - FF_WU_DURATION, DD_CFG, DD_THSI_L, DD_THSI_H, - DD_THSE_L, DD_THSE_H, - CTRL_REG1, CTRL_REG3, CTRL_REG2}; - -static inline void lis3_context_save(struct lis3lv02d *lis3) -{ - int i; - for (i = 0; i < lis3->regs_size; i++) - lis3->read(lis3, lis3->regs[i], &lis3->reg_cache[i]); - lis3->regs_stored = true; -} - -static inline void lis3_context_restore(struct lis3lv02d *lis3) -{ - int i; - if (lis3->regs_stored) - for (i = 0; i < lis3->regs_size; i++) - lis3->write(lis3, lis3->regs[i], lis3->reg_cache[i]); -} - -void lis3lv02d_poweroff(struct lis3lv02d *lis3) -{ - if (lis3->reg_ctrl) - lis3_context_save(lis3); - /* disable X,Y,Z axis and power down */ - lis3->write(lis3, CTRL_REG1, 0x00); - if (lis3->reg_ctrl) - lis3->reg_ctrl(lis3, LIS3_REG_OFF); -} -EXPORT_SYMBOL_GPL(lis3lv02d_poweroff); - -void lis3lv02d_poweron(struct lis3lv02d *lis3) -{ - u8 reg; - - lis3->init(lis3); - - /* - * Common configuration - * BDU: (12 bits sensors only) LSB and MSB values are not updated until - * both have been read. So the value read will always be correct. - * Set BOOT bit to refresh factory tuning values. - */ - lis3->read(lis3, CTRL_REG2, ®); - if (lis3->whoami == WAI_12B) - reg |= CTRL2_BDU | CTRL2_BOOT; - else - reg |= CTRL2_BOOT_8B; - lis3->write(lis3, CTRL_REG2, reg); - - /* LIS3 power on delay is quite long */ - msleep(lis3->pwron_delay / lis3lv02d_get_odr()); - - if (lis3->reg_ctrl) - lis3_context_restore(lis3); -} -EXPORT_SYMBOL_GPL(lis3lv02d_poweron); - - -static void lis3lv02d_joystick_poll(struct input_polled_dev *pidev) -{ - int x, y, z; - - mutex_lock(&lis3_dev.mutex); - lis3lv02d_get_xyz(&lis3_dev, &x, &y, &z); - input_report_abs(pidev->input, ABS_X, x); - input_report_abs(pidev->input, ABS_Y, y); - input_report_abs(pidev->input, ABS_Z, z); - input_sync(pidev->input); - mutex_unlock(&lis3_dev.mutex); -} - -static void lis3lv02d_joystick_open(struct input_polled_dev *pidev) -{ - if (lis3_dev.pm_dev) - pm_runtime_get_sync(lis3_dev.pm_dev); - - if (lis3_dev.pdata && lis3_dev.whoami == WAI_8B && lis3_dev.idev) - atomic_set(&lis3_dev.wake_thread, 1); - /* - * Update coordinates for the case where poll interval is 0 and - * the chip in running purely under interrupt control - */ - lis3lv02d_joystick_poll(pidev); -} - -static void lis3lv02d_joystick_close(struct input_polled_dev *pidev) -{ - atomic_set(&lis3_dev.wake_thread, 0); - if (lis3_dev.pm_dev) - pm_runtime_put(lis3_dev.pm_dev); -} - -static irqreturn_t lis302dl_interrupt(int irq, void *dummy) -{ - if (!test_bit(0, &lis3_dev.misc_opened)) - goto out; - - /* - * Be careful: on some HP laptops the bios force DD when on battery and - * the lid is closed. This leads to interrupts as soon as a little move - * is done. - */ - atomic_inc(&lis3_dev.count); - - wake_up_interruptible(&lis3_dev.misc_wait); - kill_fasync(&lis3_dev.async_queue, SIGIO, POLL_IN); -out: - if (atomic_read(&lis3_dev.wake_thread)) - return IRQ_WAKE_THREAD; - return IRQ_HANDLED; -} - -static void lis302dl_interrupt_handle_click(struct lis3lv02d *lis3) -{ - struct input_dev *dev = lis3->idev->input; - u8 click_src; - - mutex_lock(&lis3->mutex); - lis3->read(lis3, CLICK_SRC, &click_src); - - if (click_src & CLICK_SINGLE_X) { - input_report_key(dev, lis3->mapped_btns[0], 1); - input_report_key(dev, lis3->mapped_btns[0], 0); - } - - if (click_src & CLICK_SINGLE_Y) { - input_report_key(dev, lis3->mapped_btns[1], 1); - input_report_key(dev, lis3->mapped_btns[1], 0); - } - - if (click_src & CLICK_SINGLE_Z) { - input_report_key(dev, lis3->mapped_btns[2], 1); - input_report_key(dev, lis3->mapped_btns[2], 0); - } - input_sync(dev); - mutex_unlock(&lis3->mutex); -} - -static inline void lis302dl_data_ready(struct lis3lv02d *lis3, int index) -{ - int dummy; - - /* Dummy read to ack interrupt */ - lis3lv02d_get_xyz(lis3, &dummy, &dummy, &dummy); - lis3->data_ready_count[index]++; -} - -static irqreturn_t lis302dl_interrupt_thread1_8b(int irq, void *data) -{ - struct lis3lv02d *lis3 = data; - u8 irq_cfg = lis3->irq_cfg & LIS3_IRQ1_MASK; - - if (irq_cfg == LIS3_IRQ1_CLICK) - lis302dl_interrupt_handle_click(lis3); - else if (unlikely(irq_cfg == LIS3_IRQ1_DATA_READY)) - lis302dl_data_ready(lis3, IRQ_LINE0); - else - lis3lv02d_joystick_poll(lis3->idev); - - return IRQ_HANDLED; -} - -static irqreturn_t lis302dl_interrupt_thread2_8b(int irq, void *data) -{ - struct lis3lv02d *lis3 = data; - u8 irq_cfg = lis3->irq_cfg & LIS3_IRQ2_MASK; - - if (irq_cfg == LIS3_IRQ2_CLICK) - lis302dl_interrupt_handle_click(lis3); - else if (unlikely(irq_cfg == LIS3_IRQ2_DATA_READY)) - lis302dl_data_ready(lis3, IRQ_LINE1); - else - lis3lv02d_joystick_poll(lis3->idev); - - return IRQ_HANDLED; -} - -static int lis3lv02d_misc_open(struct inode *inode, struct file *file) -{ - if (test_and_set_bit(0, &lis3_dev.misc_opened)) - return -EBUSY; /* already open */ - - if (lis3_dev.pm_dev) - pm_runtime_get_sync(lis3_dev.pm_dev); - - atomic_set(&lis3_dev.count, 0); - return 0; -} - -static int lis3lv02d_misc_release(struct inode *inode, struct file *file) -{ - fasync_helper(-1, file, 0, &lis3_dev.async_queue); - clear_bit(0, &lis3_dev.misc_opened); /* release the device */ - if (lis3_dev.pm_dev) - pm_runtime_put(lis3_dev.pm_dev); - return 0; -} - -static ssize_t lis3lv02d_misc_read(struct file *file, char __user *buf, - size_t count, loff_t *pos) -{ - DECLARE_WAITQUEUE(wait, current); - u32 data; - unsigned char byte_data; - ssize_t retval = 1; - - if (count < 1) - return -EINVAL; - - add_wait_queue(&lis3_dev.misc_wait, &wait); - while (true) { - set_current_state(TASK_INTERRUPTIBLE); - data = atomic_xchg(&lis3_dev.count, 0); - if (data) - break; - - if (file->f_flags & O_NONBLOCK) { - retval = -EAGAIN; - goto out; - } - - if (signal_pending(current)) { - retval = -ERESTARTSYS; - goto out; - } - - schedule(); - } - - if (data < 255) - byte_data = data; - else - byte_data = 255; - - /* make sure we are not going into copy_to_user() with - * TASK_INTERRUPTIBLE state */ - set_current_state(TASK_RUNNING); - if (copy_to_user(buf, &byte_data, sizeof(byte_data))) - retval = -EFAULT; - -out: - __set_current_state(TASK_RUNNING); - remove_wait_queue(&lis3_dev.misc_wait, &wait); - - return retval; -} - -static unsigned int lis3lv02d_misc_poll(struct file *file, poll_table *wait) -{ - poll_wait(file, &lis3_dev.misc_wait, wait); - if (atomic_read(&lis3_dev.count)) - return POLLIN | POLLRDNORM; - return 0; -} - -static int lis3lv02d_misc_fasync(int fd, struct file *file, int on) -{ - return fasync_helper(fd, file, on, &lis3_dev.async_queue); -} - -static const struct file_operations lis3lv02d_misc_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .read = lis3lv02d_misc_read, - .open = lis3lv02d_misc_open, - .release = lis3lv02d_misc_release, - .poll = lis3lv02d_misc_poll, - .fasync = lis3lv02d_misc_fasync, -}; - -static struct miscdevice lis3lv02d_misc_device = { - .minor = MISC_DYNAMIC_MINOR, - .name = "freefall", - .fops = &lis3lv02d_misc_fops, -}; - -int lis3lv02d_joystick_enable(void) -{ - struct input_dev *input_dev; - int err; - int max_val, fuzz, flat; - int btns[] = {BTN_X, BTN_Y, BTN_Z}; - - if (lis3_dev.idev) - return -EINVAL; - - lis3_dev.idev = input_allocate_polled_device(); - if (!lis3_dev.idev) - return -ENOMEM; - - lis3_dev.idev->poll = lis3lv02d_joystick_poll; - lis3_dev.idev->open = lis3lv02d_joystick_open; - lis3_dev.idev->close = lis3lv02d_joystick_close; - lis3_dev.idev->poll_interval = MDPS_POLL_INTERVAL; - lis3_dev.idev->poll_interval_min = MDPS_POLL_MIN; - lis3_dev.idev->poll_interval_max = MDPS_POLL_MAX; - input_dev = lis3_dev.idev->input; - - input_dev->name = "ST LIS3LV02DL Accelerometer"; - input_dev->phys = DRIVER_NAME "/input0"; - input_dev->id.bustype = BUS_HOST; - input_dev->id.vendor = 0; - input_dev->dev.parent = &lis3_dev.pdev->dev; - - set_bit(EV_ABS, input_dev->evbit); - max_val = (lis3_dev.mdps_max_val * lis3_dev.scale) / LIS3_ACCURACY; - if (lis3_dev.whoami == WAI_12B) { - fuzz = LIS3_DEFAULT_FUZZ_12B; - flat = LIS3_DEFAULT_FLAT_12B; - } else { - fuzz = LIS3_DEFAULT_FUZZ_8B; - flat = LIS3_DEFAULT_FLAT_8B; - } - fuzz = (fuzz * lis3_dev.scale) / LIS3_ACCURACY; - flat = (flat * lis3_dev.scale) / LIS3_ACCURACY; - - input_set_abs_params(input_dev, ABS_X, -max_val, max_val, fuzz, flat); - input_set_abs_params(input_dev, ABS_Y, -max_val, max_val, fuzz, flat); - input_set_abs_params(input_dev, ABS_Z, -max_val, max_val, fuzz, flat); - - lis3_dev.mapped_btns[0] = lis3lv02d_get_axis(abs(lis3_dev.ac.x), btns); - lis3_dev.mapped_btns[1] = lis3lv02d_get_axis(abs(lis3_dev.ac.y), btns); - lis3_dev.mapped_btns[2] = lis3lv02d_get_axis(abs(lis3_dev.ac.z), btns); - - err = input_register_polled_device(lis3_dev.idev); - if (err) { - input_free_polled_device(lis3_dev.idev); - lis3_dev.idev = NULL; - } - - return err; -} -EXPORT_SYMBOL_GPL(lis3lv02d_joystick_enable); - -void lis3lv02d_joystick_disable(void) -{ - if (lis3_dev.irq) - free_irq(lis3_dev.irq, &lis3_dev); - if (lis3_dev.pdata && lis3_dev.pdata->irq2) - free_irq(lis3_dev.pdata->irq2, &lis3_dev); - - if (!lis3_dev.idev) - return; - - if (lis3_dev.irq) - misc_deregister(&lis3lv02d_misc_device); - input_unregister_polled_device(lis3_dev.idev); - input_free_polled_device(lis3_dev.idev); - lis3_dev.idev = NULL; -} -EXPORT_SYMBOL_GPL(lis3lv02d_joystick_disable); - -/* Sysfs stuff */ -static void lis3lv02d_sysfs_poweron(struct lis3lv02d *lis3) -{ - /* - * SYSFS functions are fast visitors so put-call - * immediately after the get-call. However, keep - * chip running for a while and schedule delayed - * suspend. This way periodic sysfs calls doesn't - * suffer from relatively long power up time. - */ - - if (lis3->pm_dev) { - pm_runtime_get_sync(lis3->pm_dev); - pm_runtime_put_noidle(lis3->pm_dev); - pm_schedule_suspend(lis3->pm_dev, LIS3_SYSFS_POWERDOWN_DELAY); - } -} - -static ssize_t lis3lv02d_selftest_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - s16 values[3]; - - static const char ok[] = "OK"; - static const char fail[] = "FAIL"; - static const char irq[] = "FAIL_IRQ"; - const char *res; - - lis3lv02d_sysfs_poweron(&lis3_dev); - switch (lis3lv02d_selftest(&lis3_dev, values)) { - case SELFTEST_FAIL: - res = fail; - break; - case SELFTEST_IRQ: - res = irq; - break; - case SELFTEST_OK: - default: - res = ok; - break; - } - return sprintf(buf, "%s %d %d %d\n", res, - values[0], values[1], values[2]); -} - -static ssize_t lis3lv02d_position_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int x, y, z; - - lis3lv02d_sysfs_poweron(&lis3_dev); - mutex_lock(&lis3_dev.mutex); - lis3lv02d_get_xyz(&lis3_dev, &x, &y, &z); - mutex_unlock(&lis3_dev.mutex); - return sprintf(buf, "(%d,%d,%d)\n", x, y, z); -} - -static ssize_t lis3lv02d_rate_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - lis3lv02d_sysfs_poweron(&lis3_dev); - return sprintf(buf, "%d\n", lis3lv02d_get_odr()); -} - -static ssize_t lis3lv02d_rate_set(struct device *dev, - struct device_attribute *attr, const char *buf, - size_t count) -{ - unsigned long rate; - - if (strict_strtoul(buf, 0, &rate)) - return -EINVAL; - - lis3lv02d_sysfs_poweron(&lis3_dev); - if (lis3lv02d_set_odr(rate)) - return -EINVAL; - - return count; -} - -static DEVICE_ATTR(selftest, S_IRUSR, lis3lv02d_selftest_show, NULL); -static DEVICE_ATTR(position, S_IRUGO, lis3lv02d_position_show, NULL); -static DEVICE_ATTR(rate, S_IRUGO | S_IWUSR, lis3lv02d_rate_show, - lis3lv02d_rate_set); - -static struct attribute *lis3lv02d_attributes[] = { - &dev_attr_selftest.attr, - &dev_attr_position.attr, - &dev_attr_rate.attr, - NULL -}; - -static struct attribute_group lis3lv02d_attribute_group = { - .attrs = lis3lv02d_attributes -}; - - -static int lis3lv02d_add_fs(struct lis3lv02d *lis3) -{ - lis3->pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0); - if (IS_ERR(lis3->pdev)) - return PTR_ERR(lis3->pdev); - - return sysfs_create_group(&lis3->pdev->dev.kobj, &lis3lv02d_attribute_group); -} - -int lis3lv02d_remove_fs(struct lis3lv02d *lis3) -{ - sysfs_remove_group(&lis3->pdev->dev.kobj, &lis3lv02d_attribute_group); - platform_device_unregister(lis3->pdev); - if (lis3->pm_dev) { - /* Barrier after the sysfs remove */ - pm_runtime_barrier(lis3->pm_dev); - - /* SYSFS may have left chip running. Turn off if necessary */ - if (!pm_runtime_suspended(lis3->pm_dev)) - lis3lv02d_poweroff(&lis3_dev); - - pm_runtime_disable(lis3->pm_dev); - pm_runtime_set_suspended(lis3->pm_dev); - } - kfree(lis3->reg_cache); - return 0; -} -EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs); - -static void lis3lv02d_8b_configure(struct lis3lv02d *dev, - struct lis3lv02d_platform_data *p) -{ - int err; - int ctrl2 = p->hipass_ctrl; - - if (p->click_flags) { - dev->write(dev, CLICK_CFG, p->click_flags); - dev->write(dev, CLICK_TIMELIMIT, p->click_time_limit); - dev->write(dev, CLICK_LATENCY, p->click_latency); - dev->write(dev, CLICK_WINDOW, p->click_window); - dev->write(dev, CLICK_THSZ, p->click_thresh_z & 0xf); - dev->write(dev, CLICK_THSY_X, - (p->click_thresh_x & 0xf) | - (p->click_thresh_y << 4)); - - if (dev->idev) { - struct input_dev *input_dev = lis3_dev.idev->input; - input_set_capability(input_dev, EV_KEY, BTN_X); - input_set_capability(input_dev, EV_KEY, BTN_Y); - input_set_capability(input_dev, EV_KEY, BTN_Z); - } - } - - if (p->wakeup_flags) { - dev->write(dev, FF_WU_CFG_1, p->wakeup_flags); - dev->write(dev, FF_WU_THS_1, p->wakeup_thresh & 0x7f); - /* pdata value + 1 to keep this backward compatible*/ - dev->write(dev, FF_WU_DURATION_1, p->duration1 + 1); - ctrl2 ^= HP_FF_WU1; /* Xor to keep compatible with old pdata*/ - } - - if (p->wakeup_flags2) { - dev->write(dev, FF_WU_CFG_2, p->wakeup_flags2); - dev->write(dev, FF_WU_THS_2, p->wakeup_thresh2 & 0x7f); - /* pdata value + 1 to keep this backward compatible*/ - dev->write(dev, FF_WU_DURATION_2, p->duration2 + 1); - ctrl2 ^= HP_FF_WU2; /* Xor to keep compatible with old pdata*/ - } - /* Configure hipass filters */ - dev->write(dev, CTRL_REG2, ctrl2); - - if (p->irq2) { - err = request_threaded_irq(p->irq2, - NULL, - lis302dl_interrupt_thread2_8b, - IRQF_TRIGGER_RISING | IRQF_ONESHOT | - (p->irq_flags2 & IRQF_TRIGGER_MASK), - DRIVER_NAME, &lis3_dev); - if (err < 0) - pr_err("No second IRQ. Limited functionality\n"); - } -} - -/* - * Initialise the accelerometer and the various subsystems. - * Should be rather independent of the bus system. - */ -int lis3lv02d_init_device(struct lis3lv02d *dev) -{ - int err; - irq_handler_t thread_fn; - int irq_flags = 0; - - dev->whoami = lis3lv02d_read_8(dev, WHO_AM_I); - - switch (dev->whoami) { - case WAI_12B: - pr_info("12 bits sensor found\n"); - dev->read_data = lis3lv02d_read_12; - dev->mdps_max_val = 2048; - dev->pwron_delay = LIS3_PWRON_DELAY_WAI_12B; - dev->odrs = lis3_12_rates; - dev->odr_mask = CTRL1_DF0 | CTRL1_DF1; - dev->scale = LIS3_SENSITIVITY_12B; - dev->regs = lis3_wai12_regs; - dev->regs_size = ARRAY_SIZE(lis3_wai12_regs); - break; - case WAI_8B: - pr_info("8 bits sensor found\n"); - dev->read_data = lis3lv02d_read_8; - dev->mdps_max_val = 128; - dev->pwron_delay = LIS3_PWRON_DELAY_WAI_8B; - dev->odrs = lis3_8_rates; - dev->odr_mask = CTRL1_DR; - dev->scale = LIS3_SENSITIVITY_8B; - dev->regs = lis3_wai8_regs; - dev->regs_size = ARRAY_SIZE(lis3_wai8_regs); - break; - case WAI_3DC: - pr_info("8 bits 3DC sensor found\n"); - dev->read_data = lis3lv02d_read_8; - dev->mdps_max_val = 128; - dev->pwron_delay = LIS3_PWRON_DELAY_WAI_8B; - dev->odrs = lis3_3dc_rates; - dev->odr_mask = CTRL1_ODR0|CTRL1_ODR1|CTRL1_ODR2|CTRL1_ODR3; - dev->scale = LIS3_SENSITIVITY_8B; - break; - default: - pr_err("unknown sensor type 0x%X\n", dev->whoami); - return -EINVAL; - } - - dev->reg_cache = kzalloc(max(sizeof(lis3_wai8_regs), - sizeof(lis3_wai12_regs)), GFP_KERNEL); - - if (dev->reg_cache == NULL) { - printk(KERN_ERR DRIVER_NAME "out of memory\n"); - return -ENOMEM; - } - - mutex_init(&dev->mutex); - atomic_set(&dev->wake_thread, 0); - - lis3lv02d_add_fs(dev); - lis3lv02d_poweron(dev); - - if (dev->pm_dev) { - pm_runtime_set_active(dev->pm_dev); - pm_runtime_enable(dev->pm_dev); - } - - if (lis3lv02d_joystick_enable()) - pr_err("joystick initialization failed\n"); - - /* passing in platform specific data is purely optional and only - * used by the SPI transport layer at the moment */ - if (dev->pdata) { - struct lis3lv02d_platform_data *p = dev->pdata; - - if (dev->whoami == WAI_8B) - lis3lv02d_8b_configure(dev, p); - - irq_flags = p->irq_flags1 & IRQF_TRIGGER_MASK; - - dev->irq_cfg = p->irq_cfg; - if (p->irq_cfg) - dev->write(dev, CTRL_REG3, p->irq_cfg); - - if (p->default_rate) - lis3lv02d_set_odr(p->default_rate); - } - - /* bail if we did not get an IRQ from the bus layer */ - if (!dev->irq) { - pr_debug("No IRQ. Disabling /dev/freefall\n"); - goto out; - } - - /* - * The sensor can generate interrupts for free-fall and direction - * detection (distinguishable with FF_WU_SRC and DD_SRC) but to keep - * the things simple and _fast_ we activate it only for free-fall, so - * no need to read register (very slow with ACPI). For the same reason, - * we forbid shared interrupts. - * - * IRQF_TRIGGER_RISING seems pointless on HP laptops because the - * io-apic is not configurable (and generates a warning) but I keep it - * in case of support for other hardware. - */ - if (dev->pdata && dev->whoami == WAI_8B) - thread_fn = lis302dl_interrupt_thread1_8b; - else - thread_fn = NULL; - - err = request_threaded_irq(dev->irq, lis302dl_interrupt, - thread_fn, - IRQF_TRIGGER_RISING | IRQF_ONESHOT | - irq_flags, - DRIVER_NAME, &lis3_dev); - - if (err < 0) { - pr_err("Cannot get IRQ\n"); - goto out; - } - - if (misc_register(&lis3lv02d_misc_device)) - pr_err("misc_register failed\n"); -out: - return 0; -} -EXPORT_SYMBOL_GPL(lis3lv02d_init_device); - -MODULE_DESCRIPTION("ST LIS3LV02Dx three-axis digital accelerometer driver"); -MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek"); -MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lis3lv02d.h b/drivers/hwmon/lis3lv02d.h deleted file mode 100644 index a1939589eb2..00000000000 --- a/drivers/hwmon/lis3lv02d.h +++ /dev/null @@ -1,291 +0,0 @@ -/* - * lis3lv02d.h - ST LIS3LV02DL accelerometer driver - * - * Copyright (C) 2007-2008 Yan Burman - * Copyright (C) 2008-2009 Eric Piel - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include -#include - -/* - * This driver tries to support the "digital" accelerometer chips from - * STMicroelectronics such as LIS3LV02DL, LIS302DL, LIS3L02DQ, LIS331DL, - * LIS35DE, or LIS202DL. They are very similar in terms of programming, with - * almost the same registers. In addition to differing on physical properties, - * they differ on the number of axes (2/3), precision (8/12 bits), and special - * features (freefall detection, click...). Unfortunately, not all the - * differences can be probed via a register. - * They can be connected either via I²C or SPI. - */ - -#include - -enum lis3_reg { - WHO_AM_I = 0x0F, - OFFSET_X = 0x16, - OFFSET_Y = 0x17, - OFFSET_Z = 0x18, - GAIN_X = 0x19, - GAIN_Y = 0x1A, - GAIN_Z = 0x1B, - CTRL_REG1 = 0x20, - CTRL_REG2 = 0x21, - CTRL_REG3 = 0x22, - CTRL_REG4 = 0x23, - HP_FILTER_RESET = 0x23, - STATUS_REG = 0x27, - OUTX_L = 0x28, - OUTX_H = 0x29, - OUTX = 0x29, - OUTY_L = 0x2A, - OUTY_H = 0x2B, - OUTY = 0x2B, - OUTZ_L = 0x2C, - OUTZ_H = 0x2D, - OUTZ = 0x2D, -}; - -enum lis302d_reg { - FF_WU_CFG_1 = 0x30, - FF_WU_SRC_1 = 0x31, - FF_WU_THS_1 = 0x32, - FF_WU_DURATION_1 = 0x33, - FF_WU_CFG_2 = 0x34, - FF_WU_SRC_2 = 0x35, - FF_WU_THS_2 = 0x36, - FF_WU_DURATION_2 = 0x37, - CLICK_CFG = 0x38, - CLICK_SRC = 0x39, - CLICK_THSY_X = 0x3B, - CLICK_THSZ = 0x3C, - CLICK_TIMELIMIT = 0x3D, - CLICK_LATENCY = 0x3E, - CLICK_WINDOW = 0x3F, -}; - -enum lis3lv02d_reg { - FF_WU_CFG = 0x30, - FF_WU_SRC = 0x31, - FF_WU_ACK = 0x32, - FF_WU_THS_L = 0x34, - FF_WU_THS_H = 0x35, - FF_WU_DURATION = 0x36, - DD_CFG = 0x38, - DD_SRC = 0x39, - DD_ACK = 0x3A, - DD_THSI_L = 0x3C, - DD_THSI_H = 0x3D, - DD_THSE_L = 0x3E, - DD_THSE_H = 0x3F, -}; - -enum lis3_who_am_i { - WAI_3DC = 0x33, /* 8 bits: LIS3DC, HP3DC */ - WAI_12B = 0x3A, /* 12 bits: LIS3LV02D[LQ]... */ - WAI_8B = 0x3B, /* 8 bits: LIS[23]02D[LQ]... */ - WAI_6B = 0x52, /* 6 bits: LIS331DLF - not supported */ -}; - -enum lis3lv02d_ctrl1_12b { - CTRL1_Xen = 0x01, - CTRL1_Yen = 0x02, - CTRL1_Zen = 0x04, - CTRL1_ST = 0x08, - CTRL1_DF0 = 0x10, - CTRL1_DF1 = 0x20, - CTRL1_PD0 = 0x40, - CTRL1_PD1 = 0x80, -}; - -/* Delta to ctrl1_12b version */ -enum lis3lv02d_ctrl1_8b { - CTRL1_STM = 0x08, - CTRL1_STP = 0x10, - CTRL1_FS = 0x20, - CTRL1_PD = 0x40, - CTRL1_DR = 0x80, -}; - -enum lis3lv02d_ctrl1_3dc { - CTRL1_ODR0 = 0x10, - CTRL1_ODR1 = 0x20, - CTRL1_ODR2 = 0x40, - CTRL1_ODR3 = 0x80, -}; - -enum lis3lv02d_ctrl2 { - CTRL2_DAS = 0x01, - CTRL2_SIM = 0x02, - CTRL2_DRDY = 0x04, - CTRL2_IEN = 0x08, - CTRL2_BOOT = 0x10, - CTRL2_BLE = 0x20, - CTRL2_BDU = 0x40, /* Block Data Update */ - CTRL2_FS = 0x80, /* Full Scale selection */ -}; - -enum lis3lv02d_ctrl4_3dc { - CTRL4_SIM = 0x01, - CTRL4_ST0 = 0x02, - CTRL4_ST1 = 0x04, - CTRL4_FS0 = 0x10, - CTRL4_FS1 = 0x20, -}; - -enum lis302d_ctrl2 { - HP_FF_WU2 = 0x08, - HP_FF_WU1 = 0x04, - CTRL2_BOOT_8B = 0x40, -}; - -enum lis3lv02d_ctrl3 { - CTRL3_CFS0 = 0x01, - CTRL3_CFS1 = 0x02, - CTRL3_FDS = 0x10, - CTRL3_HPFF = 0x20, - CTRL3_HPDD = 0x40, - CTRL3_ECK = 0x80, -}; - -enum lis3lv02d_status_reg { - STATUS_XDA = 0x01, - STATUS_YDA = 0x02, - STATUS_ZDA = 0x04, - STATUS_XYZDA = 0x08, - STATUS_XOR = 0x10, - STATUS_YOR = 0x20, - STATUS_ZOR = 0x40, - STATUS_XYZOR = 0x80, -}; - -enum lis3lv02d_ff_wu_cfg { - FF_WU_CFG_XLIE = 0x01, - FF_WU_CFG_XHIE = 0x02, - FF_WU_CFG_YLIE = 0x04, - FF_WU_CFG_YHIE = 0x08, - FF_WU_CFG_ZLIE = 0x10, - FF_WU_CFG_ZHIE = 0x20, - FF_WU_CFG_LIR = 0x40, - FF_WU_CFG_AOI = 0x80, -}; - -enum lis3lv02d_ff_wu_src { - FF_WU_SRC_XL = 0x01, - FF_WU_SRC_XH = 0x02, - FF_WU_SRC_YL = 0x04, - FF_WU_SRC_YH = 0x08, - FF_WU_SRC_ZL = 0x10, - FF_WU_SRC_ZH = 0x20, - FF_WU_SRC_IA = 0x40, -}; - -enum lis3lv02d_dd_cfg { - DD_CFG_XLIE = 0x01, - DD_CFG_XHIE = 0x02, - DD_CFG_YLIE = 0x04, - DD_CFG_YHIE = 0x08, - DD_CFG_ZLIE = 0x10, - DD_CFG_ZHIE = 0x20, - DD_CFG_LIR = 0x40, - DD_CFG_IEND = 0x80, -}; - -enum lis3lv02d_dd_src { - DD_SRC_XL = 0x01, - DD_SRC_XH = 0x02, - DD_SRC_YL = 0x04, - DD_SRC_YH = 0x08, - DD_SRC_ZL = 0x10, - DD_SRC_ZH = 0x20, - DD_SRC_IA = 0x40, -}; - -enum lis3lv02d_click_src_8b { - CLICK_SINGLE_X = 0x01, - CLICK_DOUBLE_X = 0x02, - CLICK_SINGLE_Y = 0x04, - CLICK_DOUBLE_Y = 0x08, - CLICK_SINGLE_Z = 0x10, - CLICK_DOUBLE_Z = 0x20, - CLICK_IA = 0x40, -}; - -enum lis3lv02d_reg_state { - LIS3_REG_OFF = 0x00, - LIS3_REG_ON = 0x01, -}; - -union axis_conversion { - struct { - int x, y, z; - }; - int as_array[3]; - -}; - -struct lis3lv02d { - void *bus_priv; /* used by the bus layer only */ - struct device *pm_dev; /* for pm_runtime purposes */ - int (*init) (struct lis3lv02d *lis3); - int (*write) (struct lis3lv02d *lis3, int reg, u8 val); - int (*read) (struct lis3lv02d *lis3, int reg, u8 *ret); - int (*blkread) (struct lis3lv02d *lis3, int reg, int len, u8 *ret); - int (*reg_ctrl) (struct lis3lv02d *lis3, bool state); - - int *odrs; /* Supported output data rates */ - u8 *regs; /* Regs to store / restore */ - int regs_size; - u8 *reg_cache; - bool regs_stored; - u8 odr_mask; /* ODR bit mask */ - u8 whoami; /* indicates measurement precision */ - s16 (*read_data) (struct lis3lv02d *lis3, int reg); - int mdps_max_val; - int pwron_delay; - int scale; /* - * relationship between 1 LBS and mG - * (1/1000th of earth gravity) - */ - - struct input_polled_dev *idev; /* input device */ - struct platform_device *pdev; /* platform device */ - struct regulator_bulk_data regulators[2]; - atomic_t count; /* interrupt count after last read */ - union axis_conversion ac; /* hw -> logical axis */ - int mapped_btns[3]; - - u32 irq; /* IRQ number */ - struct fasync_struct *async_queue; /* queue for the misc device */ - wait_queue_head_t misc_wait; /* Wait queue for the misc device */ - unsigned long misc_opened; /* bit0: whether the device is open */ - int data_ready_count[2]; - atomic_t wake_thread; - unsigned char irq_cfg; - - struct lis3lv02d_platform_data *pdata; /* for passing board config */ - struct mutex mutex; /* Serialize poll and selftest */ -}; - -int lis3lv02d_init_device(struct lis3lv02d *lis3); -int lis3lv02d_joystick_enable(void); -void lis3lv02d_joystick_disable(void); -void lis3lv02d_poweroff(struct lis3lv02d *lis3); -void lis3lv02d_poweron(struct lis3lv02d *lis3); -int lis3lv02d_remove_fs(struct lis3lv02d *lis3); - -extern struct lis3lv02d lis3_dev; diff --git a/drivers/hwmon/lis3lv02d_i2c.c b/drivers/hwmon/lis3lv02d_i2c.c deleted file mode 100644 index 8853afce85c..00000000000 --- a/drivers/hwmon/lis3lv02d_i2c.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - * drivers/hwmon/lis3lv02d_i2c.c - * - * Implements I2C interface for lis3lv02d (STMicroelectronics) accelerometer. - * Driver is based on corresponding SPI driver written by Daniel Mack - * (lis3lv02d_spi.c (C) 2009 Daniel Mack ). - * - * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). - * - * Contact: Samu Onkalo - * - * 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. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA - */ - -#include -#include -#include -#include -#include -#include -#include -#include "lis3lv02d.h" - -#define DRV_NAME "lis3lv02d_i2c" - -static const char reg_vdd[] = "Vdd"; -static const char reg_vdd_io[] = "Vdd_IO"; - -static int lis3_reg_ctrl(struct lis3lv02d *lis3, bool state) -{ - int ret; - if (state == LIS3_REG_OFF) { - ret = regulator_bulk_disable(ARRAY_SIZE(lis3->regulators), - lis3->regulators); - } else { - ret = regulator_bulk_enable(ARRAY_SIZE(lis3->regulators), - lis3->regulators); - /* Chip needs time to wakeup. Not mentioned in datasheet */ - usleep_range(10000, 20000); - } - return ret; -} - -static inline s32 lis3_i2c_write(struct lis3lv02d *lis3, int reg, u8 value) -{ - struct i2c_client *c = lis3->bus_priv; - return i2c_smbus_write_byte_data(c, reg, value); -} - -static inline s32 lis3_i2c_read(struct lis3lv02d *lis3, int reg, u8 *v) -{ - struct i2c_client *c = lis3->bus_priv; - *v = i2c_smbus_read_byte_data(c, reg); - return 0; -} - -static inline s32 lis3_i2c_blockread(struct lis3lv02d *lis3, int reg, int len, - u8 *v) -{ - struct i2c_client *c = lis3->bus_priv; - reg |= (1 << 7); /* 7th bit enables address auto incrementation */ - return i2c_smbus_read_i2c_block_data(c, reg, len, v); -} - -static int lis3_i2c_init(struct lis3lv02d *lis3) -{ - u8 reg; - int ret; - - if (lis3->reg_ctrl) - lis3_reg_ctrl(lis3, LIS3_REG_ON); - - lis3->read(lis3, WHO_AM_I, ®); - if (reg != lis3->whoami) - printk(KERN_ERR "lis3: power on failure\n"); - - /* power up the device */ - ret = lis3->read(lis3, CTRL_REG1, ®); - if (ret < 0) - return ret; - - reg |= CTRL1_PD0 | CTRL1_Xen | CTRL1_Yen | CTRL1_Zen; - return lis3->write(lis3, CTRL_REG1, reg); -} - -/* Default axis mapping but it can be overwritten by platform data */ -static union axis_conversion lis3lv02d_axis_map = - { .as_array = { LIS3_DEV_X, LIS3_DEV_Y, LIS3_DEV_Z } }; - -static int __devinit lis3lv02d_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - int ret = 0; - struct lis3lv02d_platform_data *pdata = client->dev.platform_data; - - if (pdata) { - /* Regulator control is optional */ - if (pdata->driver_features & LIS3_USE_REGULATOR_CTRL) - lis3_dev.reg_ctrl = lis3_reg_ctrl; - - if ((pdata->driver_features & LIS3_USE_BLOCK_READ) && - (i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_I2C_BLOCK))) - lis3_dev.blkread = lis3_i2c_blockread; - - if (pdata->axis_x) - lis3lv02d_axis_map.x = pdata->axis_x; - - if (pdata->axis_y) - lis3lv02d_axis_map.y = pdata->axis_y; - - if (pdata->axis_z) - lis3lv02d_axis_map.z = pdata->axis_z; - - if (pdata->setup_resources) - ret = pdata->setup_resources(); - - if (ret) - goto fail; - } - - if (lis3_dev.reg_ctrl) { - lis3_dev.regulators[0].supply = reg_vdd; - lis3_dev.regulators[1].supply = reg_vdd_io; - ret = regulator_bulk_get(&client->dev, - ARRAY_SIZE(lis3_dev.regulators), - lis3_dev.regulators); - if (ret < 0) - goto fail; - } - - lis3_dev.pdata = pdata; - lis3_dev.bus_priv = client; - lis3_dev.init = lis3_i2c_init; - lis3_dev.read = lis3_i2c_read; - lis3_dev.write = lis3_i2c_write; - lis3_dev.irq = client->irq; - lis3_dev.ac = lis3lv02d_axis_map; - lis3_dev.pm_dev = &client->dev; - - i2c_set_clientdata(client, &lis3_dev); - - /* Provide power over the init call */ - if (lis3_dev.reg_ctrl) - lis3_reg_ctrl(&lis3_dev, LIS3_REG_ON); - - ret = lis3lv02d_init_device(&lis3_dev); - - if (lis3_dev.reg_ctrl) - lis3_reg_ctrl(&lis3_dev, LIS3_REG_OFF); - - if (ret == 0) - return 0; -fail: - if (pdata && pdata->release_resources) - pdata->release_resources(); - return ret; -} - -static int __devexit lis3lv02d_i2c_remove(struct i2c_client *client) -{ - struct lis3lv02d *lis3 = i2c_get_clientdata(client); - struct lis3lv02d_platform_data *pdata = client->dev.platform_data; - - if (pdata && pdata->release_resources) - pdata->release_resources(); - - lis3lv02d_joystick_disable(); - lis3lv02d_remove_fs(&lis3_dev); - - if (lis3_dev.reg_ctrl) - regulator_bulk_free(ARRAY_SIZE(lis3->regulators), - lis3_dev.regulators); - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int lis3lv02d_i2c_suspend(struct device *dev) -{ - struct i2c_client *client = container_of(dev, struct i2c_client, dev); - struct lis3lv02d *lis3 = i2c_get_clientdata(client); - - if (!lis3->pdata || !lis3->pdata->wakeup_flags) - lis3lv02d_poweroff(lis3); - return 0; -} - -static int lis3lv02d_i2c_resume(struct device *dev) -{ - struct i2c_client *client = container_of(dev, struct i2c_client, dev); - struct lis3lv02d *lis3 = i2c_get_clientdata(client); - - /* - * pm_runtime documentation says that devices should always - * be powered on at resume. Pm_runtime turns them off after system - * wide resume is complete. - */ - if (!lis3->pdata || !lis3->pdata->wakeup_flags || - pm_runtime_suspended(dev)) - lis3lv02d_poweron(lis3); - - return 0; -} -#endif /* CONFIG_PM_SLEEP */ - -#ifdef CONFIG_PM_RUNTIME -static int lis3_i2c_runtime_suspend(struct device *dev) -{ - struct i2c_client *client = container_of(dev, struct i2c_client, dev); - struct lis3lv02d *lis3 = i2c_get_clientdata(client); - - lis3lv02d_poweroff(lis3); - return 0; -} - -static int lis3_i2c_runtime_resume(struct device *dev) -{ - struct i2c_client *client = container_of(dev, struct i2c_client, dev); - struct lis3lv02d *lis3 = i2c_get_clientdata(client); - - lis3lv02d_poweron(lis3); - return 0; -} -#endif /* CONFIG_PM_RUNTIME */ - -static const struct i2c_device_id lis3lv02d_id[] = { - {"lis3lv02d", 0 }, - {} -}; - -MODULE_DEVICE_TABLE(i2c, lis3lv02d_id); - -static const struct dev_pm_ops lis3_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(lis3lv02d_i2c_suspend, - lis3lv02d_i2c_resume) - SET_RUNTIME_PM_OPS(lis3_i2c_runtime_suspend, - lis3_i2c_runtime_resume, - NULL) -}; - -static struct i2c_driver lis3lv02d_i2c_driver = { - .driver = { - .name = DRV_NAME, - .owner = THIS_MODULE, - .pm = &lis3_pm_ops, - }, - .probe = lis3lv02d_i2c_probe, - .remove = __devexit_p(lis3lv02d_i2c_remove), - .id_table = lis3lv02d_id, -}; - -static int __init lis3lv02d_init(void) -{ - return i2c_add_driver(&lis3lv02d_i2c_driver); -} - -static void __exit lis3lv02d_exit(void) -{ - i2c_del_driver(&lis3lv02d_i2c_driver); -} - -MODULE_AUTHOR("Nokia Corporation"); -MODULE_DESCRIPTION("lis3lv02d I2C interface"); -MODULE_LICENSE("GPL"); - -module_init(lis3lv02d_init); -module_exit(lis3lv02d_exit); diff --git a/drivers/hwmon/lis3lv02d_spi.c b/drivers/hwmon/lis3lv02d_spi.c deleted file mode 100644 index c1f8a8fbf69..00000000000 --- a/drivers/hwmon/lis3lv02d_spi.c +++ /dev/null @@ -1,145 +0,0 @@ -/* - * lis3lv02d_spi - SPI glue layer for lis3lv02d - * - * Copyright (c) 2009 Daniel Mack - * - * 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 - * publishhed by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "lis3lv02d.h" - -#define DRV_NAME "lis3lv02d_spi" -#define LIS3_SPI_READ 0x80 - -static int lis3_spi_read(struct lis3lv02d *lis3, int reg, u8 *v) -{ - struct spi_device *spi = lis3->bus_priv; - int ret = spi_w8r8(spi, reg | LIS3_SPI_READ); - if (ret < 0) - return -EINVAL; - - *v = (u8) ret; - return 0; -} - -static int lis3_spi_write(struct lis3lv02d *lis3, int reg, u8 val) -{ - u8 tmp[2] = { reg, val }; - struct spi_device *spi = lis3->bus_priv; - return spi_write(spi, tmp, sizeof(tmp)); -} - -static int lis3_spi_init(struct lis3lv02d *lis3) -{ - u8 reg; - int ret; - - /* power up the device */ - ret = lis3->read(lis3, CTRL_REG1, ®); - if (ret < 0) - return ret; - - reg |= CTRL1_PD0 | CTRL1_Xen | CTRL1_Yen | CTRL1_Zen; - return lis3->write(lis3, CTRL_REG1, reg); -} - -static union axis_conversion lis3lv02d_axis_normal = - { .as_array = { 1, 2, 3 } }; - -static int __devinit lis302dl_spi_probe(struct spi_device *spi) -{ - int ret; - - spi->bits_per_word = 8; - spi->mode = SPI_MODE_0; - ret = spi_setup(spi); - if (ret < 0) - return ret; - - lis3_dev.bus_priv = spi; - lis3_dev.init = lis3_spi_init; - lis3_dev.read = lis3_spi_read; - lis3_dev.write = lis3_spi_write; - lis3_dev.irq = spi->irq; - lis3_dev.ac = lis3lv02d_axis_normal; - lis3_dev.pdata = spi->dev.platform_data; - spi_set_drvdata(spi, &lis3_dev); - - return lis3lv02d_init_device(&lis3_dev); -} - -static int __devexit lis302dl_spi_remove(struct spi_device *spi) -{ - struct lis3lv02d *lis3 = spi_get_drvdata(spi); - lis3lv02d_joystick_disable(); - lis3lv02d_poweroff(lis3); - - return lis3lv02d_remove_fs(&lis3_dev); -} - -#ifdef CONFIG_PM_SLEEP -static int lis3lv02d_spi_suspend(struct device *dev) -{ - struct spi_device *spi = to_spi_device(dev); - struct lis3lv02d *lis3 = spi_get_drvdata(spi); - - if (!lis3->pdata || !lis3->pdata->wakeup_flags) - lis3lv02d_poweroff(&lis3_dev); - - return 0; -} - -static int lis3lv02d_spi_resume(struct device *dev) -{ - struct spi_device *spi = to_spi_device(dev); - struct lis3lv02d *lis3 = spi_get_drvdata(spi); - - if (!lis3->pdata || !lis3->pdata->wakeup_flags) - lis3lv02d_poweron(lis3); - - return 0; -} -#endif - -static SIMPLE_DEV_PM_OPS(lis3lv02d_spi_pm, lis3lv02d_spi_suspend, - lis3lv02d_spi_resume); - -static struct spi_driver lis302dl_spi_driver = { - .driver = { - .name = DRV_NAME, - .owner = THIS_MODULE, - .pm = &lis3lv02d_spi_pm, - }, - .probe = lis302dl_spi_probe, - .remove = __devexit_p(lis302dl_spi_remove), -}; - -static int __init lis302dl_init(void) -{ - return spi_register_driver(&lis302dl_spi_driver); -} - -static void __exit lis302dl_exit(void) -{ - spi_unregister_driver(&lis302dl_spi_driver); -} - -module_init(lis302dl_init); -module_exit(lis302dl_exit); - -MODULE_AUTHOR("Daniel Mack "); -MODULE_DESCRIPTION("lis3lv02d SPI glue layer"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("spi:" DRV_NAME); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index b7d5ef234ac..203500d9b84 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -2,6 +2,14 @@ # Misc strange devices # +# This one has to live outside of the MISC_DEVICES conditional, +# because it may be selected by drivers/platform/x86/hp_accel. +config SENSORS_LIS3LV02D + tristate + depends on INPUT + select INPUT_POLLDEV + default n + menuconfig MISC_DEVICES bool "Misc devices" ---help--- @@ -462,5 +470,6 @@ source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" source "drivers/misc/iwmc3200top/Kconfig" source "drivers/misc/ti-st/Kconfig" +source "drivers/misc/lis3lv02d/Kconfig" endif # MISC_DEVICES diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 98009cc20cb..804f421bc07 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -42,3 +42,4 @@ obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o obj-$(CONFIG_PCH_PHUB) += pch_phub.o obj-y += ti-st/ obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o +obj-y += lis3lv02d/ diff --git a/drivers/misc/lis3lv02d/Kconfig b/drivers/misc/lis3lv02d/Kconfig new file mode 100644 index 00000000000..8f474e6fc7b --- /dev/null +++ b/drivers/misc/lis3lv02d/Kconfig @@ -0,0 +1,37 @@ +# +# STMicroelectonics LIS3LV02D and similar accelerometers +# + +config SENSORS_LIS3_SPI + tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (SPI)" + depends on !ACPI && SPI_MASTER && INPUT + select SENSORS_LIS3LV02D + default n + help + This driver provides support for the LIS3LV02Dx accelerometer connected + via SPI. The accelerometer data is readable via + /sys/devices/platform/lis3lv02d. + + This driver also provides an absolute input class device, allowing + the laptop to act as a pinball machine-esque joystick. + + This driver can also be built as modules. If so, the core module + will be called lis3lv02d and a specific module for the SPI transport + is called lis3lv02d_spi. + +config SENSORS_LIS3_I2C + tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (I2C)" + depends on I2C && INPUT + select SENSORS_LIS3LV02D + default n + help + This driver provides support for the LIS3LV02Dx accelerometer connected + via I2C. The accelerometer data is readable via + /sys/devices/platform/lis3lv02d. + + This driver also provides an absolute input class device, allowing + the device to act as a pinball machine-esque joystick. + + This driver can also be built as modules. If so, the core module + will be called lis3lv02d and a specific module for the I2C transport + is called lis3lv02d_i2c. diff --git a/drivers/misc/lis3lv02d/Makefile b/drivers/misc/lis3lv02d/Makefile new file mode 100644 index 00000000000..4bf58b16fcf --- /dev/null +++ b/drivers/misc/lis3lv02d/Makefile @@ -0,0 +1,7 @@ +# +# STMicroelectonics LIS3LV02D and similar accelerometers +# + +obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o +obj-$(CONFIG_SENSORS_LIS3_SPI) += lis3lv02d_spi.o +obj-$(CONFIG_SENSORS_LIS3_I2C) += lis3lv02d_i2c.o diff --git a/drivers/misc/lis3lv02d/lis3lv02d.c b/drivers/misc/lis3lv02d/lis3lv02d.c new file mode 100644 index 00000000000..b928bc14e97 --- /dev/null +++ b/drivers/misc/lis3lv02d/lis3lv02d.c @@ -0,0 +1,999 @@ +/* + * lis3lv02d.c - ST LIS3LV02DL accelerometer driver + * + * Copyright (C) 2007-2008 Yan Burman + * Copyright (C) 2008 Eric Piel + * Copyright (C) 2008-2009 Pavel Machek + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lis3lv02d.h" + +#define DRIVER_NAME "lis3lv02d" + +/* joystick device poll interval in milliseconds */ +#define MDPS_POLL_INTERVAL 50 +#define MDPS_POLL_MIN 0 +#define MDPS_POLL_MAX 2000 + +#define LIS3_SYSFS_POWERDOWN_DELAY 5000 /* In milliseconds */ + +#define SELFTEST_OK 0 +#define SELFTEST_FAIL -1 +#define SELFTEST_IRQ -2 + +#define IRQ_LINE0 0 +#define IRQ_LINE1 1 + +/* + * The sensor can also generate interrupts (DRDY) but it's pretty pointless + * because they are generated even if the data do not change. So it's better + * to keep the interrupt for the free-fall event. The values are updated at + * 40Hz (at the lowest frequency), but as it can be pretty time consuming on + * some low processor, we poll the sensor only at 20Hz... enough for the + * joystick. + */ + +#define LIS3_PWRON_DELAY_WAI_12B (5000) +#define LIS3_PWRON_DELAY_WAI_8B (3000) + +/* + * LIS3LV02D spec says 1024 LSBs corresponds 1 G -> 1LSB is 1000/1024 mG + * LIS302D spec says: 18 mG / digit + * LIS3_ACCURACY is used to increase accuracy of the intermediate + * calculation results. + */ +#define LIS3_ACCURACY 1024 +/* Sensitivity values for -2G +2G scale */ +#define LIS3_SENSITIVITY_12B ((LIS3_ACCURACY * 1000) / 1024) +#define LIS3_SENSITIVITY_8B (18 * LIS3_ACCURACY) + +#define LIS3_DEFAULT_FUZZ_12B 3 +#define LIS3_DEFAULT_FLAT_12B 3 +#define LIS3_DEFAULT_FUZZ_8B 1 +#define LIS3_DEFAULT_FLAT_8B 1 + +struct lis3lv02d lis3_dev = { + .misc_wait = __WAIT_QUEUE_HEAD_INITIALIZER(lis3_dev.misc_wait), +}; +EXPORT_SYMBOL_GPL(lis3_dev); + +/* just like param_set_int() but does sanity-check so that it won't point + * over the axis array size + */ +static int param_set_axis(const char *val, const struct kernel_param *kp) +{ + int ret = param_set_int(val, kp); + if (!ret) { + int val = *(int *)kp->arg; + if (val < 0) + val = -val; + if (!val || val > 3) + return -EINVAL; + } + return ret; +} + +static struct kernel_param_ops param_ops_axis = { + .set = param_set_axis, + .get = param_get_int, +}; + +module_param_array_named(axes, lis3_dev.ac.as_array, axis, NULL, 0644); +MODULE_PARM_DESC(axes, "Axis-mapping for x,y,z directions"); + +static s16 lis3lv02d_read_8(struct lis3lv02d *lis3, int reg) +{ + s8 lo; + if (lis3->read(lis3, reg, &lo) < 0) + return 0; + + return lo; +} + +static s16 lis3lv02d_read_12(struct lis3lv02d *lis3, int reg) +{ + u8 lo, hi; + + lis3->read(lis3, reg - 1, &lo); + lis3->read(lis3, reg, &hi); + /* In "12 bit right justified" mode, bit 6, bit 7, bit 8 = bit 5 */ + return (s16)((hi << 8) | lo); +} + +/** + * lis3lv02d_get_axis - For the given axis, give the value converted + * @axis: 1,2,3 - can also be negative + * @hw_values: raw values returned by the hardware + * + * Returns the converted value. + */ +static inline int lis3lv02d_get_axis(s8 axis, int hw_values[3]) +{ + if (axis > 0) + return hw_values[axis - 1]; + else + return -hw_values[-axis - 1]; +} + +/** + * lis3lv02d_get_xyz - Get X, Y and Z axis values from the accelerometer + * @lis3: pointer to the device struct + * @x: where to store the X axis value + * @y: where to store the Y axis value + * @z: where to store the Z axis value + * + * Note that 40Hz input device can eat up about 10% CPU at 800MHZ + */ +static void lis3lv02d_get_xyz(struct lis3lv02d *lis3, int *x, int *y, int *z) +{ + int position[3]; + int i; + + if (lis3->blkread) { + if (lis3_dev.whoami == WAI_12B) { + u16 data[3]; + lis3->blkread(lis3, OUTX_L, 6, (u8 *)data); + for (i = 0; i < 3; i++) + position[i] = (s16)le16_to_cpu(data[i]); + } else { + u8 data[5]; + /* Data: x, dummy, y, dummy, z */ + lis3->blkread(lis3, OUTX, 5, data); + for (i = 0; i < 3; i++) + position[i] = (s8)data[i * 2]; + } + } else { + position[0] = lis3->read_data(lis3, OUTX); + position[1] = lis3->read_data(lis3, OUTY); + position[2] = lis3->read_data(lis3, OUTZ); + } + + for (i = 0; i < 3; i++) + position[i] = (position[i] * lis3->scale) / LIS3_ACCURACY; + + *x = lis3lv02d_get_axis(lis3->ac.x, position); + *y = lis3lv02d_get_axis(lis3->ac.y, position); + *z = lis3lv02d_get_axis(lis3->ac.z, position); +} + +/* conversion btw sampling rate and the register values */ +static int lis3_12_rates[4] = {40, 160, 640, 2560}; +static int lis3_8_rates[2] = {100, 400}; +static int lis3_3dc_rates[16] = {0, 1, 10, 25, 50, 100, 200, 400, 1600, 5000}; + +/* ODR is Output Data Rate */ +static int lis3lv02d_get_odr(void) +{ + u8 ctrl; + int shift; + + lis3_dev.read(&lis3_dev, CTRL_REG1, &ctrl); + ctrl &= lis3_dev.odr_mask; + shift = ffs(lis3_dev.odr_mask) - 1; + return lis3_dev.odrs[(ctrl >> shift)]; +} + +static int lis3lv02d_set_odr(int rate) +{ + u8 ctrl; + int i, len, shift; + + if (!rate) + return -EINVAL; + + lis3_dev.read(&lis3_dev, CTRL_REG1, &ctrl); + ctrl &= ~lis3_dev.odr_mask; + len = 1 << hweight_long(lis3_dev.odr_mask); /* # of possible values */ + shift = ffs(lis3_dev.odr_mask) - 1; + + for (i = 0; i < len; i++) + if (lis3_dev.odrs[i] == rate) { + lis3_dev.write(&lis3_dev, CTRL_REG1, + ctrl | (i << shift)); + return 0; + } + return -EINVAL; +} + +static int lis3lv02d_selftest(struct lis3lv02d *lis3, s16 results[3]) +{ + u8 ctlreg, reg; + s16 x, y, z; + u8 selftest; + int ret; + u8 ctrl_reg_data; + unsigned char irq_cfg; + + mutex_lock(&lis3->mutex); + + irq_cfg = lis3->irq_cfg; + if (lis3_dev.whoami == WAI_8B) { + lis3->data_ready_count[IRQ_LINE0] = 0; + lis3->data_ready_count[IRQ_LINE1] = 0; + + /* Change interrupt cfg to data ready for selftest */ + atomic_inc(&lis3_dev.wake_thread); + lis3->irq_cfg = LIS3_IRQ1_DATA_READY | LIS3_IRQ2_DATA_READY; + lis3->read(lis3, CTRL_REG3, &ctrl_reg_data); + lis3->write(lis3, CTRL_REG3, (ctrl_reg_data & + ~(LIS3_IRQ1_MASK | LIS3_IRQ2_MASK)) | + (LIS3_IRQ1_DATA_READY | LIS3_IRQ2_DATA_READY)); + } + + if (lis3_dev.whoami == WAI_3DC) { + ctlreg = CTRL_REG4; + selftest = CTRL4_ST0; + } else { + ctlreg = CTRL_REG1; + if (lis3_dev.whoami == WAI_12B) + selftest = CTRL1_ST; + else + selftest = CTRL1_STP; + } + + lis3->read(lis3, ctlreg, ®); + lis3->write(lis3, ctlreg, (reg | selftest)); + msleep(lis3->pwron_delay / lis3lv02d_get_odr()); + + /* Read directly to avoid axis remap */ + x = lis3->read_data(lis3, OUTX); + y = lis3->read_data(lis3, OUTY); + z = lis3->read_data(lis3, OUTZ); + + /* back to normal settings */ + lis3->write(lis3, ctlreg, reg); + msleep(lis3->pwron_delay / lis3lv02d_get_odr()); + + results[0] = x - lis3->read_data(lis3, OUTX); + results[1] = y - lis3->read_data(lis3, OUTY); + results[2] = z - lis3->read_data(lis3, OUTZ); + + ret = 0; + + if (lis3_dev.whoami == WAI_8B) { + /* Restore original interrupt configuration */ + atomic_dec(&lis3_dev.wake_thread); + lis3->write(lis3, CTRL_REG3, ctrl_reg_data); + lis3->irq_cfg = irq_cfg; + + if ((irq_cfg & LIS3_IRQ1_MASK) && + lis3->data_ready_count[IRQ_LINE0] < 2) { + ret = SELFTEST_IRQ; + goto fail; + } + + if ((irq_cfg & LIS3_IRQ2_MASK) && + lis3->data_ready_count[IRQ_LINE1] < 2) { + ret = SELFTEST_IRQ; + goto fail; + } + } + + if (lis3->pdata) { + int i; + for (i = 0; i < 3; i++) { + /* Check against selftest acceptance limits */ + if ((results[i] < lis3->pdata->st_min_limits[i]) || + (results[i] > lis3->pdata->st_max_limits[i])) { + ret = SELFTEST_FAIL; + goto fail; + } + } + } + + /* test passed */ +fail: + mutex_unlock(&lis3->mutex); + return ret; +} + +/* + * Order of registers in the list affects to order of the restore process. + * Perhaps it is a good idea to set interrupt enable register as a last one + * after all other configurations + */ +static u8 lis3_wai8_regs[] = { FF_WU_CFG_1, FF_WU_THS_1, FF_WU_DURATION_1, + FF_WU_CFG_2, FF_WU_THS_2, FF_WU_DURATION_2, + CLICK_CFG, CLICK_SRC, CLICK_THSY_X, CLICK_THSZ, + CLICK_TIMELIMIT, CLICK_LATENCY, CLICK_WINDOW, + CTRL_REG1, CTRL_REG2, CTRL_REG3}; + +static u8 lis3_wai12_regs[] = {FF_WU_CFG, FF_WU_THS_L, FF_WU_THS_H, + FF_WU_DURATION, DD_CFG, DD_THSI_L, DD_THSI_H, + DD_THSE_L, DD_THSE_H, + CTRL_REG1, CTRL_REG3, CTRL_REG2}; + +static inline void lis3_context_save(struct lis3lv02d *lis3) +{ + int i; + for (i = 0; i < lis3->regs_size; i++) + lis3->read(lis3, lis3->regs[i], &lis3->reg_cache[i]); + lis3->regs_stored = true; +} + +static inline void lis3_context_restore(struct lis3lv02d *lis3) +{ + int i; + if (lis3->regs_stored) + for (i = 0; i < lis3->regs_size; i++) + lis3->write(lis3, lis3->regs[i], lis3->reg_cache[i]); +} + +void lis3lv02d_poweroff(struct lis3lv02d *lis3) +{ + if (lis3->reg_ctrl) + lis3_context_save(lis3); + /* disable X,Y,Z axis and power down */ + lis3->write(lis3, CTRL_REG1, 0x00); + if (lis3->reg_ctrl) + lis3->reg_ctrl(lis3, LIS3_REG_OFF); +} +EXPORT_SYMBOL_GPL(lis3lv02d_poweroff); + +void lis3lv02d_poweron(struct lis3lv02d *lis3) +{ + u8 reg; + + lis3->init(lis3); + + /* + * Common configuration + * BDU: (12 bits sensors only) LSB and MSB values are not updated until + * both have been read. So the value read will always be correct. + * Set BOOT bit to refresh factory tuning values. + */ + lis3->read(lis3, CTRL_REG2, ®); + if (lis3->whoami == WAI_12B) + reg |= CTRL2_BDU | CTRL2_BOOT; + else + reg |= CTRL2_BOOT_8B; + lis3->write(lis3, CTRL_REG2, reg); + + /* LIS3 power on delay is quite long */ + msleep(lis3->pwron_delay / lis3lv02d_get_odr()); + + if (lis3->reg_ctrl) + lis3_context_restore(lis3); +} +EXPORT_SYMBOL_GPL(lis3lv02d_poweron); + + +static void lis3lv02d_joystick_poll(struct input_polled_dev *pidev) +{ + int x, y, z; + + mutex_lock(&lis3_dev.mutex); + lis3lv02d_get_xyz(&lis3_dev, &x, &y, &z); + input_report_abs(pidev->input, ABS_X, x); + input_report_abs(pidev->input, ABS_Y, y); + input_report_abs(pidev->input, ABS_Z, z); + input_sync(pidev->input); + mutex_unlock(&lis3_dev.mutex); +} + +static void lis3lv02d_joystick_open(struct input_polled_dev *pidev) +{ + if (lis3_dev.pm_dev) + pm_runtime_get_sync(lis3_dev.pm_dev); + + if (lis3_dev.pdata && lis3_dev.whoami == WAI_8B && lis3_dev.idev) + atomic_set(&lis3_dev.wake_thread, 1); + /* + * Update coordinates for the case where poll interval is 0 and + * the chip in running purely under interrupt control + */ + lis3lv02d_joystick_poll(pidev); +} + +static void lis3lv02d_joystick_close(struct input_polled_dev *pidev) +{ + atomic_set(&lis3_dev.wake_thread, 0); + if (lis3_dev.pm_dev) + pm_runtime_put(lis3_dev.pm_dev); +} + +static irqreturn_t lis302dl_interrupt(int irq, void *dummy) +{ + if (!test_bit(0, &lis3_dev.misc_opened)) + goto out; + + /* + * Be careful: on some HP laptops the bios force DD when on battery and + * the lid is closed. This leads to interrupts as soon as a little move + * is done. + */ + atomic_inc(&lis3_dev.count); + + wake_up_interruptible(&lis3_dev.misc_wait); + kill_fasync(&lis3_dev.async_queue, SIGIO, POLL_IN); +out: + if (atomic_read(&lis3_dev.wake_thread)) + return IRQ_WAKE_THREAD; + return IRQ_HANDLED; +} + +static void lis302dl_interrupt_handle_click(struct lis3lv02d *lis3) +{ + struct input_dev *dev = lis3->idev->input; + u8 click_src; + + mutex_lock(&lis3->mutex); + lis3->read(lis3, CLICK_SRC, &click_src); + + if (click_src & CLICK_SINGLE_X) { + input_report_key(dev, lis3->mapped_btns[0], 1); + input_report_key(dev, lis3->mapped_btns[0], 0); + } + + if (click_src & CLICK_SINGLE_Y) { + input_report_key(dev, lis3->mapped_btns[1], 1); + input_report_key(dev, lis3->mapped_btns[1], 0); + } + + if (click_src & CLICK_SINGLE_Z) { + input_report_key(dev, lis3->mapped_btns[2], 1); + input_report_key(dev, lis3->mapped_btns[2], 0); + } + input_sync(dev); + mutex_unlock(&lis3->mutex); +} + +static inline void lis302dl_data_ready(struct lis3lv02d *lis3, int index) +{ + int dummy; + + /* Dummy read to ack interrupt */ + lis3lv02d_get_xyz(lis3, &dummy, &dummy, &dummy); + lis3->data_ready_count[index]++; +} + +static irqreturn_t lis302dl_interrupt_thread1_8b(int irq, void *data) +{ + struct lis3lv02d *lis3 = data; + u8 irq_cfg = lis3->irq_cfg & LIS3_IRQ1_MASK; + + if (irq_cfg == LIS3_IRQ1_CLICK) + lis302dl_interrupt_handle_click(lis3); + else if (unlikely(irq_cfg == LIS3_IRQ1_DATA_READY)) + lis302dl_data_ready(lis3, IRQ_LINE0); + else + lis3lv02d_joystick_poll(lis3->idev); + + return IRQ_HANDLED; +} + +static irqreturn_t lis302dl_interrupt_thread2_8b(int irq, void *data) +{ + struct lis3lv02d *lis3 = data; + u8 irq_cfg = lis3->irq_cfg & LIS3_IRQ2_MASK; + + if (irq_cfg == LIS3_IRQ2_CLICK) + lis302dl_interrupt_handle_click(lis3); + else if (unlikely(irq_cfg == LIS3_IRQ2_DATA_READY)) + lis302dl_data_ready(lis3, IRQ_LINE1); + else + lis3lv02d_joystick_poll(lis3->idev); + + return IRQ_HANDLED; +} + +static int lis3lv02d_misc_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &lis3_dev.misc_opened)) + return -EBUSY; /* already open */ + + if (lis3_dev.pm_dev) + pm_runtime_get_sync(lis3_dev.pm_dev); + + atomic_set(&lis3_dev.count, 0); + return 0; +} + +static int lis3lv02d_misc_release(struct inode *inode, struct file *file) +{ + fasync_helper(-1, file, 0, &lis3_dev.async_queue); + clear_bit(0, &lis3_dev.misc_opened); /* release the device */ + if (lis3_dev.pm_dev) + pm_runtime_put(lis3_dev.pm_dev); + return 0; +} + +static ssize_t lis3lv02d_misc_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + DECLARE_WAITQUEUE(wait, current); + u32 data; + unsigned char byte_data; + ssize_t retval = 1; + + if (count < 1) + return -EINVAL; + + add_wait_queue(&lis3_dev.misc_wait, &wait); + while (true) { + set_current_state(TASK_INTERRUPTIBLE); + data = atomic_xchg(&lis3_dev.count, 0); + if (data) + break; + + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto out; + } + + if (signal_pending(current)) { + retval = -ERESTARTSYS; + goto out; + } + + schedule(); + } + + if (data < 255) + byte_data = data; + else + byte_data = 255; + + /* make sure we are not going into copy_to_user() with + * TASK_INTERRUPTIBLE state */ + set_current_state(TASK_RUNNING); + if (copy_to_user(buf, &byte_data, sizeof(byte_data))) + retval = -EFAULT; + +out: + __set_current_state(TASK_RUNNING); + remove_wait_queue(&lis3_dev.misc_wait, &wait); + + return retval; +} + +static unsigned int lis3lv02d_misc_poll(struct file *file, poll_table *wait) +{ + poll_wait(file, &lis3_dev.misc_wait, wait); + if (atomic_read(&lis3_dev.count)) + return POLLIN | POLLRDNORM; + return 0; +} + +static int lis3lv02d_misc_fasync(int fd, struct file *file, int on) +{ + return fasync_helper(fd, file, on, &lis3_dev.async_queue); +} + +static const struct file_operations lis3lv02d_misc_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = lis3lv02d_misc_read, + .open = lis3lv02d_misc_open, + .release = lis3lv02d_misc_release, + .poll = lis3lv02d_misc_poll, + .fasync = lis3lv02d_misc_fasync, +}; + +static struct miscdevice lis3lv02d_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "freefall", + .fops = &lis3lv02d_misc_fops, +}; + +int lis3lv02d_joystick_enable(void) +{ + struct input_dev *input_dev; + int err; + int max_val, fuzz, flat; + int btns[] = {BTN_X, BTN_Y, BTN_Z}; + + if (lis3_dev.idev) + return -EINVAL; + + lis3_dev.idev = input_allocate_polled_device(); + if (!lis3_dev.idev) + return -ENOMEM; + + lis3_dev.idev->poll = lis3lv02d_joystick_poll; + lis3_dev.idev->open = lis3lv02d_joystick_open; + lis3_dev.idev->close = lis3lv02d_joystick_close; + lis3_dev.idev->poll_interval = MDPS_POLL_INTERVAL; + lis3_dev.idev->poll_interval_min = MDPS_POLL_MIN; + lis3_dev.idev->poll_interval_max = MDPS_POLL_MAX; + input_dev = lis3_dev.idev->input; + + input_dev->name = "ST LIS3LV02DL Accelerometer"; + input_dev->phys = DRIVER_NAME "/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0; + input_dev->dev.parent = &lis3_dev.pdev->dev; + + set_bit(EV_ABS, input_dev->evbit); + max_val = (lis3_dev.mdps_max_val * lis3_dev.scale) / LIS3_ACCURACY; + if (lis3_dev.whoami == WAI_12B) { + fuzz = LIS3_DEFAULT_FUZZ_12B; + flat = LIS3_DEFAULT_FLAT_12B; + } else { + fuzz = LIS3_DEFAULT_FUZZ_8B; + flat = LIS3_DEFAULT_FLAT_8B; + } + fuzz = (fuzz * lis3_dev.scale) / LIS3_ACCURACY; + flat = (flat * lis3_dev.scale) / LIS3_ACCURACY; + + input_set_abs_params(input_dev, ABS_X, -max_val, max_val, fuzz, flat); + input_set_abs_params(input_dev, ABS_Y, -max_val, max_val, fuzz, flat); + input_set_abs_params(input_dev, ABS_Z, -max_val, max_val, fuzz, flat); + + lis3_dev.mapped_btns[0] = lis3lv02d_get_axis(abs(lis3_dev.ac.x), btns); + lis3_dev.mapped_btns[1] = lis3lv02d_get_axis(abs(lis3_dev.ac.y), btns); + lis3_dev.mapped_btns[2] = lis3lv02d_get_axis(abs(lis3_dev.ac.z), btns); + + err = input_register_polled_device(lis3_dev.idev); + if (err) { + input_free_polled_device(lis3_dev.idev); + lis3_dev.idev = NULL; + } + + return err; +} +EXPORT_SYMBOL_GPL(lis3lv02d_joystick_enable); + +void lis3lv02d_joystick_disable(void) +{ + if (lis3_dev.irq) + free_irq(lis3_dev.irq, &lis3_dev); + if (lis3_dev.pdata && lis3_dev.pdata->irq2) + free_irq(lis3_dev.pdata->irq2, &lis3_dev); + + if (!lis3_dev.idev) + return; + + if (lis3_dev.irq) + misc_deregister(&lis3lv02d_misc_device); + input_unregister_polled_device(lis3_dev.idev); + input_free_polled_device(lis3_dev.idev); + lis3_dev.idev = NULL; +} +EXPORT_SYMBOL_GPL(lis3lv02d_joystick_disable); + +/* Sysfs stuff */ +static void lis3lv02d_sysfs_poweron(struct lis3lv02d *lis3) +{ + /* + * SYSFS functions are fast visitors so put-call + * immediately after the get-call. However, keep + * chip running for a while and schedule delayed + * suspend. This way periodic sysfs calls doesn't + * suffer from relatively long power up time. + */ + + if (lis3->pm_dev) { + pm_runtime_get_sync(lis3->pm_dev); + pm_runtime_put_noidle(lis3->pm_dev); + pm_schedule_suspend(lis3->pm_dev, LIS3_SYSFS_POWERDOWN_DELAY); + } +} + +static ssize_t lis3lv02d_selftest_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + s16 values[3]; + + static const char ok[] = "OK"; + static const char fail[] = "FAIL"; + static const char irq[] = "FAIL_IRQ"; + const char *res; + + lis3lv02d_sysfs_poweron(&lis3_dev); + switch (lis3lv02d_selftest(&lis3_dev, values)) { + case SELFTEST_FAIL: + res = fail; + break; + case SELFTEST_IRQ: + res = irq; + break; + case SELFTEST_OK: + default: + res = ok; + break; + } + return sprintf(buf, "%s %d %d %d\n", res, + values[0], values[1], values[2]); +} + +static ssize_t lis3lv02d_position_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int x, y, z; + + lis3lv02d_sysfs_poweron(&lis3_dev); + mutex_lock(&lis3_dev.mutex); + lis3lv02d_get_xyz(&lis3_dev, &x, &y, &z); + mutex_unlock(&lis3_dev.mutex); + return sprintf(buf, "(%d,%d,%d)\n", x, y, z); +} + +static ssize_t lis3lv02d_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + lis3lv02d_sysfs_poweron(&lis3_dev); + return sprintf(buf, "%d\n", lis3lv02d_get_odr()); +} + +static ssize_t lis3lv02d_rate_set(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + unsigned long rate; + + if (strict_strtoul(buf, 0, &rate)) + return -EINVAL; + + lis3lv02d_sysfs_poweron(&lis3_dev); + if (lis3lv02d_set_odr(rate)) + return -EINVAL; + + return count; +} + +static DEVICE_ATTR(selftest, S_IRUSR, lis3lv02d_selftest_show, NULL); +static DEVICE_ATTR(position, S_IRUGO, lis3lv02d_position_show, NULL); +static DEVICE_ATTR(rate, S_IRUGO | S_IWUSR, lis3lv02d_rate_show, + lis3lv02d_rate_set); + +static struct attribute *lis3lv02d_attributes[] = { + &dev_attr_selftest.attr, + &dev_attr_position.attr, + &dev_attr_rate.attr, + NULL +}; + +static struct attribute_group lis3lv02d_attribute_group = { + .attrs = lis3lv02d_attributes +}; + + +static int lis3lv02d_add_fs(struct lis3lv02d *lis3) +{ + lis3->pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0); + if (IS_ERR(lis3->pdev)) + return PTR_ERR(lis3->pdev); + + return sysfs_create_group(&lis3->pdev->dev.kobj, &lis3lv02d_attribute_group); +} + +int lis3lv02d_remove_fs(struct lis3lv02d *lis3) +{ + sysfs_remove_group(&lis3->pdev->dev.kobj, &lis3lv02d_attribute_group); + platform_device_unregister(lis3->pdev); + if (lis3->pm_dev) { + /* Barrier after the sysfs remove */ + pm_runtime_barrier(lis3->pm_dev); + + /* SYSFS may have left chip running. Turn off if necessary */ + if (!pm_runtime_suspended(lis3->pm_dev)) + lis3lv02d_poweroff(&lis3_dev); + + pm_runtime_disable(lis3->pm_dev); + pm_runtime_set_suspended(lis3->pm_dev); + } + kfree(lis3->reg_cache); + return 0; +} +EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs); + +static void lis3lv02d_8b_configure(struct lis3lv02d *dev, + struct lis3lv02d_platform_data *p) +{ + int err; + int ctrl2 = p->hipass_ctrl; + + if (p->click_flags) { + dev->write(dev, CLICK_CFG, p->click_flags); + dev->write(dev, CLICK_TIMELIMIT, p->click_time_limit); + dev->write(dev, CLICK_LATENCY, p->click_latency); + dev->write(dev, CLICK_WINDOW, p->click_window); + dev->write(dev, CLICK_THSZ, p->click_thresh_z & 0xf); + dev->write(dev, CLICK_THSY_X, + (p->click_thresh_x & 0xf) | + (p->click_thresh_y << 4)); + + if (dev->idev) { + struct input_dev *input_dev = lis3_dev.idev->input; + input_set_capability(input_dev, EV_KEY, BTN_X); + input_set_capability(input_dev, EV_KEY, BTN_Y); + input_set_capability(input_dev, EV_KEY, BTN_Z); + } + } + + if (p->wakeup_flags) { + dev->write(dev, FF_WU_CFG_1, p->wakeup_flags); + dev->write(dev, FF_WU_THS_1, p->wakeup_thresh & 0x7f); + /* pdata value + 1 to keep this backward compatible*/ + dev->write(dev, FF_WU_DURATION_1, p->duration1 + 1); + ctrl2 ^= HP_FF_WU1; /* Xor to keep compatible with old pdata*/ + } + + if (p->wakeup_flags2) { + dev->write(dev, FF_WU_CFG_2, p->wakeup_flags2); + dev->write(dev, FF_WU_THS_2, p->wakeup_thresh2 & 0x7f); + /* pdata value + 1 to keep this backward compatible*/ + dev->write(dev, FF_WU_DURATION_2, p->duration2 + 1); + ctrl2 ^= HP_FF_WU2; /* Xor to keep compatible with old pdata*/ + } + /* Configure hipass filters */ + dev->write(dev, CTRL_REG2, ctrl2); + + if (p->irq2) { + err = request_threaded_irq(p->irq2, + NULL, + lis302dl_interrupt_thread2_8b, + IRQF_TRIGGER_RISING | IRQF_ONESHOT | + (p->irq_flags2 & IRQF_TRIGGER_MASK), + DRIVER_NAME, &lis3_dev); + if (err < 0) + pr_err("No second IRQ. Limited functionality\n"); + } +} + +/* + * Initialise the accelerometer and the various subsystems. + * Should be rather independent of the bus system. + */ +int lis3lv02d_init_device(struct lis3lv02d *dev) +{ + int err; + irq_handler_t thread_fn; + int irq_flags = 0; + + dev->whoami = lis3lv02d_read_8(dev, WHO_AM_I); + + switch (dev->whoami) { + case WAI_12B: + pr_info("12 bits sensor found\n"); + dev->read_data = lis3lv02d_read_12; + dev->mdps_max_val = 2048; + dev->pwron_delay = LIS3_PWRON_DELAY_WAI_12B; + dev->odrs = lis3_12_rates; + dev->odr_mask = CTRL1_DF0 | CTRL1_DF1; + dev->scale = LIS3_SENSITIVITY_12B; + dev->regs = lis3_wai12_regs; + dev->regs_size = ARRAY_SIZE(lis3_wai12_regs); + break; + case WAI_8B: + pr_info("8 bits sensor found\n"); + dev->read_data = lis3lv02d_read_8; + dev->mdps_max_val = 128; + dev->pwron_delay = LIS3_PWRON_DELAY_WAI_8B; + dev->odrs = lis3_8_rates; + dev->odr_mask = CTRL1_DR; + dev->scale = LIS3_SENSITIVITY_8B; + dev->regs = lis3_wai8_regs; + dev->regs_size = ARRAY_SIZE(lis3_wai8_regs); + break; + case WAI_3DC: + pr_info("8 bits 3DC sensor found\n"); + dev->read_data = lis3lv02d_read_8; + dev->mdps_max_val = 128; + dev->pwron_delay = LIS3_PWRON_DELAY_WAI_8B; + dev->odrs = lis3_3dc_rates; + dev->odr_mask = CTRL1_ODR0|CTRL1_ODR1|CTRL1_ODR2|CTRL1_ODR3; + dev->scale = LIS3_SENSITIVITY_8B; + break; + default: + pr_err("unknown sensor type 0x%X\n", dev->whoami); + return -EINVAL; + } + + dev->reg_cache = kzalloc(max(sizeof(lis3_wai8_regs), + sizeof(lis3_wai12_regs)), GFP_KERNEL); + + if (dev->reg_cache == NULL) { + printk(KERN_ERR DRIVER_NAME "out of memory\n"); + return -ENOMEM; + } + + mutex_init(&dev->mutex); + atomic_set(&dev->wake_thread, 0); + + lis3lv02d_add_fs(dev); + lis3lv02d_poweron(dev); + + if (dev->pm_dev) { + pm_runtime_set_active(dev->pm_dev); + pm_runtime_enable(dev->pm_dev); + } + + if (lis3lv02d_joystick_enable()) + pr_err("joystick initialization failed\n"); + + /* passing in platform specific data is purely optional and only + * used by the SPI transport layer at the moment */ + if (dev->pdata) { + struct lis3lv02d_platform_data *p = dev->pdata; + + if (dev->whoami == WAI_8B) + lis3lv02d_8b_configure(dev, p); + + irq_flags = p->irq_flags1 & IRQF_TRIGGER_MASK; + + dev->irq_cfg = p->irq_cfg; + if (p->irq_cfg) + dev->write(dev, CTRL_REG3, p->irq_cfg); + + if (p->default_rate) + lis3lv02d_set_odr(p->default_rate); + } + + /* bail if we did not get an IRQ from the bus layer */ + if (!dev->irq) { + pr_debug("No IRQ. Disabling /dev/freefall\n"); + goto out; + } + + /* + * The sensor can generate interrupts for free-fall and direction + * detection (distinguishable with FF_WU_SRC and DD_SRC) but to keep + * the things simple and _fast_ we activate it only for free-fall, so + * no need to read register (very slow with ACPI). For the same reason, + * we forbid shared interrupts. + * + * IRQF_TRIGGER_RISING seems pointless on HP laptops because the + * io-apic is not configurable (and generates a warning) but I keep it + * in case of support for other hardware. + */ + if (dev->pdata && dev->whoami == WAI_8B) + thread_fn = lis302dl_interrupt_thread1_8b; + else + thread_fn = NULL; + + err = request_threaded_irq(dev->irq, lis302dl_interrupt, + thread_fn, + IRQF_TRIGGER_RISING | IRQF_ONESHOT | + irq_flags, + DRIVER_NAME, &lis3_dev); + + if (err < 0) { + pr_err("Cannot get IRQ\n"); + goto out; + } + + if (misc_register(&lis3lv02d_misc_device)) + pr_err("misc_register failed\n"); +out: + return 0; +} +EXPORT_SYMBOL_GPL(lis3lv02d_init_device); + +MODULE_DESCRIPTION("ST LIS3LV02Dx three-axis digital accelerometer driver"); +MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/lis3lv02d/lis3lv02d.h b/drivers/misc/lis3lv02d/lis3lv02d.h new file mode 100644 index 00000000000..a1939589eb2 --- /dev/null +++ b/drivers/misc/lis3lv02d/lis3lv02d.h @@ -0,0 +1,291 @@ +/* + * lis3lv02d.h - ST LIS3LV02DL accelerometer driver + * + * Copyright (C) 2007-2008 Yan Burman + * Copyright (C) 2008-2009 Eric Piel + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +/* + * This driver tries to support the "digital" accelerometer chips from + * STMicroelectronics such as LIS3LV02DL, LIS302DL, LIS3L02DQ, LIS331DL, + * LIS35DE, or LIS202DL. They are very similar in terms of programming, with + * almost the same registers. In addition to differing on physical properties, + * they differ on the number of axes (2/3), precision (8/12 bits), and special + * features (freefall detection, click...). Unfortunately, not all the + * differences can be probed via a register. + * They can be connected either via I²C or SPI. + */ + +#include + +enum lis3_reg { + WHO_AM_I = 0x0F, + OFFSET_X = 0x16, + OFFSET_Y = 0x17, + OFFSET_Z = 0x18, + GAIN_X = 0x19, + GAIN_Y = 0x1A, + GAIN_Z = 0x1B, + CTRL_REG1 = 0x20, + CTRL_REG2 = 0x21, + CTRL_REG3 = 0x22, + CTRL_REG4 = 0x23, + HP_FILTER_RESET = 0x23, + STATUS_REG = 0x27, + OUTX_L = 0x28, + OUTX_H = 0x29, + OUTX = 0x29, + OUTY_L = 0x2A, + OUTY_H = 0x2B, + OUTY = 0x2B, + OUTZ_L = 0x2C, + OUTZ_H = 0x2D, + OUTZ = 0x2D, +}; + +enum lis302d_reg { + FF_WU_CFG_1 = 0x30, + FF_WU_SRC_1 = 0x31, + FF_WU_THS_1 = 0x32, + FF_WU_DURATION_1 = 0x33, + FF_WU_CFG_2 = 0x34, + FF_WU_SRC_2 = 0x35, + FF_WU_THS_2 = 0x36, + FF_WU_DURATION_2 = 0x37, + CLICK_CFG = 0x38, + CLICK_SRC = 0x39, + CLICK_THSY_X = 0x3B, + CLICK_THSZ = 0x3C, + CLICK_TIMELIMIT = 0x3D, + CLICK_LATENCY = 0x3E, + CLICK_WINDOW = 0x3F, +}; + +enum lis3lv02d_reg { + FF_WU_CFG = 0x30, + FF_WU_SRC = 0x31, + FF_WU_ACK = 0x32, + FF_WU_THS_L = 0x34, + FF_WU_THS_H = 0x35, + FF_WU_DURATION = 0x36, + DD_CFG = 0x38, + DD_SRC = 0x39, + DD_ACK = 0x3A, + DD_THSI_L = 0x3C, + DD_THSI_H = 0x3D, + DD_THSE_L = 0x3E, + DD_THSE_H = 0x3F, +}; + +enum lis3_who_am_i { + WAI_3DC = 0x33, /* 8 bits: LIS3DC, HP3DC */ + WAI_12B = 0x3A, /* 12 bits: LIS3LV02D[LQ]... */ + WAI_8B = 0x3B, /* 8 bits: LIS[23]02D[LQ]... */ + WAI_6B = 0x52, /* 6 bits: LIS331DLF - not supported */ +}; + +enum lis3lv02d_ctrl1_12b { + CTRL1_Xen = 0x01, + CTRL1_Yen = 0x02, + CTRL1_Zen = 0x04, + CTRL1_ST = 0x08, + CTRL1_DF0 = 0x10, + CTRL1_DF1 = 0x20, + CTRL1_PD0 = 0x40, + CTRL1_PD1 = 0x80, +}; + +/* Delta to ctrl1_12b version */ +enum lis3lv02d_ctrl1_8b { + CTRL1_STM = 0x08, + CTRL1_STP = 0x10, + CTRL1_FS = 0x20, + CTRL1_PD = 0x40, + CTRL1_DR = 0x80, +}; + +enum lis3lv02d_ctrl1_3dc { + CTRL1_ODR0 = 0x10, + CTRL1_ODR1 = 0x20, + CTRL1_ODR2 = 0x40, + CTRL1_ODR3 = 0x80, +}; + +enum lis3lv02d_ctrl2 { + CTRL2_DAS = 0x01, + CTRL2_SIM = 0x02, + CTRL2_DRDY = 0x04, + CTRL2_IEN = 0x08, + CTRL2_BOOT = 0x10, + CTRL2_BLE = 0x20, + CTRL2_BDU = 0x40, /* Block Data Update */ + CTRL2_FS = 0x80, /* Full Scale selection */ +}; + +enum lis3lv02d_ctrl4_3dc { + CTRL4_SIM = 0x01, + CTRL4_ST0 = 0x02, + CTRL4_ST1 = 0x04, + CTRL4_FS0 = 0x10, + CTRL4_FS1 = 0x20, +}; + +enum lis302d_ctrl2 { + HP_FF_WU2 = 0x08, + HP_FF_WU1 = 0x04, + CTRL2_BOOT_8B = 0x40, +}; + +enum lis3lv02d_ctrl3 { + CTRL3_CFS0 = 0x01, + CTRL3_CFS1 = 0x02, + CTRL3_FDS = 0x10, + CTRL3_HPFF = 0x20, + CTRL3_HPDD = 0x40, + CTRL3_ECK = 0x80, +}; + +enum lis3lv02d_status_reg { + STATUS_XDA = 0x01, + STATUS_YDA = 0x02, + STATUS_ZDA = 0x04, + STATUS_XYZDA = 0x08, + STATUS_XOR = 0x10, + STATUS_YOR = 0x20, + STATUS_ZOR = 0x40, + STATUS_XYZOR = 0x80, +}; + +enum lis3lv02d_ff_wu_cfg { + FF_WU_CFG_XLIE = 0x01, + FF_WU_CFG_XHIE = 0x02, + FF_WU_CFG_YLIE = 0x04, + FF_WU_CFG_YHIE = 0x08, + FF_WU_CFG_ZLIE = 0x10, + FF_WU_CFG_ZHIE = 0x20, + FF_WU_CFG_LIR = 0x40, + FF_WU_CFG_AOI = 0x80, +}; + +enum lis3lv02d_ff_wu_src { + FF_WU_SRC_XL = 0x01, + FF_WU_SRC_XH = 0x02, + FF_WU_SRC_YL = 0x04, + FF_WU_SRC_YH = 0x08, + FF_WU_SRC_ZL = 0x10, + FF_WU_SRC_ZH = 0x20, + FF_WU_SRC_IA = 0x40, +}; + +enum lis3lv02d_dd_cfg { + DD_CFG_XLIE = 0x01, + DD_CFG_XHIE = 0x02, + DD_CFG_YLIE = 0x04, + DD_CFG_YHIE = 0x08, + DD_CFG_ZLIE = 0x10, + DD_CFG_ZHIE = 0x20, + DD_CFG_LIR = 0x40, + DD_CFG_IEND = 0x80, +}; + +enum lis3lv02d_dd_src { + DD_SRC_XL = 0x01, + DD_SRC_XH = 0x02, + DD_SRC_YL = 0x04, + DD_SRC_YH = 0x08, + DD_SRC_ZL = 0x10, + DD_SRC_ZH = 0x20, + DD_SRC_IA = 0x40, +}; + +enum lis3lv02d_click_src_8b { + CLICK_SINGLE_X = 0x01, + CLICK_DOUBLE_X = 0x02, + CLICK_SINGLE_Y = 0x04, + CLICK_DOUBLE_Y = 0x08, + CLICK_SINGLE_Z = 0x10, + CLICK_DOUBLE_Z = 0x20, + CLICK_IA = 0x40, +}; + +enum lis3lv02d_reg_state { + LIS3_REG_OFF = 0x00, + LIS3_REG_ON = 0x01, +}; + +union axis_conversion { + struct { + int x, y, z; + }; + int as_array[3]; + +}; + +struct lis3lv02d { + void *bus_priv; /* used by the bus layer only */ + struct device *pm_dev; /* for pm_runtime purposes */ + int (*init) (struct lis3lv02d *lis3); + int (*write) (struct lis3lv02d *lis3, int reg, u8 val); + int (*read) (struct lis3lv02d *lis3, int reg, u8 *ret); + int (*blkread) (struct lis3lv02d *lis3, int reg, int len, u8 *ret); + int (*reg_ctrl) (struct lis3lv02d *lis3, bool state); + + int *odrs; /* Supported output data rates */ + u8 *regs; /* Regs to store / restore */ + int regs_size; + u8 *reg_cache; + bool regs_stored; + u8 odr_mask; /* ODR bit mask */ + u8 whoami; /* indicates measurement precision */ + s16 (*read_data) (struct lis3lv02d *lis3, int reg); + int mdps_max_val; + int pwron_delay; + int scale; /* + * relationship between 1 LBS and mG + * (1/1000th of earth gravity) + */ + + struct input_polled_dev *idev; /* input device */ + struct platform_device *pdev; /* platform device */ + struct regulator_bulk_data regulators[2]; + atomic_t count; /* interrupt count after last read */ + union axis_conversion ac; /* hw -> logical axis */ + int mapped_btns[3]; + + u32 irq; /* IRQ number */ + struct fasync_struct *async_queue; /* queue for the misc device */ + wait_queue_head_t misc_wait; /* Wait queue for the misc device */ + unsigned long misc_opened; /* bit0: whether the device is open */ + int data_ready_count[2]; + atomic_t wake_thread; + unsigned char irq_cfg; + + struct lis3lv02d_platform_data *pdata; /* for passing board config */ + struct mutex mutex; /* Serialize poll and selftest */ +}; + +int lis3lv02d_init_device(struct lis3lv02d *lis3); +int lis3lv02d_joystick_enable(void); +void lis3lv02d_joystick_disable(void); +void lis3lv02d_poweroff(struct lis3lv02d *lis3); +void lis3lv02d_poweron(struct lis3lv02d *lis3); +int lis3lv02d_remove_fs(struct lis3lv02d *lis3); + +extern struct lis3lv02d lis3_dev; diff --git a/drivers/misc/lis3lv02d/lis3lv02d_i2c.c b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c new file mode 100644 index 00000000000..b20dfb4522d --- /dev/null +++ b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c @@ -0,0 +1,279 @@ +/* + * drivers/hwmon/lis3lv02d_i2c.c + * + * Implements I2C interface for lis3lv02d (STMicroelectronics) accelerometer. + * Driver is based on corresponding SPI driver written by Daniel Mack + * (lis3lv02d_spi.c (C) 2009 Daniel Mack ). + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: Samu Onkalo + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include "lis3lv02d.h" + +#define DRV_NAME "lis3lv02d_i2c" + +static const char reg_vdd[] = "Vdd"; +static const char reg_vdd_io[] = "Vdd_IO"; + +static int lis3_reg_ctrl(struct lis3lv02d *lis3, bool state) +{ + int ret; + if (state == LIS3_REG_OFF) { + ret = regulator_bulk_disable(ARRAY_SIZE(lis3->regulators), + lis3->regulators); + } else { + ret = regulator_bulk_enable(ARRAY_SIZE(lis3->regulators), + lis3->regulators); + /* Chip needs time to wakeup. Not mentioned in datasheet */ + usleep_range(10000, 20000); + } + return ret; +} + +static inline s32 lis3_i2c_write(struct lis3lv02d *lis3, int reg, u8 value) +{ + struct i2c_client *c = lis3->bus_priv; + return i2c_smbus_write_byte_data(c, reg, value); +} + +static inline s32 lis3_i2c_read(struct lis3lv02d *lis3, int reg, u8 *v) +{ + struct i2c_client *c = lis3->bus_priv; + *v = i2c_smbus_read_byte_data(c, reg); + return 0; +} + +static inline s32 lis3_i2c_blockread(struct lis3lv02d *lis3, int reg, int len, + u8 *v) +{ + struct i2c_client *c = lis3->bus_priv; + reg |= (1 << 7); /* 7th bit enables address auto incrementation */ + return i2c_smbus_read_i2c_block_data(c, reg, len, v); +} + +static int lis3_i2c_init(struct lis3lv02d *lis3) +{ + u8 reg; + int ret; + + if (lis3->reg_ctrl) + lis3_reg_ctrl(lis3, LIS3_REG_ON); + + lis3->read(lis3, WHO_AM_I, ®); + if (reg != lis3->whoami) + printk(KERN_ERR "lis3: power on failure\n"); + + /* power up the device */ + ret = lis3->read(lis3, CTRL_REG1, ®); + if (ret < 0) + return ret; + + reg |= CTRL1_PD0 | CTRL1_Xen | CTRL1_Yen | CTRL1_Zen; + return lis3->write(lis3, CTRL_REG1, reg); +} + +/* Default axis mapping but it can be overwritten by platform data */ +static union axis_conversion lis3lv02d_axis_map = + { .as_array = { LIS3_DEV_X, LIS3_DEV_Y, LIS3_DEV_Z } }; + +static int __devinit lis3lv02d_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct lis3lv02d_platform_data *pdata = client->dev.platform_data; + + if (pdata) { + /* Regulator control is optional */ + if (pdata->driver_features & LIS3_USE_REGULATOR_CTRL) + lis3_dev.reg_ctrl = lis3_reg_ctrl; + + if ((pdata->driver_features & LIS3_USE_BLOCK_READ) && + (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_I2C_BLOCK))) + lis3_dev.blkread = lis3_i2c_blockread; + + if (pdata->axis_x) + lis3lv02d_axis_map.x = pdata->axis_x; + + if (pdata->axis_y) + lis3lv02d_axis_map.y = pdata->axis_y; + + if (pdata->axis_z) + lis3lv02d_axis_map.z = pdata->axis_z; + + if (pdata->setup_resources) + ret = pdata->setup_resources(); + + if (ret) + goto fail; + } + + if (lis3_dev.reg_ctrl) { + lis3_dev.regulators[0].supply = reg_vdd; + lis3_dev.regulators[1].supply = reg_vdd_io; + ret = regulator_bulk_get(&client->dev, + ARRAY_SIZE(lis3_dev.regulators), + lis3_dev.regulators); + if (ret < 0) + goto fail; + } + + lis3_dev.pdata = pdata; + lis3_dev.bus_priv = client; + lis3_dev.init = lis3_i2c_init; + lis3_dev.read = lis3_i2c_read; + lis3_dev.write = lis3_i2c_write; + lis3_dev.irq = client->irq; + lis3_dev.ac = lis3lv02d_axis_map; + lis3_dev.pm_dev = &client->dev; + + i2c_set_clientdata(client, &lis3_dev); + + /* Provide power over the init call */ + if (lis3_dev.reg_ctrl) + lis3_reg_ctrl(&lis3_dev, LIS3_REG_ON); + + ret = lis3lv02d_init_device(&lis3_dev); + + if (lis3_dev.reg_ctrl) + lis3_reg_ctrl(&lis3_dev, LIS3_REG_OFF); + + if (ret == 0) + return 0; +fail: + if (pdata && pdata->release_resources) + pdata->release_resources(); + return ret; +} + +static int __devexit lis3lv02d_i2c_remove(struct i2c_client *client) +{ + struct lis3lv02d *lis3 = i2c_get_clientdata(client); + struct lis3lv02d_platform_data *pdata = client->dev.platform_data; + + if (pdata && pdata->release_resources) + pdata->release_resources(); + + lis3lv02d_joystick_disable(); + lis3lv02d_remove_fs(&lis3_dev); + + if (lis3_dev.reg_ctrl) + regulator_bulk_free(ARRAY_SIZE(lis3->regulators), + lis3_dev.regulators); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int lis3lv02d_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct lis3lv02d *lis3 = i2c_get_clientdata(client); + + if (!lis3->pdata || !lis3->pdata->wakeup_flags) + lis3lv02d_poweroff(lis3); + return 0; +} + +static int lis3lv02d_i2c_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct lis3lv02d *lis3 = i2c_get_clientdata(client); + + /* + * pm_runtime documentation says that devices should always + * be powered on at resume. Pm_runtime turns them off after system + * wide resume is complete. + */ + if (!lis3->pdata || !lis3->pdata->wakeup_flags || + pm_runtime_suspended(dev)) + lis3lv02d_poweron(lis3); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM_RUNTIME +static int lis3_i2c_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct lis3lv02d *lis3 = i2c_get_clientdata(client); + + lis3lv02d_poweroff(lis3); + return 0; +} + +static int lis3_i2c_runtime_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct lis3lv02d *lis3 = i2c_get_clientdata(client); + + lis3lv02d_poweron(lis3); + return 0; +} +#endif /* CONFIG_PM_RUNTIME */ + +static const struct i2c_device_id lis3lv02d_id[] = { + {"lis3lv02d", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lis3lv02d_id); + +static const struct dev_pm_ops lis3_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(lis3lv02d_i2c_suspend, + lis3lv02d_i2c_resume) + SET_RUNTIME_PM_OPS(lis3_i2c_runtime_suspend, + lis3_i2c_runtime_resume, + NULL) +}; + +static struct i2c_driver lis3lv02d_i2c_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = &lis3_pm_ops, + }, + .probe = lis3lv02d_i2c_probe, + .remove = __devexit_p(lis3lv02d_i2c_remove), + .id_table = lis3lv02d_id, +}; + +static int __init lis3lv02d_init(void) +{ + return i2c_add_driver(&lis3lv02d_i2c_driver); +} + +static void __exit lis3lv02d_exit(void) +{ + i2c_del_driver(&lis3lv02d_i2c_driver); +} + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("lis3lv02d I2C interface"); +MODULE_LICENSE("GPL"); + +module_init(lis3lv02d_init); +module_exit(lis3lv02d_exit); diff --git a/drivers/misc/lis3lv02d/lis3lv02d_spi.c b/drivers/misc/lis3lv02d/lis3lv02d_spi.c new file mode 100644 index 00000000000..c1f8a8fbf69 --- /dev/null +++ b/drivers/misc/lis3lv02d/lis3lv02d_spi.c @@ -0,0 +1,145 @@ +/* + * lis3lv02d_spi - SPI glue layer for lis3lv02d + * + * Copyright (c) 2009 Daniel Mack + * + * 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 + * publishhed by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lis3lv02d.h" + +#define DRV_NAME "lis3lv02d_spi" +#define LIS3_SPI_READ 0x80 + +static int lis3_spi_read(struct lis3lv02d *lis3, int reg, u8 *v) +{ + struct spi_device *spi = lis3->bus_priv; + int ret = spi_w8r8(spi, reg | LIS3_SPI_READ); + if (ret < 0) + return -EINVAL; + + *v = (u8) ret; + return 0; +} + +static int lis3_spi_write(struct lis3lv02d *lis3, int reg, u8 val) +{ + u8 tmp[2] = { reg, val }; + struct spi_device *spi = lis3->bus_priv; + return spi_write(spi, tmp, sizeof(tmp)); +} + +static int lis3_spi_init(struct lis3lv02d *lis3) +{ + u8 reg; + int ret; + + /* power up the device */ + ret = lis3->read(lis3, CTRL_REG1, ®); + if (ret < 0) + return ret; + + reg |= CTRL1_PD0 | CTRL1_Xen | CTRL1_Yen | CTRL1_Zen; + return lis3->write(lis3, CTRL_REG1, reg); +} + +static union axis_conversion lis3lv02d_axis_normal = + { .as_array = { 1, 2, 3 } }; + +static int __devinit lis302dl_spi_probe(struct spi_device *spi) +{ + int ret; + + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + ret = spi_setup(spi); + if (ret < 0) + return ret; + + lis3_dev.bus_priv = spi; + lis3_dev.init = lis3_spi_init; + lis3_dev.read = lis3_spi_read; + lis3_dev.write = lis3_spi_write; + lis3_dev.irq = spi->irq; + lis3_dev.ac = lis3lv02d_axis_normal; + lis3_dev.pdata = spi->dev.platform_data; + spi_set_drvdata(spi, &lis3_dev); + + return lis3lv02d_init_device(&lis3_dev); +} + +static int __devexit lis302dl_spi_remove(struct spi_device *spi) +{ + struct lis3lv02d *lis3 = spi_get_drvdata(spi); + lis3lv02d_joystick_disable(); + lis3lv02d_poweroff(lis3); + + return lis3lv02d_remove_fs(&lis3_dev); +} + +#ifdef CONFIG_PM_SLEEP +static int lis3lv02d_spi_suspend(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct lis3lv02d *lis3 = spi_get_drvdata(spi); + + if (!lis3->pdata || !lis3->pdata->wakeup_flags) + lis3lv02d_poweroff(&lis3_dev); + + return 0; +} + +static int lis3lv02d_spi_resume(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct lis3lv02d *lis3 = spi_get_drvdata(spi); + + if (!lis3->pdata || !lis3->pdata->wakeup_flags) + lis3lv02d_poweron(lis3); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(lis3lv02d_spi_pm, lis3lv02d_spi_suspend, + lis3lv02d_spi_resume); + +static struct spi_driver lis302dl_spi_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = &lis3lv02d_spi_pm, + }, + .probe = lis302dl_spi_probe, + .remove = __devexit_p(lis302dl_spi_remove), +}; + +static int __init lis302dl_init(void) +{ + return spi_register_driver(&lis302dl_spi_driver); +} + +static void __exit lis302dl_exit(void) +{ + spi_unregister_driver(&lis302dl_spi_driver); +} + +module_init(lis302dl_init); +module_exit(lis302dl_exit); + +MODULE_AUTHOR("Daniel Mack "); +MODULE_DESCRIPTION("lis3lv02d SPI glue layer"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:" DRV_NAME); diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c index 23f09c1b234..0a52711a636 100644 --- a/drivers/platform/x86/hp_accel.c +++ b/drivers/platform/x86/hp_accel.c @@ -37,7 +37,7 @@ #include #include #include -#include "../../hwmon/lis3lv02d.h" +#include "../../misc/lis3lv02d/lis3lv02d.h" #define DRIVER_NAME "lis3lv02d" #define ACPI_MDPS_CLASS "accelerometer" -- cgit v1.2.3-70-g09d2 From 05e82fe40faee8499b4e3ba12fddaaf013d84203 Mon Sep 17 00:00:00 2001 From: Len Sorensen Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: hwmon: (lm75) Add detection of the National Semiconductor LM75A Add support for detection of the National Semiconductor LM75A using the ID register value. Signed-off-by: Len Sorensen Signed-off-by: Jean Delvare --- Documentation/hwmon/lm75 | 5 +++++ drivers/hwmon/Kconfig | 2 +- drivers/hwmon/lm75.c | 56 +++++++++++++++++++++++++++++++++++------------- 3 files changed, 47 insertions(+), 16 deletions(-) (limited to 'drivers/hwmon') diff --git a/Documentation/hwmon/lm75 b/Documentation/hwmon/lm75 index 8e6356fe05d..a1790401fdd 100644 --- a/Documentation/hwmon/lm75 +++ b/Documentation/hwmon/lm75 @@ -7,6 +7,11 @@ Supported chips: Addresses scanned: I2C 0x48 - 0x4f Datasheet: Publicly available at the National Semiconductor website http://www.national.com/ + * National Semiconductor LM75A + Prefix: 'lm75a' + Addresses scanned: I2C 0x48 - 0x4f + Datasheet: Publicly available at the National Semiconductor website + http://www.national.com/ * Dallas Semiconductor DS75 Prefix: 'lm75' Addresses scanned: I2C 0x48 - 0x4f diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 47621abb9a0..19d72bcf966 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -521,7 +521,7 @@ config SENSORS_LM75 - Dallas Semiconductor DS75 and DS1775 - Maxim MAX6625 and MAX6626 - Microchip MCP980x - - National Semiconductor LM75 + - National Semiconductor LM75, LM75A - NXP's LM75A - ST Microelectronics STDS75 - TelCom (now Microchip) TCN75 diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index f36eb80d227..638dd0586e3 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -232,6 +232,8 @@ static const struct i2c_device_id lm75_ids[] = { }; MODULE_DEVICE_TABLE(i2c, lm75_ids); +#define LM75A_ID 0xA1 + /* Return 0 if detection is successful, -ENODEV otherwise */ static int lm75_detect(struct i2c_client *new_client, struct i2c_board_info *info) @@ -239,6 +241,7 @@ static int lm75_detect(struct i2c_client *new_client, struct i2c_adapter *adapter = new_client->adapter; int i; int cur, conf, hyst, os; + bool is_lm75a = 0; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) @@ -250,23 +253,43 @@ static int lm75_detect(struct i2c_client *new_client, addresses 0x04-0x07 returning the last read value. The cycling+unused addresses combination is not tested, since it would significantly slow the detection down and would - hardly add any value. */ + hardly add any value. + + The National Semiconductor LM75A is different than earlier + LM75s. It has an ID byte of 0xaX (where X is the chip + revision, with 1 being the only revision in existence) in + register 7, and unused registers return 0xff rather than the + last read value. */ - /* Unused addresses */ cur = i2c_smbus_read_word_data(new_client, 0); conf = i2c_smbus_read_byte_data(new_client, 1); - hyst = i2c_smbus_read_word_data(new_client, 2); - if (i2c_smbus_read_word_data(new_client, 4) != hyst - || i2c_smbus_read_word_data(new_client, 5) != hyst - || i2c_smbus_read_word_data(new_client, 6) != hyst - || i2c_smbus_read_word_data(new_client, 7) != hyst) - return -ENODEV; - os = i2c_smbus_read_word_data(new_client, 3); - if (i2c_smbus_read_word_data(new_client, 4) != os - || i2c_smbus_read_word_data(new_client, 5) != os - || i2c_smbus_read_word_data(new_client, 6) != os - || i2c_smbus_read_word_data(new_client, 7) != os) - return -ENODEV; + + /* First check for LM75A */ + if (i2c_smbus_read_byte_data(new_client, 7) == LM75A_ID) { + /* LM75A returns 0xff on unused registers so + just to be sure we check for that too. */ + if (i2c_smbus_read_byte_data(new_client, 4) != 0xff + || i2c_smbus_read_byte_data(new_client, 5) != 0xff + || i2c_smbus_read_byte_data(new_client, 6) != 0xff) + return -ENODEV; + is_lm75a = 1; + hyst = i2c_smbus_read_word_data(new_client, 2); + os = i2c_smbus_read_word_data(new_client, 3); + } else { /* Traditional style LM75 detection */ + /* Unused addresses */ + hyst = i2c_smbus_read_word_data(new_client, 2); + if (i2c_smbus_read_word_data(new_client, 4) != hyst + || i2c_smbus_read_word_data(new_client, 5) != hyst + || i2c_smbus_read_word_data(new_client, 6) != hyst + || i2c_smbus_read_word_data(new_client, 7) != hyst) + return -ENODEV; + os = i2c_smbus_read_word_data(new_client, 3); + if (i2c_smbus_read_word_data(new_client, 4) != os + || i2c_smbus_read_word_data(new_client, 5) != os + || i2c_smbus_read_word_data(new_client, 6) != os + || i2c_smbus_read_word_data(new_client, 7) != os) + return -ENODEV; + } /* Unused bits */ if (conf & 0xe0) @@ -278,9 +301,12 @@ static int lm75_detect(struct i2c_client *new_client, || i2c_smbus_read_word_data(new_client, i + 2) != hyst || i2c_smbus_read_word_data(new_client, i + 3) != os) return -ENODEV; + if (is_lm75a && i2c_smbus_read_byte_data(new_client, i + 7) + != LM75A_ID) + return -ENODEV; } - strlcpy(info->type, "lm75", I2C_NAME_SIZE); + strlcpy(info->type, is_lm75a ? "lm75a" : "lm75", I2C_NAME_SIZE); return 0; } -- cgit v1.2.3-70-g09d2 From e76f67b5babc65cd620d395a1fd231409808df90 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: hwmon: (lm75) Speed up detection Make the LM75/LM75A device detection faster: * Don't read the current temperature value when we don't use it. * Check for unused bits in the configuration register as soon as we have read its value. * Don't use word reads, not all devices support this, and some which don't misbehave when you try. * Check for cycling register values every 40 register addresses instead of every 8, it's 5 times faster and just as efficient. Some of these improvements come straight from the user-space sensors-detect script, so both detection routines are in line now. Signed-off-by: Jean Delvare Cc: Len Sorensen Acked-by: Guenter Roeck --- drivers/hwmon/lm75.c | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index 638dd0586e3..ef902d5d06a 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -240,7 +240,7 @@ static int lm75_detect(struct i2c_client *new_client, { struct i2c_adapter *adapter = new_client->adapter; int i; - int cur, conf, hyst, os; + int conf, hyst, os; bool is_lm75a = 0; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | @@ -261,8 +261,10 @@ static int lm75_detect(struct i2c_client *new_client, register 7, and unused registers return 0xff rather than the last read value. */ - cur = i2c_smbus_read_word_data(new_client, 0); + /* Unused bits */ conf = i2c_smbus_read_byte_data(new_client, 1); + if (conf & 0xe0) + return -ENODEV; /* First check for LM75A */ if (i2c_smbus_read_byte_data(new_client, 7) == LM75A_ID) { @@ -273,33 +275,29 @@ static int lm75_detect(struct i2c_client *new_client, || i2c_smbus_read_byte_data(new_client, 6) != 0xff) return -ENODEV; is_lm75a = 1; - hyst = i2c_smbus_read_word_data(new_client, 2); - os = i2c_smbus_read_word_data(new_client, 3); + hyst = i2c_smbus_read_byte_data(new_client, 2); + os = i2c_smbus_read_byte_data(new_client, 3); } else { /* Traditional style LM75 detection */ /* Unused addresses */ - hyst = i2c_smbus_read_word_data(new_client, 2); - if (i2c_smbus_read_word_data(new_client, 4) != hyst - || i2c_smbus_read_word_data(new_client, 5) != hyst - || i2c_smbus_read_word_data(new_client, 6) != hyst - || i2c_smbus_read_word_data(new_client, 7) != hyst) + hyst = i2c_smbus_read_byte_data(new_client, 2); + if (i2c_smbus_read_byte_data(new_client, 4) != hyst + || i2c_smbus_read_byte_data(new_client, 5) != hyst + || i2c_smbus_read_byte_data(new_client, 6) != hyst + || i2c_smbus_read_byte_data(new_client, 7) != hyst) return -ENODEV; - os = i2c_smbus_read_word_data(new_client, 3); - if (i2c_smbus_read_word_data(new_client, 4) != os - || i2c_smbus_read_word_data(new_client, 5) != os - || i2c_smbus_read_word_data(new_client, 6) != os - || i2c_smbus_read_word_data(new_client, 7) != os) + os = i2c_smbus_read_byte_data(new_client, 3); + if (i2c_smbus_read_byte_data(new_client, 4) != os + || i2c_smbus_read_byte_data(new_client, 5) != os + || i2c_smbus_read_byte_data(new_client, 6) != os + || i2c_smbus_read_byte_data(new_client, 7) != os) return -ENODEV; } - /* Unused bits */ - if (conf & 0xe0) - return -ENODEV; - /* Addresses cycling */ - for (i = 8; i < 0xff; i += 8) { + for (i = 8; i <= 248; i += 40) { if (i2c_smbus_read_byte_data(new_client, i + 1) != conf - || i2c_smbus_read_word_data(new_client, i + 2) != hyst - || i2c_smbus_read_word_data(new_client, i + 3) != os) + || i2c_smbus_read_byte_data(new_client, i + 2) != hyst + || i2c_smbus_read_byte_data(new_client, i + 3) != os) return -ENODEV; if (is_lm75a && i2c_smbus_read_byte_data(new_client, i + 7) != LM75A_ID) -- cgit v1.2.3-70-g09d2 From 93d0cc588345a9de304285781e010e298f74e06e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: hwmon: (abituguru*) Update my email address Signed-off-by: Hans de Goede Signed-off-by: Jean Delvare --- MAINTAINERS | 2 +- drivers/hwmon/abituguru.c | 4 ++-- drivers/hwmon/abituguru3.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers/hwmon') diff --git a/MAINTAINERS b/MAINTAINERS index 4edb2a800b5..666287a6f3d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -198,7 +198,7 @@ F: Documentation/scsi/aacraid.txt F: drivers/scsi/aacraid/ ABIT UGURU 1,2 HARDWARE MONITOR DRIVER -M: Hans de Goede +M: Hans de Goede L: lm-sensors@lm-sensors.org S: Maintained F: drivers/hwmon/abituguru.c diff --git a/drivers/hwmon/abituguru.c b/drivers/hwmon/abituguru.c index 8f07a9dda15..0e05aa179ea 100644 --- a/drivers/hwmon/abituguru.c +++ b/drivers/hwmon/abituguru.c @@ -1,5 +1,5 @@ /* - abituguru.c Copyright (c) 2005-2006 Hans de Goede + abituguru.c Copyright (c) 2005-2006 Hans de Goede 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 @@ -1505,7 +1505,7 @@ static void __exit abituguru_exit(void) platform_driver_unregister(&abituguru_driver); } -MODULE_AUTHOR("Hans de Goede "); +MODULE_AUTHOR("Hans de Goede "); MODULE_DESCRIPTION("Abit uGuru Sensor device"); MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/abituguru3.c b/drivers/hwmon/abituguru3.c index 48d21e22e93..034cebfcd27 100644 --- a/drivers/hwmon/abituguru3.c +++ b/drivers/hwmon/abituguru3.c @@ -1,7 +1,7 @@ /* abituguru3.c - Copyright (c) 2006-2008 Hans de Goede + Copyright (c) 2006-2008 Hans de Goede Copyright (c) 2008 Alistair John Strachan This program is free software; you can redistribute it and/or modify @@ -1266,7 +1266,7 @@ static void __exit abituguru3_exit(void) platform_driver_unregister(&abituguru3_driver); } -MODULE_AUTHOR("Hans de Goede "); +MODULE_AUTHOR("Hans de Goede "); MODULE_DESCRIPTION("Abit uGuru3 Sensor device"); MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From a98d506c08ffe754fa013c7f70c4d578b991fb4b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: hwmon: New driver for SMSC SCH5627 SMSC SCH5627 Super I/O chips include complete hardware monitoring capabilities. They can monitor up to 5 voltages, 4 fans and 8 temperatures. The hardware monitoring part of the SMSC SCH5627 is accessed by talking through an embedded microcontroller. An application note describing the protocol for communicating with the microcontroller is available upon request. Please mail me if you want a copy. Signed-off-by: Hans de Goede Signed-off-by: Jean Delvare --- Documentation/hwmon/sch5627 | 22 ++ MAINTAINERS | 7 + drivers/hwmon/Kconfig | 9 + drivers/hwmon/Makefile | 1 + drivers/hwmon/sch5627.c | 858 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 897 insertions(+) create mode 100644 Documentation/hwmon/sch5627 create mode 100644 drivers/hwmon/sch5627.c (limited to 'drivers/hwmon') diff --git a/Documentation/hwmon/sch5627 b/Documentation/hwmon/sch5627 new file mode 100644 index 00000000000..446a054e491 --- /dev/null +++ b/Documentation/hwmon/sch5627 @@ -0,0 +1,22 @@ +Kernel driver sch5627 +===================== + +Supported chips: + * SMSC SCH5627 + Prefix: 'sch5627' + Addresses scanned: none, address read from Super I/O config space + Datasheet: Application Note available upon request + +Author: Hans de Goede + + +Description +----------- + +SMSC SCH5627 Super I/O chips include complete hardware monitoring +capabilities. They can monitor up to 5 voltages, 4 fans and 8 temperatures. + +The hardware monitoring part of the SMSC SCH5627 is accessed by talking +through an embedded microcontroller. An application note describing the +protocol for communicating with the microcontroller is available upon +request. Please mail me if you want a copy. diff --git a/MAINTAINERS b/MAINTAINERS index 666287a6f3d..b8a123f7e81 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5769,6 +5769,13 @@ S: Supported F: Documentation/hwmon/emc2103 F: drivers/hwmon/emc2103.c +SMSC SCH5627 HARDWARE MONITOR DRIVER +M: Hans de Goede +L: lm-sensors@lm-sensors.org +S: Supported +F: Documentation/hwmon/sch5627 +F: drivers/hwmon/sch5627.c + SMSC47B397 HARDWARE MONITOR DRIVER M: "Mark M. Hoffman" L: lm-sensors@lm-sensors.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 19d72bcf966..6fad9f082f6 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -959,6 +959,15 @@ config SENSORS_SMSC47B397 This driver can also be built as a module. If so, the module will be called smsc47b397. +config SENSORS_SCH5627 + tristate "SMSC SCH5627" + help + If you say yes here you get support for the hardware monitoring + features of the SMSC SCH5627 Super-I/O chip. + + This driver can also be built as a module. If so, the module + will be called sch5627. + config SENSORS_ADS7828 tristate "Texas Instruments ADS7828" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index c068f82082c..4d0122c5221 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -90,6 +90,7 @@ obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o obj-$(CONFIG_SENSORS_S3C) += s3c-hwmon.o +obj-$(CONFIG_SENSORS_SCH5627) += sch5627.o obj-$(CONFIG_SENSORS_SHT15) += sht15.o obj-$(CONFIG_SENSORS_SHT21) += sht21.o obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o diff --git a/drivers/hwmon/sch5627.c b/drivers/hwmon/sch5627.c new file mode 100644 index 00000000000..9a51dcca9b0 --- /dev/null +++ b/drivers/hwmon/sch5627.c @@ -0,0 +1,858 @@ +/*************************************************************************** + * Copyright (C) 2010-2011 Hans de Goede * + * * + * 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. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "sch5627" +#define DEVNAME DRVNAME /* We only support one model */ + +#define SIO_SCH5627_EM_LD 0x0C /* Embedded Microcontroller LD */ +#define SIO_UNLOCK_KEY 0x55 /* Key to enable Super-I/O */ +#define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */ + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVID 0x20 /* Device ID */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x66 /* Logical device address (2 bytes) */ + +#define SIO_SCH5627_ID 0xC6 /* Chipset ID */ + +#define REGION_LENGTH 9 + +#define SCH5627_HWMON_ID 0xa5 +#define SCH5627_COMPANY_ID 0x5c +#define SCH5627_PRIMARY_ID 0xa0 + +#define SCH5627_REG_BUILD_CODE 0x39 +#define SCH5627_REG_BUILD_ID 0x3a +#define SCH5627_REG_HWMON_ID 0x3c +#define SCH5627_REG_HWMON_REV 0x3d +#define SCH5627_REG_COMPANY_ID 0x3e +#define SCH5627_REG_PRIMARY_ID 0x3f +#define SCH5627_REG_CTRL 0x40 + +#define SCH5627_NO_TEMPS 8 +#define SCH5627_NO_FANS 4 +#define SCH5627_NO_IN 5 + +static const u16 SCH5627_REG_TEMP_MSB[SCH5627_NO_TEMPS] = { + 0x2B, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x180, 0x181 }; +static const u16 SCH5627_REG_TEMP_LSN[SCH5627_NO_TEMPS] = { + 0xE2, 0xE1, 0xE1, 0xE5, 0xE5, 0xE6, 0x182, 0x182 }; +static const u16 SCH5627_REG_TEMP_HIGH_NIBBLE[SCH5627_NO_TEMPS] = { + 0, 0, 1, 1, 0, 0, 0, 1 }; +static const u16 SCH5627_REG_TEMP_HIGH[SCH5627_NO_TEMPS] = { + 0x61, 0x57, 0x59, 0x5B, 0x5D, 0x5F, 0x184, 0x186 }; +static const u16 SCH5627_REG_TEMP_ABS[SCH5627_NO_TEMPS] = { + 0x9B, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x1A8, 0x1A9 }; + +static const u16 SCH5627_REG_FAN[SCH5627_NO_FANS] = { + 0x2C, 0x2E, 0x30, 0x32 }; +static const u16 SCH5627_REG_FAN_MIN[SCH5627_NO_FANS] = { + 0x62, 0x64, 0x66, 0x68 }; + +static const u16 SCH5627_REG_IN_MSB[SCH5627_NO_IN] = { + 0x22, 0x23, 0x24, 0x25, 0x189 }; +static const u16 SCH5627_REG_IN_LSN[SCH5627_NO_IN] = { + 0xE4, 0xE4, 0xE3, 0xE3, 0x18A }; +static const u16 SCH5627_REG_IN_HIGH_NIBBLE[SCH5627_NO_IN] = { + 1, 0, 1, 0, 1 }; +static const u16 SCH5627_REG_IN_FACTOR[SCH5627_NO_IN] = { + 10745, 3660, 9765, 10745, 3660 }; +static const char * const SCH5627_IN_LABELS[SCH5627_NO_IN] = { + "VCC", "VTT", "VBAT", "VTR", "V_IN" }; + +struct sch5627_data { + unsigned short addr; + struct device *hwmon_dev; + u8 temp_max[SCH5627_NO_TEMPS]; + u8 temp_crit[SCH5627_NO_TEMPS]; + u16 fan_min[SCH5627_NO_FANS]; + + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + u16 temp[SCH5627_NO_TEMPS]; + u16 fan[SCH5627_NO_FANS]; + u16 in[SCH5627_NO_IN]; +}; + +static struct platform_device *sch5627_pdev; + +/* Super I/O functions */ +static inline int superio_inb(int base, int reg) +{ + outb(reg, base); + return inb(base + 1); +} + +static inline int superio_enter(int base) +{ + /* Don't step on other drivers' I/O space by accident */ + if (!request_muxed_region(base, 2, DRVNAME)) { + pr_err("I/O address 0x%04x already in use\n", base); + return -EBUSY; + } + + outb(SIO_UNLOCK_KEY, base); + + return 0; +} + +static inline void superio_select(int base, int ld) +{ + outb(SIO_REG_LDSEL, base); + outb(ld, base + 1); +} + +static inline void superio_exit(int base) +{ + outb(SIO_LOCK_KEY, base); + release_region(base, 2); +} + +static int sch5627_read_virtual_reg(struct sch5627_data *data, u16 reg) +{ + u8 val; + int i; + /* + * According to SMSC for the commands we use the maximum time for + * the EM to respond is 15 ms, but testing shows in practice it + * responds within 15-32 reads, so we first busy poll, and if + * that fails sleep a bit and try again until we are way past + * the 15 ms maximum response time. + */ + const int max_busy_polls = 64; + const int max_lazy_polls = 32; + + /* (Optional) Write-Clear the EC to Host Mailbox Register */ + val = inb(data->addr + 1); + outb(val, data->addr + 1); + + /* Set Mailbox Address Pointer to first location in Region 1 */ + outb(0x00, data->addr + 2); + outb(0x80, data->addr + 3); + + /* Write Request Packet Header */ + outb(0x02, data->addr + 4); /* Access Type: VREG read */ + outb(0x01, data->addr + 5); /* # of Entries: 1 Byte (8-bit) */ + outb(0x04, data->addr + 2); /* Mailbox AP to first data entry loc. */ + + /* Write Address field */ + outb(reg & 0xff, data->addr + 6); + outb(reg >> 8, data->addr + 7); + + /* Execute the Random Access Command */ + outb(0x01, data->addr); /* Write 01h to the Host-to-EC register */ + + /* EM Interface Polling "Algorithm" */ + for (i = 0; i < max_busy_polls + max_lazy_polls; i++) { + if (i >= max_busy_polls) + msleep(1); + /* Read Interrupt source Register */ + val = inb(data->addr + 8); + /* Write Clear the interrupt source bits */ + if (val) + outb(val, data->addr + 8); + /* Command Completed ? */ + if (val & 0x01) + break; + } + if (i == max_busy_polls + max_lazy_polls) { + pr_err("Max retries exceeded reading virtual " + "register 0x%04hx (%d)\n", reg, 1); + return -EIO; + } + + /* + * According to SMSC we may need to retry this, but sofar I've always + * seen this succeed in 1 try. + */ + for (i = 0; i < max_busy_polls; i++) { + /* Read EC-to-Host Register */ + val = inb(data->addr + 1); + /* Command Completed ? */ + if (val == 0x01) + break; + + if (i == 0) + pr_warn("EC reports: 0x%02x reading virtual register " + "0x%04hx\n", (unsigned int)val, reg); + } + if (i == max_busy_polls) { + pr_err("Max retries exceeded reading virtual " + "register 0x%04hx (%d)\n", reg, 2); + return -EIO; + } + + /* + * According to the SMSC app note we should now do: + * + * Set Mailbox Address Pointer to first location in Region 1 * + * outb(0x00, data->addr + 2); + * outb(0x80, data->addr + 3); + * + * But if we do that things don't work, so let's not. + */ + + /* Read Data from Mailbox */ + return inb(data->addr + 4); +} + +static int sch5627_read_virtual_reg16(struct sch5627_data *data, u16 reg) +{ + int lsb, msb; + + /* Read LSB first, this will cause the matching MSB to be latched */ + lsb = sch5627_read_virtual_reg(data, reg); + if (lsb < 0) + return lsb; + + msb = sch5627_read_virtual_reg(data, reg + 1); + if (msb < 0) + return msb; + + return lsb | (msb << 8); +} + +static int sch5627_read_virtual_reg12(struct sch5627_data *data, u16 msb_reg, + u16 lsn_reg, int high_nibble) +{ + int msb, lsn; + + /* Read MSB first, this will cause the matching LSN to be latched */ + msb = sch5627_read_virtual_reg(data, msb_reg); + if (msb < 0) + return msb; + + lsn = sch5627_read_virtual_reg(data, lsn_reg); + if (lsn < 0) + return lsn; + + if (high_nibble) + return (msb << 4) | (lsn >> 4); + else + return (msb << 4) | (lsn & 0x0f); +} + +static struct sch5627_data *sch5627_update_device(struct device *dev) +{ + struct sch5627_data *data = dev_get_drvdata(dev); + struct sch5627_data *ret = data; + int i, val; + + mutex_lock(&data->update_lock); + + /* Cache the values for 1 second */ + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + for (i = 0; i < SCH5627_NO_TEMPS; i++) { + val = sch5627_read_virtual_reg12(data, + SCH5627_REG_TEMP_MSB[i], + SCH5627_REG_TEMP_LSN[i], + SCH5627_REG_TEMP_HIGH_NIBBLE[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i] = val; + } + + for (i = 0; i < SCH5627_NO_FANS; i++) { + val = sch5627_read_virtual_reg16(data, + SCH5627_REG_FAN[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->fan[i] = val; + } + + for (i = 0; i < SCH5627_NO_IN; i++) { + val = sch5627_read_virtual_reg12(data, + SCH5627_REG_IN_MSB[i], + SCH5627_REG_IN_LSN[i], + SCH5627_REG_IN_HIGH_NIBBLE[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->in[i] = val; + } + + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +static int __devinit sch5627_read_limits(struct sch5627_data *data) +{ + int i, val; + + for (i = 0; i < SCH5627_NO_TEMPS; i++) { + /* + * Note what SMSC calls ABS, is what lm_sensors calls max + * (aka high), and HIGH is what lm_sensors calls crit. + */ + val = sch5627_read_virtual_reg(data, SCH5627_REG_TEMP_ABS[i]); + if (val < 0) + return val; + data->temp_max[i] = val; + + val = sch5627_read_virtual_reg(data, SCH5627_REG_TEMP_HIGH[i]); + if (val < 0) + return val; + data->temp_crit[i] = val; + } + for (i = 0; i < SCH5627_NO_FANS; i++) { + val = sch5627_read_virtual_reg16(data, SCH5627_REG_FAN_MIN[i]); + if (val < 0) + return val; + data->fan_min[i] = val; + } + + return 0; +} + +static int reg_to_temp(u16 reg) +{ + return (reg * 625) / 10 - 64000; +} + +static int reg_to_temp_limit(u8 reg) +{ + return (reg - 64) * 1000; +} + +static int reg_to_rpm(u16 reg) +{ + if (reg == 0) + return -EIO; + if (reg == 0xffff) + return 0; + + return 5400540 / reg; +} + +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", DEVNAME); +} + +static ssize_t show_temp(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = reg_to_temp(data->temp[attr->index]); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_temp_fault(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return snprintf(buf, PAGE_SIZE, "%d\n", data->temp[attr->index] == 0); +} + +static ssize_t show_temp_max(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = dev_get_drvdata(dev); + int val; + + val = reg_to_temp_limit(data->temp_max[attr->index]); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_temp_crit(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = dev_get_drvdata(dev); + int val; + + val = reg_to_temp_limit(data->temp_crit[attr->index]); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_fan(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = reg_to_rpm(data->fan[attr->index]); + if (val < 0) + return val; + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_fan_fault(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return snprintf(buf, PAGE_SIZE, "%d\n", + data->fan[attr->index] == 0xffff); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = dev_get_drvdata(dev); + int val = reg_to_rpm(data->fan_min[attr->index]); + if (val < 0) + return val; + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_in(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = DIV_ROUND_CLOSEST( + data->in[attr->index] * SCH5627_REG_IN_FACTOR[attr->index], + 10000); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_in_label(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return snprintf(buf, PAGE_SIZE, "%s\n", + SCH5627_IN_LABELS[attr->index]); +} + +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_temp, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_temp, NULL, 7); +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_temp_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_temp_fault, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_fault, S_IRUGO, show_temp_fault, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_fault, S_IRUGO, show_temp_fault, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_fault, S_IRUGO, show_temp_fault, NULL, 7); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp_max, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO, show_temp_max, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO, show_temp_max, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_max, S_IRUGO, show_temp_max, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_max, S_IRUGO, show_temp_max, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_max, S_IRUGO, show_temp_max, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_max, S_IRUGO, show_temp_max, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_max, S_IRUGO, show_temp_max, NULL, 7); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp_crit, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO, show_temp_crit, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_crit, S_IRUGO, show_temp_crit, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_crit, S_IRUGO, show_temp_crit, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_crit, S_IRUGO, show_temp_crit, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_crit, S_IRUGO, show_temp_crit, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_crit, S_IRUGO, show_temp_crit, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_crit, S_IRUGO, show_temp_crit, NULL, 7); + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3); +static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, show_fan_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, show_fan_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_fault, S_IRUGO, show_fan_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_fault, S_IRUGO, show_fan_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO, show_fan_min, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO, show_fan_min, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_min, S_IRUGO, show_fan_min, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_min, S_IRUGO, show_fan_min, NULL, 3); + +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_in, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_in, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_in, NULL, 4); +static SENSOR_DEVICE_ATTR(in0_label, S_IRUGO, show_in_label, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO, show_in_label, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO, show_in_label, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, show_in_label, NULL, 3); + +static struct attribute *sch5627_attributes[] = { + &dev_attr_name.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp7_input.dev_attr.attr, + &sensor_dev_attr_temp8_input.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + &sensor_dev_attr_temp5_fault.dev_attr.attr, + &sensor_dev_attr_temp6_fault.dev_attr.attr, + &sensor_dev_attr_temp7_fault.dev_attr.attr, + &sensor_dev_attr_temp8_fault.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp6_max.dev_attr.attr, + &sensor_dev_attr_temp7_max.dev_attr.attr, + &sensor_dev_attr_temp8_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp4_crit.dev_attr.attr, + &sensor_dev_attr_temp5_crit.dev_attr.attr, + &sensor_dev_attr_temp6_crit.dev_attr.attr, + &sensor_dev_attr_temp7_crit.dev_attr.attr, + &sensor_dev_attr_temp8_crit.dev_attr.attr, + + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan1_fault.dev_attr.attr, + &sensor_dev_attr_fan2_fault.dev_attr.attr, + &sensor_dev_attr_fan3_fault.dev_attr.attr, + &sensor_dev_attr_fan4_fault.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in0_label.dev_attr.attr, + &sensor_dev_attr_in1_label.dev_attr.attr, + &sensor_dev_attr_in2_label.dev_attr.attr, + &sensor_dev_attr_in3_label.dev_attr.attr, + /* No in4_label as in4 is a generic input pin */ + + NULL +}; + +static const struct attribute_group sch5627_group = { + .attrs = sch5627_attributes, +}; + +static int sch5627_remove(struct platform_device *pdev) +{ + struct sch5627_data *data = platform_get_drvdata(pdev); + + if (data->hwmon_dev) + hwmon_device_unregister(data->hwmon_dev); + + sysfs_remove_group(&pdev->dev.kobj, &sch5627_group); + platform_set_drvdata(pdev, NULL); + kfree(data); + + return 0; +} + +static int __devinit sch5627_probe(struct platform_device *pdev) +{ + struct sch5627_data *data; + int err, build_code, build_id, hwmon_rev, val; + + data = kzalloc(sizeof(struct sch5627_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start; + mutex_init(&data->update_lock); + platform_set_drvdata(pdev, data); + + val = sch5627_read_virtual_reg(data, SCH5627_REG_HWMON_ID); + if (val < 0) { + err = val; + goto error; + } + if (val != SCH5627_HWMON_ID) { + pr_err("invalid %s id: 0x%02X (expected 0x%02X)\n", "hwmon", + val, SCH5627_HWMON_ID); + err = -ENODEV; + goto error; + } + + val = sch5627_read_virtual_reg(data, SCH5627_REG_COMPANY_ID); + if (val < 0) { + err = val; + goto error; + } + if (val != SCH5627_COMPANY_ID) { + pr_err("invalid %s id: 0x%02X (expected 0x%02X)\n", "company", + val, SCH5627_COMPANY_ID); + err = -ENODEV; + goto error; + } + + val = sch5627_read_virtual_reg(data, SCH5627_REG_PRIMARY_ID); + if (val < 0) { + err = val; + goto error; + } + if (val != SCH5627_PRIMARY_ID) { + pr_err("invalid %s id: 0x%02X (expected 0x%02X)\n", "primary", + val, SCH5627_PRIMARY_ID); + err = -ENODEV; + goto error; + } + + build_code = sch5627_read_virtual_reg(data, SCH5627_REG_BUILD_CODE); + if (build_code < 0) { + err = build_code; + goto error; + } + + build_id = sch5627_read_virtual_reg16(data, SCH5627_REG_BUILD_ID); + if (build_id < 0) { + err = build_id; + goto error; + } + + hwmon_rev = sch5627_read_virtual_reg(data, SCH5627_REG_HWMON_REV); + if (hwmon_rev < 0) { + err = hwmon_rev; + goto error; + } + + val = sch5627_read_virtual_reg(data, SCH5627_REG_CTRL); + if (val < 0) { + err = val; + goto error; + } + if (!(val & 0x01)) { + pr_err("hardware monitoring not enabled\n"); + err = -ENODEV; + goto error; + } + + /* + * Read limits, we do this only once as reading a register on + * the sch5627 is quite expensive (and they don't change). + */ + err = sch5627_read_limits(data); + if (err) + goto error; + + pr_info("firmware build: code 0x%02X, id 0x%04X, hwmon: rev 0x%02X\n", + build_code, build_id, hwmon_rev); + + /* Register sysfs interface files */ + err = sysfs_create_group(&pdev->dev.kobj, &sch5627_group); + if (err) + goto error; + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + data->hwmon_dev = NULL; + goto error; + } + + return 0; + +error: + sch5627_remove(pdev); + return err; +} + +static int __init sch5627_find(int sioaddr, unsigned short *address) +{ + u8 devid; + int err = superio_enter(sioaddr); + if (err) + return err; + + devid = superio_inb(sioaddr, SIO_REG_DEVID); + if (devid != SIO_SCH5627_ID) { + pr_debug("Unsupported device id: 0x%02x\n", + (unsigned int)devid); + err = -ENODEV; + goto exit; + } + + superio_select(sioaddr, SIO_SCH5627_EM_LD); + + if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01)) { + pr_warn("Device not activated\n"); + err = -ENODEV; + goto exit; + } + + /* + * Warning the order of the low / high byte is the other way around + * as on most other superio devices!! + */ + *address = superio_inb(sioaddr, SIO_REG_ADDR) | + superio_inb(sioaddr, SIO_REG_ADDR + 1) << 8; + if (*address == 0) { + pr_warn("Base address not set\n"); + err = -ENODEV; + goto exit; + } + + pr_info("Found %s chip at %#hx\n", DEVNAME, *address); +exit: + superio_exit(sioaddr); + return err; +} + +static int __init sch5627_device_add(unsigned short address) +{ + struct resource res = { + .start = address, + .end = address + REGION_LENGTH - 1, + .flags = IORESOURCE_IO, + }; + int err; + + sch5627_pdev = platform_device_alloc(DRVNAME, address); + if (!sch5627_pdev) + return -ENOMEM; + + res.name = sch5627_pdev->name; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit_device_put; + + err = platform_device_add_resources(sch5627_pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed\n"); + goto exit_device_put; + } + + err = platform_device_add(sch5627_pdev); + if (err) { + pr_err("Device addition failed\n"); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(sch5627_pdev); + + return err; +} + +static struct platform_driver sch5627_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DRVNAME, + }, + .probe = sch5627_probe, + .remove = sch5627_remove, +}; + +static int __init sch5627_init(void) +{ + int err = -ENODEV; + unsigned short address; + + if (sch5627_find(0x4e, &address) && sch5627_find(0x2e, &address)) + goto exit; + + err = platform_driver_register(&sch5627_driver); + if (err) + goto exit; + + err = sch5627_device_add(address); + if (err) + goto exit_driver; + + return 0; + +exit_driver: + platform_driver_unregister(&sch5627_driver); +exit: + return err; +} + +static void __exit sch5627_exit(void) +{ + platform_device_unregister(sch5627_pdev); + platform_driver_unregister(&sch5627_driver); +} + +MODULE_DESCRIPTION("SMSC SCH5627 Hardware Monitoring Driver"); +MODULE_AUTHOR("Hans de Goede (hdegoede@redhat.com)"); +MODULE_LICENSE("GPL"); + +module_init(sch5627_init); +module_exit(sch5627_exit); -- cgit v1.2.3-70-g09d2 From 8c22a8f57516275afcd81c84f3724ac08cf6aa7b Mon Sep 17 00:00:00 2001 From: Dirk Eibach Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: hwmon: Add support for Texas Instruments ADS1015 Signed-off-by: Dirk Eibach Signed-off-by: Jean Delvare --- .../devicetree/bindings/hwmon/ads1015.txt | 29 +++ Documentation/hwmon/ads1015 | 67 +++++ MAINTAINERS | 8 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ads1015.c | 282 +++++++++++++++++++++ include/linux/i2c/ads1015.h | 28 ++ 7 files changed, 425 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/ads1015.txt create mode 100644 Documentation/hwmon/ads1015 create mode 100644 drivers/hwmon/ads1015.c create mode 100644 include/linux/i2c/ads1015.h (limited to 'drivers/hwmon') diff --git a/Documentation/devicetree/bindings/hwmon/ads1015.txt b/Documentation/devicetree/bindings/hwmon/ads1015.txt new file mode 100644 index 00000000000..0f30616384c --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ads1015.txt @@ -0,0 +1,29 @@ +ADS1015 (I2C) + +This device is a 12-bit A-D converter with 4 inputs. + +The inputs can be used single ended or in certain differential combinations. + +For configuration all possible combinations are mapped to 8 channels: +0: Voltage over AIN0 and AIN1. +1: Voltage over AIN0 and AIN3. +2: Voltage over AIN1 and AIN3. +3: Voltage over AIN2 and AIN3. +4: Voltage over AIN0 and GND. +5: Voltage over AIN1 and GND. +6: Voltage over AIN2 and GND. +7: Voltage over AIN3 and GND. + +Optional properties: + + - exported-channels : exported_channels is a bitmask that specifies which + channels should be accessable by the user. + +Example: +ads1015@49 { + compatible = "ti,ads1015"; + reg = <0x49>; + exported-channels = <0x14>; +}; + +In this example only channel 2 and 4 would be accessable by the user. diff --git a/Documentation/hwmon/ads1015 b/Documentation/hwmon/ads1015 new file mode 100644 index 00000000000..56ee7977b1a --- /dev/null +++ b/Documentation/hwmon/ads1015 @@ -0,0 +1,67 @@ +Kernel driver ads1015 +===================== + +Supported chips: + * Texas Instruments ADS1015 + Prefix: 'ads1015' + Datasheet: Publicly available at the Texas Instruments website : + http://focus.ti.com/lit/ds/symlink/ads1015.pdf + +Authors: + Dirk Eibach, Guntermann & Drunck GmbH + +Description +----------- + +This driver implements support for the Texas Instruments ADS1015. + +This device is a 12-bit A-D converter with 4 inputs. + +The inputs can be used single ended or in certain differential combinations. + +The inputs can be exported to 8 sysfs input files in0_input - in7_input: +in0: Voltage over AIN0 and AIN1. +in1: Voltage over AIN0 and AIN3. +in2: Voltage over AIN1 and AIN3. +in3: Voltage over AIN2 and AIN3. +in4: Voltage over AIN0 and GND. +in5: Voltage over AIN1 and GND. +in6: Voltage over AIN2 and GND. +in7: Voltage over AIN3 and GND. + +Which inputs are exported can be configured using platform data or devicetree. + +By default all inputs are exported. + +Platform Data +------------- + +In linux/i2c/ads1015.h platform data is defined as: + +struct ads1015_platform_data { + unsigned int exported_channels; +}; + +exported_channels is a bitmask that specifies which inputs should be exported. + +Example: +struct ads1015_platform_data data = { + .exported_channels = (1 << 2) | (1 << 4) +}; + +In this case only in2_input and in4_input would be created. + +Devicetree +---------- + +The ads1015 node may have an "exported-channels" property. +exported_channels is a bitmask that specifies which inputs should be exported. + +Example: +ads1015@49 { + compatible = "ti,ads1015"; + reg = <0x49>; + exported-channels = < 0x14 >; +}; + +In this case only in2_input and in4_input would be created. diff --git a/MAINTAINERS b/MAINTAINERS index b8a123f7e81..38077a65682 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -365,6 +365,14 @@ W: http://wiki-analog.com/ADP8860 S: Supported F: drivers/video/backlight/adp8860_bl.c +ADS1015 HARDWARE MONITOR DRIVER +M: Dirk Eibach +L: lm-sensors@lm-sensors.org +S: Maintained +F: Documentation/hwmon/ads1015 +F: drivers/hwmon/ads1015.c +F: include/linux/i2c/ads1015.h + ADT746X FAN DRIVER M: Colin Leroy S: Maintained diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 6fad9f082f6..e4bd13b3cd8 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -968,6 +968,16 @@ config SENSORS_SCH5627 This driver can also be built as a module. If so, the module will be called sch5627. +config SENSORS_ADS1015 + tristate "Texas Instruments ADS1015" + depends on I2C + help + If you say yes here you get support for Texas Instruments ADS1015 + 12-bit 4-input ADC device. + + This driver can also be built as a module. If so, the module + will be called ads1015. + config SENSORS_ADS7828 tristate "Texas Instruments ADS7828" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 4d0122c5221..54ca5939d02 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o obj-$(CONFIG_SENSORS_ADM1029) += adm1029.o obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o +obj-$(CONFIG_SENSORS_ADS1015) += ads1015.o obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o obj-$(CONFIG_SENSORS_ADS7871) += ads7871.o obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o diff --git a/drivers/hwmon/ads1015.c b/drivers/hwmon/ads1015.c new file mode 100644 index 00000000000..9e1585c3152 --- /dev/null +++ b/drivers/hwmon/ads1015.c @@ -0,0 +1,282 @@ +/* + * ads1015.c - lm_sensors driver for ads1015 12-bit 4-input ADC + * (C) Copyright 2010 + * Dirk Eibach, Guntermann & Drunck GmbH + * + * Based on the ads7828 driver by Steve Hardy. + * + * Datasheet available at: http://focus.ti.com/lit/ds/symlink/ads1015.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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* ADS1015 registers */ +enum { + ADS1015_CONVERSION = 0, + ADS1015_CONFIG = 1, +}; + +/* PGA fullscale voltages in mV */ +static const unsigned int fullscale_table[8] = { + 6144, 4096, 2048, 1024, 512, 256, 256, 256 }; + +#define ADS1015_CONFIG_CHANNELS 8 +#define ADS1015_DEFAULT_CHANNELS 0xff + +struct ads1015_data { + struct device *hwmon_dev; + struct mutex update_lock; /* mutex protect updates */ + struct attribute *attr_table[ADS1015_CONFIG_CHANNELS + 1]; + struct attribute_group attr_group; +}; + +static s32 ads1015_read_reg(struct i2c_client *client, unsigned int reg) +{ + s32 data = i2c_smbus_read_word_data(client, reg); + + return (data < 0) ? data : swab16(data); +} + +static s32 ads1015_write_reg(struct i2c_client *client, unsigned int reg, + u16 val) +{ + return i2c_smbus_write_word_data(client, reg, swab16(val)); +} + +static int ads1015_read_value(struct i2c_client *client, unsigned int channel, + int *value) +{ + u16 config; + s16 conversion; + unsigned int pga; + int fullscale; + unsigned int k; + struct ads1015_data *data = i2c_get_clientdata(client); + int res; + + mutex_lock(&data->update_lock); + + /* get fullscale voltage */ + res = ads1015_read_reg(client, ADS1015_CONFIG); + if (res < 0) + goto err_unlock; + config = res; + pga = (config >> 9) & 0x0007; + fullscale = fullscale_table[pga]; + + /* set channel and start single conversion */ + config &= ~(0x0007 << 12); + config |= (1 << 15) | (1 << 8) | (channel & 0x0007) << 12; + + /* wait until conversion finished */ + res = ads1015_write_reg(client, ADS1015_CONFIG, config); + if (res < 0) + goto err_unlock; + for (k = 0; k < 5; ++k) { + msleep(1); + res = ads1015_read_reg(client, ADS1015_CONFIG); + if (res < 0) + goto err_unlock; + config = res; + if (config & (1 << 15)) + break; + } + if (k == 5) { + res = -EIO; + goto err_unlock; + } + + res = ads1015_read_reg(client, ADS1015_CONVERSION); + if (res < 0) + goto err_unlock; + conversion = res; + + mutex_unlock(&data->update_lock); + + *value = DIV_ROUND_CLOSEST(conversion * fullscale, 0x7ff0); + + return 0; + +err_unlock: + mutex_unlock(&data->update_lock); + return res; +} + +/* sysfs callback function */ +static ssize_t show_in(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct i2c_client *client = to_i2c_client(dev); + int in; + int res; + + res = ads1015_read_value(client, attr->index, &in); + + return (res < 0) ? res : sprintf(buf, "%d\n", in); +} + +#define in_reg(offset)\ +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, show_in,\ + NULL, offset) + +in_reg(0); +in_reg(1); +in_reg(2); +in_reg(3); +in_reg(4); +in_reg(5); +in_reg(6); +in_reg(7); + +static struct attribute *all_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, +}; + +/* + * Driver interface + */ + +static int ads1015_remove(struct i2c_client *client) +{ + struct ads1015_data *data = i2c_get_clientdata(client); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &data->attr_group); + kfree(data); + return 0; +} + +static unsigned int ads1015_get_exported_channels(struct i2c_client *client) +{ + struct ads1015_platform_data *pdata = dev_get_platdata(&client->dev); +#ifdef CONFIG_OF + struct device_node *np = client->dev.of_node; + const __be32 *of_channels; + int of_channels_size; +#endif + + /* prefer platform data */ + if (pdata) + return pdata->exported_channels; + +#ifdef CONFIG_OF + /* fallback on OF */ + of_channels = of_get_property(np, "exported-channels", + &of_channels_size); + if (of_channels && (of_channels_size == sizeof(*of_channels))) + return be32_to_cpup(of_channels); +#endif + + /* fallback on default configuration */ + return ADS1015_DEFAULT_CHANNELS; +} + +static int ads1015_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ads1015_data *data; + int err; + unsigned int exported_channels; + unsigned int k; + unsigned int n = 0; + + data = kzalloc(sizeof(struct ads1015_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* build sysfs attribute group */ + data->attr_group.attrs = data->attr_table; + exported_channels = ads1015_get_exported_channels(client); + for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) { + if (!(exported_channels & (1<attr_table[n++] = all_attributes[k]; + } + err = sysfs_create_group(&client->dev.kobj, &data->attr_group); + if (err) + goto exit_free; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + sysfs_remove_group(&client->dev.kobj, &data->attr_group); +exit_free: + kfree(data); +exit: + return err; +} + +static const struct i2c_device_id ads1015_id[] = { + { "ads1015", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ads1015_id); + +static struct i2c_driver ads1015_driver = { + .driver = { + .name = "ads1015", + }, + .probe = ads1015_probe, + .remove = ads1015_remove, + .id_table = ads1015_id, +}; + +static int __init sensors_ads1015_init(void) +{ + return i2c_add_driver(&ads1015_driver); +} + +static void __exit sensors_ads1015_exit(void) +{ + i2c_del_driver(&ads1015_driver); +} + +MODULE_AUTHOR("Dirk Eibach "); +MODULE_DESCRIPTION("ADS1015 driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_ads1015_init); +module_exit(sensors_ads1015_exit); diff --git a/include/linux/i2c/ads1015.h b/include/linux/i2c/ads1015.h new file mode 100644 index 00000000000..8541c6acfaf --- /dev/null +++ b/include/linux/i2c/ads1015.h @@ -0,0 +1,28 @@ +/* + * Platform Data for ADS1015 12-bit 4-input ADC + * (C) Copyright 2010 + * Dirk Eibach, Guntermann & Drunck GmbH + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef LINUX_ADS1015_H +#define LINUX_ADS1015_H + +struct ads1015_platform_data { + unsigned int exported_channels; +}; + +#endif /* LINUX_ADS1015_H */ -- cgit v1.2.3-70-g09d2 From fdf241a8ed93236915c70717a4b6dfb856274496 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:37 +0100 Subject: hwmon: (ads1015) Drop dynamic attribute group It is cheaper to handle attributes individually. Signed-off-by: Jean Delvare Acked-by: Dirk Eibach --- drivers/hwmon/ads1015.c | 50 ++++++++++++++++++------------------------------- 1 file changed, 18 insertions(+), 32 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/ads1015.c b/drivers/hwmon/ads1015.c index 9e1585c3152..fa02f20b79f 100644 --- a/drivers/hwmon/ads1015.c +++ b/drivers/hwmon/ads1015.c @@ -51,8 +51,6 @@ static const unsigned int fullscale_table[8] = { struct ads1015_data { struct device *hwmon_dev; struct mutex update_lock; /* mutex protect updates */ - struct attribute *attr_table[ADS1015_CONFIG_CHANNELS + 1]; - struct attribute_group attr_group; }; static s32 ads1015_read_reg(struct i2c_client *client, unsigned int reg) @@ -141,28 +139,15 @@ static ssize_t show_in(struct device *dev, struct device_attribute *da, return (res < 0) ? res : sprintf(buf, "%d\n", in); } -#define in_reg(offset)\ -static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, show_in,\ - NULL, offset) - -in_reg(0); -in_reg(1); -in_reg(2); -in_reg(3); -in_reg(4); -in_reg(5); -in_reg(6); -in_reg(7); - -static struct attribute *all_attributes[] = { - &sensor_dev_attr_in0_input.dev_attr.attr, - &sensor_dev_attr_in1_input.dev_attr.attr, - &sensor_dev_attr_in2_input.dev_attr.attr, - &sensor_dev_attr_in3_input.dev_attr.attr, - &sensor_dev_attr_in4_input.dev_attr.attr, - &sensor_dev_attr_in5_input.dev_attr.attr, - &sensor_dev_attr_in6_input.dev_attr.attr, - &sensor_dev_attr_in7_input.dev_attr.attr, +static const struct sensor_device_attribute ads1015_in[] = { + SENSOR_ATTR(in0_input, S_IRUGO, show_in, NULL, 0), + SENSOR_ATTR(in1_input, S_IRUGO, show_in, NULL, 1), + SENSOR_ATTR(in2_input, S_IRUGO, show_in, NULL, 2), + SENSOR_ATTR(in3_input, S_IRUGO, show_in, NULL, 3), + SENSOR_ATTR(in4_input, S_IRUGO, show_in, NULL, 4), + SENSOR_ATTR(in5_input, S_IRUGO, show_in, NULL, 5), + SENSOR_ATTR(in6_input, S_IRUGO, show_in, NULL, 6), + SENSOR_ATTR(in7_input, S_IRUGO, show_in, NULL, 7), }; /* @@ -172,8 +157,11 @@ static struct attribute *all_attributes[] = { static int ads1015_remove(struct i2c_client *client) { struct ads1015_data *data = i2c_get_clientdata(client); + int k; + hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&client->dev.kobj, &data->attr_group); + for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) + device_remove_file(&client->dev, &ads1015_in[k].dev_attr); kfree(data); return 0; } @@ -210,7 +198,6 @@ static int ads1015_probe(struct i2c_client *client, int err; unsigned int exported_channels; unsigned int k; - unsigned int n = 0; data = kzalloc(sizeof(struct ads1015_data), GFP_KERNEL); if (!data) { @@ -222,16 +209,14 @@ static int ads1015_probe(struct i2c_client *client, mutex_init(&data->update_lock); /* build sysfs attribute group */ - data->attr_group.attrs = data->attr_table; exported_channels = ads1015_get_exported_channels(client); for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) { if (!(exported_channels & (1<attr_table[n++] = all_attributes[k]; + err = device_create_file(&client->dev, &ads1015_in[k].dev_attr); + if (err) + goto exit_free; } - err = sysfs_create_group(&client->dev.kobj, &data->attr_group); - if (err) - goto exit_free; data->hwmon_dev = hwmon_device_register(&client->dev); if (IS_ERR(data->hwmon_dev)) { @@ -242,7 +227,8 @@ static int ads1015_probe(struct i2c_client *client, return 0; exit_remove: - sysfs_remove_group(&client->dev.kobj, &data->attr_group); + for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) + device_remove_file(&client->dev, &ads1015_in[k].dev_attr); exit_free: kfree(data); exit: -- cgit v1.2.3-70-g09d2 From c0046867f34bb81ec3f237ebbc5241ae678b8379 Mon Sep 17 00:00:00 2001 From: Dirk Eibach Date: Mon, 21 Mar 2011 17:59:37 +0100 Subject: hwmon: (ads1015) Make gain and datarate configurable Configuration for ads1015 gain and datarate is possible via devicetree or platform data. This is a followup patch to previous ads1015 patches on Jean Delvares tree. Signed-off-by: Dirk Eibach Signed-off-by: Jean Delvare --- .../devicetree/bindings/hwmon/ads1015.txt | 88 +++++++++--- Documentation/hwmon/ads1015 | 49 ++++--- drivers/hwmon/ads1015.c | 149 +++++++++++++++------ include/linux/i2c/ads1015.h | 10 +- 4 files changed, 211 insertions(+), 85 deletions(-) (limited to 'drivers/hwmon') diff --git a/Documentation/devicetree/bindings/hwmon/ads1015.txt b/Documentation/devicetree/bindings/hwmon/ads1015.txt index 0f30616384c..918a507d115 100644 --- a/Documentation/devicetree/bindings/hwmon/ads1015.txt +++ b/Documentation/devicetree/bindings/hwmon/ads1015.txt @@ -5,25 +5,69 @@ This device is a 12-bit A-D converter with 4 inputs. The inputs can be used single ended or in certain differential combinations. For configuration all possible combinations are mapped to 8 channels: -0: Voltage over AIN0 and AIN1. -1: Voltage over AIN0 and AIN3. -2: Voltage over AIN1 and AIN3. -3: Voltage over AIN2 and AIN3. -4: Voltage over AIN0 and GND. -5: Voltage over AIN1 and GND. -6: Voltage over AIN2 and GND. -7: Voltage over AIN3 and GND. - -Optional properties: - - - exported-channels : exported_channels is a bitmask that specifies which - channels should be accessable by the user. - -Example: -ads1015@49 { - compatible = "ti,ads1015"; - reg = <0x49>; - exported-channels = <0x14>; -}; - -In this example only channel 2 and 4 would be accessable by the user. + 0: Voltage over AIN0 and AIN1. + 1: Voltage over AIN0 and AIN3. + 2: Voltage over AIN1 and AIN3. + 3: Voltage over AIN2 and AIN3. + 4: Voltage over AIN0 and GND. + 5: Voltage over AIN1 and GND. + 6: Voltage over AIN2 and GND. + 7: Voltage over AIN3 and GND. + +Each channel can be configured individually: + - pga is the programmable gain amplifier (values are full scale) + 0: +/- 6.144 V + 1: +/- 4.096 V + 2: +/- 2.048 V (default) + 3: +/- 1.024 V + 4: +/- 0.512 V + 5: +/- 0.256 V + - data_rate in samples per second + 0: 128 + 1: 250 + 2: 490 + 3: 920 + 4: 1600 (default) + 5: 2400 + 6: 3300 + +1) The /ads1015 node + + Required properties: + + - compatible : must be "ti,ads1015" + - reg : I2C bus address of the device + - #address-cells : must be <1> + - #size-cells : must be <0> + + The node contains child nodes for each channel that the platform uses. + + Example ADS1015 node: + + ads1015@49 { + compatible = "ti,ads1015"; + reg = <0x49>; + #address-cells = <1>; + #size-cells = <0>; + + [ child node definitions... ] + } + +2) channel nodes + + Required properties: + + - reg : the channel number + + Optional properties: + + - ti,gain : the programmable gain amplifier setting + - ti,datarate : the converter data rate + + Example ADS1015 channel node: + + channel@4 { + reg = <4>; + ti,gain = <3>; + ti,datarate = <5>; + }; diff --git a/Documentation/hwmon/ads1015 b/Documentation/hwmon/ads1015 index 56ee7977b1a..f6fe9c20373 100644 --- a/Documentation/hwmon/ads1015 +++ b/Documentation/hwmon/ads1015 @@ -19,7 +19,7 @@ This device is a 12-bit A-D converter with 4 inputs. The inputs can be used single ended or in certain differential combinations. -The inputs can be exported to 8 sysfs input files in0_input - in7_input: +The inputs can be made available by 8 sysfs input files in0_input - in7_input: in0: Voltage over AIN0 and AIN1. in1: Voltage over AIN0 and AIN3. in2: Voltage over AIN1 and AIN3. @@ -29,39 +29,44 @@ in5: Voltage over AIN1 and GND. in6: Voltage over AIN2 and GND. in7: Voltage over AIN3 and GND. -Which inputs are exported can be configured using platform data or devicetree. +Which inputs are available can be configured using platform data or devicetree. By default all inputs are exported. Platform Data ------------- -In linux/i2c/ads1015.h platform data is defined as: - -struct ads1015_platform_data { - unsigned int exported_channels; -}; - -exported_channels is a bitmask that specifies which inputs should be exported. +In linux/i2c/ads1015.h platform data is defined, channel_data contains +configuration data for the used input combinations: +- pga is the programmable gain amplifier (values are full scale) + 0: +/- 6.144 V + 1: +/- 4.096 V + 2: +/- 2.048 V + 3: +/- 1.024 V + 4: +/- 0.512 V + 5: +/- 0.256 V +- data_rate in samples per second + 0: 128 + 1: 250 + 2: 490 + 3: 920 + 4: 1600 + 5: 2400 + 6: 3300 Example: struct ads1015_platform_data data = { - .exported_channels = (1 << 2) | (1 << 4) + .channel_data = { + [2] = { .enabled = true, .pga = 1, .data_rate = 0 }, + [4] = { .enabled = true, .pga = 4, .data_rate = 5 }, + } }; -In this case only in2_input and in4_input would be created. +In this case only in2_input (FS +/- 4.096 V, 128 SPS) and in4_input +(FS +/- 0.512 V, 2400 SPS) would be created. Devicetree ---------- -The ads1015 node may have an "exported-channels" property. -exported_channels is a bitmask that specifies which inputs should be exported. - -Example: -ads1015@49 { - compatible = "ti,ads1015"; - reg = <0x49>; - exported-channels = < 0x14 >; -}; - -In this case only in2_input and in4_input would be created. +Configuration is also possible via devicetree: +Documentation/devicetree/bindings/hwmon/ads1015.txt diff --git a/drivers/hwmon/ads1015.c b/drivers/hwmon/ads1015.c index fa02f20b79f..e9beeda4cbe 100644 --- a/drivers/hwmon/ads1015.c +++ b/drivers/hwmon/ads1015.c @@ -45,12 +45,18 @@ enum { static const unsigned int fullscale_table[8] = { 6144, 4096, 2048, 1024, 512, 256, 256, 256 }; -#define ADS1015_CONFIG_CHANNELS 8 +/* Data rates in samples per second */ +static const unsigned int data_rate_table[8] = { + 128, 250, 490, 920, 1600, 2400, 3300, 3300 }; + #define ADS1015_DEFAULT_CHANNELS 0xff +#define ADS1015_DEFAULT_PGA 2 +#define ADS1015_DEFAULT_DATA_RATE 4 struct ads1015_data { struct device *hwmon_dev; struct mutex update_lock; /* mutex protect updates */ + struct ads1015_channel_data channel_data[ADS1015_CHANNELS]; }; static s32 ads1015_read_reg(struct i2c_client *client, unsigned int reg) @@ -71,40 +77,42 @@ static int ads1015_read_value(struct i2c_client *client, unsigned int channel, { u16 config; s16 conversion; - unsigned int pga; - int fullscale; - unsigned int k; struct ads1015_data *data = i2c_get_clientdata(client); + unsigned int pga = data->channel_data[channel].pga; + int fullscale; + unsigned int data_rate = data->channel_data[channel].data_rate; + unsigned int conversion_time_ms; int res; mutex_lock(&data->update_lock); - /* get fullscale voltage */ + /* get channel parameters */ res = ads1015_read_reg(client, ADS1015_CONFIG); if (res < 0) goto err_unlock; config = res; - pga = (config >> 9) & 0x0007; fullscale = fullscale_table[pga]; + conversion_time_ms = DIV_ROUND_UP(1000, data_rate_table[data_rate]); - /* set channel and start single conversion */ - config &= ~(0x0007 << 12); - config |= (1 << 15) | (1 << 8) | (channel & 0x0007) << 12; + /* setup and start single conversion */ + config &= 0x001f; + config |= (1 << 15) | (1 << 8); + config |= (channel & 0x0007) << 12; + config |= (pga & 0x0007) << 9; + config |= (data_rate & 0x0007) << 5; - /* wait until conversion finished */ res = ads1015_write_reg(client, ADS1015_CONFIG, config); if (res < 0) goto err_unlock; - for (k = 0; k < 5; ++k) { - msleep(1); - res = ads1015_read_reg(client, ADS1015_CONFIG); - if (res < 0) - goto err_unlock; - config = res; - if (config & (1 << 15)) - break; - } - if (k == 5) { + + /* wait until conversion finished */ + msleep(conversion_time_ms); + res = ads1015_read_reg(client, ADS1015_CONFIG); + if (res < 0) + goto err_unlock; + config = res; + if (!(config & (1 << 15))) { + /* conversion not finished in time */ res = -EIO; goto err_unlock; } @@ -160,35 +168,97 @@ static int ads1015_remove(struct i2c_client *client) int k; hwmon_device_unregister(data->hwmon_dev); - for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) + for (k = 0; k < ADS1015_CHANNELS; ++k) device_remove_file(&client->dev, &ads1015_in[k].dev_attr); kfree(data); return 0; } -static unsigned int ads1015_get_exported_channels(struct i2c_client *client) -{ - struct ads1015_platform_data *pdata = dev_get_platdata(&client->dev); #ifdef CONFIG_OF - struct device_node *np = client->dev.of_node; - const __be32 *of_channels; - int of_channels_size; +static int ads1015_get_channels_config_of(struct i2c_client *client) +{ + struct ads1015_data *data = i2c_get_clientdata(client); + struct device_node *node; + + if (!client->dev.of_node + || !of_get_next_child(client->dev.of_node, NULL)) + return -EINVAL; + + for_each_child_of_node(client->dev.of_node, node) { + const __be32 *property; + int len; + unsigned int channel; + unsigned int pga = ADS1015_DEFAULT_PGA; + unsigned int data_rate = ADS1015_DEFAULT_DATA_RATE; + + property = of_get_property(node, "reg", &len); + if (!property || len != sizeof(int)) { + dev_err(&client->dev, "invalid reg on %s\n", + node->full_name); + continue; + } + + channel = be32_to_cpup(property); + if (channel > ADS1015_CHANNELS) { + dev_err(&client->dev, + "invalid channel index %d on %s\n", + channel, node->full_name); + continue; + } + + property = of_get_property(node, "ti,gain", &len); + if (property && len == sizeof(int)) { + pga = be32_to_cpup(property); + if (pga > 6) { + dev_err(&client->dev, + "invalid gain on %s\n", + node->full_name); + } + } + + property = of_get_property(node, "ti,datarate", &len); + if (property && len == sizeof(int)) { + data_rate = be32_to_cpup(property); + if (data_rate > 7) { + dev_err(&client->dev, + "invalid data_rate on %s\n", + node->full_name); + } + } + + data->channel_data[channel].enabled = true; + data->channel_data[channel].pga = pga; + data->channel_data[channel].data_rate = data_rate; + } + + return 0; +} #endif +static void ads1015_get_channels_config(struct i2c_client *client) +{ + unsigned int k; + struct ads1015_data *data = i2c_get_clientdata(client); + struct ads1015_platform_data *pdata = dev_get_platdata(&client->dev); + /* prefer platform data */ - if (pdata) - return pdata->exported_channels; + if (pdata) { + memcpy(data->channel_data, pdata->channel_data, + sizeof(data->channel_data)); + return; + } #ifdef CONFIG_OF - /* fallback on OF */ - of_channels = of_get_property(np, "exported-channels", - &of_channels_size); - if (of_channels && (of_channels_size == sizeof(*of_channels))) - return be32_to_cpup(of_channels); + if (!ads1015_get_channels_config_of(client)) + return; #endif /* fallback on default configuration */ - return ADS1015_DEFAULT_CHANNELS; + for (k = 0; k < ADS1015_CHANNELS; ++k) { + data->channel_data[k].enabled = true; + data->channel_data[k].pga = ADS1015_DEFAULT_PGA; + data->channel_data[k].data_rate = ADS1015_DEFAULT_DATA_RATE; + } } static int ads1015_probe(struct i2c_client *client, @@ -196,7 +266,6 @@ static int ads1015_probe(struct i2c_client *client, { struct ads1015_data *data; int err; - unsigned int exported_channels; unsigned int k; data = kzalloc(sizeof(struct ads1015_data), GFP_KERNEL); @@ -209,9 +278,9 @@ static int ads1015_probe(struct i2c_client *client, mutex_init(&data->update_lock); /* build sysfs attribute group */ - exported_channels = ads1015_get_exported_channels(client); - for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) { - if (!(exported_channels & (1<channel_data[k].enabled) continue; err = device_create_file(&client->dev, &ads1015_in[k].dev_attr); if (err) @@ -227,7 +296,7 @@ static int ads1015_probe(struct i2c_client *client, return 0; exit_remove: - for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) + for (k = 0; k < ADS1015_CHANNELS; ++k) device_remove_file(&client->dev, &ads1015_in[k].dev_attr); exit_free: kfree(data); diff --git a/include/linux/i2c/ads1015.h b/include/linux/i2c/ads1015.h index 8541c6acfaf..d5aa2a04566 100644 --- a/include/linux/i2c/ads1015.h +++ b/include/linux/i2c/ads1015.h @@ -21,8 +21,16 @@ #ifndef LINUX_ADS1015_H #define LINUX_ADS1015_H +#define ADS1015_CHANNELS 8 + +struct ads1015_channel_data { + bool enabled; + unsigned int pga; + unsigned int data_rate; +}; + struct ads1015_platform_data { - unsigned int exported_channels; + struct ads1015_channel_data channel_data[ADS1015_CHANNELS]; }; #endif /* LINUX_ADS1015_H */ -- cgit v1.2.3-70-g09d2