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 --- drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/max6639.c | 639 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 650 insertions(+) create mode 100644 drivers/hwmon/max6639.c (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 297bc9a7d6e6..3bd993b0a3c8 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 dde02d99c238..f69783150b76 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 000000000000..40fd878e53dc --- /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); -- cgit v1.2.3 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 40fd878e53dc..f20d9978ee78 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 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 2549de1de4e2..c1f8a8fbf694 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 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 --- drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/lineage-pem.c | 589 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 601 insertions(+) create mode 100644 drivers/hwmon/lineage-pem.c (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 3bd993b0a3c8..d4ffc6052083 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 f69783150b76..46819818ef51 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 000000000000..d39ee24e52f6 --- /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 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 d2cc28660816..22536761a7fd 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 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 22536761a7fd..590226a7c5a2 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 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 590226a7c5a2..fbb5189141bc 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 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 --- drivers/hwmon/lm85.c | 75 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 24 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/lm85.c b/drivers/hwmon/lm85.c index fbb5189141bc..cf47e6e476ed 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 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 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 2173 insertions(+) create mode 100644 drivers/hwmon/pmbus.c create mode 100644 drivers/hwmon/pmbus.h create mode 100644 drivers/hwmon/pmbus_core.c (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index d4ffc6052083..ac116693e357 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 46819818ef51..09e67955fd57 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 000000000000..98e2e28899e2 --- /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 000000000000..a81f7f228762 --- /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 000000000000..0edab1c9553e --- /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"); -- cgit v1.2.3 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 ac116693e357..521f191fcbe3 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 09e67955fd57..0e7cf92d1b56 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 000000000000..8ebfef2ecf26 --- /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 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 521f191fcbe3..de91d7d6a38e 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 0e7cf92d1b56..9788fe248c68 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 000000000000..1d6d717060d3 --- /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 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 de91d7d6a38e..19b0af40c7aa 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 9788fe248c68..feacc7c243ce 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 000000000000..992b701b4c5e --- /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 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 0edab1c9553e..b7c64ba31f4c 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 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 b7c64ba31f4c..d025a118bf92 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 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 d025a118bf92..e9dda58a5b90 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 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 e9dda58a5b90..92540c9cd34e 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 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 92540c9cd34e..6474512f49b0 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 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 --- drivers/hwmon/Kconfig | 11 +++ drivers/hwmon/Makefile | 1 + drivers/hwmon/ltc4151.c | 256 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 drivers/hwmon/ltc4151.c (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 19b0af40c7aa..7bda62264250 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 feacc7c243ce..bd0410e4b44f 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 000000000000..4ac06b75aa60 --- /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 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 073eabedc432..66e6855f9397 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 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 66e6855f9397..1128eac7023b 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 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 1128eac7023b..5a627b9db3e8 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 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 --- drivers/hwmon/w83627ehf.c | 155 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 124 insertions(+), 31 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 5a627b9db3e8..d10fe706c856 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 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 d10fe706c856..2420f1c6f4e9 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 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 2420f1c6f4e9..2f17f99e0ae1 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 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 --- drivers/hwmon/w83627ehf.c | 791 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 652 insertions(+), 139 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 2f17f99e0ae1..b3b4f2b41dcf 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 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 b3b4f2b41dcf..df6e50260e01 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 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 df6e50260e01..460292bad74a 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 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 7bda62264250..1bfb4439e4e1 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 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 460292bad74a..d18031944960 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 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 d18031944960..f2b377c56a3a 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 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 6e06019015a5..6c3de065e04b 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 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 6c3de065e04b..67ac85870592 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 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 67ac85870592..ec29c611ed3e 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 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 ec29c611ed3e..bf939e1aa73f 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 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 bf939e1aa73f..b8b4bc9db03d 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 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 b8b4bc9db03d..62352fd397df 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 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 62352fd397df..4c17f12054a2 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 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 --- drivers/hwmon/f71882fg.c | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index 4c17f12054a2..49cf19621c0c 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 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 --- drivers/hwmon/f71882fg.c | 120 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 5 deletions(-) (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index 49cf19621c0c..eea697757891 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 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 eea697757891..a4d430ee7e20 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 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 d39ee24e52f6..58eded27f385 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