From 83bffbce745795572d20f0109395cb61690c6dcf Mon Sep 17 00:00:00 2001 From: Per Dalén Date: Wed, 6 Apr 2011 20:29:44 +0200 Subject: hwmon: Add support for MAX6642 MAX6642 is a SMBus-Compatible Remote/Local Temperature Sensor with Overtemperature Alarm from Maxim. Signed-off-by: Per Dalen Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 11 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/max6642.c | 356 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 368 insertions(+) create mode 100644 drivers/hwmon/max6642.c (limited to 'drivers') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 50e40dbd8bb6..f2d48777c508 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -727,6 +727,17 @@ config SENSORS_MAX6639 This driver can also be built as a module. If so, the module will be called max6639. +config SENSORS_MAX6642 + tristate "Maxim MAX6642 sensor chip" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for MAX6642 sensor chip. + MAX6642 is a SMBus-Compatible Remote/Local Temperature Sensor + with Overtemperature Alarm from Maxim. + + This driver can also be built as a module. If so, the module + will be called max6642. + config SENSORS_MAX6650 tristate "Maxim MAX6650 sensor chip" depends on I2C && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 967d0ea9447f..2211752d73b6 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -85,6 +85,7 @@ 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_MAX6642) += max6642.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/max6642.c b/drivers/hwmon/max6642.c new file mode 100644 index 000000000000..0f9fc40379cd --- /dev/null +++ b/drivers/hwmon/max6642.c @@ -0,0 +1,356 @@ +/* + * Driver for +/-1 degree C, SMBus-Compatible Remote/Local Temperature Sensor + * with Overtemperature Alarm + * + * Copyright (C) 2011 AppearTV AS + * + * Derived from: + * + * Based on the max1619 driver. + * Copyright (C) 2003-2004 Alexey Fisher + * Jean Delvare + * + * The MAX6642 is a sensor chip made by Maxim. + * It reports up to two temperatures (its own plus up to + * one external one). Complete datasheet can be + * obtained from Maxim's website at: + * http://datasheets.maxim-ic.com/en/ds/MAX6642.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const unsigned short normal_i2c[] = { + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, I2C_CLIENT_END }; + +/* + * The MAX6642 registers + */ + +#define MAX6642_REG_R_MAN_ID 0xFE +#define MAX6642_REG_R_CONFIG 0x03 +#define MAX6642_REG_W_CONFIG 0x09 +#define MAX6642_REG_R_STATUS 0x02 +#define MAX6642_REG_R_LOCAL_TEMP 0x00 +#define MAX6642_REG_R_LOCAL_TEMPL 0x11 +#define MAX6642_REG_R_LOCAL_HIGH 0x05 +#define MAX6642_REG_W_LOCAL_HIGH 0x0B +#define MAX6642_REG_R_REMOTE_TEMP 0x01 +#define MAX6642_REG_R_REMOTE_TEMPL 0x10 +#define MAX6642_REG_R_REMOTE_HIGH 0x07 +#define MAX6642_REG_W_REMOTE_HIGH 0x0D + +/* + * Conversions + */ + +static int temp_from_reg10(int val) +{ + return val * 250; +} + +static int temp_from_reg(int val) +{ + return val * 1000; +} + +static int temp_to_reg(int val) +{ + return val / 1000; +} + +/* + * Client data (each client gets its own) + */ + +struct max6642_data { + struct device *hwmon_dev; + struct mutex update_lock; + bool valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* registers values */ + u16 temp_input[2]; /* local/remote */ + u16 temp_high[2]; /* local/remote */ + u8 alarms; +}; + +/* + * Real code + */ + +static void max6642_init_client(struct i2c_client *client) +{ + u8 config; + struct max6642_data *data = i2c_get_clientdata(client); + + /* + * Start the conversions. + */ + config = i2c_smbus_read_byte_data(client, MAX6642_REG_R_CONFIG); + if (config & 0x40) + i2c_smbus_write_byte_data(client, MAX6642_REG_W_CONFIG, + config & 0xBF); /* run */ + + data->temp_high[0] = i2c_smbus_read_byte_data(client, + MAX6642_REG_R_LOCAL_HIGH); + data->temp_high[1] = i2c_smbus_read_byte_data(client, + MAX6642_REG_R_REMOTE_HIGH); +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int max6642_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + u8 reg_config, reg_status, man_id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* identification */ + man_id = i2c_smbus_read_byte_data(client, MAX6642_REG_R_MAN_ID); + if (man_id != 0x4D) + return -ENODEV; + + /* + * We read the config and status register, the 4 lower bits in the + * config register should be zero and bit 5, 3, 1 and 0 should be + * zero in the status register. + */ + reg_config = i2c_smbus_read_byte_data(client, MAX6642_REG_R_CONFIG); + reg_status = i2c_smbus_read_byte_data(client, MAX6642_REG_R_STATUS); + if (((reg_config & 0x0f) != 0x00) || + ((reg_status & 0x2b) != 0x00)) + return -ENODEV; + + strlcpy(info->type, "max6642", I2C_NAME_SIZE); + + return 0; +} + +static struct max6642_data *max6642_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6642_data *data = i2c_get_clientdata(client); + u16 val, tmp; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + dev_dbg(&client->dev, "Updating max6642 data.\n"); + val = i2c_smbus_read_byte_data(client, + MAX6642_REG_R_LOCAL_TEMPL); + tmp = (val >> 6) & 3; + val = i2c_smbus_read_byte_data(client, + MAX6642_REG_R_LOCAL_TEMP); + val = (val << 2) | tmp; + data->temp_input[0] = val; + val = i2c_smbus_read_byte_data(client, + MAX6642_REG_R_REMOTE_TEMPL); + tmp = (val >> 6) & 3; + val = i2c_smbus_read_byte_data(client, + MAX6642_REG_R_REMOTE_TEMP); + val = (val << 2) | tmp; + data->temp_input[1] = val; + data->alarms = i2c_smbus_read_byte_data(client, + MAX6642_REG_R_STATUS); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Sysfs stuff + */ + +static ssize_t show_temp_max10(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct max6642_data *data = max6642_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + return sprintf(buf, "%d\n", + temp_from_reg10(data->temp_input[attr->index])); +} + +static ssize_t show_temp_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct max6642_data *data = max6642_update_device(dev); + struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(attr); + + return sprintf(buf, "%d\n", temp_from_reg(data->temp_high[attr2->nr])); +} + +static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long val; + int err; + struct i2c_client *client = to_i2c_client(dev); + struct max6642_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(attr); + + err = strict_strtoul(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + data->temp_high[attr2->nr] = SENSORS_LIMIT(temp_to_reg(val), 0, 255); + i2c_smbus_write_byte_data(client, attr2->index, + data->temp_high[attr2->nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct max6642_data *data = max6642_update_device(dev); + return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_max10, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_max10, NULL, 1); +static SENSOR_DEVICE_ATTR_2(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 0, MAX6642_REG_W_LOCAL_HIGH); +static SENSOR_DEVICE_ATTR_2(temp2_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 1, MAX6642_REG_W_REMOTE_HIGH); +static SENSOR_DEVICE_ATTR(temp_fault, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 4); + +static struct attribute *max6642_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + + &sensor_dev_attr_temp_fault.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group max6642_group = { + .attrs = max6642_attributes, +}; + +static int max6642_probe(struct i2c_client *new_client, + const struct i2c_device_id *id) +{ + struct max6642_data *data; + int err; + + data = kzalloc(sizeof(struct max6642_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(new_client, data); + mutex_init(&data->update_lock); + + /* Initialize the MAX6642 chip */ + max6642_init_client(new_client); + + /* Register sysfs hooks */ + err = sysfs_create_group(&new_client->dev.kobj, &max6642_group); + if (err) + goto exit_free; + + data->hwmon_dev = hwmon_device_register(&new_client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_files; + } + + return 0; + +exit_remove_files: + sysfs_remove_group(&new_client->dev.kobj, &max6642_group); +exit_free: + kfree(data); +exit: + return err; +} + +static int max6642_remove(struct i2c_client *client) +{ + struct max6642_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &max6642_group); + + kfree(data); + return 0; +} + +/* + * Driver data (common to all clients) + */ + +static const struct i2c_device_id max6642_id[] = { + { "max6642", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max6642_id); + +static struct i2c_driver max6642_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "max6642", + }, + .probe = max6642_probe, + .remove = max6642_remove, + .id_table = max6642_id, + .detect = max6642_detect, + .address_list = normal_i2c, +}; + +static int __init max6642_init(void) +{ + return i2c_add_driver(&max6642_driver); +} + +static void __exit max6642_exit(void) +{ + i2c_del_driver(&max6642_driver); +} + +MODULE_AUTHOR("Per Dalen "); +MODULE_DESCRIPTION("MAX6642 sensor driver"); +MODULE_LICENSE("GPL"); + +module_init(max6642_init); +module_exit(max6642_exit); -- cgit v1.2.3 From 99a0378de9f887fd4d501f1baa50aaf16d01a8e8 Mon Sep 17 00:00:00 2001 From: Vivien Didelot Date: Tue, 12 Apr 2011 15:34:36 -0400 Subject: hwmon: (sht15) general code clean-up * Add a documentation file for the device. * Respect a bit more the kernel-doc syntax. * Rename some variables for clarity. * Use bool type for flags. * Use an enum for states (actions being done). Signed-off-by: Vivien Didelot Acked-by: Jonathan Cameron Signed-off-by: Guenter Roeck --- Documentation/hwmon/sht15 | 42 ++++++++ drivers/hwmon/sht15.c | 268 ++++++++++++++++++++++++++-------------------- include/linux/sht15.h | 12 ++- 3 files changed, 203 insertions(+), 119 deletions(-) create mode 100644 Documentation/hwmon/sht15 (limited to 'drivers') diff --git a/Documentation/hwmon/sht15 b/Documentation/hwmon/sht15 new file mode 100644 index 000000000000..2919c516fdbc --- /dev/null +++ b/Documentation/hwmon/sht15 @@ -0,0 +1,42 @@ +Kernel driver sht15 +=================== + +Authors: + * Wouter Horre + * Jonathan Cameron + +Supported chips: + * Sensirion SHT10 + Prefix: 'sht10' + + * Sensirion SHT11 + Prefix: 'sht11' + + * Sensirion SHT15 + Prefix: 'sht15' + + * Sensirion SHT71 + Prefix: 'sht71' + + * Sensirion SHT75 + Prefix: 'sht75' + +Datasheet: Publicly available at the Sensirion website +http://www.sensirion.ch/en/pdf/product_information/Datasheet-humidity-sensor-SHT1x.pdf + +Description +----------- + +The SHT10, SHT11, SHT15, SHT71, and SHT75 are humidity and temperature +sensors. + +The devices communicate using two GPIO lines and use the default +resolution settings of 14 bits for temperature and 12 bits for humidity. + +Note: The regulator supply name is set to "vcc". + +Sysfs interface +--------------- + +* temp1_input: temperature input +* humidity1_input: humidity input diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c index f4e617adb220..080af75c517b 100644 --- a/drivers/hwmon/sht15.c +++ b/drivers/hwmon/sht15.c @@ -9,16 +9,7 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * - * Currently ignoring checksum on readings. - * Default resolution only (14bit temp, 12bit humidity) - * Ignoring battery status. - * Heater not enabled. - * Timings are all conservative. - * - * Data sheet available (1/2009) at - * http://www.sensirion.ch/en/pdf/product_information/Datasheet-humidity-sensor-SHT1x.pdf - * - * Regulator supply name = vcc + * For further information, see the Documentation/hwmon/sht15 file. */ #include @@ -39,17 +30,21 @@ #include #include -#define SHT15_MEASURE_TEMP 3 -#define SHT15_MEASURE_RH 5 +/* Commands */ +#define SHT15_MEASURE_TEMP 0x03 +#define SHT15_MEASURE_RH 0x05 -#define SHT15_READING_NOTHING 0 -#define SHT15_READING_TEMP 1 -#define SHT15_READING_HUMID 2 +/* Min timings */ +#define SHT15_TSCKL 100 /* (nsecs) clock low */ +#define SHT15_TSCKH 100 /* (nsecs) clock high */ +#define SHT15_TSU 150 /* (nsecs) data setup time */ -/* Min timings in nsecs */ -#define SHT15_TSCKL 100 /* clock low */ -#define SHT15_TSCKH 100 /* clock high */ -#define SHT15_TSU 150 /* data setup time */ +/* Actions the driver may be doing */ +enum sht15_state { + SHT15_READING_NOTHING, + SHT15_READING_TEMP, + SHT15_READING_HUMID +}; /** * struct sht15_temppair - elements of voltage dependent temp calc @@ -61,9 +56,7 @@ struct sht15_temppair { int d1; }; -/* Table 9 from data sheet - relates temperature calculation - * to supply voltage. - */ +/* Table 9 from datasheet - relates temperature calculation to supply voltage */ static const struct sht15_temppair temppoints[] = { { 2500000, -39400 }, { 3000000, -39600 }, @@ -74,27 +67,27 @@ static const struct sht15_temppair temppoints[] = { /** * struct sht15_data - device instance specific data - * @pdata: platform data (gpio's etc) - * @read_work: bh of interrupt handler - * @wait_queue: wait queue for getting values from device - * @val_temp: last temperature value read from device - * @val_humid: last humidity value read from device - * @flag: status flag used to identify what the last request was - * @valid: are the current stored values valid (start condition) - * @last_updat: time of last update - * @read_lock: mutex to ensure only one read in progress - * at a time. - * @dev: associate device structure - * @hwmon_dev: device associated with hwmon subsystem - * @reg: associated regulator (if specified) - * @nb: notifier block to handle notifications of voltage changes - * @supply_uV: local copy of supply voltage used to allow - * use of regulator consumer if available - * @supply_uV_valid: indicates that an updated value has not yet - * been obtained from the regulator and so any calculations - * based upon it will be invalid. - * @update_supply_work: work struct that is used to update the supply_uV - * @interrupt_handled: flag used to indicate a hander has been scheduled + * @pdata: platform data (gpio's etc). + * @read_work: bh of interrupt handler. + * @wait_queue: wait queue for getting values from device. + * @val_temp: last temperature value read from device. + * @val_humid: last humidity value read from device. + * @state: state identifying the action the driver is doing. + * @measurements_valid: are the current stored measures valid (start condition). + * @last_measurement: time of last measure. + * @read_lock: mutex to ensure only one read in progress at a time. + * @dev: associate device structure. + * @hwmon_dev: device associated with hwmon subsystem. + * @reg: associated regulator (if specified). + * @nb: notifier block to handle notifications of voltage + * changes. + * @supply_uV: local copy of supply voltage used to allow use of + * regulator consumer if available. + * @supply_uV_valid: indicates that an updated value has not yet been + * obtained from the regulator and so any calculations + * based upon it will be invalid. + * @update_supply_work: work struct that is used to update the supply_uV. + * @interrupt_handled: flag used to indicate a handler has been scheduled. */ struct sht15_data { struct sht15_platform_data *pdata; @@ -102,16 +95,16 @@ struct sht15_data { wait_queue_head_t wait_queue; uint16_t val_temp; uint16_t val_humid; - u8 flag; - u8 valid; - unsigned long last_updat; + enum sht15_state state; + bool measurements_valid; + unsigned long last_measurement; struct mutex read_lock; struct device *dev; struct device *hwmon_dev; struct regulator *reg; struct notifier_block nb; int supply_uV; - int supply_uV_valid; + bool supply_uV_valid; struct work_struct update_supply_work; atomic_t interrupt_handled; }; @@ -125,6 +118,7 @@ struct sht15_data { static void sht15_connection_reset(struct sht15_data *data) { int i; + gpio_direction_output(data->pdata->gpio_data, 1); ndelay(SHT15_TSCKL); gpio_set_value(data->pdata->gpio_sck, 0); @@ -136,14 +130,14 @@ static void sht15_connection_reset(struct sht15_data *data) ndelay(SHT15_TSCKL); } } + /** * sht15_send_bit() - send an individual bit to the device * @data: device state data * @val: value of bit to be sent - **/ + */ static inline void sht15_send_bit(struct sht15_data *data, int val) { - gpio_set_value(data->pdata->gpio_data, val); ndelay(SHT15_TSU); gpio_set_value(data->pdata->gpio_sck, 1); @@ -154,12 +148,12 @@ static inline void sht15_send_bit(struct sht15_data *data, int val) /** * sht15_transmission_start() - specific sequence for new transmission - * * @data: device state data + * * Timings for this are not documented on the data sheet, so very * conservative ones used in implementation. This implements * figure 12 on the data sheet. - **/ + */ static void sht15_transmission_start(struct sht15_data *data) { /* ensure data is high and output */ @@ -180,23 +174,26 @@ static void sht15_transmission_start(struct sht15_data *data) gpio_set_value(data->pdata->gpio_sck, 0); ndelay(SHT15_TSCKL); } + /** * sht15_send_byte() - send a single byte to the device * @data: device state * @byte: value to be sent - **/ + */ static void sht15_send_byte(struct sht15_data *data, u8 byte) { int i; + for (i = 0; i < 8; i++) { sht15_send_bit(data, !!(byte & 0x80)); byte <<= 1; } } + /** * sht15_wait_for_response() - checks for ack from device * @data: device state - **/ + */ static int sht15_wait_for_response(struct sht15_data *data) { gpio_direction_input(data->pdata->gpio_data); @@ -220,27 +217,30 @@ static int sht15_wait_for_response(struct sht15_data *data) * * On entry, sck is output low, data is output pull high * and the interrupt disabled. - **/ + */ static int sht15_send_cmd(struct sht15_data *data, u8 cmd) { int ret = 0; + sht15_transmission_start(data); sht15_send_byte(data, cmd); ret = sht15_wait_for_response(data); return ret; } + /** - * sht15_update_single_val() - get a new value from device + * sht15_measurement() - get a new value from device * @data: device instance specific data * @command: command sent to request value * @timeout_msecs: timeout after which comms are assumed * to have failed are reset. - **/ -static inline int sht15_update_single_val(struct sht15_data *data, - int command, - int timeout_msecs) + */ +static int sht15_measurement(struct sht15_data *data, + int command, + int timeout_msecs) { int ret; + ret = sht15_send_cmd(data, command); if (ret) return ret; @@ -256,7 +256,7 @@ static inline int sht15_update_single_val(struct sht15_data *data, schedule_work(&data->read_work); } ret = wait_event_timeout(data->wait_queue, - (data->flag == SHT15_READING_NOTHING), + (data->state == SHT15_READING_NOTHING), msecs_to_jiffies(timeout_msecs)); if (ret == 0) {/* timeout occurred */ disable_irq_nosync(gpio_to_irq(data->pdata->gpio_data)); @@ -267,27 +267,27 @@ static inline int sht15_update_single_val(struct sht15_data *data, } /** - * sht15_update_vals() - get updated readings from device if too old + * sht15_update_measurements() - get updated measures from device if too old * @data: device state - **/ -static int sht15_update_vals(struct sht15_data *data) + */ +static int sht15_update_measurements(struct sht15_data *data) { int ret = 0; int timeout = HZ; mutex_lock(&data->read_lock); - if (time_after(jiffies, data->last_updat + timeout) - || !data->valid) { - data->flag = SHT15_READING_HUMID; - ret = sht15_update_single_val(data, SHT15_MEASURE_RH, 160); + if (time_after(jiffies, data->last_measurement + timeout) + || !data->measurements_valid) { + data->state = SHT15_READING_HUMID; + ret = sht15_measurement(data, SHT15_MEASURE_RH, 160); if (ret) goto error_ret; - data->flag = SHT15_READING_TEMP; - ret = sht15_update_single_val(data, SHT15_MEASURE_TEMP, 400); + data->state = SHT15_READING_TEMP; + ret = sht15_measurement(data, SHT15_MEASURE_TEMP, 400); if (ret) goto error_ret; - data->valid = 1; - data->last_updat = jiffies; + data->measurements_valid = true; + data->last_measurement = jiffies; } error_ret: mutex_unlock(&data->read_lock); @@ -300,7 +300,7 @@ error_ret: * @data: device state * * As per section 4.3 of the data sheet. - **/ + */ static inline int sht15_calc_temp(struct sht15_data *data) { int d1 = temppoints[0].d1; @@ -316,7 +316,7 @@ static inline int sht15_calc_temp(struct sht15_data *data) break; } - return data->val_temp*10 + d1; + return data->val_temp * 10 + d1; } /** @@ -325,23 +325,35 @@ static inline int sht15_calc_temp(struct sht15_data *data) * * This is the temperature compensated version as per section 4.2 of * the data sheet. - **/ + * + * The sensor is assumed to be V3, which is compatible with V4. + * Humidity conversion coefficients are shown in table 7 of the datasheet. + */ static inline int sht15_calc_humid(struct sht15_data *data) { - int RHlinear; /* milli percent */ + int rh_linear; /* milli percent */ int temp = sht15_calc_temp(data); const int c1 = -4; const int c2 = 40500; /* x 10 ^ -6 */ - const int c3 = -28; /* x 10 ^ -7 */ + const int c3 = -28; /* x 10 ^ -7 */ - RHlinear = c1*1000 - + c2 * data->val_humid/1000 + rh_linear = c1 * 1000 + + c2 * data->val_humid / 1000 + (data->val_humid * data->val_humid * c3) / 10000; return (temp - 25000) * (10000 + 80 * data->val_humid) - / 1000000 + RHlinear; + / 1000000 + rh_linear; } +/** + * sht15_show_temp() - show temperature measurement value in sysfs + * @dev: device. + * @attr: device attribute. + * @buf: sysfs buffer where measurement values are written to. + * + * Will be called on read access to temp1_input sysfs attribute. + * Returns number of bytes written into buffer, negative errno on error. + */ static ssize_t sht15_show_temp(struct device *dev, struct device_attribute *attr, char *buf) @@ -350,12 +362,21 @@ static ssize_t sht15_show_temp(struct device *dev, struct sht15_data *data = dev_get_drvdata(dev); /* Technically no need to read humidity as well */ - ret = sht15_update_vals(data); + ret = sht15_update_measurements(data); return ret ? ret : sprintf(buf, "%d\n", sht15_calc_temp(data)); } +/** + * sht15_show_humidity() - show humidity measurement value in sysfs + * @dev: device. + * @attr: device attribute. + * @buf: sysfs buffer where measurement values are written to. + * + * Will be called on read access to humidity1_input sysfs attribute. + * Returns number of bytes written into buffer, negative errno on error. + */ static ssize_t sht15_show_humidity(struct device *dev, struct device_attribute *attr, char *buf) @@ -363,11 +384,12 @@ static ssize_t sht15_show_humidity(struct device *dev, int ret; struct sht15_data *data = dev_get_drvdata(dev); - ret = sht15_update_vals(data); + ret = sht15_update_measurements(data); return ret ? ret : sprintf(buf, "%d\n", sht15_calc_humid(data)); -}; +} + static ssize_t show_name(struct device *dev, struct device_attribute *attr, char *buf) @@ -376,12 +398,10 @@ static ssize_t show_name(struct device *dev, return sprintf(buf, "%s\n", pdev->name); } -static SENSOR_DEVICE_ATTR(temp1_input, - S_IRUGO, sht15_show_temp, - NULL, 0); -static SENSOR_DEVICE_ATTR(humidity1_input, - S_IRUGO, sht15_show_humidity, - NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, + sht15_show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(humidity1_input, S_IRUGO, + sht15_show_humidity, NULL, 0); static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); static struct attribute *sht15_attrs[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, @@ -397,16 +417,20 @@ static const struct attribute_group sht15_attr_group = { static irqreturn_t sht15_interrupt_fired(int irq, void *d) { struct sht15_data *data = d; + /* First disable the interrupt */ disable_irq_nosync(irq); atomic_inc(&data->interrupt_handled); /* Then schedule a reading work struct */ - if (data->flag != SHT15_READING_NOTHING) + if (data->state != SHT15_READING_NOTHING) schedule_work(&data->read_work); return IRQ_HANDLED; } -/* Each byte of data is acknowledged by pulling the data line +/** + * sht15_ack() - Send an ack to the device + * + * Each byte of data is acknowledged by pulling the data line * low for one clock pulse. */ static void sht15_ack(struct sht15_data *data) @@ -421,12 +445,13 @@ static void sht15_ack(struct sht15_data *data) gpio_direction_input(data->pdata->gpio_data); } + /** * sht15_end_transmission() - notify device of end of transmission * @data: device state * * This is basically a NAK. (single clock pulse, data high) - **/ + */ static void sht15_end_transmission(struct sht15_data *data) { gpio_direction_output(data->pdata->gpio_data, 1); @@ -444,12 +469,13 @@ static void sht15_bh_read_data(struct work_struct *work_s) struct sht15_data *data = container_of(work_s, struct sht15_data, read_work); + /* Firstly, verify the line is low */ if (gpio_get_value(data->pdata->gpio_data)) { - /* If not, then start the interrupt again - care - here as could have gone low in meantime so verify - it hasn't! - */ + /* + * If not, then start the interrupt again - care here as could + * have gone low in meantime so verify it hasn't! + */ atomic_set(&data->interrupt_handled, 0); enable_irq(gpio_to_irq(data->pdata->gpio_data)); /* If still not occurred or another handler has been scheduled */ @@ -457,6 +483,7 @@ static void sht15_bh_read_data(struct work_struct *work_s) || atomic_read(&data->interrupt_handled)) return; } + /* Read the data back from the device */ for (i = 0; i < 16; ++i) { val <<= 1; @@ -468,19 +495,22 @@ static void sht15_bh_read_data(struct work_struct *work_s) if (i == 7) sht15_ack(data); } + /* Tell the device we are done */ sht15_end_transmission(data); - switch (data->flag) { + switch (data->state) { case SHT15_READING_TEMP: data->val_temp = val; break; case SHT15_READING_HUMID: data->val_humid = val; break; + default: + break; } - data->flag = SHT15_READING_NOTHING; + data->state = SHT15_READING_NOTHING; wake_up(&data->wait_queue); } @@ -500,10 +530,10 @@ static void sht15_update_voltage(struct work_struct *work_s) * * Note that as the notification code holds the regulator lock, we have * to schedule an update of the supply voltage rather than getting it directly. - **/ + */ static int sht15_invalidate_voltage(struct notifier_block *nb, - unsigned long event, - void *ignored) + unsigned long event, + void *ignored) { struct sht15_data *data = container_of(nb, struct sht15_data, nb); @@ -521,7 +551,7 @@ static int __devinit sht15_probe(struct platform_device *pdev) if (!data) { ret = -ENOMEM; - dev_err(&pdev->dev, "kzalloc failed"); + dev_err(&pdev->dev, "kzalloc failed\n"); goto error_ret; } @@ -533,13 +563,16 @@ static int __devinit sht15_probe(struct platform_device *pdev) init_waitqueue_head(&data->wait_queue); if (pdev->dev.platform_data == NULL) { - dev_err(&pdev->dev, "no platform data supplied"); + dev_err(&pdev->dev, "no platform data supplied\n"); goto err_free_data; } data->pdata = pdev->dev.platform_data; - data->supply_uV = data->pdata->supply_mv*1000; + data->supply_uV = data->pdata->supply_mv * 1000; -/* If a regulator is available, query what the supply voltage actually is!*/ + /* + * If a regulator is available, + * query what the supply voltage actually is! + */ data->reg = regulator_get(data->dev, "vcc"); if (!IS_ERR(data->reg)) { int voltage; @@ -549,21 +582,25 @@ static int __devinit sht15_probe(struct platform_device *pdev) data->supply_uV = voltage; regulator_enable(data->reg); - /* setup a notifier block to update this if another device - * causes the voltage to change */ + /* + * Setup a notifier block to update this if another device + * causes the voltage to change + */ data->nb.notifier_call = &sht15_invalidate_voltage; ret = regulator_register_notifier(data->reg, &data->nb); } -/* Try requesting the GPIOs */ + + /* Try requesting the GPIOs */ ret = gpio_request(data->pdata->gpio_sck, "SHT15 sck"); if (ret) { - dev_err(&pdev->dev, "gpio request failed"); + dev_err(&pdev->dev, "gpio request failed\n"); goto err_free_data; } gpio_direction_output(data->pdata->gpio_sck, 0); + ret = gpio_request(data->pdata->gpio_data, "SHT15 data"); if (ret) { - dev_err(&pdev->dev, "gpio request failed"); + dev_err(&pdev->dev, "gpio request failed\n"); goto err_release_gpio_sck; } ret = sysfs_create_group(&pdev->dev.kobj, &sht15_attr_group); @@ -578,7 +615,7 @@ static int __devinit sht15_probe(struct platform_device *pdev) "sht15 data", data); if (ret) { - dev_err(&pdev->dev, "failed to get irq for data line"); + dev_err(&pdev->dev, "failed to get irq for data line\n"); goto err_release_gpio_data; } disable_irq_nosync(gpio_to_irq(data->pdata->gpio_data)); @@ -590,6 +627,7 @@ static int __devinit sht15_probe(struct platform_device *pdev) ret = PTR_ERR(data->hwmon_dev); goto err_release_irq; } + return 0; err_release_irq: @@ -601,7 +639,6 @@ err_release_gpio_sck: err_free_data: kfree(data); error_ret: - return ret; } @@ -609,8 +646,10 @@ static int __devexit sht15_remove(struct platform_device *pdev) { struct sht15_data *data = platform_get_drvdata(pdev); - /* Make sure any reads from the device are done and - * prevent new ones from beginning */ + /* + * Make sure any reads from the device are done and + * prevent new ones beginning + */ mutex_lock(&data->read_lock); hwmon_device_unregister(data->hwmon_dev); sysfs_remove_group(&pdev->dev.kobj, &sht15_attr_group); @@ -625,10 +664,10 @@ static int __devexit sht15_remove(struct platform_device *pdev) gpio_free(data->pdata->gpio_sck); mutex_unlock(&data->read_lock); kfree(data); + return 0; } - /* * sht_drivers simultaneously refers to __devinit and __devexit function * which causes spurious section mismatch warning. So use __refdata to @@ -673,7 +712,6 @@ static struct platform_driver __refdata sht_drivers[] = { }, }; - static int __init sht15_init(void) { int ret; diff --git a/include/linux/sht15.h b/include/linux/sht15.h index 046bce05ecab..1e302146bdc8 100644 --- a/include/linux/sht15.h +++ b/include/linux/sht15.h @@ -8,14 +8,18 @@ * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. + * + * For further information, see the Documentation/hwmon/sht15 file. */ /** * struct sht15_platform_data - sht15 connectivity info - * @gpio_data: no. of gpio to which bidirectional data line is connected - * @gpio_sck: no. of gpio to which the data clock is connected. - * @supply_mv: supply voltage in mv. Overridden by regulator if available. - **/ + * @gpio_data: no. of gpio to which bidirectional data line is + * connected. + * @gpio_sck: no. of gpio to which the data clock is connected. + * @supply_mv: supply voltage in mv. Overridden by regulator if + * available. + */ struct sht15_platform_data { int gpio_data; int gpio_sck; -- cgit v1.2.3 From 181148ae68799f5b17f09b418b276f56e9179e99 Mon Sep 17 00:00:00 2001 From: Vivien Didelot Date: Tue, 12 Apr 2011 15:34:37 -0400 Subject: hwmon: (sht15) clean-up the probe function * Move the creation of sysfs attributes after the end of the initialization, and remove them in the error path. * Release regulator in the error path. * Add a soft reset command (need to wait 11ms before next command). Signed-off-by: Vivien Didelot Acked-by: Jonathan Cameron Signed-off-by: Guenter Roeck --- drivers/hwmon/sht15.c | 54 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c index 080af75c517b..3182b3f578e2 100644 --- a/drivers/hwmon/sht15.c +++ b/drivers/hwmon/sht15.c @@ -33,11 +33,13 @@ /* Commands */ #define SHT15_MEASURE_TEMP 0x03 #define SHT15_MEASURE_RH 0x05 +#define SHT15_SOFT_RESET 0x1E /* Min timings */ #define SHT15_TSCKL 100 /* (nsecs) clock low */ #define SHT15_TSCKH 100 /* (nsecs) clock high */ #define SHT15_TSU 150 /* (nsecs) data setup time */ +#define SHT15_TSRST 11 /* (msecs) soft reset time */ /* Actions the driver may be doing */ enum sht15_state { @@ -228,6 +230,24 @@ static int sht15_send_cmd(struct sht15_data *data, u8 cmd) return ret; } +/** + * sht15_soft_reset() - send a soft reset command + * @data: sht15 specific data. + * + * As described in section 3.2 of the datasheet. + */ +static int sht15_soft_reset(struct sht15_data *data) +{ + int ret; + + ret = sht15_send_cmd(data, SHT15_SOFT_RESET); + if (ret) + return ret; + msleep(SHT15_TSRST); + + return 0; +} + /** * sht15_measurement() - get a new value from device * @data: device instance specific data @@ -588,13 +608,20 @@ static int __devinit sht15_probe(struct platform_device *pdev) */ data->nb.notifier_call = &sht15_invalidate_voltage; ret = regulator_register_notifier(data->reg, &data->nb); + if (ret) { + dev_err(&pdev->dev, + "regulator notifier request failed\n"); + regulator_disable(data->reg); + regulator_put(data->reg); + goto err_free_data; + } } /* Try requesting the GPIOs */ ret = gpio_request(data->pdata->gpio_sck, "SHT15 sck"); if (ret) { dev_err(&pdev->dev, "gpio request failed\n"); - goto err_free_data; + goto err_release_reg; } gpio_direction_output(data->pdata->gpio_sck, 0); @@ -603,11 +630,6 @@ static int __devinit sht15_probe(struct platform_device *pdev) dev_err(&pdev->dev, "gpio request failed\n"); goto err_release_gpio_sck; } - ret = sysfs_create_group(&pdev->dev.kobj, &sht15_attr_group); - if (ret) { - dev_err(&pdev->dev, "sysfs create failed"); - goto err_release_gpio_data; - } ret = request_irq(gpio_to_irq(data->pdata->gpio_data), sht15_interrupt_fired, @@ -620,22 +642,38 @@ static int __devinit sht15_probe(struct platform_device *pdev) } disable_irq_nosync(gpio_to_irq(data->pdata->gpio_data)); sht15_connection_reset(data); - sht15_send_cmd(data, 0x1E); + ret = sht15_soft_reset(data); + if (ret) + goto err_release_irq; + + ret = sysfs_create_group(&pdev->dev.kobj, &sht15_attr_group); + if (ret) { + dev_err(&pdev->dev, "sysfs create failed\n"); + goto err_release_irq; + } data->hwmon_dev = hwmon_device_register(data->dev); if (IS_ERR(data->hwmon_dev)) { ret = PTR_ERR(data->hwmon_dev); - goto err_release_irq; + goto err_release_sysfs_group; } return 0; +err_release_sysfs_group: + sysfs_remove_group(&pdev->dev.kobj, &sht15_attr_group); err_release_irq: free_irq(gpio_to_irq(data->pdata->gpio_data), data); err_release_gpio_data: gpio_free(data->pdata->gpio_data); err_release_gpio_sck: gpio_free(data->pdata->gpio_sck); +err_release_reg: + if (!IS_ERR(data->reg)) { + regulator_unregister_notifier(data->reg, &data->nb); + regulator_disable(data->reg); + regulator_put(data->reg); + } err_free_data: kfree(data); error_ret: -- cgit v1.2.3 From cc15c7ebb424e45ba2c5ceecbe52d025219ee970 Mon Sep 17 00:00:00 2001 From: Vivien Didelot Date: Tue, 12 Apr 2011 15:34:38 -0400 Subject: hwmon: (sht15) add support for the status register * Add support for: - Heater. - End of battery notice. - Ability not to reload from OTP. - Low resolution (12bit temp, 8bit humidity). * Add an utility function to read individual bytes from the device. Signed-off-by: Vivien Didelot Acked-by: Jonathan Cameron Signed-off-by: Guenter Roeck --- Documentation/hwmon/sht15 | 29 +++++- drivers/hwmon/sht15.c | 247 +++++++++++++++++++++++++++++++++++++++------- include/linux/sht15.h | 4 + 3 files changed, 245 insertions(+), 35 deletions(-) (limited to 'drivers') diff --git a/Documentation/hwmon/sht15 b/Documentation/hwmon/sht15 index 2919c516fdbc..d1939b25eb16 100644 --- a/Documentation/hwmon/sht15 +++ b/Documentation/hwmon/sht15 @@ -4,6 +4,7 @@ Kernel driver sht15 Authors: * Wouter Horre * Jonathan Cameron + * Vivien Didelot Supported chips: * Sensirion SHT10 @@ -30,13 +31,37 @@ Description The SHT10, SHT11, SHT15, SHT71, and SHT75 are humidity and temperature sensors. -The devices communicate using two GPIO lines and use the default -resolution settings of 14 bits for temperature and 12 bits for humidity. +The devices communicate using two GPIO lines. + +Supported resolutions for the measurements are 14 bits for temperature and 12 +bits for humidity, or 12 bits for temperature and 8 bits for humidity. + +The humidity calibration coefficients are programmed into an OTP memory on the +chip. These coefficients are used to internally calibrate the signals from the +sensors. Disabling the reload of those coefficients allows saving 10ms for each +measurement and decrease power consumption, while loosing on precision. + +Some options may be set directly in the sht15_platform_data structure +or via sysfs attributes. Note: The regulator supply name is set to "vcc". +Platform data +------------- + +* no_otp_reload: + flag to indicate not to reload from OTP (default to false). +* low_resolution: + flag to indicate the temp/humidity resolution to use (default to false). + Sysfs interface --------------- * temp1_input: temperature input * humidity1_input: humidity input +* heater_enable: write 1 in this attribute to enable the on-chip heater, + 0 to disable it. Be careful not to enable the heater + for too long. +* temp1_fault: if 1, this means that the voltage is low (below 2.47V) and + measurement may be invalid. +* humidity1_fault: same as temp1_fault. diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c index 3182b3f578e2..49da089b5de9 100644 --- a/drivers/hwmon/sht15.c +++ b/drivers/hwmon/sht15.c @@ -1,6 +1,9 @@ /* * sht15.c - support for the SHT15 Temperature and Humidity Sensor * + * Portions Copyright (c) 2010-2011 Savoir-faire Linux Inc. + * Vivien Didelot + * * Copyright (c) 2009 Jonathan Cameron * * Copyright (c) 2007 Wouter Horre @@ -33,6 +36,8 @@ /* Commands */ #define SHT15_MEASURE_TEMP 0x03 #define SHT15_MEASURE_RH 0x05 +#define SHT15_WRITE_STATUS 0x06 +#define SHT15_READ_STATUS 0x07 #define SHT15_SOFT_RESET 0x1E /* Min timings */ @@ -41,6 +46,12 @@ #define SHT15_TSU 150 /* (nsecs) data setup time */ #define SHT15_TSRST 11 /* (msecs) soft reset time */ +/* Status Register Bits */ +#define SHT15_STATUS_LOW_RESOLUTION 0x01 +#define SHT15_STATUS_NO_OTP_RELOAD 0x02 +#define SHT15_STATUS_HEATER 0x04 +#define SHT15_STATUS_LOW_BATTERY 0x40 + /* Actions the driver may be doing */ enum sht15_state { SHT15_READING_NOTHING, @@ -74,9 +85,12 @@ static const struct sht15_temppair temppoints[] = { * @wait_queue: wait queue for getting values from device. * @val_temp: last temperature value read from device. * @val_humid: last humidity value read from device. + * @val_status: last status register value read from device. * @state: state identifying the action the driver is doing. * @measurements_valid: are the current stored measures valid (start condition). + * @status_valid: is the current stored status valid (start condition). * @last_measurement: time of last measure. + * @last_status: time of last status reading. * @read_lock: mutex to ensure only one read in progress at a time. * @dev: associate device structure. * @hwmon_dev: device associated with hwmon subsystem. @@ -97,9 +111,12 @@ struct sht15_data { wait_queue_head_t wait_queue; uint16_t val_temp; uint16_t val_humid; + u8 val_status; enum sht15_state state; bool measurements_valid; + bool status_valid; unsigned long last_measurement; + unsigned long last_status; struct mutex read_lock; struct device *dev; struct device *hwmon_dev; @@ -244,10 +261,105 @@ static int sht15_soft_reset(struct sht15_data *data) if (ret) return ret; msleep(SHT15_TSRST); + /* device resets default hardware status register value */ + data->val_status = 0; + + return ret; +} + +/** + * sht15_end_transmission() - notify device of end of transmission + * @data: device state. + * + * This is basically a NAK (single clock pulse, data high). + */ +static void sht15_end_transmission(struct sht15_data *data) +{ + gpio_direction_output(data->pdata->gpio_data, 1); + ndelay(SHT15_TSU); + gpio_set_value(data->pdata->gpio_sck, 1); + ndelay(SHT15_TSCKH); + gpio_set_value(data->pdata->gpio_sck, 0); + ndelay(SHT15_TSCKL); +} + +/** + * sht15_read_byte() - Read a byte back from the device + * @data: device state. + */ +static u8 sht15_read_byte(struct sht15_data *data) +{ + int i; + u8 byte = 0; + + for (i = 0; i < 8; ++i) { + byte <<= 1; + gpio_set_value(data->pdata->gpio_sck, 1); + ndelay(SHT15_TSCKH); + byte |= !!gpio_get_value(data->pdata->gpio_data); + gpio_set_value(data->pdata->gpio_sck, 0); + ndelay(SHT15_TSCKL); + } + return byte; +} + +/** + * sht15_send_status() - write the status register byte + * @data: sht15 specific data. + * @status: the byte to set the status register with. + * + * As described in figure 14 and table 5 of the datasheet. + */ +static int sht15_send_status(struct sht15_data *data, u8 status) +{ + int ret; + + ret = sht15_send_cmd(data, SHT15_WRITE_STATUS); + if (ret) + return ret; + gpio_direction_output(data->pdata->gpio_data, 1); + ndelay(SHT15_TSU); + sht15_send_byte(data, status); + ret = sht15_wait_for_response(data); + if (ret) + return ret; + data->val_status = status; return 0; } +/** + * sht15_update_status() - get updated status register from device if too old + * @data: device instance specific data. + * + * As described in figure 15 and table 5 of the datasheet. + */ +static int sht15_update_status(struct sht15_data *data) +{ + int ret = 0; + u8 status; + int timeout = HZ; + + mutex_lock(&data->read_lock); + if (time_after(jiffies, data->last_status + timeout) + || !data->status_valid) { + ret = sht15_send_cmd(data, SHT15_READ_STATUS); + if (ret) + goto error_ret; + status = sht15_read_byte(data); + + sht15_end_transmission(data); + + data->val_status = status; + data->status_valid = true; + data->last_status = jiffies; + } +error_ret: + mutex_unlock(&data->read_lock); + + return ret; +} + /** * sht15_measurement() - get a new value from device * @data: device instance specific data @@ -324,6 +436,7 @@ error_ret: static inline int sht15_calc_temp(struct sht15_data *data) { int d1 = temppoints[0].d1; + int d2 = (data->val_status & SHT15_STATUS_LOW_RESOLUTION) ? 40 : 10; int i; for (i = ARRAY_SIZE(temppoints) - 1; i > 0; i--) @@ -336,7 +449,7 @@ static inline int sht15_calc_temp(struct sht15_data *data) break; } - return data->val_temp * 10 + d1; + return data->val_temp * d2 + d1; } /** @@ -353,18 +466,85 @@ static inline int sht15_calc_humid(struct sht15_data *data) { int rh_linear; /* milli percent */ int temp = sht15_calc_temp(data); - + int c2, c3; + int t2; const int c1 = -4; - const int c2 = 40500; /* x 10 ^ -6 */ - const int c3 = -28; /* x 10 ^ -7 */ + + if (data->val_status & SHT15_STATUS_LOW_RESOLUTION) { + c2 = 648000; /* x 10 ^ -6 */ + c3 = -7200; /* x 10 ^ -7 */ + t2 = 1280; + } else { + c2 = 40500; /* x 10 ^ -6 */ + c3 = -28; /* x 10 ^ -7 */ + t2 = 80; + } rh_linear = c1 * 1000 + c2 * data->val_humid / 1000 + (data->val_humid * data->val_humid * c3) / 10000; - return (temp - 25000) * (10000 + 80 * data->val_humid) + return (temp - 25000) * (10000 + t2 * data->val_humid) / 1000000 + rh_linear; } +/** + * sht15_show_status() - show status information in sysfs + * @dev: device. + * @attr: device attribute. + * @buf: sysfs buffer where information is written to. + * + * Will be called on read access to temp1_fault, humidity1_fault + * and heater_enable sysfs attributes. + * Returns number of bytes written into buffer, negative errno on error. + */ +static ssize_t sht15_show_status(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + struct sht15_data *data = dev_get_drvdata(dev); + u8 bit = to_sensor_dev_attr(attr)->index; + + ret = sht15_update_status(data); + + return ret ? ret : sprintf(buf, "%d\n", !!(data->val_status & bit)); +} + +/** + * sht15_store_heater() - change heater state via sysfs + * @dev: device. + * @attr: device attribute. + * @buf: sysfs buffer to read the new heater state from. + * @count: length of the data. + * + * Will be called on read access to heater_enable sysfs attribute. + * Returns number of bytes actually decoded, negative errno on error. + */ +static ssize_t sht15_store_heater(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct sht15_data *data = dev_get_drvdata(dev); + long value; + u8 status; + + if (strict_strtol(buf, 10, &value)) + return -EINVAL; + + mutex_lock(&data->read_lock); + status = data->val_status & 0x07; + if (!!value) + status |= SHT15_STATUS_HEATER; + else + status &= ~SHT15_STATUS_HEATER; + + ret = sht15_send_status(data, status); + mutex_unlock(&data->read_lock); + + return ret ? ret : count; +} + /** * sht15_show_temp() - show temperature measurement value in sysfs * @dev: device. @@ -407,7 +587,6 @@ static ssize_t sht15_show_humidity(struct device *dev, ret = sht15_update_measurements(data); return ret ? ret : sprintf(buf, "%d\n", sht15_calc_humid(data)); - } static ssize_t show_name(struct device *dev, @@ -422,10 +601,19 @@ static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, sht15_show_temp, NULL, 0); static SENSOR_DEVICE_ATTR(humidity1_input, S_IRUGO, sht15_show_humidity, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, sht15_show_status, NULL, + SHT15_STATUS_LOW_BATTERY); +static SENSOR_DEVICE_ATTR(humidity1_fault, S_IRUGO, sht15_show_status, NULL, + SHT15_STATUS_LOW_BATTERY); +static SENSOR_DEVICE_ATTR(heater_enable, S_IRUGO | S_IWUSR, sht15_show_status, + sht15_store_heater, SHT15_STATUS_HEATER); static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); static struct attribute *sht15_attrs[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, &sensor_dev_attr_humidity1_input.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_humidity1_fault.dev_attr.attr, + &sensor_dev_attr_heater_enable.dev_attr.attr, &dev_attr_name.attr, NULL, }; @@ -466,25 +654,8 @@ static void sht15_ack(struct sht15_data *data) gpio_direction_input(data->pdata->gpio_data); } -/** - * sht15_end_transmission() - notify device of end of transmission - * @data: device state - * - * This is basically a NAK. (single clock pulse, data high) - */ -static void sht15_end_transmission(struct sht15_data *data) -{ - gpio_direction_output(data->pdata->gpio_data, 1); - ndelay(SHT15_TSU); - gpio_set_value(data->pdata->gpio_sck, 1); - ndelay(SHT15_TSCKH); - gpio_set_value(data->pdata->gpio_sck, 0); - ndelay(SHT15_TSCKL); -} - static void sht15_bh_read_data(struct work_struct *work_s) { - int i; uint16_t val = 0; struct sht15_data *data = container_of(work_s, struct sht15_data, @@ -505,16 +676,10 @@ static void sht15_bh_read_data(struct work_struct *work_s) } /* Read the data back from the device */ - for (i = 0; i < 16; ++i) { - val <<= 1; - gpio_set_value(data->pdata->gpio_sck, 1); - ndelay(SHT15_TSCKH); - val |= !!gpio_get_value(data->pdata->gpio_data); - gpio_set_value(data->pdata->gpio_sck, 0); - ndelay(SHT15_TSCKL); - if (i == 7) - sht15_ack(data); - } + val = sht15_read_byte(data); + val <<= 8; + sht15_ack(data); + val |= sht15_read_byte(data); /* Tell the device we are done */ sht15_end_transmission(data); @@ -568,6 +733,7 @@ static int __devinit sht15_probe(struct platform_device *pdev) { int ret = 0; struct sht15_data *data = kzalloc(sizeof(*data), GFP_KERNEL); + u8 status = 0; if (!data) { ret = -ENOMEM; @@ -588,6 +754,10 @@ static int __devinit sht15_probe(struct platform_device *pdev) } data->pdata = pdev->dev.platform_data; data->supply_uV = data->pdata->supply_mv * 1000; + if (data->pdata->no_otp_reload) + status |= SHT15_STATUS_NO_OTP_RELOAD; + if (data->pdata->low_resolution) + status |= SHT15_STATUS_LOW_RESOLUTION; /* * If a regulator is available, @@ -646,6 +816,13 @@ static int __devinit sht15_probe(struct platform_device *pdev) if (ret) goto err_release_irq; + /* write status with platform data options */ + if (status) { + ret = sht15_send_status(data, status); + if (ret) + goto err_release_irq; + } + ret = sysfs_create_group(&pdev->dev.kobj, &sht15_attr_group); if (ret) { dev_err(&pdev->dev, "sysfs create failed\n"); @@ -689,6 +866,10 @@ static int __devexit sht15_remove(struct platform_device *pdev) * prevent new ones beginning */ mutex_lock(&data->read_lock); + if (sht15_soft_reset(data)) { + mutex_unlock(&data->read_lock); + return -EFAULT; + } hwmon_device_unregister(data->hwmon_dev); sysfs_remove_group(&pdev->dev.kobj, &sht15_attr_group); if (!IS_ERR(data->reg)) { diff --git a/include/linux/sht15.h b/include/linux/sht15.h index 1e302146bdc8..d836ca617323 100644 --- a/include/linux/sht15.h +++ b/include/linux/sht15.h @@ -19,10 +19,14 @@ * @gpio_sck: no. of gpio to which the data clock is connected. * @supply_mv: supply voltage in mv. Overridden by regulator if * available. + * @no_otp_reload: flag to indicate no reload from OTP. + * @low_resolution: flag to indicate the temp/humidity resolution to use. */ struct sht15_platform_data { int gpio_data; int gpio_sck; int supply_mv; + bool no_otp_reload; + bool low_resolution; }; -- cgit v1.2.3 From 82c7465b4dd013d19858bfeac084849ae17682c0 Mon Sep 17 00:00:00 2001 From: Jerome Oufella Date: Tue, 12 Apr 2011 15:34:39 -0400 Subject: hwmon: (sht15) add support for CRC validation The sht15 sensor allows validating exchanges to and from the device using a crc8 function. An utility function to reverse a byte has also been added. Signed-off-by: Jerome Oufella Acked-by: Jonathan Cameron Signed-off-by: Guenter Roeck --- Documentation/hwmon/sht15 | 9 ++- drivers/hwmon/sht15.c | 192 +++++++++++++++++++++++++++++++++++++++++----- include/linux/sht15.h | 2 + 3 files changed, 183 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/Documentation/hwmon/sht15 b/Documentation/hwmon/sht15 index d1939b25eb16..02850bdfac18 100644 --- a/Documentation/hwmon/sht15 +++ b/Documentation/hwmon/sht15 @@ -5,6 +5,7 @@ Authors: * Wouter Horre * Jonathan Cameron * Vivien Didelot + * Jerome Oufella Supported chips: * Sensirion SHT10 @@ -44,11 +45,17 @@ measurement and decrease power consumption, while loosing on precision. Some options may be set directly in the sht15_platform_data structure or via sysfs attributes. -Note: The regulator supply name is set to "vcc". +Notes: + * The regulator supply name is set to "vcc". + * If a CRC validation fails, a soft reset command is sent, which resets + status register to its hardware default value, but the driver will try to + restore the previous device configuration. Platform data ------------- +* checksum: + set it to true to enable CRC validation of the readings (default to false). * no_otp_reload: flag to indicate not to reload from OTP (default to false). * low_resolution: diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c index 49da089b5de9..cf4330b352ef 100644 --- a/drivers/hwmon/sht15.c +++ b/drivers/hwmon/sht15.c @@ -2,6 +2,7 @@ * sht15.c - support for the SHT15 Temperature and Humidity Sensor * * Portions Copyright (c) 2010-2011 Savoir-faire Linux Inc. + * Jerome Oufella * Vivien Didelot * * Copyright (c) 2009 Jonathan Cameron @@ -78,6 +79,42 @@ static const struct sht15_temppair temppoints[] = { { 5000000, -40100 }, }; +/* Table from CRC datasheet, section 2.4 */ +static const u8 sht15_crc8_table[] = { + 0, 49, 98, 83, 196, 245, 166, 151, + 185, 136, 219, 234, 125, 76, 31, 46, + 67, 114, 33, 16, 135, 182, 229, 212, + 250, 203, 152, 169, 62, 15, 92, 109, + 134, 183, 228, 213, 66, 115, 32, 17, + 63, 14, 93, 108, 251, 202, 153, 168, + 197, 244, 167, 150, 1, 48, 99, 82, + 124, 77, 30, 47, 184, 137, 218, 235, + 61, 12, 95, 110, 249, 200, 155, 170, + 132, 181, 230, 215, 64, 113, 34, 19, + 126, 79, 28, 45, 186, 139, 216, 233, + 199, 246, 165, 148, 3, 50, 97, 80, + 187, 138, 217, 232, 127, 78, 29, 44, + 2, 51, 96, 81, 198, 247, 164, 149, + 248, 201, 154, 171, 60, 13, 94, 111, + 65, 112, 35, 18, 133, 180, 231, 214, + 122, 75, 24, 41, 190, 143, 220, 237, + 195, 242, 161, 144, 7, 54, 101, 84, + 57, 8, 91, 106, 253, 204, 159, 174, + 128, 177, 226, 211, 68, 117, 38, 23, + 252, 205, 158, 175, 56, 9, 90, 107, + 69, 116, 39, 22, 129, 176, 227, 210, + 191, 142, 221, 236, 123, 74, 25, 40, + 6, 55, 100, 85, 194, 243, 160, 145, + 71, 118, 37, 20, 131, 178, 225, 208, + 254, 207, 156, 173, 58, 11, 88, 105, + 4, 53, 102, 87, 192, 241, 162, 147, + 189, 140, 223, 238, 121, 72, 27, 42, + 193, 240, 163, 146, 5, 52, 103, 86, + 120, 73, 26, 43, 188, 141, 222, 239, + 130, 179, 224, 209, 70, 119, 36, 21, + 59, 10, 89, 104, 255, 206, 157, 172 +}; + /** * struct sht15_data - device instance specific data * @pdata: platform data (gpio's etc). @@ -86,6 +123,8 @@ static const struct sht15_temppair temppoints[] = { * @val_temp: last temperature value read from device. * @val_humid: last humidity value read from device. * @val_status: last status register value read from device. + * @checksum_ok: last value read from the device passed CRC validation. + * @checksumming: flag used to enable the data validation with CRC. * @state: state identifying the action the driver is doing. * @measurements_valid: are the current stored measures valid (start condition). * @status_valid: is the current stored status valid (start condition). @@ -112,6 +151,8 @@ struct sht15_data { uint16_t val_temp; uint16_t val_humid; u8 val_status; + bool checksum_ok; + bool checksumming; enum sht15_state state; bool measurements_valid; bool status_valid; @@ -128,6 +169,40 @@ struct sht15_data { atomic_t interrupt_handled; }; +/** + * sht15_reverse() - reverse a byte + * @byte: byte to reverse. + */ +static u8 sht15_reverse(u8 byte) +{ + u8 i, c; + + for (c = 0, i = 0; i < 8; i++) + c |= (!!(byte & (1 << i))) << (7 - i); + return c; +} + +/** + * sht15_crc8() - compute crc8 + * @data: sht15 specific data. + * @value: sht15 retrieved data. + * + * This implements section 2 of the CRC datasheet. + */ +static u8 sht15_crc8(struct sht15_data *data, + const u8 *value, + int len) +{ + u8 crc = sht15_reverse(data->val_status & 0x0F); + + while (len--) { + crc = sht15_crc8_table[*value ^ crc]; + value++; + } + + return crc; +} + /** * sht15_connection_reset() - reset the comms interface * @data: sht15 specific data @@ -267,6 +342,26 @@ static int sht15_soft_reset(struct sht15_data *data) return ret; } +/** + * sht15_ack() - send a ack + * @data: sht15 specific data. + * + * Each byte of data is acknowledged by pulling the data line + * low for one clock pulse. + */ +static void sht15_ack(struct sht15_data *data) +{ + gpio_direction_output(data->pdata->gpio_data, 0); + ndelay(SHT15_TSU); + gpio_set_value(data->pdata->gpio_sck, 1); + ndelay(SHT15_TSU); + gpio_set_value(data->pdata->gpio_sck, 0); + ndelay(SHT15_TSU); + gpio_set_value(data->pdata->gpio_data, 1); + + gpio_direction_input(data->pdata->gpio_data); +} + /** * sht15_end_transmission() - notify device of end of transmission * @data: device state. @@ -338,6 +433,9 @@ static int sht15_update_status(struct sht15_data *data) { int ret = 0; u8 status; + u8 previous_config; + u8 dev_checksum = 0; + u8 checksum_vals[2]; int timeout = HZ; mutex_lock(&data->read_lock); @@ -348,8 +446,40 @@ static int sht15_update_status(struct sht15_data *data) goto error_ret; status = sht15_read_byte(data); + if (data->checksumming) { + sht15_ack(data); + dev_checksum = sht15_reverse(sht15_read_byte(data)); + checksum_vals[0] = SHT15_READ_STATUS; + checksum_vals[1] = status; + data->checksum_ok = (sht15_crc8(data, checksum_vals, 2) + == dev_checksum); + } + sht15_end_transmission(data); + /* + * Perform checksum validation on the received data. + * Specification mentions that in case a checksum verification + * fails, a soft reset command must be sent to the device. + */ + if (data->checksumming && !data->checksum_ok) { + previous_config = data->val_status & 0x07; + ret = sht15_soft_reset(data); + if (ret) + goto error_ret; + if (previous_config) { + ret = sht15_send_status(data, previous_config); + if (ret) { + dev_err(data->dev, + "CRC validation failed, unable " + "to restore device settings\n"); + goto error_ret; + } + } + ret = -EAGAIN; + goto error_ret; + } + data->val_status = status; data->status_valid = true; data->last_status = jiffies; @@ -372,6 +502,7 @@ static int sht15_measurement(struct sht15_data *data, int timeout_msecs) { int ret; + u8 previous_config; ret = sht15_send_cmd(data, command); if (ret) @@ -395,6 +526,29 @@ static int sht15_measurement(struct sht15_data *data, sht15_connection_reset(data); return -ETIME; } + + /* + * Perform checksum validation on the received data. + * Specification mentions that in case a checksum verification fails, + * a soft reset command must be sent to the device. + */ + if (data->checksumming && !data->checksum_ok) { + previous_config = data->val_status & 0x07; + ret = sht15_soft_reset(data); + if (ret) + return ret; + if (previous_config) { + ret = sht15_send_status(data, previous_config); + if (ret) { + dev_err(data->dev, + "CRC validation failed, unable " + "to restore device settings\n"); + return ret; + } + } + return -EAGAIN; + } + return 0; } @@ -635,28 +789,11 @@ static irqreturn_t sht15_interrupt_fired(int irq, void *d) return IRQ_HANDLED; } -/** - * sht15_ack() - Send an ack to the device - * - * Each byte of data is acknowledged by pulling the data line - * low for one clock pulse. - */ -static void sht15_ack(struct sht15_data *data) -{ - gpio_direction_output(data->pdata->gpio_data, 0); - ndelay(SHT15_TSU); - gpio_set_value(data->pdata->gpio_sck, 1); - ndelay(SHT15_TSU); - gpio_set_value(data->pdata->gpio_sck, 0); - ndelay(SHT15_TSU); - gpio_set_value(data->pdata->gpio_data, 1); - - gpio_direction_input(data->pdata->gpio_data); -} - static void sht15_bh_read_data(struct work_struct *work_s) { uint16_t val = 0; + u8 dev_checksum = 0; + u8 checksum_vals[3]; struct sht15_data *data = container_of(work_s, struct sht15_data, read_work); @@ -681,6 +818,21 @@ static void sht15_bh_read_data(struct work_struct *work_s) sht15_ack(data); val |= sht15_read_byte(data); + if (data->checksumming) { + /* + * Ask the device for a checksum and read it back. + * Note: the device sends the checksum byte reversed. + */ + sht15_ack(data); + dev_checksum = sht15_reverse(sht15_read_byte(data)); + checksum_vals[0] = (data->state == SHT15_READING_TEMP) ? + SHT15_MEASURE_TEMP : SHT15_MEASURE_RH; + checksum_vals[1] = (u8) (val >> 8); + checksum_vals[2] = (u8) val; + data->checksum_ok + = (sht15_crc8(data, checksum_vals, 3) == dev_checksum); + } + /* Tell the device we are done */ sht15_end_transmission(data); @@ -754,6 +906,8 @@ static int __devinit sht15_probe(struct platform_device *pdev) } data->pdata = pdev->dev.platform_data; data->supply_uV = data->pdata->supply_mv * 1000; + if (data->pdata->checksum) + data->checksumming = true; if (data->pdata->no_otp_reload) status |= SHT15_STATUS_NO_OTP_RELOAD; if (data->pdata->low_resolution) diff --git a/include/linux/sht15.h b/include/linux/sht15.h index d836ca617323..f85c7c523da0 100644 --- a/include/linux/sht15.h +++ b/include/linux/sht15.h @@ -19,6 +19,7 @@ * @gpio_sck: no. of gpio to which the data clock is connected. * @supply_mv: supply voltage in mv. Overridden by regulator if * available. + * @checksum: flag to indicate the checksum should be validated. * @no_otp_reload: flag to indicate no reload from OTP. * @low_resolution: flag to indicate the temp/humidity resolution to use. */ @@ -26,6 +27,7 @@ struct sht15_platform_data { int gpio_data; int gpio_sck; int supply_mv; + bool checksum; bool no_otp_reload; bool low_resolution; }; -- cgit v1.2.3 From f5bae2642e3df716d2bb7bcffdf51e7a286e885e Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 2 Feb 2011 08:49:54 -0800 Subject: hwmon: Driver for MAX16065 System Manager and compatibles This patch adds hardware monitoring support for Maxim MAX16065, MAX16066, MAX16067, MAX16068, MAX16070, and MAX16071 flash-configurable system managers with nonvolatile fault registers. Signed-off-by: Guenter Roeck Reviewed-by: Jean Delvare --- Documentation/hwmon/max16065 | 98 ++++++ drivers/hwmon/Kconfig | 16 + drivers/hwmon/Makefile | 1 + drivers/hwmon/max16065.c | 717 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 832 insertions(+) create mode 100644 Documentation/hwmon/max16065 create mode 100644 drivers/hwmon/max16065.c (limited to 'drivers') diff --git a/Documentation/hwmon/max16065 b/Documentation/hwmon/max16065 new file mode 100644 index 000000000000..44b4f61e04f9 --- /dev/null +++ b/Documentation/hwmon/max16065 @@ -0,0 +1,98 @@ +Kernel driver max16065 +====================== + +Supported chips: + * Maxim MAX16065, MAX16066 + Prefixes: 'max16065', 'max16066' + Addresses scanned: - + Datasheet: + http://datasheets.maxim-ic.com/en/ds/MAX16065-MAX16066.pdf + * Maxim MAX16067 + Prefix: 'max16067' + Addresses scanned: - + Datasheet: + http://datasheets.maxim-ic.com/en/ds/MAX16067.pdf + * Maxim MAX16068 + Prefix: 'max16068' + Addresses scanned: - + Datasheet: + http://datasheets.maxim-ic.com/en/ds/MAX16068.pdf + * Maxim MAX16070/MAX16071 + Prefixes: 'max16070', 'max16071' + Addresses scanned: - + Datasheet: + http://datasheets.maxim-ic.com/en/ds/MAX16070-MAX16071.pdf + + +Author: Guenter Roeck + + +Description +----------- + +[From datasheets] The MAX16065/MAX16066 flash-configurable system managers +monitor and sequence multiple system voltages. The MAX16065/MAX16066 can also +accurately monitor (+/-2.5%) one current channel using a dedicated high-side +current-sense amplifier. The MAX16065 manages up to twelve system voltages +simultaneously, and the MAX16066 manages up to eight supply voltages. + +The MAX16067 flash-configurable system manager monitors and sequences multiple +system voltages. The MAX16067 manages up to six system voltages simultaneously. + +The MAX16068 flash-configurable system manager monitors and manages up to six +system voltages simultaneously. + +The MAX16070/MAX16071 flash-configurable system monitors supervise multiple +system voltages. The MAX16070/MAX16071 can also accurately monitor (+/-2.5%) +one current channel using a dedicated high-side current-sense amplifier. The +MAX16070 monitors up to twelve system voltages simultaneously, and the MAX16071 +monitors up to eight supply voltages. + +Each monitored channel has its own low and high critical limits. MAX16065, +MAX16066, MAX16070, and MAX16071 support an additional limit which is +configurable as either low or high secondary limit. MAX16065, MAX16066, +MAX16070, and MAX16071 also support supply current monitoring. + + +Usage Notes +----------- + +This driver does not probe for devices, since there is no register which +can be safely used to identify the chip. You will have to instantiate +the devices explicitly. Please see Documentation/i2c/instantiating-devices for +details. + + +Sysfs entries +------------- + +in[0-11]_input Input voltage measurements. + +in12_input Voltage on CSP (Current Sense Positive) pin. + Only if the chip supports current sensing and if + current sensing is enabled. + +in[0-11]_min Low warning limit. + Supported on MAX16065, MAX16066, MAX16070, and MAX16071 + only. + +in[0-11]_max High warning limit. + Supported on MAX16065, MAX16066, MAX16070, and MAX16071 + only. + + Either low or high warning limits are supported + (depending on chip configuration), but not both. + +in[0-11]_lcrit Low critical limit. + +in[0-11]_crit High critical limit. + +in[0-11]_alarm Input voltage alarm. + +curr1_input Current sense input; only if the chip supports current + sensing and if current sensing is enabled. + Displayed current assumes 0.001 Ohm current sense + resistor. + +curr1_alarm Overcurrent alarm; only if the chip supports current + sensing and if current sensing is enabled. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index f2d48777c508..dff080d7a388 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -708,6 +708,22 @@ config SENSORS_MAX1111 This driver can also be built as a module. If so, the module will be called max1111. +config SENSORS_MAX16065 + tristate "Maxim MAX16065 System Manager and compatibles" + depends on I2C + help + If you say yes here you get support for hardware monitoring + capabilities of the following Maxim System Manager chips. + MAX16065 + MAX16066 + MAX16067 + MAX16068 + MAX16070 + MAX16071 + + This driver can also be built as a module. If so, the module + will be called max16065. + config SENSORS_MAX1619 tristate "Maxim MAX1619 sensor chip" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 2211752d73b6..465f1c819fd9 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -83,6 +83,7 @@ obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o obj-$(CONFIG_SENSORS_MAX1111) += max1111.o +obj-$(CONFIG_SENSORS_MAX16065) += max16065.o obj-$(CONFIG_SENSORS_MAX1619) += max1619.o obj-$(CONFIG_SENSORS_MAX6639) += max6639.o obj-$(CONFIG_SENSORS_MAX6642) += max6642.o diff --git a/drivers/hwmon/max16065.c b/drivers/hwmon/max16065.c new file mode 100644 index 000000000000..d94a24fdf4ba --- /dev/null +++ b/drivers/hwmon/max16065.c @@ -0,0 +1,717 @@ +/* + * Driver for + * Maxim MAX16065/MAX16066 12-Channel/8-Channel, Flash-Configurable + * System Managers with Nonvolatile Fault Registers + * Maxim MAX16067/MAX16068 6-Channel, Flash-Configurable System Managers + * with Nonvolatile Fault Registers + * Maxim MAX16070/MAX16071 12-Channel/8-Channel, Flash-Configurable System + * Monitors with Nonvolatile Fault Registers + * + * 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; version 2 of the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum chips { max16065, max16066, max16067, max16068, max16070, max16071 }; + +/* + * Registers + */ +#define MAX16065_ADC(x) ((x) * 2) + +#define MAX16065_CURR_SENSE 0x18 +#define MAX16065_CSP_ADC 0x19 +#define MAX16065_FAULT(x) (0x1b + (x)) +#define MAX16065_SCALE(x) (0x43 + (x)) +#define MAX16065_CURR_CONTROL 0x47 +#define MAX16065_LIMIT(l, x) (0x48 + (l) + (x) * 3) /* + * l: limit + * 0: min/max + * 1: crit + * 2: lcrit + * x: ADC index + */ + +#define MAX16065_SW_ENABLE 0x73 + +#define MAX16065_WARNING_OV (1 << 3) /* Set if secondary threshold is OV + warning */ + +#define MAX16065_CURR_ENABLE (1 << 0) + +#define MAX16065_NUM_LIMIT 3 +#define MAX16065_NUM_ADC 12 /* maximum number of ADC channels */ + +static const int max16065_num_adc[] = { + [max16065] = 12, + [max16066] = 8, + [max16067] = 6, + [max16068] = 6, + [max16070] = 12, + [max16071] = 8, +}; + +static const bool max16065_have_secondary[] = { + [max16065] = true, + [max16066] = true, + [max16067] = false, + [max16068] = false, + [max16070] = true, + [max16071] = true, +}; + +static const bool max16065_have_current[] = { + [max16065] = true, + [max16066] = true, + [max16067] = false, + [max16068] = false, + [max16070] = true, + [max16071] = true, +}; + +struct max16065_data { + enum chips type; + struct device *hwmon_dev; + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + int num_adc; + bool have_current; + int curr_gain; + /* limits are in mV */ + int limit[MAX16065_NUM_LIMIT][MAX16065_NUM_ADC]; + int range[MAX16065_NUM_ADC + 1];/* voltage range */ + int adc[MAX16065_NUM_ADC + 1]; /* adc values (raw) including csp_adc */ + int curr_sense; + int fault[2]; +}; + +static const int max16065_adc_range[] = { 5560, 2780, 1390, 0 }; +static const int max16065_csp_adc_range[] = { 7000, 14000 }; + +/* ADC registers have 10 bit resolution. */ +static inline int ADC_TO_MV(int adc, int range) +{ + return (adc * range) / 1024; +} + +/* + * Limit registers have 8 bit resolution and match upper 8 bits of ADC + * registers. + */ +static inline int LIMIT_TO_MV(int limit, int range) +{ + return limit * range / 256; +} + +static inline int MV_TO_LIMIT(int mv, int range) +{ + return SENSORS_LIMIT(DIV_ROUND_CLOSEST(mv * 256, range), 0, 255); +} + +static inline int ADC_TO_CURR(int adc, int gain) +{ + return adc * 1400000 / gain * 255; +} + +/* + * max16065_read_adc() + * + * Read 16 bit value from , . + * Upper 8 bits are in , lower 2 bits are in bits 7:6 of . + */ +static int max16065_read_adc(struct i2c_client *client, int reg) +{ + int rv; + + rv = i2c_smbus_read_word_data(client, reg); + if (unlikely(rv < 0)) + return rv; + return ((rv & 0xff) << 2) | ((rv >> 14) & 0x03); +} + +static struct max16065_data *max16065_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max16065_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + int i; + + for (i = 0; i < data->num_adc; i++) + data->adc[i] + = max16065_read_adc(client, MAX16065_ADC(i)); + + if (data->have_current) { + data->adc[MAX16065_NUM_ADC] + = max16065_read_adc(client, MAX16065_CSP_ADC); + data->curr_sense + = i2c_smbus_read_byte_data(client, + MAX16065_CURR_SENSE); + } + + for (i = 0; i < DIV_ROUND_UP(data->num_adc, 8); i++) + data->fault[i] + = i2c_smbus_read_byte_data(client, MAX16065_FAULT(i)); + + data->last_updated = jiffies; + data->valid = 1; + } + mutex_unlock(&data->update_lock); + return data; +} + +static ssize_t max16065_show_alarm(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(da); + struct max16065_data *data = max16065_update_device(dev); + int val = data->fault[attr2->nr]; + + if (val < 0) + return val; + + val &= (1 << attr2->index); + if (val) + i2c_smbus_write_byte_data(to_i2c_client(dev), + MAX16065_FAULT(attr2->nr), val); + + return snprintf(buf, PAGE_SIZE, "%d\n", !!val); +} + +static ssize_t max16065_show_input(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct max16065_data *data = max16065_update_device(dev); + int adc = data->adc[attr->index]; + + if (unlikely(adc < 0)) + return adc; + + return snprintf(buf, PAGE_SIZE, "%d\n", + ADC_TO_MV(adc, data->range[attr->index])); +} + +static ssize_t max16065_show_current(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct max16065_data *data = max16065_update_device(dev); + + if (unlikely(data->curr_sense < 0)) + return data->curr_sense; + + return snprintf(buf, PAGE_SIZE, "%d\n", + ADC_TO_CURR(data->curr_sense, data->curr_gain)); +} + +static ssize_t max16065_set_limit(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(da); + struct i2c_client *client = to_i2c_client(dev); + struct max16065_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + int limit; + + err = strict_strtoul(buf, 10, &val); + if (unlikely(err < 0)) + return err; + + limit = MV_TO_LIMIT(val, data->range[attr2->index]); + + mutex_lock(&data->update_lock); + data->limit[attr2->nr][attr2->index] + = LIMIT_TO_MV(limit, data->range[attr2->index]); + i2c_smbus_write_byte_data(client, + MAX16065_LIMIT(attr2->nr, attr2->index), + limit); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t max16065_show_limit(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(da); + struct i2c_client *client = to_i2c_client(dev); + struct max16065_data *data = i2c_get_clientdata(client); + + return snprintf(buf, PAGE_SIZE, "%d\n", + data->limit[attr2->nr][attr2->index]); +} + +/* Construct a sensor_device_attribute structure for each register */ + +/* Input voltages */ +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, max16065_show_input, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, max16065_show_input, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, max16065_show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, max16065_show_input, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, max16065_show_input, NULL, 4); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, max16065_show_input, NULL, 5); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, max16065_show_input, NULL, 6); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, max16065_show_input, NULL, 7); +static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, max16065_show_input, NULL, 8); +static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, max16065_show_input, NULL, 9); +static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, max16065_show_input, NULL, 10); +static SENSOR_DEVICE_ATTR(in11_input, S_IRUGO, max16065_show_input, NULL, 11); +static SENSOR_DEVICE_ATTR(in12_input, S_IRUGO, max16065_show_input, NULL, 12); + +/* Input voltages lcrit */ +static SENSOR_DEVICE_ATTR_2(in0_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 0); +static SENSOR_DEVICE_ATTR_2(in1_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 1); +static SENSOR_DEVICE_ATTR_2(in2_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 2); +static SENSOR_DEVICE_ATTR_2(in3_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 3); +static SENSOR_DEVICE_ATTR_2(in4_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 4); +static SENSOR_DEVICE_ATTR_2(in5_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 5); +static SENSOR_DEVICE_ATTR_2(in6_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 6); +static SENSOR_DEVICE_ATTR_2(in7_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 7); +static SENSOR_DEVICE_ATTR_2(in8_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 8); +static SENSOR_DEVICE_ATTR_2(in9_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 9); +static SENSOR_DEVICE_ATTR_2(in10_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 10); +static SENSOR_DEVICE_ATTR_2(in11_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 11); + +/* Input voltages crit */ +static SENSOR_DEVICE_ATTR_2(in0_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 0); +static SENSOR_DEVICE_ATTR_2(in1_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 1); +static SENSOR_DEVICE_ATTR_2(in2_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 2); +static SENSOR_DEVICE_ATTR_2(in3_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 3); +static SENSOR_DEVICE_ATTR_2(in4_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 4); +static SENSOR_DEVICE_ATTR_2(in5_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 5); +static SENSOR_DEVICE_ATTR_2(in6_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 6); +static SENSOR_DEVICE_ATTR_2(in7_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 7); +static SENSOR_DEVICE_ATTR_2(in8_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 8); +static SENSOR_DEVICE_ATTR_2(in9_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 9); +static SENSOR_DEVICE_ATTR_2(in10_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 10); +static SENSOR_DEVICE_ATTR_2(in11_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 11); + +/* Input voltages min */ +static SENSOR_DEVICE_ATTR_2(in0_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 0); +static SENSOR_DEVICE_ATTR_2(in1_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 1); +static SENSOR_DEVICE_ATTR_2(in2_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 2); +static SENSOR_DEVICE_ATTR_2(in3_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 3); +static SENSOR_DEVICE_ATTR_2(in4_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 4); +static SENSOR_DEVICE_ATTR_2(in5_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 5); +static SENSOR_DEVICE_ATTR_2(in6_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 6); +static SENSOR_DEVICE_ATTR_2(in7_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 7); +static SENSOR_DEVICE_ATTR_2(in8_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 8); +static SENSOR_DEVICE_ATTR_2(in9_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 9); +static SENSOR_DEVICE_ATTR_2(in10_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 10); +static SENSOR_DEVICE_ATTR_2(in11_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 11); + +/* Input voltages max */ +static SENSOR_DEVICE_ATTR_2(in0_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 0); +static SENSOR_DEVICE_ATTR_2(in1_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 1); +static SENSOR_DEVICE_ATTR_2(in2_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 2); +static SENSOR_DEVICE_ATTR_2(in3_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 3); +static SENSOR_DEVICE_ATTR_2(in4_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 4); +static SENSOR_DEVICE_ATTR_2(in5_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 5); +static SENSOR_DEVICE_ATTR_2(in6_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 6); +static SENSOR_DEVICE_ATTR_2(in7_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 7); +static SENSOR_DEVICE_ATTR_2(in8_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 8); +static SENSOR_DEVICE_ATTR_2(in9_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 9); +static SENSOR_DEVICE_ATTR_2(in10_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 10); +static SENSOR_DEVICE_ATTR_2(in11_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 11); + +/* alarms */ +static SENSOR_DEVICE_ATTR_2(in0_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 0); +static SENSOR_DEVICE_ATTR_2(in1_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 1); +static SENSOR_DEVICE_ATTR_2(in2_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 2); +static SENSOR_DEVICE_ATTR_2(in3_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 3); +static SENSOR_DEVICE_ATTR_2(in4_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 4); +static SENSOR_DEVICE_ATTR_2(in5_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 5); +static SENSOR_DEVICE_ATTR_2(in6_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 6); +static SENSOR_DEVICE_ATTR_2(in7_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 7); +static SENSOR_DEVICE_ATTR_2(in8_alarm, S_IRUGO, max16065_show_alarm, NULL, + 1, 0); +static SENSOR_DEVICE_ATTR_2(in9_alarm, S_IRUGO, max16065_show_alarm, NULL, + 1, 1); +static SENSOR_DEVICE_ATTR_2(in10_alarm, S_IRUGO, max16065_show_alarm, NULL, + 1, 2); +static SENSOR_DEVICE_ATTR_2(in11_alarm, S_IRUGO, max16065_show_alarm, NULL, + 1, 3); + +/* Current and alarm */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, max16065_show_current, NULL, 0); +static SENSOR_DEVICE_ATTR_2(curr1_alarm, S_IRUGO, max16065_show_alarm, NULL, + 1, 4); + +/* + * Finally, construct an array of pointers to members of the above objects, + * as required for sysfs_create_group() + */ +static struct attribute *max16065_basic_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_lcrit.dev_attr.attr, + &sensor_dev_attr_in0_crit.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_lcrit.dev_attr.attr, + &sensor_dev_attr_in1_crit.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_lcrit.dev_attr.attr, + &sensor_dev_attr_in2_crit.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_lcrit.dev_attr.attr, + &sensor_dev_attr_in3_crit.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_lcrit.dev_attr.attr, + &sensor_dev_attr_in4_crit.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_lcrit.dev_attr.attr, + &sensor_dev_attr_in5_crit.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_lcrit.dev_attr.attr, + &sensor_dev_attr_in6_crit.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_lcrit.dev_attr.attr, + &sensor_dev_attr_in7_crit.dev_attr.attr, + &sensor_dev_attr_in7_alarm.dev_attr.attr, + + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in8_lcrit.dev_attr.attr, + &sensor_dev_attr_in8_crit.dev_attr.attr, + &sensor_dev_attr_in8_alarm.dev_attr.attr, + + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in9_lcrit.dev_attr.attr, + &sensor_dev_attr_in9_crit.dev_attr.attr, + &sensor_dev_attr_in9_alarm.dev_attr.attr, + + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in10_lcrit.dev_attr.attr, + &sensor_dev_attr_in10_crit.dev_attr.attr, + &sensor_dev_attr_in10_alarm.dev_attr.attr, + + &sensor_dev_attr_in11_input.dev_attr.attr, + &sensor_dev_attr_in11_lcrit.dev_attr.attr, + &sensor_dev_attr_in11_crit.dev_attr.attr, + &sensor_dev_attr_in11_alarm.dev_attr.attr, + + NULL +}; + +static struct attribute *max16065_current_attributes[] = { + &sensor_dev_attr_in12_input.dev_attr.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr1_alarm.dev_attr.attr, + NULL +}; + +static struct attribute *max16065_min_attributes[] = { + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in8_min.dev_attr.attr, + &sensor_dev_attr_in9_min.dev_attr.attr, + &sensor_dev_attr_in10_min.dev_attr.attr, + &sensor_dev_attr_in11_min.dev_attr.attr, + NULL +}; + +static struct attribute *max16065_max_attributes[] = { + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in8_max.dev_attr.attr, + &sensor_dev_attr_in9_max.dev_attr.attr, + &sensor_dev_attr_in10_max.dev_attr.attr, + &sensor_dev_attr_in11_max.dev_attr.attr, + NULL +}; + +static const struct attribute_group max16065_basic_group = { + .attrs = max16065_basic_attributes, +}; + +static const struct attribute_group max16065_current_group = { + .attrs = max16065_current_attributes, +}; + +static const struct attribute_group max16065_min_group = { + .attrs = max16065_min_attributes, +}; + +static const struct attribute_group max16065_max_group = { + .attrs = max16065_max_attributes, +}; + +static void max16065_cleanup(struct i2c_client *client) +{ + sysfs_remove_group(&client->dev.kobj, &max16065_max_group); + sysfs_remove_group(&client->dev.kobj, &max16065_min_group); + sysfs_remove_group(&client->dev.kobj, &max16065_current_group); + sysfs_remove_group(&client->dev.kobj, &max16065_basic_group); +} + +static int max16065_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct max16065_data *data; + int i, j, val, ret; + bool have_secondary; /* true if chip has secondary limits */ + bool secondary_is_max = false; /* secondary limits reflect max */ + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -ENODEV; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (unlikely(!data)) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + data->num_adc = max16065_num_adc[id->driver_data]; + data->have_current = max16065_have_current[id->driver_data]; + have_secondary = max16065_have_secondary[id->driver_data]; + + if (have_secondary) { + val = i2c_smbus_read_byte_data(client, MAX16065_SW_ENABLE); + if (unlikely(val < 0)) { + ret = val; + goto out_free; + } + secondary_is_max = val & MAX16065_WARNING_OV; + } + + /* Read scale registers, convert to range */ + for (i = 0; i < DIV_ROUND_UP(data->num_adc, 4); i++) { + val = i2c_smbus_read_byte_data(client, MAX16065_SCALE(i)); + if (unlikely(val < 0)) { + ret = val; + goto out_free; + } + for (j = 0; j < 4 && i * 4 + j < data->num_adc; j++) { + data->range[i * 4 + j] = + max16065_adc_range[(val >> (j * 2)) & 0x3]; + } + } + + /* Read limits */ + for (i = 0; i < MAX16065_NUM_LIMIT; i++) { + if (i == 0 && !have_secondary) + continue; + + for (j = 0; j < data->num_adc; j++) { + val = i2c_smbus_read_byte_data(client, + MAX16065_LIMIT(i, j)); + if (unlikely(val < 0)) { + ret = val; + goto out_free; + } + data->limit[i][j] = LIMIT_TO_MV(val, data->range[j]); + } + } + + /* Register sysfs hooks */ + for (i = 0; i < data->num_adc * 4; i++) { + /* Do not create sysfs entry if channel is disabled */ + if (!data->range[i / 4]) + continue; + + ret = sysfs_create_file(&client->dev.kobj, + max16065_basic_attributes[i]); + if (unlikely(ret)) + goto out; + } + + if (have_secondary) { + struct attribute **attr = secondary_is_max ? + max16065_max_attributes : max16065_min_attributes; + + for (i = 0; i < data->num_adc; i++) { + if (!data->range[i]) + continue; + + ret = sysfs_create_file(&client->dev.kobj, attr[i]); + if (unlikely(ret)) + goto out; + } + } + + if (data->have_current) { + val = i2c_smbus_read_byte_data(client, MAX16065_CURR_CONTROL); + if (unlikely(val < 0)) { + ret = val; + goto out; + } + if (val & MAX16065_CURR_ENABLE) { + /* + * Current gain is 6, 12, 24, 48 based on values in + * bit 2,3. + */ + data->curr_gain = 6 << ((val >> 2) & 0x03); + data->range[MAX16065_NUM_ADC] + = max16065_csp_adc_range[(val >> 1) & 0x01]; + ret = sysfs_create_group(&client->dev.kobj, + &max16065_current_group); + if (unlikely(ret)) + goto out; + } else { + data->have_current = false; + } + } + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (unlikely(IS_ERR(data->hwmon_dev))) { + ret = PTR_ERR(data->hwmon_dev); + goto out; + } + return 0; + +out: + max16065_cleanup(client); +out_free: + kfree(data); + return ret; +} + +static int max16065_remove(struct i2c_client *client) +{ + struct max16065_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + max16065_cleanup(client); + kfree(data); + + return 0; +} + +static const struct i2c_device_id max16065_id[] = { + { "max16065", max16065 }, + { "max16066", max16066 }, + { "max16067", max16067 }, + { "max16068", max16068 }, + { "max16070", max16070 }, + { "max16071", max16071 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, max16065_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max16065_driver = { + .driver = { + .name = "max16065", + }, + .probe = max16065_probe, + .remove = max16065_remove, + .id_table = max16065_id, +}; + +static int __init max16065_init(void) +{ + return i2c_add_driver(&max16065_driver); +} + +static void __exit max16065_exit(void) +{ + i2c_del_driver(&max16065_driver); +} + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("MAX16065 driver"); +MODULE_LICENSE("GPL"); + +module_init(max16065_init); +module_exit(max16065_exit); -- cgit v1.2.3 From b4ce237b7f7d31abe9c5b0c443a64573ace4933d Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 11 Mar 2011 18:09:33 -0800 Subject: hwmon: (pmbus) Introduce infrastructure to detect sensors and limit registers This commit replaces hard-coded sensor and limit register detection code with table based sensor detection. This change eliminates code block repetition and reduces code size. Signed-off-by: Guenter Roeck Reviewed-by: Tom Grennan --- drivers/hwmon/pmbus_core.c | 935 ++++++++++++++++++++------------------------- 1 file changed, 421 insertions(+), 514 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus_core.c b/drivers/hwmon/pmbus_core.c index 196ffafafd88..4e42a2994644 100644 --- a/drivers/hwmon/pmbus_core.c +++ b/drivers/hwmon/pmbus_core.c @@ -793,53 +793,6 @@ static void pmbus_add_label(struct pmbus_data *data, 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_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, - 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 -}; - -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. @@ -900,499 +853,431 @@ static void pmbus_find_max_attr(struct i2c_client *client, /* * 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, 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, 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, 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, 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, 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); +/* + * The pmbus_limit_attr structure describes a single limit attribute + * and its associated alarm attribute. + */ +struct pmbus_limit_attr { + u8 reg; /* Limit register */ + const char *attr; /* Attribute name */ + const char *alarm; /* Alarm attribute name */ + u32 sbit; /* Alarm attribute status bit */ +}; + +/* + * The pmbus_sensor_attr structure describes one sensor attribute. This + * description includes a reference to the associated limit attributes. + */ +struct pmbus_sensor_attr { + u8 reg; /* sensor register */ + enum pmbus_sensor_classes class;/* sensor class */ + const char *label; /* sensor label */ + bool paged; /* true if paged sensor */ + bool update; /* true if update needed */ + bool compare; /* true if compare function needed */ + u32 func; /* sensor mask */ + u32 sfunc; /* sensor status mask */ + int sbase; /* status base register */ + u32 gbit; /* generic status bit */ + const struct pmbus_limit_attr *limit;/* limit registers */ + int nlimit; /* # of limit registers */ +}; + +/* + * Add a set of limit attributes and, if supported, the associated + * alarm attributes. + */ +static bool pmbus_add_limit_attrs(struct i2c_client *client, + struct pmbus_data *data, + const struct pmbus_driver_info *info, + const char *name, int index, int page, + int cbase, + const struct pmbus_sensor_attr *attr) +{ + const struct pmbus_limit_attr *l = attr->limit; + int nlimit = attr->nlimit; + bool have_alarm = false; + int i, cindex; + + for (i = 0; i < nlimit; i++) { + if (pmbus_check_word_register(client, page, l->reg)) { + cindex = data->num_sensors; + pmbus_add_sensor(data, name, l->attr, index, page, + l->reg, attr->class, attr->update, + false); + if (info->func[page] & attr->sfunc) { + if (attr->compare) { + pmbus_add_boolean_cmp(data, name, + l->alarm, index, + cbase, cindex, + attr->sbase + page, l->sbit); + } else { + pmbus_add_boolean_reg(data, name, + l->alarm, index, + attr->sbase + page, l->sbit); + } 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, true); - in_index++; + l++; } + return have_alarm; +} - /* - * 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, 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, 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, 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, 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, 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; - } - } +static void pmbus_add_sensor_attrs_one(struct i2c_client *client, + struct pmbus_data *data, + const struct pmbus_driver_info *info, + const char *name, + int index, int page, + const struct pmbus_sensor_attr *attr) +{ + bool have_alarm; + int cbase = data->num_sensors; + + if (attr->label) + pmbus_add_label(data, name, index, attr->label, + attr->paged ? page + 1 : 0); + pmbus_add_sensor(data, name, "input", index, page, attr->reg, + attr->class, true, true); + if (attr->sfunc) { + have_alarm = pmbus_add_limit_attrs(client, data, info, name, + index, page, cbase, attr); /* * Add generic alarm attribute only if there are no individual - * attributes. + * alarm attributes, and if there is a global alarm bit. */ - if (!have_alarm) - pmbus_add_boolean_reg(data, "in", "alarm", - in_index, + if (!have_alarm && attr->gbit) + pmbus_add_boolean_reg(data, name, "alarm", index, PB_STATUS_BASE + page, - PB_STATUS_VOUT_OV); - in_index++; + attr->gbit); } +} - /* - * Current sensors - */ +static void pmbus_add_sensor_attrs(struct i2c_client *client, + struct pmbus_data *data, + const char *name, + const struct pmbus_sensor_attr *attrs, + int nattrs) +{ + const struct pmbus_driver_info *info = data->info; + int index, i; - /* - * 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, 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, 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, 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); + index = 1; + for (i = 0; i < nattrs; i++) { + int page, pages; + + pages = attrs->paged ? info->pages : 1; + for (page = 0; page < pages; page++) { + if (!(info->func[page] & attrs->func)) + continue; + pmbus_add_sensor_attrs_one(client, data, info, name, + index, page, attrs); + index++; } - in_index++; + attrs++; } +} - /* - * 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, 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, 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, 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, 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++; +static const struct pmbus_limit_attr vin_limit_attrs[] = { + { + .reg = PMBUS_VIN_UV_WARN_LIMIT, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_VOLTAGE_UV_WARNING, + }, { + .reg = PMBUS_VIN_UV_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_VOLTAGE_UV_FAULT, + }, { + .reg = PMBUS_VIN_OV_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_VOLTAGE_OV_WARNING, + }, { + .reg = PMBUS_VIN_OV_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_VOLTAGE_OV_FAULT, + }, +}; + +static const struct pmbus_limit_attr vout_limit_attrs[] = { + { + .reg = PMBUS_VOUT_UV_WARN_LIMIT, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_VOLTAGE_UV_WARNING, + }, { + .reg = PMBUS_VOUT_UV_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_VOLTAGE_UV_FAULT, + }, { + .reg = PMBUS_VOUT_OV_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_VOLTAGE_OV_WARNING, + }, { + .reg = PMBUS_VOUT_OV_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_VOLTAGE_OV_FAULT, } +}; - /* - * 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, 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, 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++; +static const struct pmbus_sensor_attr voltage_attributes[] = { + { + .reg = PMBUS_READ_VIN, + .class = PSC_VOLTAGE_IN, + .label = "vin", + .func = PMBUS_HAVE_VIN, + .sfunc = PMBUS_HAVE_STATUS_INPUT, + .sbase = PB_STATUS_INPUT_BASE, + .gbit = PB_STATUS_VIN_UV, + .limit = vin_limit_attrs, + .nlimit = ARRAY_SIZE(vin_limit_attrs), + }, { + .reg = PMBUS_READ_VCAP, + .class = PSC_VOLTAGE_IN, + .label = "vcap", + .func = PMBUS_HAVE_VCAP, + }, { + .reg = PMBUS_READ_VOUT, + .class = PSC_VOLTAGE_OUT, + .label = "vout", + .paged = true, + .func = PMBUS_HAVE_VOUT, + .sfunc = PMBUS_HAVE_STATUS_VOUT, + .sbase = PB_STATUS_VOUT_BASE, + .gbit = PB_STATUS_VOUT_OV, + .limit = vout_limit_attrs, + .nlimit = ARRAY_SIZE(vout_limit_attrs), } +}; - /* - * Output Power sensors - */ - for (page = 0; page < info->pages; page++) { - bool need_alarm = false; +/* Current attributes */ + +static const struct pmbus_limit_attr iin_limit_attrs[] = { + { + .reg = PMBUS_IIN_OC_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_IIN_OC_WARNING, + }, { + .reg = PMBUS_IIN_OC_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_IIN_OC_FAULT, + } +}; - if (!(info->func[page] & PMBUS_HAVE_POUT)) - continue; +static const struct pmbus_limit_attr iout_limit_attrs[] = { + { + .reg = PMBUS_IOUT_OC_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_IOUT_OC_WARNING, + }, { + .reg = PMBUS_IOUT_UC_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_IOUT_UC_FAULT, + }, { + .reg = PMBUS_IOUT_OC_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_IOUT_OC_FAULT, + } +}; - 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, 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, 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, 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, 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++; +static const struct pmbus_sensor_attr current_attributes[] = { + { + .reg = PMBUS_READ_IIN, + .class = PSC_CURRENT_IN, + .label = "iin", + .func = PMBUS_HAVE_IIN, + .sfunc = PMBUS_HAVE_STATUS_INPUT, + .sbase = PB_STATUS_INPUT_BASE, + .limit = iin_limit_attrs, + .nlimit = ARRAY_SIZE(iin_limit_attrs), + }, { + .reg = PMBUS_READ_IOUT, + .class = PSC_CURRENT_OUT, + .label = "iout", + .paged = true, + .func = PMBUS_HAVE_IOUT, + .sfunc = PMBUS_HAVE_STATUS_IOUT, + .sbase = PB_STATUS_IOUT_BASE, + .gbit = PB_STATUS_IOUT_OC, + .limit = iout_limit_attrs, + .nlimit = ARRAY_SIZE(iout_limit_attrs), } +}; - /* - * Temperature sensors - */ - in_index = 1; - for (page = 0; page < info->pages; page++) { - int t; +/* Power attributes */ - for (t = 0; t < ARRAY_SIZE(pmbus_temp_registers); t++) { - bool have_alarm = false; +static const struct pmbus_limit_attr pin_limit_attrs[] = { + { + .reg = PMBUS_PIN_OP_WARN_LIMIT, + .attr = "max", + .alarm = "alarm", + .sbit = PB_PIN_OP_WARNING, + } +}; - /* - * 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; +static const struct pmbus_limit_attr pout_limit_attrs[] = { + { + .reg = PMBUS_POUT_MAX, + .attr = "cap", + .alarm = "cap_alarm", + .sbit = PB_POWER_LIMITING, + }, { + .reg = PMBUS_POUT_OP_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_POUT_OP_WARNING, + }, { + .reg = PMBUS_POUT_OP_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_POUT_OP_FAULT, + } +}; - if (!pmbus_check_word_register - (client, page, pmbus_temp_registers[t])) - continue; +static const struct pmbus_sensor_attr power_attributes[] = { + { + .reg = PMBUS_READ_PIN, + .class = PSC_POWER, + .label = "pin", + .func = PMBUS_HAVE_PIN, + .sfunc = PMBUS_HAVE_STATUS_INPUT, + .sbase = PB_STATUS_INPUT_BASE, + .limit = pin_limit_attrs, + .nlimit = ARRAY_SIZE(pin_limit_attrs), + }, { + .reg = PMBUS_READ_POUT, + .class = PSC_POWER, + .label = "pout", + .paged = true, + .func = PMBUS_HAVE_POUT, + .sfunc = PMBUS_HAVE_STATUS_IOUT, + .sbase = PB_STATUS_IOUT_BASE, + .limit = pout_limit_attrs, + .nlimit = ARRAY_SIZE(pout_limit_attrs), + } +}; - i0 = data->num_sensors; - pmbus_add_sensor(data, "temp", "input", in_index, page, - pmbus_temp_registers[t], - PSC_TEMPERATURE, true, true); +/* Temperature atributes */ + +static const struct pmbus_limit_attr temp_limit_attrs[] = { + { + .reg = PMBUS_UT_WARN_LIMIT, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_TEMP_UT_WARNING, + }, { + .reg = PMBUS_UT_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_TEMP_UT_FAULT, + }, { + .reg = PMBUS_OT_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_TEMP_OT_WARNING, + }, { + .reg = PMBUS_OT_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_TEMP_OT_FAULT, + } +}; - /* - * 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. - * - * 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, true, 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, true, 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, true, 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, true, 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++; - } +static const struct pmbus_sensor_attr temp_attributes[] = { + { + .reg = PMBUS_READ_TEMPERATURE_1, + .class = PSC_TEMPERATURE, + .paged = true, + .update = true, + .compare = true, + .func = PMBUS_HAVE_TEMP, + .sfunc = PMBUS_HAVE_STATUS_TEMP, + .sbase = PB_STATUS_TEMP_BASE, + .gbit = PB_STATUS_TEMPERATURE, + .limit = temp_limit_attrs, + .nlimit = ARRAY_SIZE(temp_limit_attrs), + }, { + .reg = PMBUS_READ_TEMPERATURE_2, + .class = PSC_TEMPERATURE, + .paged = true, + .update = true, + .compare = true, + .func = PMBUS_HAVE_TEMP2, + .sfunc = PMBUS_HAVE_STATUS_TEMP, + .sbase = PB_STATUS_TEMP_BASE, + .gbit = PB_STATUS_TEMPERATURE, + .limit = temp_limit_attrs, + .nlimit = ARRAY_SIZE(temp_limit_attrs), + }, { + .reg = PMBUS_READ_TEMPERATURE_3, + .class = PSC_TEMPERATURE, + .paged = true, + .update = true, + .compare = true, + .func = PMBUS_HAVE_TEMP3, + .sfunc = PMBUS_HAVE_STATUS_TEMP, + .sbase = PB_STATUS_TEMP_BASE, + .gbit = PB_STATUS_TEMPERATURE, + .limit = temp_limit_attrs, + .nlimit = ARRAY_SIZE(temp_limit_attrs), } +}; + +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 +}; + +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 +}; + +/* Fans */ +static void pmbus_add_fan_attributes(struct i2c_client *client, + struct pmbus_data *data) +{ + const struct pmbus_driver_info *info = data->info; + int index = 1; + int page; - /* - * Fans - */ - in_index = 1; for (page = 0; page < info->pages; page++) { int f; @@ -1419,8 +1304,7 @@ static void pmbus_find_attributes(struct i2c_client *client, (!(regval & (PB_FAN_1_INSTALLED >> ((f & 1) * 4))))) continue; - i0 = data->num_sensors; - pmbus_add_sensor(data, "fan", "input", in_index, page, + pmbus_add_sensor(data, "fan", "input", index, page, pmbus_fan_registers[f], PSC_FAN, true, true); @@ -1438,17 +1322,40 @@ static void pmbus_find_attributes(struct i2c_client *client, else base = PB_STATUS_FAN_BASE + page; pmbus_add_boolean_reg(data, "fan", "alarm", - in_index, base, + index, base, PB_FAN_FAN1_WARNING >> (f & 1)); pmbus_add_boolean_reg(data, "fan", "fault", - in_index, base, + index, base, PB_FAN_FAN1_FAULT >> (f & 1)); } - in_index++; + index++; } } } +static void pmbus_find_attributes(struct i2c_client *client, + struct pmbus_data *data) +{ + /* Voltage sensors */ + pmbus_add_sensor_attrs(client, data, "in", voltage_attributes, + ARRAY_SIZE(voltage_attributes)); + + /* Current sensors */ + pmbus_add_sensor_attrs(client, data, "curr", current_attributes, + ARRAY_SIZE(current_attributes)); + + /* Power sensors */ + pmbus_add_sensor_attrs(client, data, "power", power_attributes, + ARRAY_SIZE(power_attributes)); + + /* Temperature sensors */ + pmbus_add_sensor_attrs(client, data, "temp", temp_attributes, + ARRAY_SIZE(temp_attributes)); + + /* Fans */ + pmbus_add_fan_attributes(client, data); +} + /* * Identify chip parameters. * This function is called for all chips. -- cgit v1.2.3 From 2cfa6aedb32c9c1226094ed383dc3c9b3e2ecddb Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 8 Mar 2011 23:00:10 -0800 Subject: hwmon: (pmbus) Expand scope of device specific get_status function Some devices use non-standard registers to access various functionality. This does not only affect status registers, but other registers as well. Rename local get_status function to get_byte_data to reflect this requirement. Signed-off-by: Guenter Roeck Reviewed-by: Tom Grennan --- drivers/hwmon/max34440.c | 6 +++--- drivers/hwmon/max8688.c | 4 ++-- drivers/hwmon/pmbus.h | 10 ++++------ drivers/hwmon/pmbus_core.c | 29 ++++++++++++++++++----------- 4 files changed, 27 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/max34440.c b/drivers/hwmon/max34440.c index 992b701b4c5e..db11e1a175b2 100644 --- a/drivers/hwmon/max34440.c +++ b/drivers/hwmon/max34440.c @@ -32,7 +32,7 @@ enum chips { max34440, max34441 }; #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) +static int max34440_read_byte_data(struct i2c_client *client, int page, int reg) { int ret; int mfg_status; @@ -108,7 +108,7 @@ static struct pmbus_driver_info max34440_info[] = { .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, + .read_byte_data = max34440_read_byte_data, }, [max34441] = { .pages = 12, @@ -149,7 +149,7 @@ static struct pmbus_driver_info max34440_info[] = { .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, + .read_byte_data = max34440_read_byte_data, }, }; diff --git a/drivers/hwmon/max8688.c b/drivers/hwmon/max8688.c index 8ebfef2ecf26..7fb93f4e9f21 100644 --- a/drivers/hwmon/max8688.c +++ b/drivers/hwmon/max8688.c @@ -37,7 +37,7 @@ #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) +static int max8688_read_byte_data(struct i2c_client *client, int page, int reg) { int ret = 0; int mfg_status; @@ -110,7 +110,7 @@ static struct pmbus_driver_info max8688_info = { .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, + .read_byte_data = max8688_read_byte_data, }; static int max8688_probe(struct i2c_client *client, diff --git a/drivers/hwmon/pmbus.h b/drivers/hwmon/pmbus.h index a81f7f228762..50647ab7235a 100644 --- a/drivers/hwmon/pmbus.h +++ b/drivers/hwmon/pmbus.h @@ -281,13 +281,11 @@ struct pmbus_driver_info { 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. + * The following functions map manufacturing specific register values + * to PMBus standard register values. Specify only if mapping is + * necessary. */ - int (*get_status)(struct i2c_client *client, int page, int reg); + int (*read_byte_data)(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 diff --git a/drivers/hwmon/pmbus_core.c b/drivers/hwmon/pmbus_core.c index 4e42a2994644..43ec1057c1f7 100644 --- a/drivers/hwmon/pmbus_core.c +++ b/drivers/hwmon/pmbus_core.c @@ -270,18 +270,22 @@ const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client) } EXPORT_SYMBOL_GPL(pmbus_get_driver_info); -static int pmbus_get_status(struct i2c_client *client, int page, int reg) +/* + * _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if + * a device specific mapping funcion exists and calls it if necessary. + */ +static int _pmbus_read_byte_data(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 (info->read_byte_data) { + status = info->read_byte_data(client, page, reg); if (status != -ENODATA) return status; } - return pmbus_read_byte_data(client, page, reg); + return pmbus_read_byte_data(client, page, reg); } static struct pmbus_data *pmbus_update_device(struct device *dev) @@ -302,38 +306,41 @@ static struct pmbus_data *pmbus_update_device(struct device *dev) if (!(info->func[i] & PMBUS_HAVE_STATUS_VOUT)) continue; data->status[PB_STATUS_VOUT_BASE + i] - = pmbus_get_status(client, i, PMBUS_STATUS_VOUT); + = _pmbus_read_byte_data(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); + = _pmbus_read_byte_data(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); + = _pmbus_read_byte_data(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); + = _pmbus_read_byte_data(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); + = _pmbus_read_byte_data(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); + = _pmbus_read_byte_data(client, 0, + PMBUS_STATUS_INPUT); for (i = 0; i < data->num_sensors; i++) { struct pmbus_sensor *sensor = &data->sensors[i]; -- cgit v1.2.3 From 6586b14c7bdec22f59dc749ead1d01d542d793b6 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 9 Mar 2011 07:23:54 -0800 Subject: hwmon: (pmbus) Use device specific function to read fan configuration Fan control implementation tends to be device specific, so start using the device specific function call to read fan configuration registers. Signed-off-by: Guenter Roeck Reviewed-by: Tom Grennan --- drivers/hwmon/pmbus_core.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus_core.c b/drivers/hwmon/pmbus_core.c index 43ec1057c1f7..98799bab69ce 100644 --- a/drivers/hwmon/pmbus_core.c +++ b/drivers/hwmon/pmbus_core.c @@ -1295,9 +1295,7 @@ static void pmbus_add_fan_attributes(struct i2c_client *client, break; if (!pmbus_check_word_register(client, page, - pmbus_fan_registers[f]) - || !pmbus_check_byte_register(client, page, - pmbus_fan_config_registers[f])) + pmbus_fan_registers[f])) break; /* @@ -1305,7 +1303,7 @@ static void pmbus_add_fan_attributes(struct i2c_client *client, * Each fan configuration register covers multiple fans, * so we have to do some magic. */ - regval = pmbus_read_byte_data(client, page, + regval = _pmbus_read_byte_data(client, page, pmbus_fan_config_registers[f]); if (regval < 0 || (!(regval & (PB_FAN_1_INSTALLED >> ((f & 1) * 4))))) -- cgit v1.2.3 From 84fc4585c152f34a1a90a03b7abb88a2e1218a42 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 6 Mar 2011 15:54:27 -0800 Subject: hwmon: (pmbus) Add support for TI UCD9200 series of PWM System Controllers This patch adds hardware monitoring support for TI UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, and UCD9248 Digital PWM System Controllers. Signed-off-by: Guenter Roeck Reviewed-by: Tom Grennan --- Documentation/hwmon/ucd9200 | 112 +++++++++++++++++++++++ drivers/hwmon/Kconfig | 10 +++ drivers/hwmon/Makefile | 1 + drivers/hwmon/ucd9200.c | 210 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 333 insertions(+) create mode 100644 Documentation/hwmon/ucd9200 create mode 100644 drivers/hwmon/ucd9200.c (limited to 'drivers') diff --git a/Documentation/hwmon/ucd9200 b/Documentation/hwmon/ucd9200 new file mode 100644 index 000000000000..3c58607f72fe --- /dev/null +++ b/Documentation/hwmon/ucd9200 @@ -0,0 +1,112 @@ +Kernel driver ucd9200 +===================== + +Supported chips: + * TI UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, and UCD9248 + Prefixes: 'ucd9220', 'ucd9222', 'ucd9224', 'ucd9240', 'ucd9244', 'ucd9246', + 'ucd9248' + Addresses scanned: - + Datasheets: + http://focus.ti.com/lit/ds/symlink/ucd9220.pdf + http://focus.ti.com/lit/ds/symlink/ucd9222.pdf + http://focus.ti.com/lit/ds/symlink/ucd9224.pdf + http://focus.ti.com/lit/ds/symlink/ucd9240.pdf + http://focus.ti.com/lit/ds/symlink/ucd9244.pdf + http://focus.ti.com/lit/ds/symlink/ucd9246.pdf + http://focus.ti.com/lit/ds/symlink/ucd9248.pdf + +Author: Guenter Roeck + + +Description +----------- + +[From datasheets] UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, and +UCD9248 are multi-rail, multi-phase synchronous buck digital PWM controllers +designed for non-isolated DC/DC power applications. The devices integrate +dedicated circuitry for DC/DC loop management with flash memory and a serial +interface to support configuration, monitoring and management. + +This driver is a client driver to the core PMBus driver. Please see +Documentation/hwmon/pmbus for details on PMBus client drivers. + + +Usage Notes +----------- + +This driver does not auto-detect devices. You will have to instantiate the +devices explicitly. Please see Documentation/i2c/instantiating-devices for +details. + + +Platform data support +--------------------- + +The driver supports standard PMBus driver platform data. Please see +Documentation/hwmon/pmbus for details. + + +Sysfs entries +------------- + +The following attributes are supported. Limits are read-write; all other +attributes are read-only. + +in1_label "vin". +in1_input Measured voltage. From READ_VIN register. +in1_min Minumum Voltage. From VIN_UV_WARN_LIMIT register. +in1_max Maximum voltage. From VIN_OV_WARN_LIMIT register. +in1_lcrit Critical minumum Voltage. VIN_UV_FAULT_LIMIT register. +in1_crit Critical maximum voltage. From VIN_OV_FAULT_LIMIT register. +in1_min_alarm Voltage low alarm. From VIN_UV_WARNING status. +in1_max_alarm Voltage high alarm. From VIN_OV_WARNING status. +in1_lcrit_alarm Voltage critical low alarm. From VIN_UV_FAULT status. +in1_crit_alarm Voltage critical high alarm. From VIN_OV_FAULT status. + +in[2-5]_label "vout[1-4]". +in[2-5]_input Measured voltage. From READ_VOUT register. +in[2-5]_min Minumum Voltage. From VOUT_UV_WARN_LIMIT register. +in[2-5]_max Maximum voltage. From VOUT_OV_WARN_LIMIT register. +in[2-5]_lcrit Critical minumum Voltage. VOUT_UV_FAULT_LIMIT register. +in[2-5]_crit Critical maximum voltage. From VOUT_OV_FAULT_LIMIT register. +in[2-5]_min_alarm Voltage low alarm. From VOLTAGE_UV_WARNING status. +in[2-5]_max_alarm Voltage high alarm. From VOLTAGE_OV_WARNING status. +in[2-5]_lcrit_alarm Voltage critical low alarm. From VOLTAGE_UV_FAULT status. +in[2-5]_crit_alarm Voltage critical high alarm. From VOLTAGE_OV_FAULT status. + +curr1_label "iin". +curr1_input Measured current. From READ_IIN register. + +curr[2-5]_label "iout[1-4]". +curr[2-5]_input Measured current. From READ_IOUT register. +curr[2-5]_max Maximum current. From IOUT_OC_WARN_LIMIT register. +curr[2-5]_lcrit Critical minumum output current. From IOUT_UC_FAULT_LIMIT + register. +curr[2-5]_crit Critical maximum current. From IOUT_OC_FAULT_LIMIT register. +curr[2-5]_max_alarm Current high alarm. From IOUT_OC_WARNING status. +curr[2-5]_crit_alarm Current critical high alarm. From IOUT_OC_FAULT status. + +power1_input Measured input power. From READ_PIN register. +power1_label "pin" + +power[2-5]_input Measured output power. From READ_POUT register. +power[2-5]_label "pout[1-4]" + + The number of output voltage, current, and power + attribute sets is determined by the number of enabled + rails. See chip datasheets for details. + +temp[1-5]_input Measured temperatures. From READ_TEMPERATURE_1 and + READ_TEMPERATURE_2 registers. + temp1 is the chip internal temperature. temp[2-5] are + rail temperatures. temp[2-5] attributes are only + created for enabled rails. See chip datasheets for + details. +temp[1-5]_max Maximum temperature. From OT_WARN_LIMIT register. +temp[1-5]_crit Critical high temperature. From OT_FAULT_LIMIT register. +temp[1-5]_max_alarm Temperature high alarm. +temp[1-5]_crit_alarm Temperature critical high alarm. + +fan1_input Fan RPM. ucd9240 only. +fan1_alarm Fan alarm. ucd9240 only. +fan1_fault Fan fault. ucd9240 only. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index dff080d7a388..07c4a860dffe 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -857,6 +857,16 @@ config SENSORS_MAX8688 This driver can also be built as a module. If so, the module will be called max8688. +config SENSORS_UCD9200 + tristate "TI UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, UCD9248" + default n + help + If you say yes here you get hardware monitoring support for TI + UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, and UCD9248. + + This driver can also be built as a module. If so, the module will + be called ucd9200. + endif # PMBUS config SENSORS_SHT15 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 465f1c819fd9..1afe02961dec 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -123,6 +123,7 @@ obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o obj-$(CONFIG_SENSORS_MAX16064) += max16064.o obj-$(CONFIG_SENSORS_MAX34440) += max34440.o obj-$(CONFIG_SENSORS_MAX8688) += max8688.o +obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG diff --git a/drivers/hwmon/ucd9200.c b/drivers/hwmon/ucd9200.c new file mode 100644 index 000000000000..ffcc1cf3609d --- /dev/null +++ b/drivers/hwmon/ucd9200.c @@ -0,0 +1,210 @@ +/* + * Hardware monitoring driver for ucd9200 series Digital PWM System Controllers + * + * 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 +#include +#include "pmbus.h" + +#define UCD9200_PHASE_INFO 0xd2 +#define UCD9200_DEVICE_ID 0xfd + +enum chips { ucd9200, ucd9220, ucd9222, ucd9224, ucd9240, ucd9244, ucd9246, + ucd9248 }; + +static const struct i2c_device_id ucd9200_id[] = { + {"ucd9200", ucd9200}, + {"ucd9220", ucd9220}, + {"ucd9222", ucd9222}, + {"ucd9224", ucd9224}, + {"ucd9240", ucd9240}, + {"ucd9244", ucd9244}, + {"ucd9246", ucd9246}, + {"ucd9248", ucd9248}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ucd9200_id); + +static int ucd9200_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + struct pmbus_driver_info *info; + const struct i2c_device_id *mid; + int i, j, ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, UCD9200_DEVICE_ID, + block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read device ID\n"); + return ret; + } + block_buffer[ret] = '\0'; + dev_info(&client->dev, "Device ID %s\n", block_buffer); + + mid = NULL; + for (i = 0; i < ARRAY_SIZE(ucd9200_id); i++) { + mid = &ucd9200_id[i]; + if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) + break; + } + if (!mid || !strlen(mid->name)) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + if (id->driver_data != ucd9200 && id->driver_data != mid->driver_data) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + id->name, mid->name); + + info = kzalloc(sizeof(struct pmbus_driver_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret = i2c_smbus_read_block_data(client, UCD9200_PHASE_INFO, + block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read phase information\n"); + goto out; + } + + /* + * Calculate number of configured pages (rails) from PHASE_INFO + * register. + * Rails have to be sequential, so we can abort after finding + * the first unconfigured rail. + */ + info->pages = 0; + for (i = 0; i < ret; i++) { + if (!block_buffer[i]) + break; + info->pages++; + } + if (!info->pages) { + dev_err(&client->dev, "No rails configured\n"); + ret = -ENODEV; + goto out; + } + dev_info(&client->dev, "%d rails configured\n", info->pages); + + /* + * Set PHASE registers on all pages to 0xff to ensure that phase + * specific commands will apply to all phases of a given page (rail). + * This only affects the READ_IOUT and READ_TEMPERATURE2 registers. + * READ_IOUT will return the sum of currents of all phases of a rail, + * and READ_TEMPERATURE2 will return the maximum temperature detected + * for the the phases of the rail. + */ + for (i = 0; i < info->pages; i++) { + /* + * Setting PAGE & PHASE fails once in a while for no obvious + * reason, so we need to retry a couple of times. + */ + for (j = 0; j < 3; j++) { + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); + if (ret < 0) + continue; + ret = i2c_smbus_write_byte_data(client, PMBUS_PHASE, + 0xff); + if (ret < 0) + continue; + break; + } + if (ret < 0) { + dev_err(&client->dev, + "Failed to initialize PHASE registers\n"); + goto out; + } + } + if (info->pages > 1) + i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | + PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + + for (i = 1; i < info->pages; i++) + info->func[i] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_POUT | + PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + + /* ucd9240 supports a single fan */ + if (mid->driver_data == ucd9240) + info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12; + + ret = pmbus_do_probe(client, mid, info); + if (ret < 0) + goto out; + return 0; +out: + kfree(info); + return ret; +} + +static int ucd9200_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; +} + + +/* This is the driver that will be inserted */ +static struct i2c_driver ucd9200_driver = { + .driver = { + .name = "ucd9200", + }, + .probe = ucd9200_probe, + .remove = ucd9200_remove, + .id_table = ucd9200_id, +}; + +static int __init ucd9200_init(void) +{ + return i2c_add_driver(&ucd9200_driver); +} + +static void __exit ucd9200_exit(void) +{ + i2c_del_driver(&ucd9200_driver); +} + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for TI UCD922x, UCD924x"); +MODULE_LICENSE("GPL"); +module_init(ucd9200_init); +module_exit(ucd9200_exit); -- cgit v1.2.3 From 0c0a0615163e025eb978547969467012529e01d5 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 8 Mar 2011 22:01:39 -0800 Subject: hwmon: (pmbus) Support for TI UCD90xxx series Sequencer and System Health Controllers Hardware monitoring support for TI UCD90120, UCD90124, UCD9090, and UCD90910 Sequencer and System Health Controllers. Signed-off-by: Guenter Roeck Reviewed-by: Tom Grennan --- Documentation/hwmon/ucd9000 | 110 ++++++++++++++++++ drivers/hwmon/Kconfig | 14 ++- drivers/hwmon/Makefile | 1 + drivers/hwmon/ucd9000.c | 278 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 Documentation/hwmon/ucd9000 create mode 100644 drivers/hwmon/ucd9000.c (limited to 'drivers') diff --git a/Documentation/hwmon/ucd9000 b/Documentation/hwmon/ucd9000 new file mode 100644 index 000000000000..40ca6db50c48 --- /dev/null +++ b/Documentation/hwmon/ucd9000 @@ -0,0 +1,110 @@ +Kernel driver ucd9000 +===================== + +Supported chips: + * TI UCD90120, UCD90124, UCD9090, and UCD90910 + Prefixes: 'ucd90120', 'ucd90124', 'ucd9090', 'ucd90910' + Addresses scanned: - + Datasheets: + http://focus.ti.com/lit/ds/symlink/ucd90120.pdf + http://focus.ti.com/lit/ds/symlink/ucd90124.pdf + http://focus.ti.com/lit/ds/symlink/ucd9090.pdf + http://focus.ti.com/lit/ds/symlink/ucd90910.pdf + +Author: Guenter Roeck + + +Description +----------- + +From datasheets: + +The UCD90120 Power Supply Sequencer and System Health Monitor monitors and +sequences up to 12 independent voltage rails. The device integrates a 12-bit +ADC with a 2.5V internal reference for monitoring up to 13 power supply voltage, +current, or temperature inputs. + +The UCD90124 is a 12-rail PMBus/I2C addressable power-supply sequencer and +system-health monitor. The device integrates a 12-bit ADC for monitoring up to +13 power-supply voltage, current, or temperature inputs. Twenty-six GPIO pins +can be used for power supply enables, power-on reset signals, external +interrupts, cascading, or other system functions. Twelve of these pins offer PWM +functionality. Using these pins, the UCD90124 offers support for fan control, +margining, and general-purpose PWM functions. + +The UCD9090 is a 10-rail PMBus/I2C addressable power-supply sequencer and +monitor. The device integrates a 12-bit ADC for monitoring up to 10 power-supply +voltage inputs. Twenty-three GPIO pins can be used for power supply enables, +power-on reset signals, external interrupts, cascading, or other system +functions. Ten of these pins offer PWM functionality. Using these pins, the +UCD9090 offers support for margining, and general-purpose PWM functions. + +The UCD90910 is a ten-rail I2C / PMBus addressable power-supply sequencer and +system-health monitor. The device integrates a 12-bit ADC for monitoring up to +13 power-supply voltage, current, or temperature inputs. + +This driver is a client driver to the core PMBus driver. Please see +Documentation/hwmon/pmbus for details on PMBus client drivers. + + +Usage Notes +----------- + +This driver does not auto-detect devices. You will have to instantiate the +devices explicitly. Please see Documentation/i2c/instantiating-devices for +details. + + +Platform data support +--------------------- + +The driver supports standard PMBus driver platform data. Please see +Documentation/hwmon/pmbus for details. + + +Sysfs entries +------------- + +The following attributes are supported. Limits are read-write; all other +attributes are read-only. + +in[1-12]_label "vout[1-12]". +in[1-12]_input Measured voltage. From READ_VOUT register. +in[1-12]_min Minumum Voltage. From VOUT_UV_WARN_LIMIT register. +in[1-12]_max Maximum voltage. From VOUT_OV_WARN_LIMIT register. +in[1-12]_lcrit Critical minumum Voltage. VOUT_UV_FAULT_LIMIT register. +in[1-12]_crit Critical maximum voltage. From VOUT_OV_FAULT_LIMIT register. +in[1-12]_min_alarm Voltage low alarm. From VOLTAGE_UV_WARNING status. +in[1-12]_max_alarm Voltage high alarm. From VOLTAGE_OV_WARNING status. +in[1-12]_lcrit_alarm Voltage critical low alarm. From VOLTAGE_UV_FAULT status. +in[1-12]_crit_alarm Voltage critical high alarm. From VOLTAGE_OV_FAULT status. + +curr[1-12]_label "iout[1-12]". +curr[1-12]_input Measured current. From READ_IOUT register. +curr[1-12]_max Maximum current. From IOUT_OC_WARN_LIMIT register. +curr[1-12]_lcrit Critical minumum output current. From IOUT_UC_FAULT_LIMIT + register. +curr[1-12]_crit Critical maximum current. From IOUT_OC_FAULT_LIMIT register. +curr[1-12]_max_alarm Current high alarm. From IOUT_OC_WARNING status. +curr[1-12]_crit_alarm Current critical high alarm. From IOUT_OC_FAULT status. + + For each attribute index, either voltage or current is + reported, but not both. If voltage or current is + reported depends on the chip configuration. + +temp[1-2]_input Measured temperatures. From READ_TEMPERATURE_1 and + READ_TEMPERATURE_2 registers. +temp[1-2]_max Maximum temperature. From OT_WARN_LIMIT register. +temp[1-2]_crit Critical high temperature. From OT_FAULT_LIMIT register. +temp[1-2]_max_alarm Temperature high alarm. +temp[1-2]_crit_alarm Temperature critical high alarm. + +fan[1-4]_input Fan RPM. +fan[1-4]_alarm Fan alarm. +fan[1-4]_fault Fan fault. + + Fan attributes are only available on chips supporting + fan control (UCD90124, UCD90910). Attribute files are + created only for enabled fans. + Note that even though UCD90910 supports up to 10 fans, + only up to four fans are currently supported. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 07c4a860dffe..d00fd39f6ee3 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -857,12 +857,24 @@ config SENSORS_MAX8688 This driver can also be built as a module. If so, the module will be called max8688. +config SENSORS_UCD9000 + tristate "TI UCD90120, UCD90124, UCD9090, UCD90910" + default n + help + If you say yes here you get hardware monitoring support for TI + UCD90120, UCD90124, UCD9090, UCD90910 Sequencer and System Health + Controllers. + + This driver can also be built as a module. If so, the module will + be called ucd9000. + config SENSORS_UCD9200 tristate "TI UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, UCD9248" default n help If you say yes here you get hardware monitoring support for TI - UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, and UCD9248. + UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, and UCD9248 + Digital PWM System Controllers. This driver can also be built as a module. If so, the module will be called ucd9200. diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 1afe02961dec..c4a269b06925 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -123,6 +123,7 @@ obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o obj-$(CONFIG_SENSORS_MAX16064) += max16064.o obj-$(CONFIG_SENSORS_MAX34440) += max34440.o obj-$(CONFIG_SENSORS_MAX8688) += max8688.o +obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG diff --git a/drivers/hwmon/ucd9000.c b/drivers/hwmon/ucd9000.c new file mode 100644 index 000000000000..ace1c7319734 --- /dev/null +++ b/drivers/hwmon/ucd9000.c @@ -0,0 +1,278 @@ +/* + * Hardware monitoring driver for UCD90xxx Sequencer and System Health + * Controller series + * + * 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 +#include +#include "pmbus.h" + +enum chips { ucd9000, ucd90120, ucd90124, ucd9090, ucd90910 }; + +#define UCD9000_MONITOR_CONFIG 0xd5 +#define UCD9000_NUM_PAGES 0xd6 +#define UCD9000_FAN_CONFIG_INDEX 0xe7 +#define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_DEVICE_ID 0xfd + +#define UCD9000_MON_TYPE(x) (((x) >> 5) & 0x07) +#define UCD9000_MON_PAGE(x) ((x) & 0x0f) + +#define UCD9000_MON_VOLTAGE 1 +#define UCD9000_MON_TEMPERATURE 2 +#define UCD9000_MON_CURRENT 3 +#define UCD9000_MON_VOLTAGE_HW 4 + +#define UCD9000_NUM_FAN 4 + +struct ucd9000_data { + u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; + struct pmbus_driver_info info; +}; +#define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) + +static int ucd9000_get_fan_config(struct i2c_client *client, int fan) +{ + int fan_config = 0; + struct ucd9000_data *data + = to_ucd9000_data(pmbus_get_driver_info(client)); + + if (data->fan_data[fan][3] & 1) + fan_config |= PB_FAN_2_INSTALLED; /* Use lower bit position */ + + /* Pulses/revolution */ + fan_config |= (data->fan_data[fan][3] & 0x06) >> 1; + + return fan_config; +} + +static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret = 0; + int fan_config; + + switch (reg) { + case PMBUS_FAN_CONFIG_12: + if (page) + return -EINVAL; + + ret = ucd9000_get_fan_config(client, 0); + if (ret < 0) + return ret; + fan_config = ret << 4; + ret = ucd9000_get_fan_config(client, 1); + if (ret < 0) + return ret; + fan_config |= ret; + ret = fan_config; + break; + case PMBUS_FAN_CONFIG_34: + if (page) + return -EINVAL; + + ret = ucd9000_get_fan_config(client, 2); + if (ret < 0) + return ret; + fan_config = ret << 4; + ret = ucd9000_get_fan_config(client, 3); + if (ret < 0) + return ret; + fan_config |= ret; + ret = fan_config; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static const struct i2c_device_id ucd9000_id[] = { + {"ucd9000", ucd9000}, + {"ucd90120", ucd90120}, + {"ucd90124", ucd90124}, + {"ucd9090", ucd9090}, + {"ucd90910", ucd90910}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ucd9000_id); + +static int ucd9000_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + struct ucd9000_data *data; + struct pmbus_driver_info *info; + const struct i2c_device_id *mid; + int i, ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, UCD9000_DEVICE_ID, + block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read device ID\n"); + return ret; + } + block_buffer[ret] = '\0'; + dev_info(&client->dev, "Device ID %s\n", block_buffer); + + mid = NULL; + for (i = 0; i < ARRAY_SIZE(ucd9000_id); i++) { + mid = &ucd9000_id[i]; + if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) + break; + } + if (!mid || !strlen(mid->name)) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + + if (id->driver_data != ucd9000 && id->driver_data != mid->driver_data) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + id->name, mid->name); + + data = kzalloc(sizeof(struct ucd9000_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + info = &data->info; + + ret = i2c_smbus_read_byte_data(client, UCD9000_NUM_PAGES); + if (ret < 0) { + dev_err(&client->dev, + "Failed to read number of active pages\n"); + goto out; + } + info->pages = ret; + if (!info->pages) { + dev_err(&client->dev, "No pages configured\n"); + ret = -ENODEV; + goto out; + } + + /* The internal temperature sensor is always active */ + info->func[0] = PMBUS_HAVE_TEMP; + + /* Everything else is configurable */ + ret = i2c_smbus_read_block_data(client, UCD9000_MONITOR_CONFIG, + block_buffer); + if (ret <= 0) { + dev_err(&client->dev, "Failed to read configuration data\n"); + ret = -ENODEV; + goto out; + } + for (i = 0; i < ret; i++) { + int page = UCD9000_MON_PAGE(block_buffer[i]); + + if (page >= info->pages) + continue; + + switch (UCD9000_MON_TYPE(block_buffer[i])) { + case UCD9000_MON_VOLTAGE: + case UCD9000_MON_VOLTAGE_HW: + info->func[page] |= PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_VOUT; + break; + case UCD9000_MON_TEMPERATURE: + info->func[page] |= PMBUS_HAVE_TEMP2 + | PMBUS_HAVE_STATUS_TEMP; + break; + case UCD9000_MON_CURRENT: + info->func[page] |= PMBUS_HAVE_IOUT + | PMBUS_HAVE_STATUS_IOUT; + break; + default: + break; + } + } + + /* Fan configuration */ + if (mid->driver_data == ucd90124) { + for (i = 0; i < UCD9000_NUM_FAN; i++) { + i2c_smbus_write_byte_data(client, + UCD9000_FAN_CONFIG_INDEX, i); + ret = i2c_smbus_read_block_data(client, + UCD9000_FAN_CONFIG, + data->fan_data[i]); + if (ret < 0) + goto out; + } + i2c_smbus_write_byte_data(client, UCD9000_FAN_CONFIG_INDEX, 0); + + info->read_byte_data = ucd9000_read_byte_data; + info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 + | PMBUS_HAVE_FAN34 | PMBUS_HAVE_STATUS_FAN34; + } + + ret = pmbus_do_probe(client, mid, info); + if (ret < 0) + goto out; + return 0; + +out: + kfree(data); + return ret; +} + +static int ucd9000_remove(struct i2c_client *client) +{ + int ret; + struct ucd9000_data *data; + + data = to_ucd9000_data(pmbus_get_driver_info(client)); + ret = pmbus_do_remove(client); + kfree(data); + return ret; +} + + +/* This is the driver that will be inserted */ +static struct i2c_driver ucd9000_driver = { + .driver = { + .name = "ucd9000", + }, + .probe = ucd9000_probe, + .remove = ucd9000_remove, + .id_table = ucd9000_id, +}; + +static int __init ucd9000_init(void) +{ + return i2c_add_driver(&ucd9000_driver); +} + +static void __exit ucd9000_exit(void) +{ + i2c_del_driver(&ucd9000_driver); +} + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for TI UCD90xxx"); +MODULE_LICENSE("GPL"); +module_init(ucd9000_init); +module_exit(ucd9000_exit); -- cgit v1.2.3 From 83f7649c52871d4b0799c209c364374b682fa4a8 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 17 Mar 2011 13:16:01 -0700 Subject: hwmon: (pmbus) Add support for Analog Devices ADM1275 Add support for Analog Devices ADM1275 Hot-Swap Controller and Digital Power Monitor Signed-off-by: Guenter Roeck Reviewed-by: Tom Grennan --- Documentation/hwmon/adm1275 | 60 ++++++++++++++++++++++ drivers/hwmon/Kconfig | 10 ++++ drivers/hwmon/Makefile | 1 + drivers/hwmon/adm1275.c | 121 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 192 insertions(+) create mode 100644 Documentation/hwmon/adm1275 create mode 100644 drivers/hwmon/adm1275.c (limited to 'drivers') diff --git a/Documentation/hwmon/adm1275 b/Documentation/hwmon/adm1275 new file mode 100644 index 000000000000..6a3a6476cf20 --- /dev/null +++ b/Documentation/hwmon/adm1275 @@ -0,0 +1,60 @@ +Kernel driver adm1275 +===================== + +Supported chips: + * Analog Devices ADM1275 + Prefix: 'adm1275' + Addresses scanned: - + Datasheet: www.analog.com/static/imported-files/data_sheets/ADM1275.pdf + +Author: Guenter Roeck + + +Description +----------- + +This driver supports hardware montoring for Analog Devices ADM1275 Hot-Swap +Controller and Digital Power Monitor. + +The ADM1275 is a hot-swap controller that allows a circuit board to be removed +from or inserted into a live backplane. It also features current and voltage +readback via an integrated 12-bit analog-to-digital converter (ADC), accessed +using a PMBus. interface. + +The driver is a client driver to the core PMBus driver. Please see +Documentation/hwmon/pmbus for details on PMBus client drivers. + + +Usage Notes +----------- + +This driver does not auto-detect devices. You will have to instantiate the +devices explicitly. Please see Documentation/i2c/instantiating-devices for +details. + + +Platform data support +--------------------- + +The driver supports standard PMBus driver platform data. Please see +Documentation/hwmon/pmbus for details. + + +Sysfs entries +------------- + +The following attributes are supported. Limits are read-write; all other +attributes are read-only. + +in1_label "vin1" or "vout1" depending on chip variant and + configuration. +in1_input Measured voltage. From READ_VOUT register. +in1_min Minumum Voltage. From VOUT_UV_WARN_LIMIT register. +in1_max Maximum voltage. From VOUT_OV_WARN_LIMIT register. +in1_min_alarm Voltage low alarm. From VOLTAGE_UV_WARNING status. +in1_max_alarm Voltage high alarm. From VOLTAGE_OV_WARNING status. + +curr1_label "iout1" +curr1_input Measured current. From READ_IOUT register. +curr1_max Maximum current. From IOUT_OC_WARN_LIMIT register. +curr1_max_alarm Current high alarm. From IOUT_OC_WARN_LIMIT register. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index d00fd39f6ee3..4abd3735a76e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -827,6 +827,16 @@ config SENSORS_PMBUS This driver can also be built as a module. If so, the module will be called pmbus. +config SENSORS_ADM1275 + tristate "Analog Devices ADM1275" + default n + help + If you say yes here you get hardware monitoring support for Analog + Devices ADM1275 Hot-Swap Controller and Digital Power Monitor. + + This driver can also be built as a module. If so, the module will + be called adm1275. + config SENSORS_MAX16064 tristate "Maxim MAX16064" default n diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index c4a269b06925..21c9b17abad5 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -120,6 +120,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_ADM1275) += adm1275.o obj-$(CONFIG_SENSORS_MAX16064) += max16064.o obj-$(CONFIG_SENSORS_MAX34440) += max34440.o obj-$(CONFIG_SENSORS_MAX8688) += max8688.o diff --git a/drivers/hwmon/adm1275.c b/drivers/hwmon/adm1275.c new file mode 100644 index 000000000000..c2ee2048ab91 --- /dev/null +++ b/drivers/hwmon/adm1275.c @@ -0,0 +1,121 @@ +/* + * Hardware monitoring driver for Analog Devices ADM1275 Hot-Swap Controller + * and Digital Power Monitor + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define ADM1275_PMON_CONFIG 0xd4 + +#define ADM1275_VIN_VOUT_SELECT (1 << 6) +#define ADM1275_VRANGE (1 << 5) + +static int adm1275_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int config; + struct pmbus_driver_info *info; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA)) + return -ENODEV; + + info = kzalloc(sizeof(struct pmbus_driver_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + config = i2c_smbus_read_byte_data(client, ADM1275_PMON_CONFIG); + if (config < 0) + return config; + + info->pages = 1; + info->direct[PSC_VOLTAGE_IN] = true; + info->direct[PSC_VOLTAGE_OUT] = true; + info->direct[PSC_CURRENT_OUT] = true; + info->m[PSC_CURRENT_OUT] = 800; + info->b[PSC_CURRENT_OUT] = 20475; + info->R[PSC_CURRENT_OUT] = -1; + info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; + + if (config & ADM1275_VRANGE) { + info->m[PSC_VOLTAGE_IN] = 19045; + info->b[PSC_VOLTAGE_IN] = 0; + info->R[PSC_VOLTAGE_IN] = -2; + info->m[PSC_VOLTAGE_OUT] = 19045; + info->b[PSC_VOLTAGE_OUT] = 0; + info->R[PSC_VOLTAGE_OUT] = -2; + } else { + info->m[PSC_VOLTAGE_IN] = 6666; + info->b[PSC_VOLTAGE_IN] = 0; + info->R[PSC_VOLTAGE_IN] = -1; + info->m[PSC_VOLTAGE_OUT] = 6666; + info->b[PSC_VOLTAGE_OUT] = 0; + info->R[PSC_VOLTAGE_OUT] = -1; + } + + if (config & ADM1275_VIN_VOUT_SELECT) + info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + else + info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT; + + return pmbus_do_probe(client, id, info); +} + +static int adm1275_remove(struct i2c_client *client) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + int ret; + + ret = pmbus_do_remove(client); + kfree(info); + return ret; +} + +static const struct i2c_device_id adm1275_id[] = { + {"adm1275", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, adm1275_id); + +static struct i2c_driver adm1275_driver = { + .driver = { + .name = "adm1275", + }, + .probe = adm1275_probe, + .remove = adm1275_remove, + .id_table = adm1275_id, +}; + +static int __init adm1275_init(void) +{ + return i2c_add_driver(&adm1275_driver); +} + +static void __exit adm1275_exit(void) +{ + i2c_del_driver(&adm1275_driver); +} + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1275"); +MODULE_LICENSE("GPL"); +module_init(adm1275_init); +module_exit(adm1275_exit); -- cgit v1.2.3 From 199e0de7f5df31a4fc485d4aaaf8a07718252ace Mon Sep 17 00:00:00 2001 From: Durgadoss R Date: Fri, 20 May 2011 01:29:35 +0530 Subject: hwmon: (coretemp) Merge pkgtemp with coretemp This patch merges the pkgtemp with coretemp driver. The sysfs interfaces for all cores in the same pkg are shown under one directory, in hwmon. It also supports CONFIG_HOTPLUG_CPU. So, the sysfs interfaces are created when each core comes online and are removed when it goes offline. Signed-off-by: Durgadoss R Cc: Fenghua Yu [guenter.roeck@ericsson.com: Fixed section reference errors] Signed-off-by: Guenter Roeck --- Documentation/hwmon/coretemp | 21 +- drivers/hwmon/coretemp.c | 665 +++++++++++++++++++++++++++++-------------- 2 files changed, 469 insertions(+), 217 deletions(-) (limited to 'drivers') diff --git a/Documentation/hwmon/coretemp b/Documentation/hwmon/coretemp index 25568f844804..f85e913a3401 100644 --- a/Documentation/hwmon/coretemp +++ b/Documentation/hwmon/coretemp @@ -15,8 +15,13 @@ Author: Rudolf Marek Description ----------- +This driver permits reading the DTS (Digital Temperature Sensor) embedded +inside Intel CPUs. This driver can read both the per-core and per-package +temperature using the appropriate sensors. The per-package sensor is new; +as of now, it is present only in the SandyBridge platform. The driver will +show the temperature of all cores inside a package under a single device +directory inside hwmon. -This driver permits reading temperature sensor embedded inside Intel Core CPU. Temperature is measured in degrees Celsius and measurement resolution is 1 degree C. Valid temperatures are from 0 to TjMax degrees C, because the actual value of temperature register is in fact a delta from TjMax. @@ -27,13 +32,15 @@ mechanism will perform actions to forcibly cool down the processor. Alarm may be raised, if the temperature grows enough (more than TjMax) to trigger the Out-Of-Spec bit. Following table summarizes the exported sysfs files: -temp1_input - Core temperature (in millidegrees Celsius). -temp1_max - All cooling devices should be turned on (on Core2). -temp1_crit - Maximum junction temperature (in millidegrees Celsius). -temp1_crit_alarm - Set when Out-of-spec bit is set, never clears. +All Sysfs entries are named with their core_id (represented here by 'X'). +tempX_input - Core temperature (in millidegrees Celsius). +tempX_max - All cooling devices should be turned on (on Core2). +tempX_crit - Maximum junction temperature (in millidegrees Celsius). +tempX_crit_alarm - Set when Out-of-spec bit is set, never clears. Correct CPU operation is no longer guaranteed. -temp1_label - Contains string "Core X", where X is processor - number. +tempX_label - Contains string "Core X", where X is processor + number. For Package temp, this will be "Physical id Y", + where Y is the package number. The TjMax temperature is set to 85 degrees C if undocumented model specific register (UMSR) 0xee has bit 30 set. If not the TjMax is 100 degrees C as diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 194ca0aa8b0c..ece3b7da6538 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -41,122 +41,146 @@ #define DRVNAME "coretemp" -typedef enum { SHOW_TEMP, SHOW_TJMAX, SHOW_TTARGET, SHOW_LABEL, - SHOW_NAME } SHOW; +#define BASE_SYSFS_ATTR_NO 2 /* Sysfs Base attr no for coretemp */ +#define NUM_REAL_CORES 16 /* Number of Real cores per cpu */ +#define CORETEMP_NAME_LENGTH 17 /* String Length of attrs */ +#define MAX_ATTRS 5 /* Maximum no of per-core attrs */ +#define MAX_CORE_DATA (NUM_REAL_CORES + BASE_SYSFS_ATTR_NO) + +#ifdef CONFIG_SMP +#define TO_PHYS_ID(cpu) cpu_data(cpu).phys_proc_id +#define TO_CORE_ID(cpu) cpu_data(cpu).cpu_core_id +#define TO_ATTR_NO(cpu) (TO_CORE_ID(cpu) + BASE_SYSFS_ATTR_NO) +#else +#define TO_PHYS_ID(cpu) (cpu) +#define TO_CORE_ID(cpu) (cpu) +#define TO_ATTR_NO(cpu) (cpu) +#endif /* - * Functions declaration + * Per-Core Temperature Data + * @last_updated: The time when the current temperature value was updated + * earlier (in jiffies). + * @cpu_core_id: The CPU Core from which temperature values should be read + * This value is passed as "id" field to rdmsr/wrmsr functions. + * @status_reg: One of IA32_THERM_STATUS or IA32_PACKAGE_THERM_STATUS, + * from where the temperature values should be read. + * @is_pkg_data: If this is 1, the temp_data holds pkgtemp data. + * Otherwise, temp_data holds coretemp data. + * @valid: If this is 1, the current temperature is valid. */ - -static struct coretemp_data *coretemp_update_device(struct device *dev); - -struct coretemp_data { - struct device *hwmon_dev; - struct mutex update_lock; - const char *name; - u32 id; - u16 core_id; - char valid; /* zero until following fields are valid */ - unsigned long last_updated; /* in jiffies */ +struct temp_data { int temp; - int tjmax; int ttarget; - u8 alarm; + int tjmax; + unsigned long last_updated; + unsigned int cpu; + u32 cpu_core_id; + u32 status_reg; + bool is_pkg_data; + bool valid; + struct sensor_device_attribute sd_attrs[MAX_ATTRS]; + char attr_name[MAX_ATTRS][CORETEMP_NAME_LENGTH]; + struct mutex update_lock; }; -/* - * Sysfs stuff - */ +/* Platform Data per Physical CPU */ +struct platform_data { + struct device *hwmon_dev; + u16 phys_proc_id; + struct temp_data *core_data[MAX_CORE_DATA]; + struct device_attribute name_attr; +}; -static ssize_t show_name(struct device *dev, struct device_attribute - *devattr, char *buf) +struct pdev_entry { + struct list_head list; + struct platform_device *pdev; + unsigned int cpu; + u16 phys_proc_id; + u16 cpu_core_id; +}; + +static LIST_HEAD(pdev_list); +static DEFINE_MUTEX(pdev_list_mutex); + +static ssize_t show_name(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + return sprintf(buf, "%s\n", DRVNAME); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, char *buf) { - int ret; struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct coretemp_data *data = dev_get_drvdata(dev); + struct platform_data *pdata = dev_get_drvdata(dev); + struct temp_data *tdata = pdata->core_data[attr->index]; + + if (tdata->is_pkg_data) + return sprintf(buf, "Physical id %u\n", pdata->phys_proc_id); - if (attr->index == SHOW_NAME) - ret = sprintf(buf, "%s\n", data->name); - else /* show label */ - ret = sprintf(buf, "Core %d\n", data->core_id); - return ret; + return sprintf(buf, "Core %u\n", tdata->cpu_core_id); } -static ssize_t show_alarm(struct device *dev, struct device_attribute - *devattr, char *buf) +static ssize_t show_crit_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) { - struct coretemp_data *data = coretemp_update_device(dev); - /* read the Out-of-spec log, never clear */ - return sprintf(buf, "%d\n", data->alarm); + u32 eax, edx; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct platform_data *pdata = dev_get_drvdata(dev); + struct temp_data *tdata = pdata->core_data[attr->index]; + + rdmsr_on_cpu(tdata->cpu, tdata->status_reg, &eax, &edx); + + return sprintf(buf, "%d\n", (eax >> 5) & 1); } -static ssize_t show_temp(struct device *dev, - struct device_attribute *devattr, char *buf) +static ssize_t show_tjmax(struct device *dev, + struct device_attribute *devattr, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct coretemp_data *data = coretemp_update_device(dev); - int err; + struct platform_data *pdata = dev_get_drvdata(dev); - if (attr->index == SHOW_TEMP) - err = data->valid ? sprintf(buf, "%d\n", data->temp) : -EAGAIN; - else if (attr->index == SHOW_TJMAX) - err = sprintf(buf, "%d\n", data->tjmax); - else - err = sprintf(buf, "%d\n", data->ttarget); - return err; + return sprintf(buf, "%d\n", pdata->core_data[attr->index]->tjmax); } -static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, - SHOW_TEMP); -static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp, NULL, - SHOW_TJMAX); -static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp, NULL, - SHOW_TTARGET); -static DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL); -static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_name, NULL, SHOW_LABEL); -static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, SHOW_NAME); - -static struct attribute *coretemp_attributes[] = { - &sensor_dev_attr_name.dev_attr.attr, - &sensor_dev_attr_temp1_label.dev_attr.attr, - &dev_attr_temp1_crit_alarm.attr, - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp1_crit.dev_attr.attr, - NULL -}; +static ssize_t show_ttarget(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct platform_data *pdata = dev_get_drvdata(dev); -static const struct attribute_group coretemp_group = { - .attrs = coretemp_attributes, -}; + return sprintf(buf, "%d\n", pdata->core_data[attr->index]->ttarget); +} -static struct coretemp_data *coretemp_update_device(struct device *dev) +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) { - struct coretemp_data *data = dev_get_drvdata(dev); - - mutex_lock(&data->update_lock); + u32 eax, edx; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct platform_data *pdata = dev_get_drvdata(dev); + struct temp_data *tdata = pdata->core_data[attr->index]; - if (!data->valid || time_after(jiffies, data->last_updated + HZ)) { - u32 eax, edx; + mutex_lock(&tdata->update_lock); - data->valid = 0; - rdmsr_on_cpu(data->id, MSR_IA32_THERM_STATUS, &eax, &edx); - data->alarm = (eax >> 5) & 1; - /* update only if data has been valid */ + /* Check whether the time interval has elapsed */ + if (!tdata->valid || time_after(jiffies, tdata->last_updated + HZ)) { + rdmsr_on_cpu(tdata->cpu, tdata->status_reg, &eax, &edx); + tdata->valid = 0; + /* Check whether the data is valid */ if (eax & 0x80000000) { - data->temp = data->tjmax - (((eax >> 16) - & 0x7f) * 1000); - data->valid = 1; - } else { - dev_dbg(dev, "Temperature data invalid (0x%x)\n", eax); + tdata->temp = tdata->tjmax - + (((eax >> 16) & 0x7f) * 1000); + tdata->valid = 1; } - data->last_updated = jiffies; + tdata->last_updated = jiffies; } - mutex_unlock(&data->update_lock); - return data; + mutex_unlock(&tdata->update_lock); + return tdata->valid ? sprintf(buf, "%d\n", tdata->temp) : -EAGAIN; } -static int __devinit adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) +static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) { /* The 100C is default for both mobile and non mobile CPUs */ @@ -244,8 +268,7 @@ static int __devinit adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device * return tjmax; } -static int __devinit get_tjmax(struct cpuinfo_x86 *c, u32 id, - struct device *dev) +static int get_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) { /* The 100C is default for both mobile and non mobile CPUs */ int err; @@ -300,115 +323,293 @@ static void __devinit get_ucode_rev_on_cpu(void *edx) rdmsr(MSR_IA32_UCODE_REV, eax, *(u32 *)edx); } -static int __devinit coretemp_probe(struct platform_device *pdev) +static int get_pkg_tjmax(unsigned int cpu, struct device *dev) { - struct coretemp_data *data; - struct cpuinfo_x86 *c = &cpu_data(pdev->id); int err; - u32 eax, edx; + u32 eax, edx, val; - if (!(data = kzalloc(sizeof(struct coretemp_data), GFP_KERNEL))) { - err = -ENOMEM; - dev_err(&pdev->dev, "Out of memory\n"); - goto exit; + err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); + if (!err) { + val = (eax >> 16) & 0xff; + if ((val > 80) && (val < 120)) + return val * 1000; } + dev_warn(dev, "Unable to read Pkg-TjMax from CPU:%u\n", cpu); + return 100000; /* Default TjMax: 100 degree celsius */ +} - data->id = pdev->id; -#ifdef CONFIG_SMP - data->core_id = c->cpu_core_id; -#endif - data->name = "coretemp"; - mutex_init(&data->update_lock); +static int create_name_attr(struct platform_data *pdata, struct device *dev) +{ + pdata->name_attr.attr.name = "name"; + pdata->name_attr.attr.mode = S_IRUGO; + pdata->name_attr.show = show_name; + return device_create_file(dev, &pdata->name_attr); +} - /* test if we can access the THERM_STATUS MSR */ - err = rdmsr_safe_on_cpu(data->id, MSR_IA32_THERM_STATUS, &eax, &edx); - if (err) { - dev_err(&pdev->dev, - "Unable to access THERM_STATUS MSR, giving up\n"); - goto exit_free; +static int create_core_attrs(struct temp_data *tdata, struct device *dev, + int attr_no) +{ + int err, i; + static ssize_t (*rd_ptr[MAX_ATTRS]) (struct device *dev, + struct device_attribute *devattr, char *buf) = { + show_label, show_crit_alarm, show_ttarget, + show_temp, show_tjmax }; + static const char *names[MAX_ATTRS] = { + "temp%d_label", "temp%d_crit_alarm", + "temp%d_max", "temp%d_input", + "temp%d_crit" }; + + for (i = 0; i < MAX_ATTRS; i++) { + snprintf(tdata->attr_name[i], CORETEMP_NAME_LENGTH, names[i], + attr_no); + tdata->sd_attrs[i].dev_attr.attr.name = tdata->attr_name[i]; + tdata->sd_attrs[i].dev_attr.attr.mode = S_IRUGO; + tdata->sd_attrs[i].dev_attr.show = rd_ptr[i]; + tdata->sd_attrs[i].dev_attr.store = NULL; + tdata->sd_attrs[i].index = attr_no; + err = device_create_file(dev, &tdata->sd_attrs[i].dev_attr); + if (err) + goto exit_free; } + return 0; + +exit_free: + while (--i >= 0) + device_remove_file(dev, &tdata->sd_attrs[i].dev_attr); + return err; +} + +static void update_ttarget(__u8 cpu_model, struct temp_data *tdata, + struct device *dev) +{ + int err; + u32 eax, edx; + + /* + * Initialize ttarget value. Eventually this will be + * initialized with the value from MSR_IA32_THERM_INTERRUPT + * register. If IA32_TEMPERATURE_TARGET is supported, this + * value will be over written below. + * To Do: Patch to initialize ttarget from MSR_IA32_THERM_INTERRUPT + */ + tdata->ttarget = tdata->tjmax - 20000; - /* Check if we have problem with errata AE18 of Core processors: - Readings might stop update when processor visited too deep sleep, - fixed for stepping D0 (6EC). - */ + /* + * Read the still undocumented IA32_TEMPERATURE_TARGET. It exists + * on older CPUs but not in this register, + * Atoms don't have it either. + */ + if ((cpu_model > 0xe) && (cpu_model != 0x1c)) { + err = rdmsr_safe_on_cpu(tdata->cpu, + MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); + if (err) { + dev_warn(dev, + "Unable to read IA32_TEMPERATURE_TARGET MSR\n"); + } else { + tdata->ttarget = tdata->tjmax - + (((eax >> 8) & 0xff) * 1000); + } + } +} + +static int chk_ucode_version(struct platform_device *pdev) +{ + struct cpuinfo_x86 *c = &cpu_data(pdev->id); + int err; + u32 edx; + /* + * Check if we have problem with errata AE18 of Core processors: + * Readings might stop update when processor visited too deep sleep, + * fixed for stepping D0 (6EC). + */ if ((c->x86_model == 0xe) && (c->x86_mask < 0xc)) { /* check for microcode update */ - err = smp_call_function_single(data->id, get_ucode_rev_on_cpu, + err = smp_call_function_single(pdev->id, get_ucode_rev_on_cpu, &edx, 1); if (err) { dev_err(&pdev->dev, "Cannot determine microcode revision of " - "CPU#%u (%d)!\n", data->id, err); - err = -ENODEV; - goto exit_free; + "CPU#%u (%d)!\n", pdev->id, err); + return -ENODEV; } else if (edx < 0x39) { - err = -ENODEV; dev_err(&pdev->dev, "Errata AE18 not fixed, update BIOS or " "microcode of the CPU!\n"); - goto exit_free; + return -ENODEV; } } + return 0; +} + +static struct platform_device *coretemp_get_pdev(unsigned int cpu) +{ + u16 phys_proc_id = TO_PHYS_ID(cpu); + struct pdev_entry *p; + + mutex_lock(&pdev_list_mutex); + + list_for_each_entry(p, &pdev_list, list) + if (p->phys_proc_id == phys_proc_id) { + mutex_unlock(&pdev_list_mutex); + return p->pdev; + } + + mutex_unlock(&pdev_list_mutex); + return NULL; +} + +static struct temp_data *init_temp_data(unsigned int cpu, int pkg_flag) +{ + struct temp_data *tdata; + + tdata = kzalloc(sizeof(struct temp_data), GFP_KERNEL); + if (!tdata) + return NULL; + + tdata->status_reg = pkg_flag ? MSR_IA32_PACKAGE_THERM_STATUS : + MSR_IA32_THERM_STATUS; + tdata->is_pkg_data = pkg_flag; + tdata->cpu = cpu; + tdata->cpu_core_id = TO_CORE_ID(cpu); + mutex_init(&tdata->update_lock); + return tdata; +} - data->tjmax = get_tjmax(c, data->id, &pdev->dev); - platform_set_drvdata(pdev, data); +static int create_core_data(struct platform_data *pdata, + struct platform_device *pdev, + unsigned int cpu, int pkg_flag) +{ + struct temp_data *tdata; + struct cpuinfo_x86 *c = &cpu_data(cpu); + u32 eax, edx; + int err, attr_no; /* - * read the still undocumented IA32_TEMPERATURE_TARGET. It exists - * on older CPUs but not in this register, - * Atoms don't have it either. + * Find attr number for sysfs: + * We map the attr number to core id of the CPU + * The attr number is always core id + 2 + * The Pkgtemp will always show up as temp1_*, if available */ + attr_no = pkg_flag ? 1 : TO_ATTR_NO(cpu); - if ((c->x86_model > 0xe) && (c->x86_model != 0x1c)) { - err = rdmsr_safe_on_cpu(data->id, MSR_IA32_TEMPERATURE_TARGET, - &eax, &edx); - if (err) { - dev_warn(&pdev->dev, "Unable to read" - " IA32_TEMPERATURE_TARGET MSR\n"); - } else { - data->ttarget = data->tjmax - - (((eax >> 8) & 0xff) * 1000); - err = device_create_file(&pdev->dev, - &sensor_dev_attr_temp1_max.dev_attr); - if (err) - goto exit_free; - } - } + if (attr_no > MAX_CORE_DATA - 1) + return -ERANGE; + + /* Skip if it is a HT core, Not an error */ + if (pdata->core_data[attr_no] != NULL) + return 0; - if ((err = sysfs_create_group(&pdev->dev.kobj, &coretemp_group))) - goto exit_dev; + tdata = init_temp_data(cpu, pkg_flag); + if (!tdata) + return -ENOMEM; - data->hwmon_dev = hwmon_device_register(&pdev->dev); - if (IS_ERR(data->hwmon_dev)) { - err = PTR_ERR(data->hwmon_dev); - dev_err(&pdev->dev, "Class registration failed (%d)\n", - err); - goto exit_class; - } + /* Test if we can access the status register */ + err = rdmsr_safe_on_cpu(cpu, tdata->status_reg, &eax, &edx); + if (err) + goto exit_free; + + /* We can access status register. Get Critical Temperature */ + if (pkg_flag) + tdata->tjmax = get_pkg_tjmax(pdev->id, &pdev->dev); + else + tdata->tjmax = get_tjmax(c, cpu, &pdev->dev); + + update_ttarget(c->x86_model, tdata, &pdev->dev); + pdata->core_data[attr_no] = tdata; + + /* Create sysfs interfaces */ + err = create_core_attrs(tdata, &pdev->dev, attr_no); + if (err) + goto exit_free; return 0; +exit_free: + kfree(tdata); + return err; +} + +static void coretemp_add_core(unsigned int cpu, int pkg_flag) +{ + struct platform_data *pdata; + struct platform_device *pdev = coretemp_get_pdev(cpu); + int err; + + if (!pdev) + return; + + pdata = platform_get_drvdata(pdev); + + err = create_core_data(pdata, pdev, cpu, pkg_flag); + if (err) + dev_err(&pdev->dev, "Adding Core %u failed\n", cpu); +} + +static void coretemp_remove_core(struct platform_data *pdata, + struct device *dev, int indx) +{ + int i; + struct temp_data *tdata = pdata->core_data[indx]; + + /* Remove the sysfs attributes */ + for (i = 0; i < MAX_ATTRS; i++) + device_remove_file(dev, &tdata->sd_attrs[i].dev_attr); + + kfree(pdata->core_data[indx]); + pdata->core_data[indx] = NULL; +} + +static int __devinit coretemp_probe(struct platform_device *pdev) +{ + struct platform_data *pdata; + int err; -exit_class: - sysfs_remove_group(&pdev->dev.kobj, &coretemp_group); -exit_dev: - device_remove_file(&pdev->dev, &sensor_dev_attr_temp1_max.dev_attr); + /* Check the microcode version of the CPU */ + err = chk_ucode_version(pdev); + if (err) + return err; + + /* Initialize the per-package data structures */ + pdata = kzalloc(sizeof(struct platform_data), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + err = create_name_attr(pdata, &pdev->dev); + if (err) + goto exit_free; + + pdata->phys_proc_id = TO_PHYS_ID(pdev->id); + platform_set_drvdata(pdev, pdata); + + pdata->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(pdata->hwmon_dev)) { + err = PTR_ERR(pdata->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit_name; + } + return 0; + +exit_name: + device_remove_file(&pdev->dev, &pdata->name_attr); + platform_set_drvdata(pdev, NULL); exit_free: - kfree(data); -exit: + kfree(pdata); return err; } static int __devexit coretemp_remove(struct platform_device *pdev) { - struct coretemp_data *data = platform_get_drvdata(pdev); + struct platform_data *pdata = platform_get_drvdata(pdev); + int i; - hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&pdev->dev.kobj, &coretemp_group); - device_remove_file(&pdev->dev, &sensor_dev_attr_temp1_max.dev_attr); + for (i = MAX_CORE_DATA - 1; i >= 0; --i) + if (pdata->core_data[i]) + coretemp_remove_core(pdata, &pdev->dev, i); + + device_remove_file(&pdev->dev, &pdata->name_attr); + hwmon_device_unregister(pdata->hwmon_dev); platform_set_drvdata(pdev, NULL); - kfree(data); + kfree(pdata); return 0; } @@ -421,50 +622,14 @@ static struct platform_driver coretemp_driver = { .remove = __devexit_p(coretemp_remove), }; -struct pdev_entry { - struct list_head list; - struct platform_device *pdev; - unsigned int cpu; -#ifdef CONFIG_SMP - u16 phys_proc_id; - u16 cpu_core_id; -#endif -}; - -static LIST_HEAD(pdev_list); -static DEFINE_MUTEX(pdev_list_mutex); - static int __cpuinit coretemp_device_add(unsigned int cpu) { int err; struct platform_device *pdev; struct pdev_entry *pdev_entry; - struct cpuinfo_x86 *c = &cpu_data(cpu); - - /* - * CPUID.06H.EAX[0] indicates whether the CPU has thermal - * sensors. We check this bit only, all the early CPUs - * without thermal sensors will be filtered out. - */ - if (!cpu_has(c, X86_FEATURE_DTS)) { - pr_info("CPU (model=0x%x) has no thermal sensor\n", - c->x86_model); - return 0; - } mutex_lock(&pdev_list_mutex); -#ifdef CONFIG_SMP - /* Skip second HT entry of each core */ - list_for_each_entry(pdev_entry, &pdev_list, list) { - if (c->phys_proc_id == pdev_entry->phys_proc_id && - c->cpu_core_id == pdev_entry->cpu_core_id) { - err = 0; /* Not an error */ - goto exit; - } - } -#endif - pdev = platform_device_alloc(DRVNAME, cpu); if (!pdev) { err = -ENOMEM; @@ -486,10 +651,9 @@ static int __cpuinit coretemp_device_add(unsigned int cpu) pdev_entry->pdev = pdev; pdev_entry->cpu = cpu; -#ifdef CONFIG_SMP - pdev_entry->phys_proc_id = c->phys_proc_id; - pdev_entry->cpu_core_id = c->cpu_core_id; -#endif + pdev_entry->phys_proc_id = TO_PHYS_ID(cpu); + pdev_entry->cpu_core_id = TO_CORE_ID(cpu); + list_add_tail(&pdev_entry->list, &pdev_list); mutex_unlock(&pdev_list_mutex); @@ -504,28 +668,108 @@ exit: return err; } -static void __cpuinit coretemp_device_remove(unsigned int cpu) +static void coretemp_device_remove(unsigned int cpu) { - struct pdev_entry *p; - unsigned int i; + struct pdev_entry *p, *n; + u16 phys_proc_id = TO_PHYS_ID(cpu); mutex_lock(&pdev_list_mutex); - list_for_each_entry(p, &pdev_list, list) { - if (p->cpu != cpu) + list_for_each_entry_safe(p, n, &pdev_list, list) { + if (p->phys_proc_id != phys_proc_id) continue; - platform_device_unregister(p->pdev); list_del(&p->list); - mutex_unlock(&pdev_list_mutex); kfree(p); - for_each_cpu(i, cpu_sibling_mask(cpu)) - if (i != cpu && !coretemp_device_add(i)) - break; - return; } mutex_unlock(&pdev_list_mutex); } +static bool is_any_core_online(struct platform_data *pdata) +{ + int i; + + /* Find online cores, except pkgtemp data */ + for (i = MAX_CORE_DATA - 1; i >= 0; --i) { + if (pdata->core_data[i] && + !pdata->core_data[i]->is_pkg_data) { + return true; + } + } + return false; +} + +static void __cpuinit get_core_online(unsigned int cpu) +{ + struct cpuinfo_x86 *c = &cpu_data(cpu); + struct platform_device *pdev = coretemp_get_pdev(cpu); + int err; + + /* + * CPUID.06H.EAX[0] indicates whether the CPU has thermal + * sensors. We check this bit only, all the early CPUs + * without thermal sensors will be filtered out. + */ + if (!cpu_has(c, X86_FEATURE_DTS)) + return; + + if (!pdev) { + /* + * Alright, we have DTS support. + * We are bringing the _first_ core in this pkg + * online. So, initialize per-pkg data structures and + * then bring this core online. + */ + err = coretemp_device_add(cpu); + if (err) + return; + /* + * Check whether pkgtemp support is available. + * If so, add interfaces for pkgtemp. + */ + if (cpu_has(c, X86_FEATURE_PTS)) + coretemp_add_core(cpu, 1); + } + /* + * Physical CPU device already exists. + * So, just add interfaces for this core. + */ + coretemp_add_core(cpu, 0); +} + +static void __cpuinit put_core_offline(unsigned int cpu) +{ + int i, indx; + struct platform_data *pdata; + struct platform_device *pdev = coretemp_get_pdev(cpu); + + /* If the physical CPU device does not exist, just return */ + if (!pdev) + return; + + pdata = platform_get_drvdata(pdev); + + indx = TO_ATTR_NO(cpu); + + if (pdata->core_data[indx] && pdata->core_data[indx]->cpu == cpu) + coretemp_remove_core(pdata, &pdev->dev, indx); + + /* Online the HT version of this core, if any */ + for_each_cpu(i, cpu_sibling_mask(cpu)) { + if (i != cpu) { + get_core_online(i); + break; + } + } + /* + * If all cores in this pkg are offline, remove the device. + * coretemp_device_remove calls unregister_platform_device, + * which in turn calls coretemp_remove. This removes the + * pkgtemp entry and does other clean ups. + */ + if (!is_any_core_online(pdata)) + coretemp_device_remove(cpu); +} + static int __cpuinit coretemp_cpu_callback(struct notifier_block *nfb, unsigned long action, void *hcpu) { @@ -534,10 +778,10 @@ static int __cpuinit coretemp_cpu_callback(struct notifier_block *nfb, switch (action) { case CPU_ONLINE: case CPU_DOWN_FAILED: - coretemp_device_add(cpu); + get_core_online(cpu); break; case CPU_DOWN_PREPARE: - coretemp_device_remove(cpu); + put_core_offline(cpu); break; } return NOTIFY_OK; @@ -547,6 +791,7 @@ static struct notifier_block coretemp_cpu_notifier __refdata = { .notifier_call = coretemp_cpu_callback, }; + static int __init coretemp_init(void) { int i, err = -ENODEV; @@ -560,7 +805,7 @@ static int __init coretemp_init(void) goto exit; for_each_online_cpu(i) - coretemp_device_add(i); + get_core_online(i); #ifndef CONFIG_HOTPLUG_CPU if (list_empty(&pdev_list)) { -- cgit v1.2.3 From 5e99c2f1e0c86d27ed2a5261442126cd0935bc69 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 20 May 2011 07:28:06 -0700 Subject: hwmon: Remove pkgtemp driver After the merge of pkgtemp functionality into the coretemp driver, the pkgtemp driver is no longer necessary. Remove it. Signed-off-by: Guenter Roeck Cc: Fenghua Yu Acked-by: Jean Delvare --- Documentation/hwmon/pkgtemp | 36 ---- drivers/hwmon/Kconfig | 7 - drivers/hwmon/Makefile | 1 - drivers/hwmon/pkgtemp.c | 444 -------------------------------------------- 4 files changed, 488 deletions(-) delete mode 100644 Documentation/hwmon/pkgtemp delete mode 100644 drivers/hwmon/pkgtemp.c (limited to 'drivers') diff --git a/Documentation/hwmon/pkgtemp b/Documentation/hwmon/pkgtemp deleted file mode 100644 index c8e1fb0fadd3..000000000000 --- a/Documentation/hwmon/pkgtemp +++ /dev/null @@ -1,36 +0,0 @@ -Kernel driver pkgtemp -====================== - -Supported chips: - * Intel family - Prefix: 'pkgtemp' - CPUID: - Datasheet: Intel 64 and IA-32 Architectures Software Developer's Manual - Volume 3A: System Programming Guide - -Author: Fenghua Yu - -Description ------------ - -This driver permits reading package level temperature sensor embedded inside -Intel CPU package. The sensors can be in core, uncore, memory controller, or -other components in a package. The feature is first implemented in Intel Sandy -Bridge platform. - -Temperature is measured in degrees Celsius and measurement resolution is -1 degree C. Valid temperatures are from 0 to TjMax degrees C, because the actual -value of temperature register is in fact a delta from TjMax. - -Temperature known as TjMax is the maximum junction temperature of package. -We get this from MSR_IA32_TEMPERATURE_TARGET. If the MSR is not accessible, -we define TjMax as 100 degrees Celsius. At this temperature, protection -mechanism will perform actions to forcibly cool down the package. Alarm -may be raised, if the temperature grows enough (more than TjMax) to trigger -the Out-Of-Spec bit. Following table summarizes the exported sysfs files: - -temp1_input - Package temperature (in millidegrees Celsius). -temp1_max - All cooling devices should be turned on. -temp1_crit - Maximum junction temperature (in millidegrees Celsius). -temp1_crit_alarm - Set when Out-of-spec bit is set, never clears. - Correct CPU operation is no longer guaranteed. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 4abd3735a76e..43221beb9e97 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -408,13 +408,6 @@ config SENSORS_CORETEMP sensor inside your CPU. Most of the family 6 CPUs are supported. Check Documentation/hwmon/coretemp for details. -config SENSORS_PKGTEMP - tristate "Intel processor package temperature sensor" - depends on X86 && EXPERIMENTAL - help - If you say yes here you get support for the package level temperature - sensor inside your CPU. Check documentation/driver for details. - config SENSORS_IBMAEM tristate "IBM Active Energy Manager temperature/power sensors and control" select IPMI_SI diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 21c9b17abad5..28e8d52f6379 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -40,7 +40,6 @@ obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o -obj-$(CONFIG_SENSORS_PKGTEMP) += pkgtemp.o obj-$(CONFIG_SENSORS_DME1737) += dme1737.o obj-$(CONFIG_SENSORS_DS620) += ds620.o obj-$(CONFIG_SENSORS_DS1621) += ds1621.o diff --git a/drivers/hwmon/pkgtemp.c b/drivers/hwmon/pkgtemp.c deleted file mode 100644 index 21c817d98123..000000000000 --- a/drivers/hwmon/pkgtemp.c +++ /dev/null @@ -1,444 +0,0 @@ -/* - * pkgtemp.c - Linux kernel module for processor package hardware monitoring - * - * Copyright (C) 2010 Fenghua Yu - * - * Inspired from many hwmon drivers especially coretemp. - * - * 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; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301 USA. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DRVNAME "pkgtemp" - -enum { SHOW_TEMP, SHOW_TJMAX, SHOW_TTARGET, SHOW_LABEL, SHOW_NAME }; - -/* - * Functions declaration - */ - -static struct pkgtemp_data *pkgtemp_update_device(struct device *dev); - -struct pkgtemp_data { - struct device *hwmon_dev; - struct mutex update_lock; - const char *name; - u32 id; - u16 phys_proc_id; - char valid; /* zero until following fields are valid */ - unsigned long last_updated; /* in jiffies */ - int temp; - int tjmax; - int ttarget; - u8 alarm; -}; - -/* - * Sysfs stuff - */ - -static ssize_t show_name(struct device *dev, struct device_attribute - *devattr, char *buf) -{ - int ret; - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct pkgtemp_data *data = dev_get_drvdata(dev); - - if (attr->index == SHOW_NAME) - ret = sprintf(buf, "%s\n", data->name); - else /* show label */ - ret = sprintf(buf, "physical id %d\n", - data->phys_proc_id); - return ret; -} - -static ssize_t show_alarm(struct device *dev, struct device_attribute - *devattr, char *buf) -{ - struct pkgtemp_data *data = pkgtemp_update_device(dev); - /* read the Out-of-spec log, never clear */ - return sprintf(buf, "%d\n", data->alarm); -} - -static ssize_t show_temp(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct pkgtemp_data *data = pkgtemp_update_device(dev); - int err = 0; - - if (attr->index == SHOW_TEMP) - err = data->valid ? sprintf(buf, "%d\n", data->temp) : -EAGAIN; - else if (attr->index == SHOW_TJMAX) - err = sprintf(buf, "%d\n", data->tjmax); - else - err = sprintf(buf, "%d\n", data->ttarget); - return err; -} - -static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, SHOW_TEMP); -static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp, NULL, SHOW_TJMAX); -static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp, NULL, SHOW_TTARGET); -static DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL); -static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_name, NULL, SHOW_LABEL); -static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, SHOW_NAME); - -static struct attribute *pkgtemp_attributes[] = { - &sensor_dev_attr_name.dev_attr.attr, - &sensor_dev_attr_temp1_label.dev_attr.attr, - &dev_attr_temp1_crit_alarm.attr, - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp1_crit.dev_attr.attr, - NULL -}; - -static const struct attribute_group pkgtemp_group = { - .attrs = pkgtemp_attributes, -}; - -static struct pkgtemp_data *pkgtemp_update_device(struct device *dev) -{ - struct pkgtemp_data *data = dev_get_drvdata(dev); - unsigned int cpu; - int err; - - mutex_lock(&data->update_lock); - - if (!data->valid || time_after(jiffies, data->last_updated + HZ)) { - u32 eax, edx; - - data->valid = 0; - cpu = data->id; - err = rdmsr_on_cpu(cpu, MSR_IA32_PACKAGE_THERM_STATUS, - &eax, &edx); - if (!err) { - data->alarm = (eax >> 5) & 1; - data->temp = data->tjmax - (((eax >> 16) - & 0x7f) * 1000); - data->valid = 1; - } else - dev_dbg(dev, "Temperature data invalid (0x%x)\n", eax); - - data->last_updated = jiffies; - } - - mutex_unlock(&data->update_lock); - return data; -} - -static int get_tjmax(int cpu, struct device *dev) -{ - int default_tjmax = 100000; - int err; - u32 eax, edx; - u32 val; - - /* IA32_TEMPERATURE_TARGET contains the TjMax value */ - err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); - if (!err) { - val = (eax >> 16) & 0xff; - if ((val > 80) && (val < 120)) { - dev_info(dev, "TjMax is %d C.\n", val); - return val * 1000; - } - } - dev_warn(dev, "Unable to read TjMax from CPU.\n"); - return default_tjmax; -} - -static int __devinit pkgtemp_probe(struct platform_device *pdev) -{ - struct pkgtemp_data *data; - int err; - u32 eax, edx; -#ifdef CONFIG_SMP - struct cpuinfo_x86 *c = &cpu_data(pdev->id); -#endif - - data = kzalloc(sizeof(struct pkgtemp_data), GFP_KERNEL); - if (!data) { - err = -ENOMEM; - dev_err(&pdev->dev, "Out of memory\n"); - goto exit; - } - - data->id = pdev->id; -#ifdef CONFIG_SMP - data->phys_proc_id = c->phys_proc_id; -#endif - data->name = "pkgtemp"; - mutex_init(&data->update_lock); - - /* test if we can access the THERM_STATUS MSR */ - err = rdmsr_safe_on_cpu(data->id, MSR_IA32_PACKAGE_THERM_STATUS, - &eax, &edx); - if (err) { - dev_err(&pdev->dev, - "Unable to access THERM_STATUS MSR, giving up\n"); - goto exit_free; - } - - data->tjmax = get_tjmax(data->id, &pdev->dev); - platform_set_drvdata(pdev, data); - - err = rdmsr_safe_on_cpu(data->id, MSR_IA32_TEMPERATURE_TARGET, - &eax, &edx); - if (err) { - dev_warn(&pdev->dev, "Unable to read" - " IA32_TEMPERATURE_TARGET MSR\n"); - } else { - data->ttarget = data->tjmax - (((eax >> 8) & 0xff) * 1000); - err = device_create_file(&pdev->dev, - &sensor_dev_attr_temp1_max.dev_attr); - if (err) - goto exit_free; - } - - err = sysfs_create_group(&pdev->dev.kobj, &pkgtemp_group); - if (err) - goto exit_dev; - - data->hwmon_dev = hwmon_device_register(&pdev->dev); - if (IS_ERR(data->hwmon_dev)) { - err = PTR_ERR(data->hwmon_dev); - dev_err(&pdev->dev, "Class registration failed (%d)\n", - err); - goto exit_class; - } - - return 0; - -exit_class: - sysfs_remove_group(&pdev->dev.kobj, &pkgtemp_group); -exit_dev: - device_remove_file(&pdev->dev, &sensor_dev_attr_temp1_max.dev_attr); -exit_free: - kfree(data); -exit: - return err; -} - -static int __devexit pkgtemp_remove(struct platform_device *pdev) -{ - struct pkgtemp_data *data = platform_get_drvdata(pdev); - - hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&pdev->dev.kobj, &pkgtemp_group); - device_remove_file(&pdev->dev, &sensor_dev_attr_temp1_max.dev_attr); - platform_set_drvdata(pdev, NULL); - kfree(data); - return 0; -} - -static struct platform_driver pkgtemp_driver = { - .driver = { - .owner = THIS_MODULE, - .name = DRVNAME, - }, - .probe = pkgtemp_probe, - .remove = __devexit_p(pkgtemp_remove), -}; - -struct pdev_entry { - struct list_head list; - struct platform_device *pdev; - unsigned int cpu; -#ifdef CONFIG_SMP - u16 phys_proc_id; -#endif -}; - -static LIST_HEAD(pdev_list); -static DEFINE_MUTEX(pdev_list_mutex); - -static int __cpuinit pkgtemp_device_add(unsigned int cpu) -{ - int err; - struct platform_device *pdev; - struct pdev_entry *pdev_entry; - struct cpuinfo_x86 *c = &cpu_data(cpu); - - if (!cpu_has(c, X86_FEATURE_PTS)) - return 0; - - mutex_lock(&pdev_list_mutex); - -#ifdef CONFIG_SMP - /* Only keep the first entry in each package */ - list_for_each_entry(pdev_entry, &pdev_list, list) { - if (c->phys_proc_id == pdev_entry->phys_proc_id) { - err = 0; /* Not an error */ - goto exit; - } - } -#endif - - pdev = platform_device_alloc(DRVNAME, cpu); - if (!pdev) { - err = -ENOMEM; - pr_err("Device allocation failed\n"); - goto exit; - } - - pdev_entry = kzalloc(sizeof(struct pdev_entry), GFP_KERNEL); - if (!pdev_entry) { - err = -ENOMEM; - goto exit_device_put; - } - - err = platform_device_add(pdev); - if (err) { - pr_err("Device addition failed (%d)\n", err); - goto exit_device_free; - } - -#ifdef CONFIG_SMP - pdev_entry->phys_proc_id = c->phys_proc_id; -#endif - pdev_entry->pdev = pdev; - pdev_entry->cpu = cpu; - list_add_tail(&pdev_entry->list, &pdev_list); - mutex_unlock(&pdev_list_mutex); - - return 0; - -exit_device_free: - kfree(pdev_entry); -exit_device_put: - platform_device_put(pdev); -exit: - mutex_unlock(&pdev_list_mutex); - return err; -} - -static void __cpuinit pkgtemp_device_remove(unsigned int cpu) -{ - struct pdev_entry *p; - unsigned int i; - int err; - - mutex_lock(&pdev_list_mutex); - list_for_each_entry(p, &pdev_list, list) { - if (p->cpu != cpu) - continue; - - platform_device_unregister(p->pdev); - list_del(&p->list); - mutex_unlock(&pdev_list_mutex); - kfree(p); - for_each_cpu(i, cpu_core_mask(cpu)) { - if (i != cpu) { - err = pkgtemp_device_add(i); - if (!err) - break; - } - } - return; - } - mutex_unlock(&pdev_list_mutex); -} - -static int __cpuinit pkgtemp_cpu_callback(struct notifier_block *nfb, - unsigned long action, void *hcpu) -{ - unsigned int cpu = (unsigned long) hcpu; - - switch (action) { - case CPU_ONLINE: - case CPU_DOWN_FAILED: - pkgtemp_device_add(cpu); - break; - case CPU_DOWN_PREPARE: - pkgtemp_device_remove(cpu); - break; - } - return NOTIFY_OK; -} - -static struct notifier_block pkgtemp_cpu_notifier __refdata = { - .notifier_call = pkgtemp_cpu_callback, -}; - -static int __init pkgtemp_init(void) -{ - int i, err = -ENODEV; - - /* quick check if we run Intel */ - if (cpu_data(0).x86_vendor != X86_VENDOR_INTEL) - goto exit; - - err = platform_driver_register(&pkgtemp_driver); - if (err) - goto exit; - - for_each_online_cpu(i) - pkgtemp_device_add(i); - -#ifndef CONFIG_HOTPLUG_CPU - if (list_empty(&pdev_list)) { - err = -ENODEV; - goto exit_driver_unreg; - } -#endif - - register_hotcpu_notifier(&pkgtemp_cpu_notifier); - return 0; - -#ifndef CONFIG_HOTPLUG_CPU -exit_driver_unreg: - platform_driver_unregister(&pkgtemp_driver); -#endif -exit: - return err; -} - -static void __exit pkgtemp_exit(void) -{ - struct pdev_entry *p, *n; - - unregister_hotcpu_notifier(&pkgtemp_cpu_notifier); - mutex_lock(&pdev_list_mutex); - list_for_each_entry_safe(p, n, &pdev_list, list) { - platform_device_unregister(p->pdev); - list_del(&p->list); - kfree(p); - } - mutex_unlock(&pdev_list_mutex); - platform_driver_unregister(&pkgtemp_driver); -} - -MODULE_AUTHOR("Fenghua Yu "); -MODULE_DESCRIPTION("Intel processor package temperature monitor"); -MODULE_LICENSE("GPL"); - -module_init(pkgtemp_init) -module_exit(pkgtemp_exit) -- cgit v1.2.3 From 4cc452758fd250bb5968c583d825bb0e68d65db0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 19 May 2011 14:45:48 -0700 Subject: hwmon: (coretemp) Fix checkpatch errors Fix remaining checkpatch errors in the coretemp driver. Signed-off-by: Guenter Roeck Cc: Fenghua Yu Cc: Durgadoss R Acked-by: Jean Delvare --- drivers/hwmon/coretemp.c | 67 ++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 30 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index ece3b7da6538..5c7cd60d5f9d 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -35,9 +35,9 @@ #include #include #include +#include #include #include -#include #define DRVNAME "coretemp" @@ -170,7 +170,7 @@ static ssize_t show_temp(struct device *dev, /* Check whether the data is valid */ if (eax & 0x80000000) { tdata->temp = tdata->tjmax - - (((eax >> 16) & 0x7f) * 1000); + ((eax >> 16) & 0x7f) * 1000; tdata->valid = 1; } tdata->last_updated = jiffies; @@ -193,9 +193,8 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) /* Early chips have no MSR for TjMax */ - if ((c->x86_model == 0xf) && (c->x86_mask < 4)) { + if (c->x86_model == 0xf && c->x86_mask < 4) usemsr_ee = 0; - } /* Atom CPUs */ @@ -214,14 +213,14 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) pci_dev_put(host_bridge); } - if ((c->x86_model > 0xe) && (usemsr_ee)) { + if (c->x86_model > 0xe && usemsr_ee) { u8 platform_id; - /* Now we can detect the mobile CPU using Intel provided table - http://softwarecommunity.intel.com/Wiki/Mobility/720.htm - For Core2 cores, check MSR 0x17, bit 28 1 = Mobile CPU - */ - + /* + * Now we can detect the mobile CPU using Intel provided table + * http://softwarecommunity.intel.com/Wiki/Mobility/720.htm + * For Core2 cores, check MSR 0x17, bit 28 1 = Mobile CPU + */ err = rdmsr_safe_on_cpu(id, 0x17, &eax, &edx); if (err) { dev_warn(dev, @@ -229,20 +228,26 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) " CPU\n"); usemsr_ee = 0; } else if (c->x86_model < 0x17 && !(eax & 0x10000000)) { - /* Trust bit 28 up to Penryn, I could not find any - documentation on that; if you happen to know - someone at Intel please ask */ + /* + * Trust bit 28 up to Penryn, I could not find any + * documentation on that; if you happen to know + * someone at Intel please ask + */ usemsr_ee = 0; } else { /* Platform ID bits 52:50 (EDX starts at bit 32) */ platform_id = (edx >> 18) & 0x7; - /* Mobile Penryn CPU seems to be platform ID 7 or 5 - (guesswork) */ - if ((c->x86_model == 0x17) && - ((platform_id == 5) || (platform_id == 7))) { - /* If MSR EE bit is set, set it to 90 degrees C, - otherwise 105 degrees C */ + /* + * Mobile Penryn CPU seems to be platform ID 7 or 5 + * (guesswork) + */ + if (c->x86_model == 0x17 && + (platform_id == 5 || platform_id == 7)) { + /* + * If MSR EE bit is set, set it to 90 degrees C, + * otherwise 105 degrees C + */ tjmax_ee = 90000; tjmax = 105000; } @@ -250,7 +255,6 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) } if (usemsr_ee) { - err = rdmsr_safe_on_cpu(id, 0xee, &eax, &edx); if (err) { dev_warn(dev, @@ -259,9 +263,11 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) } else if (eax & 0x40000000) { tjmax = tjmax_ee; } - /* if we dont use msr EE it means we are desktop CPU (with exeception - of Atom) */ } else if (tjmax == 100000) { + /* + * If we don't use msr EE it means we are desktop CPU + * (with exeception of Atom) + */ dev_warn(dev, "Using relative temperature scale!\n"); } @@ -275,8 +281,10 @@ static int get_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) u32 eax, edx; u32 val; - /* A new feature of current Intel(R) processors, the - IA32_TEMPERATURE_TARGET contains the TjMax value */ + /* + * A new feature of current Intel(R) processors, the + * IA32_TEMPERATURE_TARGET contains the TjMax value + */ err = rdmsr_safe_on_cpu(id, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); if (err) { dev_warn(dev, "Unable to read TjMax from CPU.\n"); @@ -286,7 +294,7 @@ static int get_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) * If the TjMax is not plausible, an assumption * will be used */ - if ((val > 80) && (val < 120)) { + if (val > 80 && val < 120) { dev_info(dev, "TjMax is %d C.\n", val); return val * 1000; } @@ -331,7 +339,7 @@ static int get_pkg_tjmax(unsigned int cpu, struct device *dev) err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); if (!err) { val = (eax >> 16) & 0xff; - if ((val > 80) && (val < 120)) + if (val > 80 && val < 120) return val * 1000; } dev_warn(dev, "Unable to read Pkg-TjMax from CPU:%u\n", cpu); @@ -399,7 +407,7 @@ static void update_ttarget(__u8 cpu_model, struct temp_data *tdata, * on older CPUs but not in this register, * Atoms don't have it either. */ - if ((cpu_model > 0xe) && (cpu_model != 0x1c)) { + if (cpu_model > 0xe && cpu_model != 0x1c) { err = rdmsr_safe_on_cpu(tdata->cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); if (err) { @@ -407,7 +415,7 @@ static void update_ttarget(__u8 cpu_model, struct temp_data *tdata, "Unable to read IA32_TEMPERATURE_TARGET MSR\n"); } else { tdata->ttarget = tdata->tjmax - - (((eax >> 8) & 0xff) * 1000); + ((eax >> 8) & 0xff) * 1000; } } } @@ -423,7 +431,7 @@ static int chk_ucode_version(struct platform_device *pdev) * Readings might stop update when processor visited too deep sleep, * fixed for stepping D0 (6EC). */ - if ((c->x86_model == 0xe) && (c->x86_mask < 0xc)) { + if (c->x86_model == 0xe && c->x86_mask < 0xc) { /* check for microcode update */ err = smp_call_function_single(pdev->id, get_ucode_rev_on_cpu, &edx, 1); @@ -791,7 +799,6 @@ static struct notifier_block coretemp_cpu_notifier __refdata = { .notifier_call = coretemp_cpu_callback, }; - static int __init coretemp_init(void) { int i, err = -ENODEV; -- cgit v1.2.3