From b6757042200f8c876be99213712f4a101334e203 Mon Sep 17 00:00:00 2001 From: Andrea Gelmini Date: Sat, 21 May 2016 13:41:00 +0200 Subject: hwmon: (abituguru) Fix typos in documentation Signed-off-by: Andrea Gelmini [groeck: Updated subject] Signed-off-by: Guenter Roeck diff --git a/Documentation/hwmon/abituguru b/Documentation/hwmon/abituguru index f1d4fe4..44013d2 100644 --- a/Documentation/hwmon/abituguru +++ b/Documentation/hwmon/abituguru @@ -24,7 +24,7 @@ Supported chips: AW9D-MAX) (2) 1) For revisions 2 and 3 uGuru's the driver can autodetect the sensortype (Volt or Temp) for bank1 sensors, for revision 1 uGuru's - this doesnot always work. For these uGuru's the autodection can + this does not always work. For these uGuru's the autodetection can be overridden with the bank1_types module param. For all 3 known revison 1 motherboards the correct use of this param is: bank1_types=1,1,0,0,0,0,0,2,0,0,0,0,2,0,0,1 -- cgit v0.10.2 From 76b5808e8cb9d8be214a1bbf613e19b6e3ebb3fb Mon Sep 17 00:00:00 2001 From: Andrea Gelmini Date: Sat, 21 May 2016 13:41:07 +0200 Subject: hwmon: (max1668) Fix typo in documentation Signed-off-by: Andrea Gelmini [groeck: Updated subject] Signed-off-by: Guenter Roeck diff --git a/Documentation/hwmon/max1668 b/Documentation/hwmon/max1668 index 0616ed9..8f9d570 100644 --- a/Documentation/hwmon/max1668 +++ b/Documentation/hwmon/max1668 @@ -17,7 +17,7 @@ This driver implements support for the Maxim MAX1668, MAX1805 and MAX1989 chips. The three devices are very similar, but the MAX1805 has a reduced feature -set; only two remote temperature inputs vs the four avaible on the other +set; only two remote temperature inputs vs the four available on the other two ICs. The driver is able to distinguish between the devices and creates sysfs -- cgit v0.10.2 From 7c84f7f80d6fcea36246b793d06c3555ca53ddcd Mon Sep 17 00:00:00 2001 From: David Frey Date: Thu, 2 Jun 2016 09:59:11 +0200 Subject: hwmon: add support for Sensirion SHT3x sensors This driver implements support for the Sensirion SHT3x-DIS chip, a humidity and temperature sensor. Temperature is measured in degrees celsius, relative humidity is expressed as a percentage. In the sysfs interface, all values are scaled by 1000, i.e. the value for 31.5 degrees celsius is 31500. Signed-off-by: Pascal Sachs [groeck: Fixed 'Variable length array is used' gcc warning] Signed-off-by: Guenter Roeck diff --git a/Documentation/hwmon/sht3x b/Documentation/hwmon/sht3x new file mode 100644 index 0000000..f5aa633 --- /dev/null +++ b/Documentation/hwmon/sht3x @@ -0,0 +1,72 @@ +Kernel driver sht3x +=================== + +Supported chips: + * Sensirion SHT3x-DIS + Prefix: 'sht3x' + Addresses scanned: none + Datasheet: http://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/Humidity/Sensirion_Humidity_Datasheet_SHT3x_DIS.pdf + +Author: + David Frey + Pascal Sachs + +Description +----------- + +This driver implements support for the Sensirion SHT3x-DIS chip, a humidity +and temperature sensor. Temperature is measured in degrees celsius, relative +humidity is expressed as a percentage. In the sysfs interface, all values are +scaled by 1000, i.e. the value for 31.5 degrees celsius is 31500. + +The device communicates with the I2C protocol. Sensors can have the I2C +addresses 0x44 or 0x45, depending on the wiring. See +Documentation/i2c/instantiating-devices for methods to instantiate the device. + +There are two options configurable by means of sht3x_platform_data: +1. blocking (pull the I2C clock line down while performing the measurement) or + non-blocking mode. Blocking mode will guarantee the fastest result but + the I2C bus will be busy during that time. By default, non-blocking mode + is used. Make sure clock-stretching works properly on your device if you + want to use blocking mode. +2. high or low accuracy. High accuracy is used by default and using it is + strongly recommended. + +The sht3x sensor supports a single shot mode as well as 5 periodic measure +modes, which can be controlled with the update_interval sysfs interface. +The allowed update_interval in milliseconds are as follows: + * 0 single shot mode + * 2000 0.5 Hz periodic measurement + * 1000 1 Hz periodic measurement + * 500 2 Hz periodic measurement + * 250 4 Hz periodic measurement + * 100 10 Hz periodic measurement + +In the periodic measure mode, the sensor automatically triggers a measurement +with the configured update interval on the chip. When a temperature or humidity +reading exceeds the configured limits, the alert attribute is set to 1 and +the alert pin on the sensor is set to high. +When the temperature and humidity readings move back between the hysteresis +values, the alert bit is set to 0 and the alert pin on the sensor is set to +low. + +sysfs-Interface +--------------- + +temp1_input: temperature input +humidity1_input: humidity input +temp1_max: temperature max value +temp1_max_hyst: temperature hysteresis value for max limit +humidity1_max: humidity max value +humidity1_max_hyst: humidity hysteresis value for max limit +temp1_min: temperature min value +temp1_min_hyst: temperature hysteresis value for min limit +humidity1_min: humidity min value +humidity1_min_hyst: humidity hysteresis value for min limit +temp1_alarm: alarm flag is set to 1 if the temperature is outside the + configured limits. Alarm only works in periodic measure mode +humidity1_alarm: alarm flag is set to 1 if the humidity is outside the + configured limits. Alarm only works in periodic measure mode +update_interval: update interval, 0 for single shot, interval in msec + for periodic measurement. If the interval is not supported + by the sensor, the next faster interval is chosen diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index ff94007..e0be99a 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1265,6 +1265,17 @@ config SENSORS_SHT21 This driver can also be built as a module. If so, the module will be called sht21. +config SENSORS_SHT3x + tristate "Sensiron humidity and temperature sensors. SHT3x and compat." + depends on I2C + select CRC8 + help + If you say yes here you get support for the Sensiron SHT30 and SHT31 + humidity and temperature sensors. + + This driver can also be built as a module. If so, the module + will be called sht3x. + config SENSORS_SHTC1 tristate "Sensiron humidity and temperature sensors. SHTC1 and compat." depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 2ef5b7c..406b18b 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -138,6 +138,7 @@ obj-$(CONFIG_SENSORS_SCH5627) += sch5627.o obj-$(CONFIG_SENSORS_SCH5636) += sch5636.o obj-$(CONFIG_SENSORS_SHT15) += sht15.o obj-$(CONFIG_SENSORS_SHT21) += sht21.o +obj-$(CONFIG_SENSORS_SHT3x) += sht3x.o obj-$(CONFIG_SENSORS_SHTC1) += shtc1.o obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o obj-$(CONFIG_SENSORS_SMM665) += smm665.o diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c new file mode 100644 index 0000000..707a3f8 --- /dev/null +++ b/drivers/hwmon/sht3x.c @@ -0,0 +1,725 @@ +/* Sensirion SHT3x-DIS humidity and temperature sensor driver. + * The SHT3x comes in many different versions, this driver is for the + * I2C version only. + * + * Copyright (C) 2016 Sensirion AG, Switzerland + * Author: David Frey + * Author: Pascal Sachs + * + * 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 +#include +#include +#include +#include +#include +#include + +/* commands (high precision mode) */ +static const unsigned char sht3x_cmd_measure_blocking_hpm[] = { 0x2c, 0x06 }; +static const unsigned char sht3x_cmd_measure_nonblocking_hpm[] = { 0x24, 0x00 }; + +/* commands (low power mode) */ +static const unsigned char sht3x_cmd_measure_blocking_lpm[] = { 0x2c, 0x10 }; +static const unsigned char sht3x_cmd_measure_nonblocking_lpm[] = { 0x24, 0x16 }; + +/* commands for periodic mode */ +static const unsigned char sht3x_cmd_measure_periodic_mode[] = { 0xe0, 0x00 }; +static const unsigned char sht3x_cmd_break[] = { 0x30, 0x93 }; + +/* other commands */ +static const unsigned char sht3x_cmd_read_status_reg[] = { 0xf3, 0x2d }; +static const unsigned char sht3x_cmd_clear_status_reg[] = { 0x30, 0x41 }; + +/* delays for non-blocking i2c commands, both in us */ +#define SHT3X_NONBLOCKING_WAIT_TIME_HPM 15000 +#define SHT3X_NONBLOCKING_WAIT_TIME_LPM 4000 + +#define SHT3X_WORD_LEN 2 +#define SHT3X_CMD_LENGTH 2 +#define SHT3X_CRC8_LEN 1 +#define SHT3X_RESPONSE_LENGTH 6 +#define SHT3X_CRC8_POLYNOMIAL 0x31 +#define SHT3X_CRC8_INIT 0xFF +#define SHT3X_MIN_TEMPERATURE -45000 +#define SHT3X_MAX_TEMPERATURE 130000 +#define SHT3X_MIN_HUMIDITY 0 +#define SHT3X_MAX_HUMIDITY 100000 + +enum sht3x_chips { + sht3x, + sts3x, +}; + +enum sht3x_limits { + limit_max = 0, + limit_max_hyst, + limit_min, + limit_min_hyst, +}; + +DECLARE_CRC8_TABLE(sht3x_crc8_table); + +/* periodic measure commands (high precision mode) */ +static const char periodic_measure_commands_hpm[][SHT3X_CMD_LENGTH] = { + /* 0.5 measurements per second */ + {0x20, 0x32}, + /* 1 measurements per second */ + {0x21, 0x30}, + /* 2 measurements per second */ + {0x22, 0x36}, + /* 4 measurements per second */ + {0x23, 0x34}, + /* 10 measurements per second */ + {0x27, 0x37}, +}; + +/* periodic measure commands (low power mode) */ +static const char periodic_measure_commands_lpm[][SHT3X_CMD_LENGTH] = { + /* 0.5 measurements per second */ + {0x20, 0x2f}, + /* 1 measurements per second */ + {0x21, 0x2d}, + /* 2 measurements per second */ + {0x22, 0x2b}, + /* 4 measurements per second */ + {0x23, 0x29}, + /* 10 measurements per second */ + {0x27, 0x2a}, +}; + +struct sht3x_limit_commands { + const char read_command[SHT3X_CMD_LENGTH]; + const char write_command[SHT3X_CMD_LENGTH]; +}; + +static const struct sht3x_limit_commands limit_commands[] = { + /* temp1_max, humidity1_max */ + [limit_max] = { {0xe1, 0x1f}, {0x61, 0x1d} }, + /* temp_1_max_hyst, humidity1_max_hyst */ + [limit_max_hyst] = { {0xe1, 0x14}, {0x61, 0x16} }, + /* temp1_min, humidity1_min */ + [limit_min] = { {0xe1, 0x02}, {0x61, 0x00} }, + /* temp_1_min_hyst, humidity1_min_hyst */ + [limit_min_hyst] = { {0xe1, 0x09}, {0x61, 0x0B} }, +}; + +#define SHT3X_NUM_LIMIT_CMD ARRAY_SIZE(limit_commands) + +static const u16 mode_to_update_interval[] = { + 0, + 2000, + 1000, + 500, + 250, + 100, +}; + +struct sht3x_data { + struct i2c_client *client; + struct mutex i2c_lock; /* lock for sending i2c commands */ + struct mutex data_lock; /* lock for updating driver data */ + + u8 mode; + const unsigned char *command; + u32 wait_time; /* in us*/ + unsigned long last_update; /* last update in periodic mode*/ + + struct sht3x_platform_data setup; + + /* + * cached values for temperature and humidity and limits + * the limits arrays have the following order: + * max, max_hyst, min, min_hyst + */ + int temperature; + int temperature_limits[SHT3X_NUM_LIMIT_CMD]; + u32 humidity; + u32 humidity_limits[SHT3X_NUM_LIMIT_CMD]; +}; + +static u8 get_mode_from_update_interval(u16 value) +{ + size_t index; + u8 number_of_modes = ARRAY_SIZE(mode_to_update_interval); + + if (value == 0) + return 0; + + /* find next faster update interval */ + for (index = 1; index < number_of_modes; index++) { + if (mode_to_update_interval[index] <= value) + return index; + } + + return number_of_modes - 1; +} + +static int sht3x_read_from_command(struct i2c_client *client, + struct sht3x_data *data, + const char *command, + char *buf, int length, u32 wait_time) +{ + int ret; + + mutex_lock(&data->i2c_lock); + ret = i2c_master_send(client, command, SHT3X_CMD_LENGTH); + + if (ret != SHT3X_CMD_LENGTH) { + ret = ret < 0 ? ret : -EIO; + goto out; + } + + if (wait_time) + usleep_range(wait_time, wait_time + 1000); + + ret = i2c_master_recv(client, buf, length); + if (ret != length) { + ret = ret < 0 ? ret : -EIO; + goto out; + } + + ret = 0; +out: + mutex_unlock(&data->i2c_lock); + return ret; +} + +static int sht3x_extract_temperature(u16 raw) +{ + /* + * From datasheet: + * T = -45 + 175 * ST / 2^16 + * Adapted for integer fixed point (3 digit) arithmetic. + */ + return ((21875 * (int)raw) >> 13) - 45000; +} + +static u32 sht3x_extract_humidity(u16 raw) +{ + /* + * From datasheet: + * RH = 100 * SRH / 2^16 + * Adapted for integer fixed point (3 digit) arithmetic. + */ + return (12500 * (u32)raw) >> 13; +} + +static struct sht3x_data *sht3x_update_client(struct device *dev) +{ + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u16 interval_ms = mode_to_update_interval[data->mode]; + unsigned long interval_jiffies = msecs_to_jiffies(interval_ms); + unsigned char buf[SHT3X_RESPONSE_LENGTH]; + u16 val; + int ret = 0; + + mutex_lock(&data->data_lock); + /* + * Only update cached readings once per update interval in periodic + * mode. In single shot mode the sensor measures values on demand, so + * every time the sysfs interface is called, a measurement is triggered. + * In periodic mode however, the measurement process is handled + * internally by the sensor and reading out sensor values only makes + * sense if a new reading is available. + */ + if (time_after(jiffies, data->last_update + interval_jiffies)) { + ret = sht3x_read_from_command(client, data, data->command, buf, + sizeof(buf), data->wait_time); + if (ret) + goto out; + + val = be16_to_cpup((__be16 *)buf); + data->temperature = sht3x_extract_temperature(val); + val = be16_to_cpup((__be16 *)(buf + 3)); + data->humidity = sht3x_extract_humidity(val); + data->last_update = jiffies; + } + +out: + mutex_unlock(&data->data_lock); + if (ret) + return ERR_PTR(ret); + + return data; +} + +/* sysfs attributes */ +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sht3x_data *data = sht3x_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->temperature); +} + +static ssize_t humidity1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sht3x_data *data = sht3x_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%u\n", data->humidity); +} + +/* + * limits_update must only be called from probe or with data_lock held + */ +static int limits_update(struct sht3x_data *data) +{ + int ret; + u8 index; + int temperature; + u32 humidity; + u16 raw; + char buffer[SHT3X_RESPONSE_LENGTH]; + const struct sht3x_limit_commands *commands; + struct i2c_client *client = data->client; + + for (index = 0; index < SHT3X_NUM_LIMIT_CMD; index++) { + commands = &limit_commands[index]; + ret = sht3x_read_from_command(client, data, + commands->read_command, buffer, + SHT3X_RESPONSE_LENGTH, 0); + + if (ret) + return ret; + + raw = be16_to_cpup((__be16 *)buffer); + temperature = sht3x_extract_temperature((raw & 0x01ff) << 7); + humidity = sht3x_extract_humidity(raw & 0xfe00); + data->temperature_limits[index] = temperature; + data->humidity_limits[index] = humidity; + } + + return ret; +} + +static ssize_t temp1_limit_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sht3x_data *data = dev_get_drvdata(dev); + u8 index = to_sensor_dev_attr(attr)->index; + int temperature_limit = data->temperature_limits[index]; + + return scnprintf(buf, PAGE_SIZE, "%d\n", temperature_limit); +} + +static ssize_t humidity1_limit_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sht3x_data *data = dev_get_drvdata(dev); + u8 index = to_sensor_dev_attr(attr)->index; + u32 humidity_limit = data->humidity_limits[index]; + + return scnprintf(buf, PAGE_SIZE, "%u\n", humidity_limit); +} + +/* + * limit_store must only be called with data_lock held + */ +static size_t limit_store(struct device *dev, + size_t count, + u8 index, + int temperature, + u32 humidity) +{ + char buffer[SHT3X_CMD_LENGTH + SHT3X_WORD_LEN + SHT3X_CRC8_LEN]; + char *position = buffer; + int ret; + u16 raw; + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + const struct sht3x_limit_commands *commands; + + commands = &limit_commands[index]; + + memcpy(position, commands->write_command, SHT3X_CMD_LENGTH); + position += SHT3X_CMD_LENGTH; + /* + * ST = (T + 45) / 175 * 2^16 + * SRH = RH / 100 * 2^16 + * adapted for fixed point arithmetic and packed the same as + * in limit_show() + */ + raw = ((u32)(temperature + 45000) * 24543) >> (16 + 7); + raw |= ((humidity * 42950) >> 16) & 0xfe00; + + *((__be16 *)position) = cpu_to_be16(raw); + position += SHT3X_WORD_LEN; + *position = crc8(sht3x_crc8_table, + position - SHT3X_WORD_LEN, + SHT3X_WORD_LEN, + SHT3X_CRC8_INIT); + + mutex_lock(&data->i2c_lock); + ret = i2c_master_send(client, buffer, sizeof(buffer)); + mutex_unlock(&data->i2c_lock); + + if (ret != sizeof(buffer)) + return ret < 0 ? ret : -EIO; + + data->temperature_limits[index] = temperature; + data->humidity_limits[index] = humidity; + return count; +} + +static ssize_t temp1_limit_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int temperature; + int ret; + struct sht3x_data *data = dev_get_drvdata(dev); + u8 index = to_sensor_dev_attr(attr)->index; + + ret = kstrtoint(buf, 0, &temperature); + if (ret) + return ret; + + temperature = clamp_val(temperature, SHT3X_MIN_TEMPERATURE, + SHT3X_MAX_TEMPERATURE); + mutex_lock(&data->data_lock); + ret = limit_store(dev, count, index, temperature, + data->humidity_limits[index]); + mutex_unlock(&data->data_lock); + + return ret; +} + +static ssize_t humidity1_limit_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + u32 humidity; + int ret; + struct sht3x_data *data = dev_get_drvdata(dev); + u8 index = to_sensor_dev_attr(attr)->index; + + ret = kstrtou32(buf, 0, &humidity); + if (ret) + return ret; + + humidity = clamp_val(humidity, SHT3X_MIN_HUMIDITY, SHT3X_MAX_HUMIDITY); + mutex_lock(&data->data_lock); + ret = limit_store(dev, count, index, data->temperature_limits[index], + humidity); + mutex_unlock(&data->data_lock); + + return ret; +} + +static void sht3x_select_command(struct sht3x_data *data) +{ + /* + * In blocking mode (clock stretching mode) the I2C bus + * is blocked for other traffic, thus the call to i2c_master_recv() + * will wait until the data is ready. For non blocking mode, we + * have to wait ourselves. + */ + if (data->mode > 0) { + data->command = sht3x_cmd_measure_periodic_mode; + data->wait_time = 0; + } else if (data->setup.blocking_io) { + data->command = data->setup.high_precision ? + sht3x_cmd_measure_blocking_hpm : + sht3x_cmd_measure_blocking_lpm; + data->wait_time = 0; + } else { + if (data->setup.high_precision) { + data->command = sht3x_cmd_measure_nonblocking_hpm; + data->wait_time = SHT3X_NONBLOCKING_WAIT_TIME_HPM; + } else { + data->command = sht3x_cmd_measure_nonblocking_lpm; + data->wait_time = SHT3X_NONBLOCKING_WAIT_TIME_LPM; + } + } +} + +static int status_register_read(struct device *dev, + struct device_attribute *attr, + char *buffer, int length) +{ + int ret; + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + ret = sht3x_read_from_command(client, data, sht3x_cmd_read_status_reg, + buffer, length, 0); + + return ret; +} + +static ssize_t temp1_alarm_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + char buffer[SHT3X_WORD_LEN + SHT3X_CRC8_LEN]; + int ret; + + ret = status_register_read(dev, attr, buffer, + SHT3X_WORD_LEN + SHT3X_CRC8_LEN); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%d\n", !!(buffer[0] & 0x04)); +} + +static ssize_t humidity1_alarm_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + char buffer[SHT3X_WORD_LEN + SHT3X_CRC8_LEN]; + int ret; + + ret = status_register_read(dev, attr, buffer, + SHT3X_WORD_LEN + SHT3X_CRC8_LEN); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%d\n", !!(buffer[0] & 0x08)); +} + +static ssize_t update_interval_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sht3x_data *data = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%u\n", + mode_to_update_interval[data->mode]); +} + +static ssize_t update_interval_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + u16 update_interval; + u8 mode; + int ret; + const char *command; + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + ret = kstrtou16(buf, 0, &update_interval); + if (ret) + return ret; + + mode = get_mode_from_update_interval(update_interval); + + mutex_lock(&data->data_lock); + /* mode did not change */ + if (mode == data->mode) { + mutex_unlock(&data->data_lock); + return count; + } + + mutex_lock(&data->i2c_lock); + /* + * Abort periodic measure mode. + * To do any changes to the configuration while in periodic mode, we + * have to send a break command to the sensor, which then falls back + * to single shot (mode = 0). + */ + if (data->mode > 0) { + ret = i2c_master_send(client, sht3x_cmd_break, + SHT3X_CMD_LENGTH); + if (ret != SHT3X_CMD_LENGTH) + goto out; + data->mode = 0; + } + + if (mode > 0) { + if (data->setup.high_precision) + command = periodic_measure_commands_hpm[mode - 1]; + else + command = periodic_measure_commands_lpm[mode - 1]; + + /* select mode */ + ret = i2c_master_send(client, command, SHT3X_CMD_LENGTH); + if (ret != SHT3X_CMD_LENGTH) + goto out; + } + + /* select mode and command */ + data->mode = mode; + sht3x_select_command(data); + +out: + mutex_unlock(&data->i2c_lock); + mutex_unlock(&data->data_lock); + if (ret != SHT3X_CMD_LENGTH) + return ret < 0 ? ret : -EIO; + + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, temp1_input_show, NULL, 0); +static SENSOR_DEVICE_ATTR(humidity1_input, S_IRUGO, humidity1_input_show, + NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, + temp1_limit_show, temp1_limit_store, + limit_max); +static SENSOR_DEVICE_ATTR(humidity1_max, S_IRUGO | S_IWUSR, + humidity1_limit_show, humidity1_limit_store, + limit_max); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO | S_IWUSR, + temp1_limit_show, temp1_limit_store, + limit_max_hyst); +static SENSOR_DEVICE_ATTR(humidity1_max_hyst, S_IRUGO | S_IWUSR, + humidity1_limit_show, humidity1_limit_store, + limit_max_hyst); +static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, + temp1_limit_show, temp1_limit_store, + limit_min); +static SENSOR_DEVICE_ATTR(humidity1_min, S_IRUGO | S_IWUSR, + humidity1_limit_show, humidity1_limit_store, + limit_min); +static SENSOR_DEVICE_ATTR(temp1_min_hyst, S_IRUGO | S_IWUSR, + temp1_limit_show, temp1_limit_store, + limit_min_hyst); +static SENSOR_DEVICE_ATTR(humidity1_min_hyst, S_IRUGO | S_IWUSR, + humidity1_limit_show, humidity1_limit_store, + limit_min_hyst); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, temp1_alarm_show, NULL, 0); +static SENSOR_DEVICE_ATTR(humidity1_alarm, S_IRUGO, humidity1_alarm_show, + NULL, 0); +static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR, + update_interval_show, update_interval_store, 0); + +static struct attribute *sht3x_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_humidity1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_humidity1_max.dev_attr.attr, + &sensor_dev_attr_humidity1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_min_hyst.dev_attr.attr, + &sensor_dev_attr_humidity1_min.dev_attr.attr, + &sensor_dev_attr_humidity1_min_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_humidity1_alarm.dev_attr.attr, + &sensor_dev_attr_update_interval.dev_attr.attr, + NULL +}; + +static struct attribute *sts3x_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(sht3x); +ATTRIBUTE_GROUPS(sts3x); + +static int sht3x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct sht3x_data *data; + struct device *hwmon_dev; + struct i2c_adapter *adap = client->adapter; + struct device *dev = &client->dev; + const struct attribute_group **attribute_groups; + + /* + * we require full i2c support since the sht3x uses multi-byte read and + * writes as well as multi-byte commands which are not supported by + * the smbus protocol + */ + if (!i2c_check_functionality(adap, I2C_FUNC_I2C)) + return -ENODEV; + + ret = i2c_master_send(client, sht3x_cmd_clear_status_reg, + SHT3X_CMD_LENGTH); + if (ret != SHT3X_CMD_LENGTH) + return ret < 0 ? ret : -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->setup.blocking_io = false; + data->setup.high_precision = true; + data->mode = 0; + data->last_update = 0; + data->client = client; + crc8_populate_msb(sht3x_crc8_table, SHT3X_CRC8_POLYNOMIAL); + + if (client->dev.platform_data) + data->setup = *(struct sht3x_platform_data *)dev->platform_data; + + sht3x_select_command(data); + + mutex_init(&data->i2c_lock); + mutex_init(&data->data_lock); + + ret = limits_update(data); + if (ret) + return ret; + + if (id->driver_data == sts3x) + attribute_groups = sts3x_groups; + else + attribute_groups = sht3x_groups; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + client->name, + data, + attribute_groups); + + if (IS_ERR(hwmon_dev)) + dev_dbg(dev, "unable to register hwmon device\n"); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +/* device ID table */ +static const struct i2c_device_id sht3x_ids[] = { + {"sht3x", sht3x}, + {"sts3x", sts3x}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, sht3x_ids); + +static struct i2c_driver sht3x_i2c_driver = { + .driver.name = "sht3x", + .probe = sht3x_probe, + .id_table = sht3x_ids, +}; + +module_i2c_driver(sht3x_i2c_driver); + +MODULE_AUTHOR("David Frey "); +MODULE_AUTHOR("Pascal Sachs "); +MODULE_DESCRIPTION("Sensirion SHT3x humidity and temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/sht3x.h b/include/linux/platform_data/sht3x.h new file mode 100644 index 0000000..2e5eea3 --- /dev/null +++ b/include/linux/platform_data/sht3x.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2016 Sensirion AG, Switzerland + * Author: David Frey + * Author: Pascal Sachs + * + * 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. + * + */ + +#ifndef __SHT3X_H_ +#define __SHT3X_H_ + +struct sht3x_platform_data { + bool blocking_io; + bool high_precision; +}; +#endif /* __SHT3X_H_ */ -- cgit v0.10.2 From c0a68601804dcb4ee8a141e42e1e6893b6b0610c Mon Sep 17 00:00:00 2001 From: "Andrew F. Davis" Date: Wed, 8 Jun 2016 12:00:54 -0500 Subject: hwmon: (tmp401) Add support for TI TMP461 Signed-off-by: Andrew F. Davis Signed-off-by: Guenter Roeck diff --git a/Documentation/hwmon/tmp401 b/Documentation/hwmon/tmp401 index 711f75e..2d9ca42 100644 --- a/Documentation/hwmon/tmp401 +++ b/Documentation/hwmon/tmp401 @@ -22,6 +22,9 @@ Supported chips: Prefix: 'tmp435' Addresses scanned: I2C 0x48 - 0x4f Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp435.html + * Texas Instruments TMP461 + Prefix: 'tmp461' + Datasheet: http://www.ti.com/product/tmp461 Authors: Hans de Goede @@ -31,8 +34,8 @@ Description ----------- This driver implements support for Texas Instruments TMP401, TMP411, -TMP431, TMP432 and TMP435 chips. These chips implement one or two remote -and one local temperature sensors. Temperature is measured in degrees +TMP431, TMP432, TMP435, and TMP461 chips. These chips implement one or two +remote and one local temperature sensors. Temperature is measured in degrees Celsius. Resolution of the remote sensor is 0.0625 degree. Local sensor resolution can be set to 0.5, 0.25, 0.125 or 0.0625 degree (not supported by the driver so far, so using the default resolution of 0.5 @@ -55,3 +58,10 @@ some additional features. TMP432 is compatible with TMP401 and TMP431. It supports two external temperature sensors. + +TMP461 is compatible with TMP401. It supports offset correction +that is applied to the remote sensor. + +* Sensor offset values are temperature values + + Exported via sysfs attribute tempX_offset diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index e0be99a..be63b14 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1572,7 +1572,7 @@ config SENSORS_TMP401 depends on I2C help If you say yes here you get support for Texas Instruments TMP401, - TMP411, TMP431, TMP432 and TMP435 temperature sensor chips. + TMP411, TMP431, TMP432, TMP435, and TMP461 temperature sensor chips. This driver can also be built as a module. If so, the module will be called tmp401. diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index ccf4cff..eeeed2c 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -47,7 +47,7 @@ static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4c, 0x4d, 0x4e, 0x4f, I2C_CLIENT_END }; -enum chips { tmp401, tmp411, tmp431, tmp432, tmp435 }; +enum chips { tmp401, tmp411, tmp431, tmp432, tmp435, tmp461 }; /* * The TMP401 registers, note some registers have different addresses for @@ -62,31 +62,34 @@ enum chips { tmp401, tmp411, tmp431, tmp432, tmp435 }; #define TMP401_MANUFACTURER_ID_REG 0xFE #define TMP401_DEVICE_ID_REG 0xFF -static const u8 TMP401_TEMP_MSB_READ[6][2] = { +static const u8 TMP401_TEMP_MSB_READ[7][2] = { { 0x00, 0x01 }, /* temp */ { 0x06, 0x08 }, /* low limit */ { 0x05, 0x07 }, /* high limit */ { 0x20, 0x19 }, /* therm (crit) limit */ { 0x30, 0x34 }, /* lowest */ { 0x32, 0x36 }, /* highest */ + { 0, 0x11 }, /* offset */ }; -static const u8 TMP401_TEMP_MSB_WRITE[6][2] = { +static const u8 TMP401_TEMP_MSB_WRITE[7][2] = { { 0, 0 }, /* temp (unused) */ { 0x0C, 0x0E }, /* low limit */ { 0x0B, 0x0D }, /* high limit */ { 0x20, 0x19 }, /* therm (crit) limit */ { 0x30, 0x34 }, /* lowest */ { 0x32, 0x36 }, /* highest */ + { 0, 0x11 }, /* offset */ }; -static const u8 TMP401_TEMP_LSB[6][2] = { +static const u8 TMP401_TEMP_LSB[7][2] = { { 0x15, 0x10 }, /* temp */ { 0x17, 0x14 }, /* low limit */ { 0x16, 0x13 }, /* high limit */ { 0, 0 }, /* therm (crit) limit (unused) */ { 0x31, 0x35 }, /* lowest */ { 0x33, 0x37 }, /* highest */ + { 0, 0x12 }, /* offset */ }; static const u8 TMP432_TEMP_MSB_READ[4][3] = { @@ -149,6 +152,7 @@ static const struct i2c_device_id tmp401_id[] = { { "tmp431", tmp431 }, { "tmp432", tmp432 }, { "tmp435", tmp435 }, + { "tmp461", tmp461 }, { } }; MODULE_DEVICE_TABLE(i2c, tmp401_id); @@ -170,7 +174,7 @@ struct tmp401_data { /* register values */ u8 status[4]; u8 config; - u16 temp[6][3]; + u16 temp[7][3]; u8 temp_crit_hyst; }; @@ -613,6 +617,22 @@ static const struct attribute_group tmp432_group = { }; /* + * Additional features of the TMP461 chip. + * The TMP461 temperature offset for the remote channel. + */ +static SENSOR_DEVICE_ATTR_2(temp2_offset, S_IWUSR | S_IRUGO, show_temp, + store_temp, 6, 1); + +static struct attribute *tmp461_attributes[] = { + &sensor_dev_attr_temp2_offset.dev_attr.attr, + NULL +}; + +static const struct attribute_group tmp461_group = { + .attrs = tmp461_attributes, +}; + +/* * Begin non sysfs callback code (aka Real code) */ @@ -714,7 +734,7 @@ static int tmp401_probe(struct i2c_client *client, const struct i2c_device_id *id) { static const char * const names[] = { - "TMP401", "TMP411", "TMP431", "TMP432", "TMP435" + "TMP401", "TMP411", "TMP431", "TMP432", "TMP435", "TMP461" }; struct device *dev = &client->dev; struct device *hwmon_dev; @@ -745,6 +765,9 @@ static int tmp401_probe(struct i2c_client *client, if (data->kind == tmp432) data->groups[groups++] = &tmp432_group; + if (data->kind == tmp461) + data->groups[groups++] = &tmp461_group; + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, data, data->groups); if (IS_ERR(hwmon_dev)) -- cgit v0.10.2 From 7cb6dcff1956ec9e338abfa2f298d2971cfbab79 Mon Sep 17 00:00:00 2001 From: "Andrew F. Davis" Date: Fri, 10 Jun 2016 10:32:33 -0500 Subject: hwmon: Add support for INA3221 Triple Current/Voltage Monitors Add support for the the INA3221 26v capable, Triple channel, Bi-Directional, Zero-Drift, Low-/High-Side, Current/Voltage Monitor with I2C interface. Signed-off-by: Andrew F. Davis Signed-off-by: Guenter Roeck diff --git a/Documentation/hwmon/ina3221 b/Documentation/hwmon/ina3221 new file mode 100644 index 0000000..0ff7485 --- /dev/null +++ b/Documentation/hwmon/ina3221 @@ -0,0 +1,35 @@ +Kernel driver ina3221 +===================== + +Supported chips: + * Texas Instruments INA3221 + Prefix: 'ina3221' + Addresses: I2C 0x40 - 0x43 + Datasheet: Publicly available at the Texas Instruments website + http://www.ti.com/ + +Author: Andrew F. Davis + +Description +----------- + +The Texas Instruments INA3221 monitors voltage, current, and power on the high +side of up to three D.C. power supplies. The INA3221 monitors both shunt drop +and supply voltage, with programmable conversion times and averaging, current +and power are calculated host-side from these. + +Sysfs entries +------------- + +in[123]_input Bus voltage(mV) channels +curr[123]_input Current(mA) measurement channels +shunt[123]_resistor Shunt resistance(uOhm) channels +curr[123]_crit Critical alert current(mA) setting, activates the + corresponding alarm when the respective current + is above this value +curr[123]_crit_alarm Critical alert current limit exceeded +curr[123]_max Warning alert current(mA) setting, activates the + corresponding alarm when the respective current + average is above this value. +curr[123]_max_alarm Warning alert current limit exceeded +in[456]_input Shunt voltage(uV) for channels 1, 2, and 3 respectively diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index be63b14..b2aa13b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1525,6 +1525,17 @@ config SENSORS_INA2XX This driver can also be built as a module. If so, the module will be called ina2xx. +config SENSORS_INA3221 + tristate "Texas Instruments INA3221 Triple Power Monitor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the TI INA3221 Triple Power + Monitor. + + This driver can also be built as a module. If so, the module + will be called ina3221. + config SENSORS_TC74 tristate "Microchip TC74" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 406b18b..446a4e7 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -77,6 +77,7 @@ obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o obj-$(CONFIG_SENSORS_INA209) += ina209.o obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o +obj-$(CONFIG_SENSORS_INA3221) += ina3221.o obj-$(CONFIG_SENSORS_IT87) += it87.o obj-$(CONFIG_SENSORS_JC42) += jc42.o obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o diff --git a/drivers/hwmon/ina3221.c b/drivers/hwmon/ina3221.c new file mode 100644 index 0000000..d055b6a --- /dev/null +++ b/drivers/hwmon/ina3221.c @@ -0,0 +1,446 @@ +/* + * INA3221 Triple Current/Voltage Monitor + * + * Copyright (C) 2016 Texas Instruments Incorporated - http://www.ti.com/ + * Andrew F. Davis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#define INA3221_DRIVER_NAME "ina3221" + +#define INA3221_CONFIG 0x00 +#define INA3221_SHUNT1 0x01 +#define INA3221_BUS1 0x02 +#define INA3221_SHUNT2 0x03 +#define INA3221_BUS2 0x04 +#define INA3221_SHUNT3 0x05 +#define INA3221_BUS3 0x06 +#define INA3221_CRIT1 0x07 +#define INA3221_WARN1 0x08 +#define INA3221_CRIT2 0x09 +#define INA3221_WARN2 0x0a +#define INA3221_CRIT3 0x0b +#define INA3221_WARN3 0x0c +#define INA3221_MASK_ENABLE 0x0f + +#define INA3221_CONFIG_MODE_SHUNT BIT(1) +#define INA3221_CONFIG_MODE_BUS BIT(2) +#define INA3221_CONFIG_MODE_CONTINUOUS BIT(3) + +#define INA3221_RSHUNT_DEFAULT 10000 + +enum ina3221_fields { + /* Configuration */ + F_RST, + + /* Alert Flags */ + F_WF3, F_WF2, F_WF1, + F_CF3, F_CF2, F_CF1, + + /* sentinel */ + F_MAX_FIELDS +}; + +static const struct reg_field ina3221_reg_fields[] = { + [F_RST] = REG_FIELD(INA3221_CONFIG, 15, 15), + + [F_WF3] = REG_FIELD(INA3221_MASK_ENABLE, 3, 3), + [F_WF2] = REG_FIELD(INA3221_MASK_ENABLE, 4, 4), + [F_WF1] = REG_FIELD(INA3221_MASK_ENABLE, 5, 5), + [F_CF3] = REG_FIELD(INA3221_MASK_ENABLE, 7, 7), + [F_CF2] = REG_FIELD(INA3221_MASK_ENABLE, 8, 8), + [F_CF1] = REG_FIELD(INA3221_MASK_ENABLE, 9, 9), +}; + +enum ina3221_channels { + INA3221_CHANNEL1, + INA3221_CHANNEL2, + INA3221_CHANNEL3, + INA3221_NUM_CHANNELS +}; + +static const unsigned int register_channel[] = { + [INA3221_SHUNT1] = INA3221_CHANNEL1, + [INA3221_SHUNT2] = INA3221_CHANNEL2, + [INA3221_SHUNT3] = INA3221_CHANNEL3, + [INA3221_CRIT1] = INA3221_CHANNEL1, + [INA3221_CRIT2] = INA3221_CHANNEL2, + [INA3221_CRIT3] = INA3221_CHANNEL3, + [INA3221_WARN1] = INA3221_CHANNEL1, + [INA3221_WARN2] = INA3221_CHANNEL2, + [INA3221_WARN3] = INA3221_CHANNEL3, +}; + +/** + * struct ina3221_data - device specific information + * @regmap: Register map of the device + * @fields: Register fields of the device + * @shunt_resistors: Array of resistor values per channel + */ +struct ina3221_data { + struct regmap *regmap; + struct regmap_field *fields[F_MAX_FIELDS]; + unsigned int shunt_resistors[INA3221_NUM_CHANNELS]; +}; + +static int ina3221_read_value(struct ina3221_data *ina, unsigned int reg, + int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(ina->regmap, reg, ®val); + if (ret) + return ret; + + *val = sign_extend32(regval >> 3, 12); + + return 0; +} + +static ssize_t ina3221_show_bus_voltage(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina3221_data *ina = dev_get_drvdata(dev); + unsigned int reg = sd_attr->index; + int val, voltage_mv, ret; + + ret = ina3221_read_value(ina, reg, &val); + if (ret) + return ret; + + voltage_mv = val * 8; + + return snprintf(buf, PAGE_SIZE, "%d\n", voltage_mv); +} + +static ssize_t ina3221_show_shunt_voltage(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina3221_data *ina = dev_get_drvdata(dev); + unsigned int reg = sd_attr->index; + int val, voltage_uv, ret; + + ret = ina3221_read_value(ina, reg, &val); + if (ret) + return ret; + voltage_uv = val * 40; + + return snprintf(buf, PAGE_SIZE, "%d\n", voltage_uv); +} + +static ssize_t ina3221_show_current(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina3221_data *ina = dev_get_drvdata(dev); + unsigned int reg = sd_attr->index; + unsigned int channel = register_channel[reg]; + unsigned int resistance_uo = ina->shunt_resistors[channel]; + int val, current_ma, voltage_nv, ret; + + ret = ina3221_read_value(ina, reg, &val); + if (ret) + return ret; + voltage_nv = val * 40000; + + current_ma = DIV_ROUND_CLOSEST(voltage_nv, resistance_uo); + + return snprintf(buf, PAGE_SIZE, "%d\n", current_ma); +} + +static ssize_t ina3221_set_current(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina3221_data *ina = dev_get_drvdata(dev); + unsigned int reg = sd_attr->index; + unsigned int channel = register_channel[reg]; + unsigned int resistance_uo = ina->shunt_resistors[channel]; + int val, current_ma, voltage_uv, ret; + + ret = kstrtoint(buf, 0, ¤t_ma); + if (ret) + return ret; + + /* clamp current */ + current_ma = clamp_val(current_ma, + INT_MIN / resistance_uo, + INT_MAX / resistance_uo); + + voltage_uv = DIV_ROUND_CLOSEST(current_ma * resistance_uo, 1000); + + /* clamp voltage */ + voltage_uv = clamp_val(voltage_uv, -163800, 163800); + + /* 1 / 40uV(scale) << 3(register shift) = 5 */ + val = DIV_ROUND_CLOSEST(voltage_uv, 5) & 0xfff8; + + ret = regmap_write(ina->regmap, reg, val); + if (ret) + return ret; + + return count; +} + +static ssize_t ina3221_show_shunt(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina3221_data *ina = dev_get_drvdata(dev); + unsigned int channel = sd_attr->index; + unsigned int resistance_uo; + + resistance_uo = ina->shunt_resistors[channel]; + + return snprintf(buf, PAGE_SIZE, "%d\n", resistance_uo); +} + +static ssize_t ina3221_set_shunt(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina3221_data *ina = dev_get_drvdata(dev); + unsigned int channel = sd_attr->index; + unsigned int val; + int ret; + + ret = kstrtouint(buf, 0, &val); + if (ret) + return ret; + + if (val == 0) + return -EINVAL; + + ina->shunt_resistors[channel] = val; + + return count; +} + +static ssize_t ina3221_show_alert(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina3221_data *ina = dev_get_drvdata(dev); + unsigned int field = sd_attr->index; + unsigned int regval; + int ret; + + ret = regmap_field_read(ina->fields[field], ®val); + if (ret) + return ret; + + return snprintf(buf, PAGE_SIZE, "%d\n", regval); +} + +/* bus voltage */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, + ina3221_show_bus_voltage, NULL, INA3221_BUS1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, + ina3221_show_bus_voltage, NULL, INA3221_BUS2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, + ina3221_show_bus_voltage, NULL, INA3221_BUS3); + +/* calculated current */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, + ina3221_show_current, NULL, INA3221_SHUNT1); +static SENSOR_DEVICE_ATTR(curr2_input, S_IRUGO, + ina3221_show_current, NULL, INA3221_SHUNT2); +static SENSOR_DEVICE_ATTR(curr3_input, S_IRUGO, + ina3221_show_current, NULL, INA3221_SHUNT3); + +/* shunt resistance */ +static SENSOR_DEVICE_ATTR(shunt1_resistor, S_IRUGO | S_IWUSR, + ina3221_show_shunt, ina3221_set_shunt, INA3221_CHANNEL1); +static SENSOR_DEVICE_ATTR(shunt2_resistor, S_IRUGO | S_IWUSR, + ina3221_show_shunt, ina3221_set_shunt, INA3221_CHANNEL2); +static SENSOR_DEVICE_ATTR(shunt3_resistor, S_IRUGO | S_IWUSR, + ina3221_show_shunt, ina3221_set_shunt, INA3221_CHANNEL3); + +/* critical current */ +static SENSOR_DEVICE_ATTR(curr1_crit, S_IRUGO | S_IWUSR, + ina3221_show_current, ina3221_set_current, INA3221_CRIT1); +static SENSOR_DEVICE_ATTR(curr2_crit, S_IRUGO | S_IWUSR, + ina3221_show_current, ina3221_set_current, INA3221_CRIT2); +static SENSOR_DEVICE_ATTR(curr3_crit, S_IRUGO | S_IWUSR, + ina3221_show_current, ina3221_set_current, INA3221_CRIT3); + +/* critical current alert */ +static SENSOR_DEVICE_ATTR(curr1_crit_alarm, S_IRUGO, + ina3221_show_alert, NULL, F_CF1); +static SENSOR_DEVICE_ATTR(curr2_crit_alarm, S_IRUGO, + ina3221_show_alert, NULL, F_CF2); +static SENSOR_DEVICE_ATTR(curr3_crit_alarm, S_IRUGO, + ina3221_show_alert, NULL, F_CF3); + +/* warning current */ +static SENSOR_DEVICE_ATTR(curr1_max, S_IRUGO | S_IWUSR, + ina3221_show_current, ina3221_set_current, INA3221_WARN1); +static SENSOR_DEVICE_ATTR(curr2_max, S_IRUGO | S_IWUSR, + ina3221_show_current, ina3221_set_current, INA3221_WARN2); +static SENSOR_DEVICE_ATTR(curr3_max, S_IRUGO | S_IWUSR, + ina3221_show_current, ina3221_set_current, INA3221_WARN3); + +/* warning current alert */ +static SENSOR_DEVICE_ATTR(curr1_max_alarm, S_IRUGO, + ina3221_show_alert, NULL, F_WF1); +static SENSOR_DEVICE_ATTR(curr2_max_alarm, S_IRUGO, + ina3221_show_alert, NULL, F_WF2); +static SENSOR_DEVICE_ATTR(curr3_max_alarm, S_IRUGO, + ina3221_show_alert, NULL, F_WF3); + +/* shunt voltage */ +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, + ina3221_show_shunt_voltage, NULL, INA3221_SHUNT1); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, + ina3221_show_shunt_voltage, NULL, INA3221_SHUNT2); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, + ina3221_show_shunt_voltage, NULL, INA3221_SHUNT3); + +static struct attribute *ina3221_attrs[] = { + /* channel 1 */ + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_shunt1_resistor.dev_attr.attr, + &sensor_dev_attr_curr1_crit.dev_attr.attr, + &sensor_dev_attr_curr1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_curr1_max.dev_attr.attr, + &sensor_dev_attr_curr1_max_alarm.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + + /* channel 2 */ + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_curr2_input.dev_attr.attr, + &sensor_dev_attr_shunt2_resistor.dev_attr.attr, + &sensor_dev_attr_curr2_crit.dev_attr.attr, + &sensor_dev_attr_curr2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_curr2_max.dev_attr.attr, + &sensor_dev_attr_curr2_max_alarm.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + + /* channel 3 */ + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_curr3_input.dev_attr.attr, + &sensor_dev_attr_shunt3_resistor.dev_attr.attr, + &sensor_dev_attr_curr3_crit.dev_attr.attr, + &sensor_dev_attr_curr3_crit_alarm.dev_attr.attr, + &sensor_dev_attr_curr3_max.dev_attr.attr, + &sensor_dev_attr_curr3_max_alarm.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + + NULL, +}; +ATTRIBUTE_GROUPS(ina3221); + +static const struct regmap_range ina3221_yes_ranges[] = { + regmap_reg_range(INA3221_SHUNT1, INA3221_BUS3), + regmap_reg_range(INA3221_MASK_ENABLE, INA3221_MASK_ENABLE), +}; + +static const struct regmap_access_table ina3221_volatile_table = { + .yes_ranges = ina3221_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(ina3221_yes_ranges), +}; + +static const struct regmap_config ina3221_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + + .cache_type = REGCACHE_RBTREE, + .volatile_table = &ina3221_volatile_table, +}; + +static int ina3221_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ina3221_data *ina; + struct device *hwmon_dev; + int i, ret; + + ina = devm_kzalloc(dev, sizeof(*ina), GFP_KERNEL); + if (!ina) + return -ENOMEM; + + ina->regmap = devm_regmap_init_i2c(client, &ina3221_regmap_config); + if (IS_ERR(ina->regmap)) { + dev_err(dev, "Unable to allocate register map\n"); + return PTR_ERR(ina->regmap); + } + + for (i = 0; i < F_MAX_FIELDS; i++) { + ina->fields[i] = devm_regmap_field_alloc(dev, + ina->regmap, + ina3221_reg_fields[i]); + if (IS_ERR(ina->fields[i])) { + dev_err(dev, "Unable to allocate regmap fields\n"); + return PTR_ERR(ina->fields[i]); + } + } + + for (i = 0; i < INA3221_NUM_CHANNELS; i++) + ina->shunt_resistors[i] = INA3221_RSHUNT_DEFAULT; + + ret = regmap_field_write(ina->fields[F_RST], true); + if (ret) { + dev_err(dev, "Unable to reset device\n"); + return ret; + } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + client->name, + ina, ina3221_groups); + if (IS_ERR(hwmon_dev)) { + dev_err(dev, "Unable to register hwmon device\n"); + return PTR_ERR(hwmon_dev); + } + + return 0; +} + +static const struct of_device_id ina3221_of_match_table[] = { + { .compatible = "ti,ina3221", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ina3221_of_match_table); + +static const struct i2c_device_id ina3221_ids[] = { + { "ina3221", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, ina3221_ids); + +static struct i2c_driver ina3221_i2c_driver = { + .probe = ina3221_probe, + .driver = { + .name = INA3221_DRIVER_NAME, + .of_match_table = ina3221_of_match_table, + }, + .id_table = ina3221_ids, +}; +module_i2c_driver(ina3221_i2c_driver); + +MODULE_AUTHOR("Andrew F. Davis "); +MODULE_DESCRIPTION("Texas Instruments INA3221 HWMon Driver"); +MODULE_LICENSE("GPL v2"); -- cgit v0.10.2 From 165720d9ff3e33f626802da6cef3118ebfc6940c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 10 Jun 2016 21:01:28 -0700 Subject: hwmon: Update guildelines for submitting patches Add more details to the guidelines for submitting patches. Signed-off-by: Guenter Roeck diff --git a/Documentation/hwmon/submitting-patches b/Documentation/hwmon/submitting-patches index d201828..57f6030 100644 --- a/Documentation/hwmon/submitting-patches +++ b/Documentation/hwmon/submitting-patches @@ -15,10 +15,15 @@ increase the chances of your change being accepted. Documentation/SubmittingPatches Documentation/CodingStyle -* If your patch generates checkpatch warnings, please refrain from explanations - such as "I don't like that coding style". Keep in mind that each unnecessary - warning helps hiding a real problem. If you don't like the kernel coding - style, don't write kernel drivers. +* Please run your patch through 'checkpatch --strict'. There should be no + errors, no warnings, and few if any check messages. If there are any + messages, please be prepared to explain. + +* If your patch generates checkpatch errors, warnings, or check messages, + please refrain from explanations such as "I prefer that coding style". + Keep in mind that each unnecessary message helps hiding a real problem, + and a consistent coding style makes it easier for others to understand + and review the code. * Please test your patch thoroughly. We are not your test group. Sometimes a patch can not or not completely be tested because of missing @@ -61,15 +66,30 @@ increase the chances of your change being accepted. * Make sure that all dependencies are listed in Kconfig. +* Please list include files in alphabetic order. + +* Please align continuation lines with '(' on the previous line. + * Avoid forward declarations if you can. Rearrange the code if necessary. +* Avoid macros to generate groups of sensor attributes. It not only confuses + checkpatch, but also makes it more difficult to review the code. + * Avoid calculations in macros and macro-generated functions. While such macros may save a line or so in the source, it obfuscates the code and makes code review more difficult. It may also result in code which is more complicated than necessary. Use inline functions or just regular functions instead. +* Limit the number of kernel log messages. In general, your driver should not + generate an error message just because a runtime operation failed. Report + errors to user space instead, using an appropriate error code. Keep in mind + that kernel error log messages not only fill up the kernel log, but also are + printed synchronously, most likely with interrupt disabled, often to a serial + console. Excessive logging can seriously affect system performance. + * Use devres functions whenever possible to allocate resources. For rationale and supported functions, please see Documentation/driver-model/devres.txt. + If a function is not supported by devres, consider using devm_add_action(). * If the driver has a detect function, make sure it is silent. Debug messages and messages printed after a successful detection are acceptable, but it @@ -96,8 +116,16 @@ increase the chances of your change being accepted. writing to it might cause a bad misconfiguration. * Make sure there are no race conditions in the probe function. Specifically, - completely initialize your chip first, then create sysfs entries and register - with the hwmon subsystem. + completely initialize your chip and your driver first, then register with + the hwmon subsystem. + +* Use devm_hwmon_device_register_with_groups() or, if your driver needs a remove + function, hwmon_device_register_with_groups() to register your driver with the + hwmon subsystem. Try using devm_add_action() instead of a remove function if + possible. Do not use hwmon_device_register(). + +* Your driver should be buildable as module. If not, please be prepared to + explain why it has to be built into the kernel. * Do not provide support for deprecated sysfs attributes. -- cgit v0.10.2 From 157926c013b39561fd1e3b93f8df96fb0ed17648 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Tue, 1 Jul 2014 22:29:28 +0800 Subject: hwmon: (ad7314) Convert to devm_hwmon_device_register_with_groups Use ATTRIBUTE_GROUPS macro and devm_hwmon_device_register_with_groups() to simplify the code a bit. Signed-off-by: Axel Lin Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/ad7314.c b/drivers/hwmon/ad7314.c index 202c1fbb..8ea3593 100644 --- a/drivers/hwmon/ad7314.c +++ b/drivers/hwmon/ad7314.c @@ -37,7 +37,6 @@ enum ad7314_variant { struct ad7314_data { struct spi_device *spi_dev; - struct device *hwmon_dev; u16 rx ____cacheline_aligned; }; @@ -88,62 +87,30 @@ static ssize_t ad7314_show_temperature(struct device *dev, } } -static ssize_t ad7314_show_name(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - return sprintf(buf, "%s\n", to_spi_device(dev)->modalias); -} - -static DEVICE_ATTR(name, S_IRUGO, ad7314_show_name, NULL); static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, ad7314_show_temperature, NULL, 0); -static struct attribute *ad7314_attributes[] = { - &dev_attr_name.attr, +static struct attribute *ad7314_attrs[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, NULL, }; -static const struct attribute_group ad7314_group = { - .attrs = ad7314_attributes, -}; +ATTRIBUTE_GROUPS(ad7314); static int ad7314_probe(struct spi_device *spi_dev) { - int ret; struct ad7314_data *chip; + struct device *hwmon_dev; chip = devm_kzalloc(&spi_dev->dev, sizeof(*chip), GFP_KERNEL); if (chip == NULL) return -ENOMEM; - spi_set_drvdata(spi_dev, chip); - - ret = sysfs_create_group(&spi_dev->dev.kobj, &ad7314_group); - if (ret < 0) - return ret; - - chip->hwmon_dev = hwmon_device_register(&spi_dev->dev); - if (IS_ERR(chip->hwmon_dev)) { - ret = PTR_ERR(chip->hwmon_dev); - goto error_remove_group; - } chip->spi_dev = spi_dev; - - return 0; -error_remove_group: - sysfs_remove_group(&spi_dev->dev.kobj, &ad7314_group); - return ret; -} - -static int ad7314_remove(struct spi_device *spi_dev) -{ - struct ad7314_data *chip = spi_get_drvdata(spi_dev); - - hwmon_device_unregister(chip->hwmon_dev); - sysfs_remove_group(&spi_dev->dev.kobj, &ad7314_group); - - return 0; + hwmon_dev = devm_hwmon_device_register_with_groups(&spi_dev->dev, + spi_dev->modalias, + chip, ad7314_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); } static const struct spi_device_id ad7314_id[] = { @@ -159,7 +126,6 @@ static struct spi_driver ad7314_driver = { .name = "ad7314", }, .probe = ad7314_probe, - .remove = ad7314_remove, .id_table = ad7314_id, }; -- cgit v0.10.2 From 699f279d998cf95809babc987abd4a409eada5b2 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Sun, 29 Jun 2014 21:39:11 +0800 Subject: hwmon: (jz4740) Convert to devm_hwmon_device_register_with_groups Use ATTRIBUTE_GROUPS macro and devm_hwmon_device_register_with_groups() to simplify the code a bit. Signed-off-by: Axel Lin Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/jz4740-hwmon.c b/drivers/hwmon/jz4740-hwmon.c index df9b344..0621ee1 100644 --- a/drivers/hwmon/jz4740-hwmon.c +++ b/drivers/hwmon/jz4740-hwmon.c @@ -29,23 +29,13 @@ struct jz4740_hwmon { void __iomem *base; - int irq; - const struct mfd_cell *cell; - struct device *hwmon; - + struct platform_device *pdev; struct completion read_completion; - struct mutex lock; }; -static ssize_t jz4740_hwmon_show_name(struct device *dev, - struct device_attribute *dev_attr, char *buf) -{ - return sprintf(buf, "jz4740\n"); -} - static irqreturn_t jz4740_hwmon_irq(int irq, void *data) { struct jz4740_hwmon *hwmon = data; @@ -58,6 +48,7 @@ static ssize_t jz4740_hwmon_read_adcin(struct device *dev, struct device_attribute *dev_attr, char *buf) { struct jz4740_hwmon *hwmon = dev_get_drvdata(dev); + struct platform_device *pdev = hwmon->pdev; struct completion *completion = &hwmon->read_completion; long t; unsigned long val; @@ -68,7 +59,7 @@ static ssize_t jz4740_hwmon_read_adcin(struct device *dev, reinit_completion(completion); enable_irq(hwmon->irq); - hwmon->cell->enable(to_platform_device(dev)); + hwmon->cell->enable(pdev); t = wait_for_completion_interruptible_timeout(completion, HZ); @@ -80,7 +71,7 @@ static ssize_t jz4740_hwmon_read_adcin(struct device *dev, ret = t ? t : -ETIMEDOUT; } - hwmon->cell->disable(to_platform_device(dev)); + hwmon->cell->disable(pdev); disable_irq(hwmon->irq); mutex_unlock(&hwmon->lock); @@ -88,26 +79,24 @@ static ssize_t jz4740_hwmon_read_adcin(struct device *dev, return ret; } -static DEVICE_ATTR(name, S_IRUGO, jz4740_hwmon_show_name, NULL); static DEVICE_ATTR(in0_input, S_IRUGO, jz4740_hwmon_read_adcin, NULL); -static struct attribute *jz4740_hwmon_attributes[] = { - &dev_attr_name.attr, +static struct attribute *jz4740_attrs[] = { &dev_attr_in0_input.attr, NULL }; -static const struct attribute_group jz4740_hwmon_attr_group = { - .attrs = jz4740_hwmon_attributes, -}; +ATTRIBUTE_GROUPS(jz4740); static int jz4740_hwmon_probe(struct platform_device *pdev) { int ret; + struct device *dev = &pdev->dev; struct jz4740_hwmon *hwmon; + struct device *hwmon_dev; struct resource *mem; - hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL); + hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL); if (!hwmon) return -ENOMEM; @@ -125,12 +114,11 @@ static int jz4740_hwmon_probe(struct platform_device *pdev) if (IS_ERR(hwmon->base)) return PTR_ERR(hwmon->base); + hwmon->pdev = pdev; init_completion(&hwmon->read_completion); mutex_init(&hwmon->lock); - platform_set_drvdata(pdev, hwmon); - - ret = devm_request_irq(&pdev->dev, hwmon->irq, jz4740_hwmon_irq, 0, + ret = devm_request_irq(dev, hwmon->irq, jz4740_hwmon_irq, 0, pdev->name, hwmon); if (ret) { dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); @@ -138,38 +126,13 @@ static int jz4740_hwmon_probe(struct platform_device *pdev) } disable_irq(hwmon->irq); - ret = sysfs_create_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group); - if (ret) { - dev_err(&pdev->dev, "Failed to create sysfs group: %d\n", ret); - return ret; - } - - hwmon->hwmon = hwmon_device_register(&pdev->dev); - if (IS_ERR(hwmon->hwmon)) { - ret = PTR_ERR(hwmon->hwmon); - goto err_remove_file; - } - - return 0; - -err_remove_file: - sysfs_remove_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group); - return ret; -} - -static int jz4740_hwmon_remove(struct platform_device *pdev) -{ - struct jz4740_hwmon *hwmon = platform_get_drvdata(pdev); - - hwmon_device_unregister(hwmon->hwmon); - sysfs_remove_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group); - - return 0; + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "jz4740", hwmon, + jz4740_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); } static struct platform_driver jz4740_hwmon_driver = { .probe = jz4740_hwmon_probe, - .remove = jz4740_hwmon_remove, .driver = { .name = "jz4740-hwmon", }, -- cgit v0.10.2 From 68f86c75ca1eaf790907eb43c68f9f2996474116 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Wed, 2 Jul 2014 20:31:21 +0800 Subject: hwmon: (ads7871) Convert to devm_hwmon_device_register_with_groups Use ATTRIBUTE_GROUPS macro and devm_hwmon_device_register_with_groups() to simplify the code a bit. The update_lock mutex is not used, so remove it. Signed-off-by: Axel Lin Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/ads7871.c b/drivers/hwmon/ads7871.c index 4fd9e4d..59bd7b9 100644 --- a/drivers/hwmon/ads7871.c +++ b/drivers/hwmon/ads7871.c @@ -66,14 +66,12 @@ #include #include #include -#include #include #define DEVICE_NAME "ads7871" struct ads7871_data { - struct device *hwmon_dev; - struct mutex update_lock; + struct spi_device *spi; }; static int ads7871_read_reg8(struct spi_device *spi, int reg) @@ -101,7 +99,8 @@ static int ads7871_write_reg8(struct spi_device *spi, int reg, u8 val) static ssize_t show_voltage(struct device *dev, struct device_attribute *da, char *buf) { - struct spi_device *spi = to_spi_device(dev); + struct ads7871_data *pdata = dev_get_drvdata(dev); + struct spi_device *spi = pdata->spi; struct sensor_device_attribute *attr = to_sensor_dev_attr(da); int ret, val, i = 0; uint8_t channel, mux_cnv; @@ -139,12 +138,6 @@ static ssize_t show_voltage(struct device *dev, } } -static ssize_t ads7871_show_name(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - return sprintf(buf, "%s\n", to_spi_device(dev)->modalias); -} - static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_voltage, NULL, 0); static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_voltage, NULL, 1); static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_voltage, NULL, 2); @@ -154,9 +147,7 @@ static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, show_voltage, NULL, 5); static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, show_voltage, NULL, 6); static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, show_voltage, NULL, 7); -static DEVICE_ATTR(name, S_IRUGO, ads7871_show_name, NULL); - -static struct attribute *ads7871_attributes[] = { +static struct attribute *ads7871_attrs[] = { &sensor_dev_attr_in0_input.dev_attr.attr, &sensor_dev_attr_in1_input.dev_attr.attr, &sensor_dev_attr_in2_input.dev_attr.attr, @@ -165,21 +156,18 @@ static struct attribute *ads7871_attributes[] = { &sensor_dev_attr_in5_input.dev_attr.attr, &sensor_dev_attr_in6_input.dev_attr.attr, &sensor_dev_attr_in7_input.dev_attr.attr, - &dev_attr_name.attr, NULL }; -static const struct attribute_group ads7871_group = { - .attrs = ads7871_attributes, -}; +ATTRIBUTE_GROUPS(ads7871); static int ads7871_probe(struct spi_device *spi) { - int ret, err; + struct device *dev = &spi->dev; + int ret; uint8_t val; struct ads7871_data *pdata; - - dev_dbg(&spi->dev, "probe\n"); + struct device *hwmon_dev; /* Configure the SPI bus */ spi->mode = (SPI_MODE_0); @@ -193,7 +181,7 @@ static int ads7871_probe(struct spi_device *spi) ads7871_write_reg8(spi, REG_OSC_CONTROL, val); ret = ads7871_read_reg8(spi, REG_OSC_CONTROL); - dev_dbg(&spi->dev, "REG_OSC_CONTROL write:%x, read:%x\n", val, ret); + dev_dbg(dev, "REG_OSC_CONTROL write:%x, read:%x\n", val, ret); /* * because there is no other error checking on an SPI bus * we need to make sure we really have a chip @@ -201,46 +189,23 @@ static int ads7871_probe(struct spi_device *spi) if (val != ret) return -ENODEV; - pdata = devm_kzalloc(&spi->dev, sizeof(struct ads7871_data), - GFP_KERNEL); + pdata = devm_kzalloc(dev, sizeof(struct ads7871_data), GFP_KERNEL); if (!pdata) return -ENOMEM; - err = sysfs_create_group(&spi->dev.kobj, &ads7871_group); - if (err < 0) - return err; - - spi_set_drvdata(spi, pdata); + pdata->spi = spi; - pdata->hwmon_dev = hwmon_device_register(&spi->dev); - if (IS_ERR(pdata->hwmon_dev)) { - err = PTR_ERR(pdata->hwmon_dev); - goto error_remove; - } - - return 0; - -error_remove: - sysfs_remove_group(&spi->dev.kobj, &ads7871_group); - return err; -} - -static int ads7871_remove(struct spi_device *spi) -{ - struct ads7871_data *pdata = spi_get_drvdata(spi); - - hwmon_device_unregister(pdata->hwmon_dev); - sysfs_remove_group(&spi->dev.kobj, &ads7871_group); - return 0; + hwmon_dev = devm_hwmon_device_register_with_groups(dev, spi->modalias, + pdata, + ads7871_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); } static struct spi_driver ads7871_driver = { .driver = { .name = DEVICE_NAME, }, - .probe = ads7871_probe, - .remove = ads7871_remove, }; module_spi_driver(ads7871_driver); -- cgit v0.10.2 From 747bc8b063ae6b66447918280922253721cb1d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sat, 18 Jun 2016 00:54:48 +0200 Subject: hwmon: (dell-smm) Detect fan with index=2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some Dell machines (e.g. Dell Precision M3800) have two fans, first with index=0 and second with index=2. So export also attributes for third fan device with index=2. Reported-by: Tolga Cakir Signed-off-by: Pali Rohár Tested-by: Tolga Cakir Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 2ac87d5..571d498 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -81,6 +81,7 @@ static bool disallow_fan_type_call; #define I8K_HWMON_HAVE_TEMP4 (1 << 3) #define I8K_HWMON_HAVE_FAN1 (1 << 4) #define I8K_HWMON_HAVE_FAN2 (1 << 5) +#define I8K_HWMON_HAVE_FAN3 (1 << 6) MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)"); MODULE_AUTHOR("Pali Rohár "); @@ -252,7 +253,7 @@ static int _i8k_get_fan_type(int fan) static int i8k_get_fan_type(int fan) { /* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */ - static int types[2] = { INT_MIN, INT_MIN }; + static int types[3] = { INT_MIN, INT_MIN, INT_MIN }; if (types[fan] == INT_MIN) types[fan] = _i8k_get_fan_type(fan); @@ -719,6 +720,12 @@ static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL, 1); static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm, i8k_hwmon_set_pwm, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, i8k_hwmon_show_fan, NULL, + 2); +static SENSOR_DEVICE_ATTR(fan3_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL, + 2); +static SENSOR_DEVICE_ATTR(pwm3, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm, + i8k_hwmon_set_pwm, 2); static struct attribute *i8k_attrs[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, /* 0 */ @@ -735,6 +742,9 @@ static struct attribute *i8k_attrs[] = { &sensor_dev_attr_fan2_input.dev_attr.attr, /* 11 */ &sensor_dev_attr_fan2_label.dev_attr.attr, /* 12 */ &sensor_dev_attr_pwm2.dev_attr.attr, /* 13 */ + &sensor_dev_attr_fan3_input.dev_attr.attr, /* 14 */ + &sensor_dev_attr_fan3_label.dev_attr.attr, /* 15 */ + &sensor_dev_attr_pwm3.dev_attr.attr, /* 16 */ NULL }; @@ -742,7 +752,7 @@ static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr, int index) { if (disallow_fan_type_call && - (index == 9 || index == 12)) + (index == 9 || index == 12 || index == 15)) return 0; if (index >= 0 && index <= 1 && !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1)) @@ -762,6 +772,9 @@ static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr, if (index >= 11 && index <= 13 && !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2)) return 0; + if (index >= 14 && index <= 16 && + !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN3)) + return 0; return attr->mode; } @@ -807,6 +820,13 @@ static int __init i8k_init_hwmon(void) if (err >= 0) i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2; + /* Third fan attributes, if fan status or type is OK */ + err = i8k_get_fan_status(2); + if (err < 0) + err = i8k_get_fan_type(2); + if (err >= 0) + i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN3; + i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "dell_smm", NULL, i8k_groups); if (IS_ERR(i8k_hwmon_dev)) { -- cgit v0.10.2 From 9d58bec0966bfc87948244b81056137e6d214e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sat, 18 Jun 2016 00:54:49 +0200 Subject: hwmon: (dell-smm) In debug mode log duration of SMM calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allow us to debug how long take each SMM call and how long is system frozen in SMM handler. Signed-off-by: Pali Rohár Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 571d498..acf9c03 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -140,6 +140,14 @@ static int i8k_smm(struct smm_regs *regs) int eax = regs->eax; cpumask_var_t old_mask; +#ifdef DEBUG + int ebx = regs->ebx; + unsigned long duration; + ktime_t calltime, delta, rettime; + + calltime = ktime_get(); +#endif + /* SMM requires CPU 0 */ if (!alloc_cpumask_var(&old_mask, GFP_KERNEL)) return -ENOMEM; @@ -211,6 +219,15 @@ static int i8k_smm(struct smm_regs *regs) out: set_cpus_allowed_ptr(current, old_mask); free_cpumask_var(old_mask); + +#ifdef DEBUG + rettime = ktime_get(); + delta = ktime_sub(rettime, calltime); + duration = ktime_to_ns(delta) >> 10; + pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x (took %7lu usecs)\n", eax, ebx, + (rc ? 0xffff : regs->eax & 0xffff), duration); +#endif + return rc; } -- cgit v0.10.2 From 51b8c2cd92f1b59657a13339dffcca79c76126d1 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 24 Jun 2016 18:31:32 +0100 Subject: hwmon: (emc6w201): trivial fix of spelling mistake "Unknwown" -> "Unknown" trivial fix to spelling mistake in dev_dbg message Signed-off-by: Colin Ian King Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/emc6w201.c b/drivers/hwmon/emc6w201.c index ada9071..f37fe201 100644 --- a/drivers/hwmon/emc6w201.c +++ b/drivers/hwmon/emc6w201.c @@ -464,7 +464,7 @@ static int emc6w201_detect(struct i2c_client *client, if (verstep < 0 || (verstep & 0xF0) != 0xB0) return -ENODEV; if ((verstep & 0x0F) > 2) { - dev_dbg(&client->dev, "Unknwown EMC6W201 stepping %d\n", + dev_dbg(&client->dev, "Unknown EMC6W201 stepping %d\n", verstep & 0x0F); return -ENODEV; } -- cgit v0.10.2 From 9ad0df1adac20d694fbb8e7cb7bac04e0645a927 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 24 Jun 2016 19:41:57 -0700 Subject: hwmon: (ina3221) Fix negative limits The result of an integer divide by an unsigned is undefined. This causes unexpected results when writing negative values into the limit registers. Maintain the shunt_resistors variables as signed integer to avoid the problem. Also, for simplicity and ease of use, clamp shunt resistor value on writes instead of rejecting bad values. Cc: Andrew F. Davis Acked-by: Andrew F. Davis Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/ina3221.c b/drivers/hwmon/ina3221.c index d055b6a..e6b4950 100644 --- a/drivers/hwmon/ina3221.c +++ b/drivers/hwmon/ina3221.c @@ -95,7 +95,7 @@ static const unsigned int register_channel[] = { struct ina3221_data { struct regmap *regmap; struct regmap_field *fields[F_MAX_FIELDS]; - unsigned int shunt_resistors[INA3221_NUM_CHANNELS]; + int shunt_resistors[INA3221_NUM_CHANNELS]; }; static int ina3221_read_value(struct ina3221_data *ina, unsigned int reg, @@ -155,7 +155,7 @@ static ssize_t ina3221_show_current(struct device *dev, struct ina3221_data *ina = dev_get_drvdata(dev); unsigned int reg = sd_attr->index; unsigned int channel = register_channel[reg]; - unsigned int resistance_uo = ina->shunt_resistors[channel]; + int resistance_uo = ina->shunt_resistors[channel]; int val, current_ma, voltage_nv, ret; ret = ina3221_read_value(ina, reg, &val); @@ -176,7 +176,7 @@ static ssize_t ina3221_set_current(struct device *dev, struct ina3221_data *ina = dev_get_drvdata(dev); unsigned int reg = sd_attr->index; unsigned int channel = register_channel[reg]; - unsigned int resistance_uo = ina->shunt_resistors[channel]; + int resistance_uo = ina->shunt_resistors[channel]; int val, current_ma, voltage_uv, ret; ret = kstrtoint(buf, 0, ¤t_ma); @@ -223,15 +223,14 @@ static ssize_t ina3221_set_shunt(struct device *dev, struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); struct ina3221_data *ina = dev_get_drvdata(dev); unsigned int channel = sd_attr->index; - unsigned int val; + int val; int ret; - ret = kstrtouint(buf, 0, &val); + ret = kstrtoint(buf, 0, &val); if (ret) return ret; - if (val == 0) - return -EINVAL; + val = clamp_val(val, 1, INT_MAX); ina->shunt_resistors[channel] = val; -- cgit v0.10.2 From a31887dc9be1a65cde2562ed10e7fbf5c82581a2 Mon Sep 17 00:00:00 2001 From: Alison Schofield Date: Mon, 27 Jun 2016 17:23:27 -0700 Subject: hwmon: (jc42) Add support for Microchip MCP9808 temperature sensor MCP9808 is not officially compliant to JC-42, similar to MCP9804, but its registers are compatible to JC-42. Signed-off-by: Alison Schofield Cc: Daniel Baluta Signed-off-by: Guenter Roeck diff --git a/Documentation/hwmon/jc42 b/Documentation/hwmon/jc42 index f7f1830a..b4b671f 100644 --- a/Documentation/hwmon/jc42 +++ b/Documentation/hwmon/jc42 @@ -18,10 +18,11 @@ Supported chips: * Maxim MAX6604 Datasheets: http://datasheets.maxim-ic.com/en/ds/MAX6604.pdf - * Microchip MCP9804, MCP9805, MCP98242, MCP98243, MCP98244, MCP9843 + * Microchip MCP9804, MCP9805, MCP9808, MCP98242, MCP98243, MCP98244, MCP9843 Datasheets: http://ww1.microchip.com/downloads/en/DeviceDoc/22203C.pdf http://ww1.microchip.com/downloads/en/DeviceDoc/21977b.pdf + http://ww1.microchip.com/downloads/en/DeviceDoc/25095A.pdf http://ww1.microchip.com/downloads/en/DeviceDoc/21996a.pdf http://ww1.microchip.com/downloads/en/DeviceDoc/22153c.pdf http://ww1.microchip.com/downloads/en/DeviceDoc/22327A.pdf diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index b2aa13b..91f145e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -645,8 +645,8 @@ config SENSORS_JC42 temperature sensors, which are used on many DDR3 memory modules for mobile devices and servers. Support will include, but not be limited to, ADT7408, AT30TS00, CAT34TS02, CAT6095, MAX6604, MCP9804, MCP9805, - MCP98242, MCP98243, MCP98244, MCP9843, SE97, SE98, STTS424(E), - STTS2002, STTS3000, TSE2002, TSE2004, TS3000, and TS3001. + MCP9808, MCP98242, MCP98243, MCP98244, MCP9843, SE97, SE98, + STTS424(E), STTS2002, STTS3000, TSE2002, TSE2004, TS3000, and TS3001. This driver can also be built as a module. If so, the module will be called jc42. diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c index 9887d32..f67c1bb 100644 --- a/drivers/hwmon/jc42.c +++ b/drivers/hwmon/jc42.c @@ -104,6 +104,9 @@ static const unsigned short normal_i2c[] = { #define MCP9804_DEVID 0x0200 #define MCP9804_DEVID_MASK 0xfffc +#define MCP9808_DEVID 0x0400 +#define MCP9808_DEVID_MASK 0xfffc + #define MCP98242_DEVID 0x2000 #define MCP98242_DEVID_MASK 0xfffc @@ -160,6 +163,7 @@ static struct jc42_chips jc42_chips[] = { { IDT_MANID, TS3001_DEVID, TS3001_DEVID_MASK }, { MAX_MANID, MAX6604_DEVID, MAX6604_DEVID_MASK }, { MCP_MANID, MCP9804_DEVID, MCP9804_DEVID_MASK }, + { MCP_MANID, MCP9808_DEVID, MCP9808_DEVID_MASK }, { MCP_MANID, MCP98242_DEVID, MCP98242_DEVID_MASK }, { MCP_MANID, MCP98243_DEVID, MCP98243_DEVID_MASK }, { MCP_MANID, MCP98244_DEVID, MCP98244_DEVID_MASK }, -- cgit v0.10.2 From b17ea1ca1da115050959d24353e58ccae8aacb58 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 19 Jun 2016 20:09:54 -0700 Subject: hwmon: (tmp102) Use devm_add_action to register cleanup function By registering a cleanup function with devm_add_action(), we can simplify the error path in the probe function and drop the remove function entirely. Acked-by: Nishanth Menon Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c index f1e96fd..befd06b 100644 --- a/drivers/hwmon/tmp102.c +++ b/drivers/hwmon/tmp102.c @@ -52,7 +52,6 @@ struct tmp102 { struct i2c_client *client; - struct device *hwmon_dev; struct mutex lock; u16 config_orig; unsigned long last_update; @@ -173,6 +172,15 @@ static const struct thermal_zone_of_device_ops tmp102_of_thermal_ops = { .get_temp = tmp102_read_temp, }; +static void tmp102_restore_config(void *data) +{ + struct tmp102 *tmp102 = data; + struct i2c_client *client = tmp102->client; + + i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, + tmp102->config_orig); +} + static int tmp102_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -201,66 +209,43 @@ static int tmp102_probe(struct i2c_client *client, return status; } tmp102->config_orig = status; + + devm_add_action(dev, tmp102_restore_config, tmp102); + status = i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, TMP102_CONFIG); if (status < 0) { dev_err(dev, "error writing config register\n"); - goto fail_restore_config; + return status; } status = i2c_smbus_read_word_swapped(client, TMP102_CONF_REG); if (status < 0) { dev_err(dev, "error reading config register\n"); - goto fail_restore_config; + return status; } status &= ~TMP102_CONFIG_RD_ONLY; if (status != TMP102_CONFIG) { dev_err(dev, "config settings did not stick\n"); - status = -ENODEV; - goto fail_restore_config; + return -ENODEV; } tmp102->last_update = jiffies; /* Mark that we are not ready with data until conversion is complete */ tmp102->first_time = true; mutex_init(&tmp102->lock); - hwmon_dev = hwmon_device_register_with_groups(dev, client->name, - tmp102, tmp102_groups); + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + tmp102, + tmp102_groups); if (IS_ERR(hwmon_dev)) { dev_dbg(dev, "unable to register hwmon device\n"); - status = PTR_ERR(hwmon_dev); - goto fail_restore_config; + return PTR_ERR(hwmon_dev); } - tmp102->hwmon_dev = hwmon_dev; devm_thermal_zone_of_sensor_register(hwmon_dev, 0, hwmon_dev, &tmp102_of_thermal_ops); dev_info(dev, "initialized\n"); return 0; - -fail_restore_config: - i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, - tmp102->config_orig); - return status; -} - -static int tmp102_remove(struct i2c_client *client) -{ - struct tmp102 *tmp102 = i2c_get_clientdata(client); - - hwmon_device_unregister(tmp102->hwmon_dev); - - /* Stop monitoring if device was stopped originally */ - if (tmp102->config_orig & TMP102_CONF_SD) { - int config; - - config = i2c_smbus_read_word_swapped(client, TMP102_CONF_REG); - if (config >= 0) - i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, - config | TMP102_CONF_SD); - } - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -303,7 +288,6 @@ static struct i2c_driver tmp102_driver = { .driver.name = DRIVER_NAME, .driver.pm = &tmp102_dev_pm_ops, .probe = tmp102_probe, - .remove = tmp102_remove, .id_table = tmp102_id, }; -- cgit v0.10.2 From 4e6163e859cd572acb4bc2525e00714f6c1905c8 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 22 Jun 2016 10:40:07 -0700 Subject: hwmon: (tmp102) Drop FSF address The FSF address can change, so drop it from the driver. Acked-by: Nishanth Menon Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c index befd06b..5bdf262 100644 --- a/drivers/hwmon/tmp102.c +++ b/drivers/hwmon/tmp102.c @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA */ #include -- cgit v0.10.2 From eacc48ce3accfc9092594794b009a40f91331b0e Mon Sep 17 00:00:00 2001 From: Alison Schofield Date: Mon, 4 Jul 2016 12:19:28 -0700 Subject: hwmon: (jc42) Add I2C_CLASS_HWMON to detection class In 2011, commit 774466add7c ("hwmon: (jc42) Change detection class") changed the detection class of these chips to I2C_CLASS_SPD based on this premise: "makes more sense because these chips always live on memory modules" Today these chips have applications beyond memory modules. Examples are JC42.4 compatible chips such as MCP9804 and MCP9808, but also MCP9805, which is marked as JC42.4 compliant and suggested for use not only for DIMMS, but also as generic temperature sensor. Add I2C_CLASS_HWMON as an additional detection class to allow detection by hwmon class i2c adapters. Signed-off-by: Alison Schofield Cc: Daniel Baluta [groeck: Updated description] Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c index f67c1bb..d9c4f78 100644 --- a/drivers/hwmon/jc42.c +++ b/drivers/hwmon/jc42.c @@ -542,7 +542,7 @@ static const struct i2c_device_id jc42_id[] = { MODULE_DEVICE_TABLE(i2c, jc42_id); static struct i2c_driver jc42_driver = { - .class = I2C_CLASS_SPD, + .class = I2C_CLASS_SPD | I2C_CLASS_HWMON, .driver = { .name = "jc42", .pm = JC42_DEV_PM_OPS, -- cgit v0.10.2 From 9e37d3e2298e7ca2a9d210532c77a325d07816fe Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 19 Jun 2016 17:06:48 -0700 Subject: hwmon: (lm75) Handle cleanup with devm_add_action Use devm_add_action() to register the function to restore the original chip configuration. Use devm_hwmon_device_register_with_groups() to register the hwmon device, and drop the remove function as no longer needed. Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index 69166ab..0df7455 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -76,7 +76,6 @@ static const u8 LM75_REG_TEMP[3] = { /* Each client has this additional data */ struct lm75_data { struct i2c_client *client; - struct device *hwmon_dev; struct mutex update_lock; u8 orig_conf; u8 resolution; /* In bits, between 9 and 12 */ @@ -185,10 +184,19 @@ static const struct thermal_zone_of_device_ops lm75_of_thermal_ops = { /* device probe and removal */ +static void lm75_remove(void *data) +{ + struct lm75_data *lm75 = data; + struct i2c_client *client = lm75->client; + + i2c_smbus_write_byte_data(client, LM75_REG_CONF, lm75->orig_conf); +} + static int lm75_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; + struct device *hwmon_dev; struct lm75_data *data; int status; u8 set_mask, clr_mask; @@ -298,29 +306,22 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id) new |= set_mask; if (status != new) lm75_write_value(client, LM75_REG_CONF, new); - dev_dbg(dev, "Config %02x\n", new); - data->hwmon_dev = hwmon_device_register_with_groups(dev, client->name, - data, lm75_groups); - if (IS_ERR(data->hwmon_dev)) - return PTR_ERR(data->hwmon_dev); + devm_add_action(dev, lm75_remove, data); - devm_thermal_zone_of_sensor_register(data->hwmon_dev, 0, - data->hwmon_dev, - &lm75_of_thermal_ops); + dev_dbg(dev, "Config %02x\n", new); - dev_info(dev, "%s: sensor '%s'\n", - dev_name(data->hwmon_dev), client->name); + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, lm75_groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); - return 0; -} + devm_thermal_zone_of_sensor_register(hwmon_dev, 0, + hwmon_dev, + &lm75_of_thermal_ops); -static int lm75_remove(struct i2c_client *client) -{ - struct lm75_data *data = i2c_get_clientdata(client); + dev_info(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name); - hwmon_device_unregister(data->hwmon_dev); - lm75_write_value(client, LM75_REG_CONF, data->orig_conf); return 0; } @@ -489,7 +490,6 @@ static struct i2c_driver lm75_driver = { .pm = LM75_DEV_PM_OPS, }, .probe = lm75_probe, - .remove = lm75_remove, .id_table = lm75_ids, .detect = lm75_detect, .address_list = normal_i2c, -- cgit v0.10.2 From 38aefb41b3803873dc366918a2e22f22dca78eac Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 19 Jun 2016 17:11:13 -0700 Subject: hwmon: (lm75) Drop lm75_read_value and lm75_write_value lm75_read_value and lm75_write_value don't really add any value. Replace with direct smbus access functions. Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index 0df7455..7b18cbd 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -89,8 +89,6 @@ struct lm75_data { 2 = hyst */ }; -static int lm75_read_value(struct i2c_client *client, u8 reg); -static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value); static struct lm75_data *lm75_update_device(struct device *dev); @@ -156,7 +154,7 @@ static ssize_t set_temp(struct device *dev, struct device_attribute *da, temp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX); data->temp[nr] = DIV_ROUND_CLOSEST(temp << (resolution - 8), 1000) << (16 - resolution); - lm75_write_value(client, LM75_REG_TEMP[nr], data->temp[nr]); + i2c_smbus_write_word_swapped(client, LM75_REG_TEMP[nr], data->temp[nr]); mutex_unlock(&data->update_lock); return count; } @@ -296,7 +294,7 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id) } /* configure as specified */ - status = lm75_read_value(client, LM75_REG_CONF); + status = i2c_smbus_read_byte_data(client, LM75_REG_CONF); if (status < 0) { dev_dbg(dev, "Can't read config? %d\n", status); return status; @@ -305,7 +303,7 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id) new = status & ~clr_mask; new |= set_mask; if (status != new) - lm75_write_value(client, LM75_REG_CONF, new); + i2c_smbus_write_byte_data(client, LM75_REG_CONF, new); devm_add_action(dev, lm75_remove, data); @@ -450,13 +448,13 @@ static int lm75_suspend(struct device *dev) { int status; struct i2c_client *client = to_i2c_client(dev); - status = lm75_read_value(client, LM75_REG_CONF); + status = i2c_smbus_read_byte_data(client, LM75_REG_CONF); if (status < 0) { dev_dbg(&client->dev, "Can't read config? %d\n", status); return status; } status = status | LM75_SHUTDOWN; - lm75_write_value(client, LM75_REG_CONF, status); + i2c_smbus_write_byte_data(client, LM75_REG_CONF, status); return 0; } @@ -464,13 +462,13 @@ static int lm75_resume(struct device *dev) { int status; struct i2c_client *client = to_i2c_client(dev); - status = lm75_read_value(client, LM75_REG_CONF); + status = i2c_smbus_read_byte_data(client, LM75_REG_CONF); if (status < 0) { dev_dbg(&client->dev, "Can't read config? %d\n", status); return status; } status = status & ~LM75_SHUTDOWN; - lm75_write_value(client, LM75_REG_CONF, status); + i2c_smbus_write_byte_data(client, LM75_REG_CONF, status); return 0; } @@ -497,29 +495,6 @@ static struct i2c_driver lm75_driver = { /*-----------------------------------------------------------------------*/ -/* register access */ - -/* - * All registers are word-sized, except for the configuration register. - * LM75 uses a high-byte first convention, which is exactly opposite to - * the SMBus standard. - */ -static int lm75_read_value(struct i2c_client *client, u8 reg) -{ - if (reg == LM75_REG_CONF) - return i2c_smbus_read_byte_data(client, reg); - else - return i2c_smbus_read_word_swapped(client, reg); -} - -static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value) -{ - if (reg == LM75_REG_CONF) - return i2c_smbus_write_byte_data(client, reg, value); - else - return i2c_smbus_write_word_swapped(client, reg, value); -} - static struct lm75_data *lm75_update_device(struct device *dev) { struct lm75_data *data = dev_get_drvdata(dev); @@ -536,7 +511,8 @@ static struct lm75_data *lm75_update_device(struct device *dev) for (i = 0; i < ARRAY_SIZE(data->temp); i++) { int status; - status = lm75_read_value(client, LM75_REG_TEMP[i]); + status = i2c_smbus_read_word_swapped(client, + LM75_REG_TEMP[i]); if (unlikely(status < 0)) { dev_dbg(dev, "LM75: Failed to read value: reg %d, error %d\n", -- cgit v0.10.2 From 5f7e5e29ab60967a009d307dc4fdecce57efaa9c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 19 Jun 2016 17:56:22 -0700 Subject: hwmon: (lm75) Add update_interval attribute Since we know the chip's update interval, let's make it available to the user. Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index 7b18cbd..fe83f70 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -159,16 +159,29 @@ static ssize_t set_temp(struct device *dev, struct device_attribute *da, return count; } +static ssize_t show_update_interval(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct lm75_data *data = lm75_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%u\n", jiffies_to_msecs(data->sample_time)); +} + static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp, set_temp, 1); static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, show_temp, set_temp, 2); static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static DEVICE_ATTR(update_interval, S_IRUGO, show_update_interval, NULL); static struct attribute *lm75_attrs[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, &sensor_dev_attr_temp1_max.dev_attr.attr, &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &dev_attr_update_interval.attr, NULL }; -- cgit v0.10.2 From e65365fed87f5385c04124b6e7ab8967ca600b26 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 19 Jun 2016 17:49:19 -0700 Subject: hwmon: (lm75) Convert to use regmap Convert to use regmap. Leave caching to regmap and drop the register update function. While this can result in additional read operations if the temperature register is read continuously, it avoids re-reading the limit registers and thus overall reduces complexity. Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 91f145e..f271353 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -958,6 +958,7 @@ config SENSORS_LM75 tristate "National Semiconductor LM75 and compatibles" depends on I2C depends on THERMAL || !THERMAL_OF + select REGMAP_I2C help If you say yes here you get support for one common type of temperature sensor chip, with models including: diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index fe83f70..547a9c8 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -26,8 +26,8 @@ #include #include #include -#include #include +#include #include #include "lm75.h" @@ -66,32 +66,21 @@ static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c, /* The LM75 registers */ +#define LM75_REG_TEMP 0x00 #define LM75_REG_CONF 0x01 -static const u8 LM75_REG_TEMP[3] = { - 0x00, /* input */ - 0x03, /* max */ - 0x02, /* hyst */ -}; +#define LM75_REG_HYST 0x02 +#define LM75_REG_MAX 0x03 /* Each client has this additional data */ struct lm75_data { struct i2c_client *client; - struct mutex update_lock; + struct regmap *regmap; u8 orig_conf; u8 resolution; /* In bits, between 9 and 12 */ u8 resolution_limits; - char valid; /* !=0 if registers are valid */ - unsigned long last_updated; /* In jiffies */ - unsigned long sample_time; /* In jiffies */ - s16 temp[3]; /* Register values, - 0 = input - 1 = max - 2 = hyst */ + unsigned int sample_time; /* In ms */ }; -static struct lm75_data *lm75_update_device(struct device *dev); - - /*-----------------------------------------------------------------------*/ static inline long lm75_reg_to_mc(s16 temp, u8 resolution) @@ -103,12 +92,15 @@ static inline long lm75_reg_to_mc(s16 temp, u8 resolution) static int lm75_read_temp(void *dev, int *temp) { - struct lm75_data *data = lm75_update_device(dev); + struct lm75_data *data = dev_get_drvdata(dev); + unsigned int _temp; + int err; - if (IS_ERR(data)) - return PTR_ERR(data); + err = regmap_read(data->regmap, LM75_REG_TEMP, &_temp); + if (err < 0) + return err; - *temp = lm75_reg_to_mc(data->temp[0], data->resolution); + *temp = lm75_reg_to_mc(_temp, data->resolution); return 0; } @@ -117,13 +109,15 @@ static ssize_t show_temp(struct device *dev, struct device_attribute *da, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(da); - struct lm75_data *data = lm75_update_device(dev); + struct lm75_data *data = dev_get_drvdata(dev); + unsigned int temp = 0; + int err; - if (IS_ERR(data)) - return PTR_ERR(data); + err = regmap_read(data->regmap, attr->index, &temp); + if (err < 0) + return err; - return sprintf(buf, "%ld\n", lm75_reg_to_mc(data->temp[attr->index], - data->resolution)); + return sprintf(buf, "%ld\n", lm75_reg_to_mc(temp, data->resolution)); } static ssize_t set_temp(struct device *dev, struct device_attribute *da, @@ -131,8 +125,6 @@ static ssize_t set_temp(struct device *dev, struct device_attribute *da, { struct sensor_device_attribute *attr = to_sensor_dev_attr(da); struct lm75_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - int nr = attr->index; long temp; int error; u8 resolution; @@ -150,31 +142,29 @@ static ssize_t set_temp(struct device *dev, struct device_attribute *da, else resolution = data->resolution; - mutex_lock(&data->update_lock); temp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX); - data->temp[nr] = DIV_ROUND_CLOSEST(temp << (resolution - 8), - 1000) << (16 - resolution); - i2c_smbus_write_word_swapped(client, LM75_REG_TEMP[nr], data->temp[nr]); - mutex_unlock(&data->update_lock); + temp = DIV_ROUND_CLOSEST(temp << (resolution - 8), + 1000) << (16 - resolution); + error = regmap_write(data->regmap, attr->index, temp); + if (error < 0) + return error; + return count; } static ssize_t show_update_interval(struct device *dev, struct device_attribute *da, char *buf) { - struct lm75_data *data = lm75_update_device(dev); - - if (IS_ERR(data)) - return PTR_ERR(data); + struct lm75_data *data = dev_get_drvdata(dev); - return sprintf(buf, "%u\n", jiffies_to_msecs(data->sample_time)); + return sprintf(buf, "%u\n", data->sample_time); } static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, - show_temp, set_temp, 1); + show_temp, set_temp, LM75_REG_MAX); static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, - show_temp, set_temp, 2); -static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); + show_temp, set_temp, LM75_REG_HYST); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, LM75_REG_TEMP); static DEVICE_ATTR(update_interval, S_IRUGO, show_update_interval, NULL); static struct attribute *lm75_attrs[] = { @@ -195,6 +185,27 @@ static const struct thermal_zone_of_device_ops lm75_of_thermal_ops = { /* device probe and removal */ +static bool lm75_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return reg != LM75_REG_TEMP; +} + +static bool lm75_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg == LM75_REG_TEMP; +} + +static const struct regmap_config lm75_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = LM75_REG_MAX, + .writeable_reg = lm75_is_writeable_reg, + .volatile_reg = lm75_is_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_BIG, + .cache_type = REGCACHE_RBTREE, + .use_single_rw = true, +}; + static void lm75_remove(void *data) { struct lm75_data *lm75 = data; @@ -223,8 +234,10 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id) return -ENOMEM; data->client = client; - i2c_set_clientdata(client, data); - mutex_init(&data->update_lock); + + data->regmap = devm_regmap_init_i2c(client, &lm75_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); /* Set to LM75 resolution (9 bits, 1/2 degree C) and range. * Then tweak to be more precise when appropriate. @@ -236,7 +249,7 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id) case adt75: clr_mask |= 1 << 5; /* not one-shot mode */ data->resolution = 12; - data->sample_time = HZ / 8; + data->sample_time = MSEC_PER_SEC / 8; break; case ds1775: case ds75: @@ -244,35 +257,35 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id) clr_mask |= 3 << 5; set_mask |= 2 << 5; /* 11-bit mode */ data->resolution = 11; - data->sample_time = HZ; + data->sample_time = MSEC_PER_SEC; break; case ds7505: set_mask |= 3 << 5; /* 12-bit mode */ data->resolution = 12; - data->sample_time = HZ / 4; + data->sample_time = MSEC_PER_SEC / 4; break; case g751: case lm75: case lm75a: data->resolution = 9; - data->sample_time = HZ / 2; + data->sample_time = MSEC_PER_SEC / 2; break; case lm75b: data->resolution = 11; - data->sample_time = HZ / 4; + data->sample_time = MSEC_PER_SEC / 4; break; case max6625: data->resolution = 9; - data->sample_time = HZ / 4; + data->sample_time = MSEC_PER_SEC / 4; break; case max6626: data->resolution = 12; data->resolution_limits = 9; - data->sample_time = HZ / 4; + data->sample_time = MSEC_PER_SEC / 4; break; case tcn75: data->resolution = 9; - data->sample_time = HZ / 8; + data->sample_time = MSEC_PER_SEC / 8; break; case mcp980x: data->resolution_limits = 9; @@ -281,14 +294,14 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id) case tmp101: set_mask |= 3 << 5; /* 12-bit mode */ data->resolution = 12; - data->sample_time = HZ; + data->sample_time = MSEC_PER_SEC; clr_mask |= 1 << 7; /* not one-shot mode */ break; case tmp112: set_mask |= 3 << 5; /* 12-bit mode */ clr_mask |= 1 << 7; /* not one-shot mode */ data->resolution = 12; - data->sample_time = HZ / 4; + data->sample_time = MSEC_PER_SEC / 4; break; case tmp105: case tmp175: @@ -297,12 +310,12 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id) set_mask |= 3 << 5; /* 12-bit mode */ clr_mask |= 1 << 7; /* not one-shot mode */ data->resolution = 12; - data->sample_time = HZ / 2; + data->sample_time = MSEC_PER_SEC / 2; break; case tmp75c: clr_mask |= 1 << 5; /* not one-shot mode */ data->resolution = 12; - data->sample_time = HZ / 4; + data->sample_time = MSEC_PER_SEC / 4; break; } @@ -506,45 +519,6 @@ static struct i2c_driver lm75_driver = { .address_list = normal_i2c, }; -/*-----------------------------------------------------------------------*/ - -static struct lm75_data *lm75_update_device(struct device *dev) -{ - struct lm75_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - struct lm75_data *ret = data; - - mutex_lock(&data->update_lock); - - if (time_after(jiffies, data->last_updated + data->sample_time) - || !data->valid) { - int i; - dev_dbg(&client->dev, "Starting lm75 update\n"); - - for (i = 0; i < ARRAY_SIZE(data->temp); i++) { - int status; - - status = i2c_smbus_read_word_swapped(client, - LM75_REG_TEMP[i]); - if (unlikely(status < 0)) { - dev_dbg(dev, - "LM75: Failed to read value: reg %d, error %d\n", - LM75_REG_TEMP[i], status); - ret = ERR_PTR(status); - data->valid = 0; - goto abort; - } - data->temp[i] = status; - } - data->last_updated = jiffies; - data->valid = 1; - } - -abort: - mutex_unlock(&data->update_lock); - return ret; -} - module_i2c_driver(lm75_driver); MODULE_AUTHOR("Frodo Looijaard "); -- cgit v0.10.2 From 1f17a444b42bd7522016417a871f0485abeffda4 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 13 Jun 2016 06:19:11 -0700 Subject: hwmon: (lm90) Use devm_add_action for cleanup Use devm_add_action where possible to simplify error handling and cleanup on remove. Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index e30a593..4b530ef 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -369,7 +369,6 @@ struct lm90_data { struct device *hwmon_dev; const struct attribute_group *groups[6]; struct mutex update_lock; - struct regulator *regulator; char valid; /* zero until following fields are valid */ unsigned long last_updated; /* in jiffies */ int kind; @@ -1404,8 +1403,11 @@ static int lm90_detect(struct i2c_client *client, return 0; } -static void lm90_restore_conf(struct i2c_client *client, struct lm90_data *data) +static void lm90_restore_conf(void *_data) { + struct lm90_data *data = _data; + struct i2c_client *client = data->client; + /* Restore initial configuration */ i2c_smbus_write_byte_data(client, LM90_REG_W_CONVRATE, data->convrate_orig); @@ -1456,6 +1458,8 @@ static void lm90_init_client(struct i2c_client *client, struct lm90_data *data) config &= 0xBF; /* run */ if (config != data->config_orig) /* Only write if changed */ i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, config); + + devm_add_action(&client->dev, lm90_restore_conf, data); } static bool lm90_is_tripped(struct i2c_client *client, u16 *status) @@ -1506,6 +1510,16 @@ static irqreturn_t lm90_irq_thread(int irq, void *dev_id) return IRQ_NONE; } +static void lm90_remove_pec(void *dev) +{ + device_remove_file(dev, &dev_attr_pec); +} + +static void lm90_regulator_disable(void *regulator) +{ + regulator_disable(regulator); +} + static int lm90_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -1526,6 +1540,8 @@ static int lm90_probe(struct i2c_client *client, return err; } + devm_add_action(dev, lm90_regulator_disable, regulator); + data = devm_kzalloc(dev, sizeof(struct lm90_data), GFP_KERNEL); if (!data) return -ENOMEM; @@ -1534,8 +1550,6 @@ static int lm90_probe(struct i2c_client *client, i2c_set_clientdata(client, data); mutex_init(&data->update_lock); - data->regulator = regulator; - /* Set the device type */ data->kind = id->driver_data; if (data->kind == adm1032) { @@ -1577,15 +1591,14 @@ static int lm90_probe(struct i2c_client *client, if (client->flags & I2C_CLIENT_PEC) { err = device_create_file(dev, &dev_attr_pec); if (err) - goto exit_restore; + return err; + devm_add_action(dev, lm90_remove_pec, dev); } data->hwmon_dev = hwmon_device_register_with_groups(dev, client->name, data, data->groups); - if (IS_ERR(data->hwmon_dev)) { - err = PTR_ERR(data->hwmon_dev); - goto exit_remove_pec; - } + if (IS_ERR(data->hwmon_dev)) + return PTR_ERR(data->hwmon_dev); if (client->irq) { dev_dbg(dev, "IRQ: %d\n", client->irq); @@ -1603,12 +1616,6 @@ static int lm90_probe(struct i2c_client *client, exit_unregister: hwmon_device_unregister(data->hwmon_dev); -exit_remove_pec: - device_remove_file(dev, &dev_attr_pec); -exit_restore: - lm90_restore_conf(client, data); - regulator_disable(data->regulator); - return err; } @@ -1617,9 +1624,6 @@ static int lm90_remove(struct i2c_client *client) struct lm90_data *data = i2c_get_clientdata(client); hwmon_device_unregister(data->hwmon_dev); - device_remove_file(&client->dev, &dev_attr_pec); - lm90_restore_conf(client, data); - regulator_disable(data->regulator); return 0; } -- cgit v0.10.2 From 6e5f62b9e3651e619a6baee9e11f6d9c8e4fd657 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 13 Jun 2016 06:28:03 -0700 Subject: hwmon: (lm90) Use devm_hwmon_device_register_with_groups Since all other cleanup handled with devm_add_action, we can use devm_hwmon_device_register_with_groups() to register the hwmon device, and drop the remove function. Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 4b530ef..9d733cb 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -366,7 +366,6 @@ enum lm90_temp11_reg_index { struct lm90_data { struct i2c_client *client; - struct device *hwmon_dev; const struct attribute_group *groups[6]; struct mutex update_lock; char valid; /* zero until following fields are valid */ @@ -1527,6 +1526,7 @@ static int lm90_probe(struct i2c_client *client, struct i2c_adapter *adapter = to_i2c_adapter(dev->parent); struct lm90_data *data; struct regulator *regulator; + struct device *hwmon_dev; int groups = 0; int err; @@ -1595,10 +1595,10 @@ static int lm90_probe(struct i2c_client *client, devm_add_action(dev, lm90_remove_pec, dev); } - data->hwmon_dev = hwmon_device_register_with_groups(dev, client->name, - data, data->groups); - if (IS_ERR(data->hwmon_dev)) - return PTR_ERR(data->hwmon_dev); + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); if (client->irq) { dev_dbg(dev, "IRQ: %d\n", client->irq); @@ -1608,24 +1608,11 @@ static int lm90_probe(struct i2c_client *client, "lm90", client); if (err < 0) { dev_err(dev, "cannot request IRQ %d\n", client->irq); - goto exit_unregister; + return err; } } return 0; - -exit_unregister: - hwmon_device_unregister(data->hwmon_dev); - return err; -} - -static int lm90_remove(struct i2c_client *client) -{ - struct lm90_data *data = i2c_get_clientdata(client); - - hwmon_device_unregister(data->hwmon_dev); - - return 0; } static void lm90_alert(struct i2c_client *client, unsigned int flag) @@ -1659,7 +1646,6 @@ static struct i2c_driver lm90_driver = { .name = "lm90", }, .probe = lm90_probe, - .remove = lm90_remove, .alert = lm90_alert, .id_table = lm90_id, .detect = lm90_detect, -- cgit v0.10.2 From 37ad04d7a9a5c8c066e2e17d960f6563e194e53a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 13 Jun 2016 06:57:37 -0700 Subject: hwmon: (lm90) Simplify read functions Return both error code and register value as return code from read functions, and always check for errors. This reduces code size on x86_64 by more than 1k while at the same time improving error resiliency. Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 9d733cb..2a330bd 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -171,7 +171,6 @@ enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680, #define SA56004_REG_R_LOCAL_TEMPL 0x22 -#define LM90_DEF_CONVRATE_RVAL 6 /* Def conversion rate register value */ #define LM90_MAX_CONVRATE_MS 16000 /* Maximum conversion rate in ms */ /* TMP451 registers */ @@ -410,7 +409,7 @@ static inline s32 adm1032_write_byte(struct i2c_client *client, u8 value) * because we don't want the address pointer to change between the write * byte and the read byte transactions. */ -static int lm90_read_reg(struct i2c_client *client, u8 reg, u8 *value) +static int lm90_read_reg(struct i2c_client *client, u8 reg) { int err; @@ -421,20 +420,12 @@ static int lm90_read_reg(struct i2c_client *client, u8 reg, u8 *value) } else err = i2c_smbus_read_byte_data(client, reg); - if (err < 0) { - dev_warn(&client->dev, "Register %#02x read failed (%d)\n", - reg, err); - return err; - } - *value = err; - - return 0; + return err; } -static int lm90_read16(struct i2c_client *client, u8 regh, u8 regl, u16 *value) +static int lm90_read16(struct i2c_client *client, u8 regh, u8 regl) { - int err; - u8 oldh, newh, l; + int oldh, newh, l; /* * There is a trick here. We have to read two registers to have the @@ -449,18 +440,21 @@ static int lm90_read16(struct i2c_client *client, u8 regh, u8 regl, u16 *value) * we have to read the low byte again, and now we believe we have a * correct reading. */ - if ((err = lm90_read_reg(client, regh, &oldh)) - || (err = lm90_read_reg(client, regl, &l)) - || (err = lm90_read_reg(client, regh, &newh))) - return err; + oldh = lm90_read_reg(client, regh); + if (oldh < 0) + return oldh; + l = lm90_read_reg(client, regl); + if (l < 0) + return l; + newh = lm90_read_reg(client, regh); + if (newh < 0) + return newh; if (oldh != newh) { - err = lm90_read_reg(client, regl, &l); - if (err) - return err; + l = lm90_read_reg(client, regl); + if (l < 0) + return l; } - *value = (newh << 8) | l; - - return 0; + return (newh << 8) | l; } /* @@ -471,20 +465,23 @@ static int lm90_read16(struct i2c_client *client, u8 regh, u8 regl, u16 *value) * various registers have different meanings as a result of selecting a * non-default remote channel. */ -static inline void lm90_select_remote_channel(struct i2c_client *client, - struct lm90_data *data, - int channel) +static inline int lm90_select_remote_channel(struct i2c_client *client, + struct lm90_data *data, + int channel) { - u8 config; + int config; if (data->kind == max6696) { - lm90_read_reg(client, LM90_REG_R_CONFIG1, &config); + config = lm90_read_reg(client, LM90_REG_R_CONFIG1); + if (config < 0) + return config; config &= ~0x08; if (channel) config |= 0x08; i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, config); } + return 0; } /* @@ -516,104 +513,153 @@ static struct lm90_data *lm90_update_device(struct device *dev) struct lm90_data *data = dev_get_drvdata(dev); struct i2c_client *client = data->client; unsigned long next_update; + int val = 0; mutex_lock(&data->update_lock); next_update = data->last_updated + msecs_to_jiffies(data->update_interval); if (time_after(jiffies, next_update) || !data->valid) { - u8 h, l; - u8 alarms; - dev_dbg(&client->dev, "Updating lm90 data.\n"); - lm90_read_reg(client, LM90_REG_R_LOCAL_LOW, - &data->temp8[LOCAL_LOW]); - lm90_read_reg(client, LM90_REG_R_LOCAL_HIGH, - &data->temp8[LOCAL_HIGH]); - lm90_read_reg(client, LM90_REG_R_LOCAL_CRIT, - &data->temp8[LOCAL_CRIT]); - lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT, - &data->temp8[REMOTE_CRIT]); - lm90_read_reg(client, LM90_REG_R_TCRIT_HYST, &data->temp_hyst); + val = lm90_read_reg(client, LM90_REG_R_LOCAL_LOW); + if (val < 0) + goto error; + data->temp8[LOCAL_LOW] = val; + + val = lm90_read_reg(client, LM90_REG_R_LOCAL_HIGH); + if (val < 0) + goto error; + data->temp8[LOCAL_HIGH] = val; + + val = lm90_read_reg(client, LM90_REG_R_LOCAL_CRIT); + if (val < 0) + goto error; + data->temp8[LOCAL_CRIT] = val; + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT); + if (val < 0) + goto error; + data->temp8[REMOTE_CRIT] = val; + val = lm90_read_reg(client, LM90_REG_R_TCRIT_HYST); + if (val < 0) + goto error; + data->temp_hyst = val; if (data->reg_local_ext) { - lm90_read16(client, LM90_REG_R_LOCAL_TEMP, - data->reg_local_ext, - &data->temp11[LOCAL_TEMP]); + val = lm90_read16(client, LM90_REG_R_LOCAL_TEMP, + data->reg_local_ext); + if (val < 0) + goto error; + data->temp11[LOCAL_TEMP] = val; } else { - if (lm90_read_reg(client, LM90_REG_R_LOCAL_TEMP, - &h) == 0) - data->temp11[LOCAL_TEMP] = h << 8; + val = lm90_read_reg(client, LM90_REG_R_LOCAL_TEMP); + if (val < 0) + goto error; + data->temp11[LOCAL_TEMP] = val << 8; } - lm90_read16(client, LM90_REG_R_REMOTE_TEMPH, - LM90_REG_R_REMOTE_TEMPL, - &data->temp11[REMOTE_TEMP]); - - if (lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH, &h) == 0) { - data->temp11[REMOTE_LOW] = h << 8; - if ((data->flags & LM90_HAVE_REM_LIMIT_EXT) - && lm90_read_reg(client, LM90_REG_R_REMOTE_LOWL, - &l) == 0) - data->temp11[REMOTE_LOW] |= l; + val = lm90_read16(client, LM90_REG_R_REMOTE_TEMPH, + LM90_REG_R_REMOTE_TEMPL); + if (val < 0) + goto error; + data->temp11[REMOTE_TEMP] = val; + + lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH); + if (val < 0) + goto error; + data->temp11[REMOTE_LOW] = val << 8; + if (data->flags & LM90_HAVE_REM_LIMIT_EXT) { + val = lm90_read_reg(client, LM90_REG_R_REMOTE_LOWL); + if (val < 0) + goto error; + data->temp11[REMOTE_LOW] |= val; } - if (lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH, &h) == 0) { - data->temp11[REMOTE_HIGH] = h << 8; - if ((data->flags & LM90_HAVE_REM_LIMIT_EXT) - && lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHL, - &l) == 0) - data->temp11[REMOTE_HIGH] |= l; + val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH); + if (val < 0) + goto error; + data->temp11[REMOTE_HIGH] = val << 8; + if (data->flags & LM90_HAVE_REM_LIMIT_EXT) { + val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHL); + if (val < 0) + goto error; + data->temp11[REMOTE_HIGH] |= val; } if (data->flags & LM90_HAVE_OFFSET) { - if (lm90_read_reg(client, LM90_REG_R_REMOTE_OFFSH, - &h) == 0 - && lm90_read_reg(client, LM90_REG_R_REMOTE_OFFSL, - &l) == 0) - data->temp11[REMOTE_OFFSET] = (h << 8) | l; + val = lm90_read16(client, LM90_REG_R_REMOTE_OFFSH, + LM90_REG_R_REMOTE_OFFSL); + if (val < 0) + goto error; + data->temp11[REMOTE_OFFSET] = val; } if (data->flags & LM90_HAVE_EMERGENCY) { - lm90_read_reg(client, MAX6659_REG_R_LOCAL_EMERG, - &data->temp8[LOCAL_EMERG]); - lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG, - &data->temp8[REMOTE_EMERG]); + val = lm90_read_reg(client, MAX6659_REG_R_LOCAL_EMERG); + if (val < 0) + goto error; + data->temp8[LOCAL_EMERG] = val; + val = lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG); + if (val < 0) + goto error; + data->temp8[REMOTE_EMERG] = val; } - lm90_read_reg(client, LM90_REG_R_STATUS, &alarms); - data->alarms = alarms; /* save as 16 bit value */ + val = lm90_read_reg(client, LM90_REG_R_STATUS); + if (val < 0) + goto error; + data->alarms = val; /* lower 8 bit of alarms */ if (data->kind == max6696) { - lm90_select_remote_channel(client, data, 1); - lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT, - &data->temp8[REMOTE2_CRIT]); - lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG, - &data->temp8[REMOTE2_EMERG]); - lm90_read16(client, LM90_REG_R_REMOTE_TEMPH, - LM90_REG_R_REMOTE_TEMPL, - &data->temp11[REMOTE2_TEMP]); - if (!lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH, &h)) - data->temp11[REMOTE2_LOW] = h << 8; - if (!lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH, &h)) - data->temp11[REMOTE2_HIGH] = h << 8; + val = lm90_select_remote_channel(client, data, 1); + if (val < 0) + goto error; + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT); + if (val < 0) + goto error; + data->temp8[REMOTE2_CRIT] = val; + + val = lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG); + if (val < 0) + goto error; + data->temp8[REMOTE2_EMERG] = val; + + val = lm90_read16(client, LM90_REG_R_REMOTE_TEMPH, + LM90_REG_R_REMOTE_TEMPL); + if (val < 0) + goto error; + data->temp11[REMOTE2_TEMP] = val; + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH); + if (val < 0) + goto error; + data->temp11[REMOTE2_LOW] = val << 8; + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH); + if (val < 0) + goto error; + data->temp11[REMOTE2_HIGH] = val << 8; + lm90_select_remote_channel(client, data, 0); - if (!lm90_read_reg(client, MAX6696_REG_R_STATUS2, - &alarms)) - data->alarms |= alarms << 8; + val = lm90_read_reg(client, MAX6696_REG_R_STATUS2); + if (val < 0) + goto error; + data->alarms |= val << 8; } /* * Re-enable ALERT# output if it was originally enabled and * relevant alarms are all clear */ - if ((data->config_orig & 0x80) == 0 - && (data->alarms & data->alert_alarms) == 0) { - u8 config; + if (!(data->config_orig & 0x80) && + !(data->alarms & data->alert_alarms)) { + val = lm90_read_reg(client, LM90_REG_R_CONFIG1); + if (val < 0) + goto error; - lm90_read_reg(client, LM90_REG_R_CONFIG1, &config); - if (config & 0x80) { + if (val & 0x80) { dev_dbg(&client->dev, "Re-enabling ALERT#\n"); i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, - config & ~0x80); + val & ~0x80); } } @@ -621,8 +667,12 @@ static struct lm90_data *lm90_update_device(struct device *dev) data->valid = 1; } +error: mutex_unlock(&data->update_lock); + if (val < 0) + return ERR_PTR(val); + return data; } @@ -764,6 +814,9 @@ static ssize_t show_temp8(struct device *dev, struct device_attribute *devattr, struct lm90_data *data = lm90_update_device(dev); int temp; + if (IS_ERR(data)) + return PTR_ERR(data); + if (data->kind == adt7461 || data->kind == tmp451) temp = temp_from_u8_adt7461(data, data->temp8[attr->index]); else if (data->kind == max6646) @@ -830,6 +883,9 @@ static ssize_t show_temp11(struct device *dev, struct device_attribute *devattr, struct lm90_data *data = lm90_update_device(dev); int temp; + if (IS_ERR(data)) + return PTR_ERR(data); + if (data->kind == adt7461 || data->kind == tmp451) temp = temp_from_u16_adt7461(data, data->temp11[attr->index]); else if (data->kind == max6646) @@ -905,6 +961,9 @@ static ssize_t show_temphyst(struct device *dev, struct lm90_data *data = lm90_update_device(dev); int temp; + if (IS_ERR(data)) + return PTR_ERR(data); + if (data->kind == adt7461 || data->kind == tmp451) temp = temp_from_u8_adt7461(data, data->temp8[attr->index]); else if (data->kind == max6646) @@ -951,6 +1010,10 @@ static ssize_t show_alarms(struct device *dev, struct device_attribute *dummy, char *buf) { struct lm90_data *data = lm90_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->alarms); } @@ -961,6 +1024,9 @@ static ssize_t show_alarm(struct device *dev, struct device_attribute struct lm90_data *data = lm90_update_device(dev); int bitnr = attr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1); } @@ -1414,24 +1480,22 @@ static void lm90_restore_conf(void *_data) data->config_orig); } -static void lm90_init_client(struct i2c_client *client, struct lm90_data *data) +static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) { - u8 config, convrate; + int config, convrate; - if (lm90_read_reg(client, LM90_REG_R_CONVRATE, &convrate) < 0) { - dev_warn(&client->dev, "Failed to read convrate register!\n"); - convrate = LM90_DEF_CONVRATE_RVAL; - } + convrate = lm90_read_reg(client, LM90_REG_R_CONVRATE); + if (convrate < 0) + return convrate; data->convrate_orig = convrate; /* * Start the conversions. */ lm90_set_convrate(client, data, 500); /* 500ms; 2Hz conversion rate */ - if (lm90_read_reg(client, LM90_REG_R_CONFIG1, &config) < 0) { - dev_warn(&client->dev, "Initialization failed!\n"); - return; - } + config = lm90_read_reg(client, LM90_REG_R_CONFIG1); + if (config < 0) + return config; data->config_orig = config; /* Check Temperature Range Select */ @@ -1459,17 +1523,24 @@ static void lm90_init_client(struct i2c_client *client, struct lm90_data *data) i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, config); devm_add_action(&client->dev, lm90_restore_conf, data); + + return 0; } static bool lm90_is_tripped(struct i2c_client *client, u16 *status) { struct lm90_data *data = i2c_get_clientdata(client); - u8 st, st2 = 0; + int st, st2 = 0; - lm90_read_reg(client, LM90_REG_R_STATUS, &st); + st = lm90_read_reg(client, LM90_REG_R_STATUS); + if (st < 0) + return false; - if (data->kind == max6696) - lm90_read_reg(client, MAX6696_REG_R_STATUS2, &st2); + if (data->kind == max6696) { + st2 = lm90_read_reg(client, MAX6696_REG_R_STATUS2); + if (st2 < 0) + return false; + } *status = st | (st2 << 8); @@ -1571,7 +1642,11 @@ static int lm90_probe(struct i2c_client *client, data->max_convrate = lm90_params[data->kind].max_convrate; /* Initialize the LM90 chip */ - lm90_init_client(client, data); + err = lm90_init_client(client, data); + if (err < 0) { + dev_err(dev, "Failed to initialize device\n"); + return err; + } /* Register sysfs hooks */ data->groups[groups++] = &lm90_group; @@ -1627,13 +1702,16 @@ static void lm90_alert(struct i2c_client *client, unsigned int flag) */ struct lm90_data *data = i2c_get_clientdata(client); - if ((data->flags & LM90_HAVE_BROKEN_ALERT) - && (alarms & data->alert_alarms)) { - u8 config; + if ((data->flags & LM90_HAVE_BROKEN_ALERT) && + (alarms & data->alert_alarms)) { + int config; + dev_dbg(&client->dev, "Disabling ALERT#\n"); - lm90_read_reg(client, LM90_REG_R_CONFIG1, &config); - i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, - config | 0x80); + config = lm90_read_reg(client, LM90_REG_R_CONFIG1); + if (config >= 0) + i2c_smbus_write_byte_data(client, + LM90_REG_W_CONFIG1, + config | 0x80); } } else { dev_info(&client->dev, "Everything OK\n"); -- cgit v0.10.2 From 10bfef47bd259a800a731a2b459599b14cfb8052 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 13 Jun 2016 19:26:45 -0700 Subject: hwmon: (lm90) Read limit registers only once Read limit registers only once at startup or after errors to improve driver performance. Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 2a330bd..322a73d 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -508,6 +508,102 @@ static void lm90_set_convrate(struct i2c_client *client, struct lm90_data *data, data->update_interval = DIV_ROUND_CLOSEST(update_interval, 64); } +static int lm90_update_limits(struct device *dev) +{ + struct lm90_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int val; + + val = lm90_read_reg(client, LM90_REG_R_LOCAL_CRIT); + if (val < 0) + return val; + data->temp8[LOCAL_CRIT] = val; + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT); + if (val < 0) + return val; + data->temp8[REMOTE_CRIT] = val; + + val = lm90_read_reg(client, LM90_REG_R_TCRIT_HYST); + if (val < 0) + return val; + data->temp_hyst = val; + + lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH); + if (val < 0) + return val; + data->temp11[REMOTE_LOW] = val << 8; + + if (data->flags & LM90_HAVE_REM_LIMIT_EXT) { + val = lm90_read_reg(client, LM90_REG_R_REMOTE_LOWL); + if (val < 0) + return val; + data->temp11[REMOTE_LOW] |= val; + } + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH); + if (val < 0) + return val; + data->temp11[REMOTE_HIGH] = val << 8; + + if (data->flags & LM90_HAVE_REM_LIMIT_EXT) { + val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHL); + if (val < 0) + return val; + data->temp11[REMOTE_HIGH] |= val; + } + + if (data->flags & LM90_HAVE_OFFSET) { + val = lm90_read16(client, LM90_REG_R_REMOTE_OFFSH, + LM90_REG_R_REMOTE_OFFSL); + if (val < 0) + return val; + data->temp11[REMOTE_OFFSET] = val; + } + + if (data->flags & LM90_HAVE_EMERGENCY) { + val = lm90_read_reg(client, MAX6659_REG_R_LOCAL_EMERG); + if (val < 0) + return val; + data->temp8[LOCAL_EMERG] = val; + + val = lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG); + if (val < 0) + return val; + data->temp8[REMOTE_EMERG] = val; + } + + if (data->kind == max6696) { + val = lm90_select_remote_channel(client, data, 1); + if (val < 0) + return val; + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT); + if (val < 0) + return val; + data->temp8[REMOTE2_CRIT] = val; + + val = lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG); + if (val < 0) + return val; + data->temp8[REMOTE2_EMERG] = val; + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH); + if (val < 0) + return val; + data->temp11[REMOTE2_LOW] = val << 8; + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH); + if (val < 0) + return val; + data->temp11[REMOTE2_HIGH] = val << 8; + + lm90_select_remote_channel(client, data, 0); + } + + return 0; +} + static struct lm90_data *lm90_update_device(struct device *dev) { struct lm90_data *data = dev_get_drvdata(dev); @@ -517,10 +613,19 @@ static struct lm90_data *lm90_update_device(struct device *dev) mutex_lock(&data->update_lock); + if (!data->valid) { + val = lm90_update_limits(dev); + if (val < 0) + goto error; + } + next_update = data->last_updated + msecs_to_jiffies(data->update_interval); if (time_after(jiffies, next_update) || !data->valid) { dev_dbg(&client->dev, "Updating lm90 data.\n"); + + data->valid = 0; + val = lm90_read_reg(client, LM90_REG_R_LOCAL_LOW); if (val < 0) goto error; @@ -531,20 +636,6 @@ static struct lm90_data *lm90_update_device(struct device *dev) goto error; data->temp8[LOCAL_HIGH] = val; - val = lm90_read_reg(client, LM90_REG_R_LOCAL_CRIT); - if (val < 0) - goto error; - data->temp8[LOCAL_CRIT] = val; - - val = lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT); - if (val < 0) - goto error; - data->temp8[REMOTE_CRIT] = val; - val = lm90_read_reg(client, LM90_REG_R_TCRIT_HYST); - if (val < 0) - goto error; - data->temp_hyst = val; - if (data->reg_local_ext) { val = lm90_read16(client, LM90_REG_R_LOCAL_TEMP, data->reg_local_ext); @@ -563,44 +654,6 @@ static struct lm90_data *lm90_update_device(struct device *dev) goto error; data->temp11[REMOTE_TEMP] = val; - lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH); - if (val < 0) - goto error; - data->temp11[REMOTE_LOW] = val << 8; - if (data->flags & LM90_HAVE_REM_LIMIT_EXT) { - val = lm90_read_reg(client, LM90_REG_R_REMOTE_LOWL); - if (val < 0) - goto error; - data->temp11[REMOTE_LOW] |= val; - } - val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH); - if (val < 0) - goto error; - data->temp11[REMOTE_HIGH] = val << 8; - if (data->flags & LM90_HAVE_REM_LIMIT_EXT) { - val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHL); - if (val < 0) - goto error; - data->temp11[REMOTE_HIGH] |= val; - } - - if (data->flags & LM90_HAVE_OFFSET) { - val = lm90_read16(client, LM90_REG_R_REMOTE_OFFSH, - LM90_REG_R_REMOTE_OFFSL); - if (val < 0) - goto error; - data->temp11[REMOTE_OFFSET] = val; - } - if (data->flags & LM90_HAVE_EMERGENCY) { - val = lm90_read_reg(client, MAX6659_REG_R_LOCAL_EMERG); - if (val < 0) - goto error; - data->temp8[LOCAL_EMERG] = val; - val = lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG); - if (val < 0) - goto error; - data->temp8[REMOTE_EMERG] = val; - } val = lm90_read_reg(client, LM90_REG_R_STATUS); if (val < 0) goto error; @@ -611,32 +664,12 @@ static struct lm90_data *lm90_update_device(struct device *dev) if (val < 0) goto error; - val = lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT); - if (val < 0) - goto error; - data->temp8[REMOTE2_CRIT] = val; - - val = lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG); - if (val < 0) - goto error; - data->temp8[REMOTE2_EMERG] = val; - val = lm90_read16(client, LM90_REG_R_REMOTE_TEMPH, LM90_REG_R_REMOTE_TEMPL); if (val < 0) goto error; data->temp11[REMOTE2_TEMP] = val; - val = lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH); - if (val < 0) - goto error; - data->temp11[REMOTE2_LOW] = val << 8; - - val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH); - if (val < 0) - goto error; - data->temp11[REMOTE2_HIGH] = val << 8; - lm90_select_remote_channel(client, data, 0); val = lm90_read_reg(client, MAX6696_REG_R_STATUS2); -- cgit v0.10.2 From 2f83ab77b43807edf695ca9a31673be2197a33b3 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 18 Jun 2016 15:39:08 -0700 Subject: hwmon: (lm90) Use bool for valid flag Use bool for valid flag and leave it up to the compiler to find an optimal representation. Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 322a73d..c38d6e4 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -367,7 +367,7 @@ struct lm90_data { struct i2c_client *client; const struct attribute_group *groups[6]; struct mutex update_lock; - char valid; /* zero until following fields are valid */ + bool valid; /* true if register values are valid */ unsigned long last_updated; /* in jiffies */ int kind; u32 flags; @@ -624,7 +624,7 @@ static struct lm90_data *lm90_update_device(struct device *dev) if (time_after(jiffies, next_update) || !data->valid) { dev_dbg(&client->dev, "Updating lm90 data.\n"); - data->valid = 0; + data->valid = false; val = lm90_read_reg(client, LM90_REG_R_LOCAL_LOW); if (val < 0) @@ -697,7 +697,7 @@ static struct lm90_data *lm90_update_device(struct device *dev) } data->last_updated = jiffies; - data->valid = 1; + data->valid = true; } error: -- cgit v0.10.2 From 589f707c72426793a0f537592098a5a2a3117dc0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 18 Jun 2016 19:58:26 -0700 Subject: hwmon: (lm90) Drop unnecessary else statements checkpatch rightfully complains that else after return is unnecessary. Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index c38d6e4..f51e758 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -790,16 +790,14 @@ static inline int temp_from_u8_adt7461(struct lm90_data *data, u8 val) { if (data->flags & LM90_FLAG_ADT7461_EXT) return (val - 64) * 1000; - else - return temp_from_s8(val); + return temp_from_s8(val); } static inline int temp_from_u16_adt7461(struct lm90_data *data, u16 val) { if (data->flags & LM90_FLAG_ADT7461_EXT) return (val - 0x4000) / 64 * 250; - else - return temp_from_s16(val); + return temp_from_s16(val); } static u8 temp_to_u8_adt7461(struct lm90_data *data, long val) @@ -810,13 +808,12 @@ static u8 temp_to_u8_adt7461(struct lm90_data *data, long val) if (val >= 191000) return 0xFF; return (val + 500 + 64000) / 1000; - } else { - if (val <= 0) - return 0; - if (val >= 127000) - return 127; - return (val + 500) / 1000; } + if (val <= 0) + return 0; + if (val >= 127000) + return 127; + return (val + 500) / 1000; } static u16 temp_to_u16_adt7461(struct lm90_data *data, long val) @@ -827,13 +824,12 @@ static u16 temp_to_u16_adt7461(struct lm90_data *data, long val) if (val >= 191750) return 0xFFC0; return (val + 64000 + 125) / 250 * 64; - } else { - if (val <= 0) - return 0; - if (val >= 127750) - return 0x7FC0; - return (val + 125) / 250 * 64; } + if (val <= 0) + return 0; + if (val >= 127750) + return 0x7FC0; + return (val + 125) / 250 * 64; } /* -- cgit v0.10.2 From 3d8f7a89a1979be7f631cf06e02f882adf144885 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 19 Jun 2016 20:34:57 -0700 Subject: hwmon: (tmp102) Improve handling of initial read delay If the chip was in shutdown mode when the driver was loaded, the first conversion is ready no more than 35 milli-seconds after the chip was taken out of shutdown. The driver delay was so far set to 333 ms (HZ / 3), which is much higher than the maximum time needed by the chip. Reduce the time to 35 milli-seconds. Introduce a 'valid' flag to ensure that sensor data is actually read even if requested less than 333 ms after the driver was loaded. Acked-by: Nishanth Menon Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c index 5bdf262..fd4a451 100644 --- a/drivers/hwmon/tmp102.c +++ b/drivers/hwmon/tmp102.c @@ -13,6 +13,7 @@ * GNU General Public License for more details. */ +#include #include #include #include @@ -46,13 +47,16 @@ #define TMP102_TLOW_REG 0x02 #define TMP102_THIGH_REG 0x03 +#define CONVERSION_TIME_MS 35 /* in milli-seconds */ + struct tmp102 { struct i2c_client *client; struct mutex lock; u16 config_orig; unsigned long last_update; + unsigned long ready_time; + bool valid; int temp[3]; - bool first_time; }; /* convert left adjusted 13-bit TMP102 register value to milliCelsius */ @@ -73,13 +77,14 @@ static const u8 tmp102_reg[] = { TMP102_THIGH_REG, }; -static struct tmp102 *tmp102_update_device(struct device *dev) +static void tmp102_update_device(struct device *dev) { struct tmp102 *tmp102 = dev_get_drvdata(dev); struct i2c_client *client = tmp102->client; mutex_lock(&tmp102->lock); - if (time_after(jiffies, tmp102->last_update + HZ / 3)) { + if (!tmp102->valid || + time_after(jiffies, tmp102->last_update + HZ / 3)) { int i; for (i = 0; i < ARRAY_SIZE(tmp102->temp); ++i) { int status = i2c_smbus_read_word_swapped(client, @@ -88,22 +93,22 @@ static struct tmp102 *tmp102_update_device(struct device *dev) tmp102->temp[i] = tmp102_reg_to_mC(status); } tmp102->last_update = jiffies; - tmp102->first_time = false; + tmp102->valid = true; } mutex_unlock(&tmp102->lock); - return tmp102; } static int tmp102_read_temp(void *dev, int *temp) { - struct tmp102 *tmp102 = tmp102_update_device(dev); + struct tmp102 *tmp102 = dev_get_drvdata(dev); - /* Is it too early even to return a conversion? */ - if (tmp102->first_time) { + if (time_before(jiffies, tmp102->ready_time)) { dev_dbg(dev, "%s: Conversion not ready yet..\n", __func__); return -EAGAIN; } + tmp102_update_device(dev); + *temp = tmp102->temp[0]; return 0; @@ -114,12 +119,13 @@ static ssize_t tmp102_show_temp(struct device *dev, char *buf) { struct sensor_device_attribute *sda = to_sensor_dev_attr(attr); - struct tmp102 *tmp102 = tmp102_update_device(dev); + struct tmp102 *tmp102 = dev_get_drvdata(dev); - /* Is it too early even to return a read? */ - if (tmp102->first_time) + if (time_before(jiffies, tmp102->ready_time)) return -EAGAIN; + tmp102_update_device(dev); + return sprintf(buf, "%d\n", tmp102->temp[sda->index]); } @@ -224,11 +230,18 @@ static int tmp102_probe(struct i2c_client *client, dev_err(dev, "config settings did not stick\n"); return -ENODEV; } - tmp102->last_update = jiffies; - /* Mark that we are not ready with data until conversion is complete */ - tmp102->first_time = true; + mutex_init(&tmp102->lock); + tmp102->ready_time = jiffies; + if (tmp102->config_orig & TMP102_CONF_SD) { + /* + * Mark that we are not ready with data until the first + * conversion is complete + */ + tmp102->ready_time += msecs_to_jiffies(CONVERSION_TIME_MS); + } + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, tmp102, tmp102_groups); @@ -261,12 +274,15 @@ static int tmp102_suspend(struct device *dev) static int tmp102_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); + struct tmp102 *tmp102 = i2c_get_clientdata(client); int config; config = i2c_smbus_read_word_swapped(client, TMP102_CONF_REG); if (config < 0) return config; + tmp102->ready_time = jiffies + msecs_to_jiffies(CONVERSION_TIME_MS); + config &= ~TMP102_CONF_SD; return i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, config); } -- cgit v0.10.2 From a9f92ccf334f7e15124193b1d7b89b7e6e6624e2 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 22 Jun 2016 10:01:57 -0700 Subject: hwmon: (tmp102) Rework chip configuration So far the chip was forced into polarity 0, even if it was preconfigured differently. Do not touch the polarity when configuring the chip. Also, the configuration register was read beack to check if the configuration 'sticks'. Ultimately, that is similar to checking if the chip is a tmp102 in the first place. Checking if a write into the configuration register was successful is really not the way to do it, and quite risky if the chip is not a tmp102, so drop that check. Instead, verify if the configuration register has unexpected bits set before writing into it. Acked-by: Nishanth Menon Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c index fd4a451..82a8a29 100644 --- a/drivers/hwmon/tmp102.c +++ b/drivers/hwmon/tmp102.c @@ -47,6 +47,17 @@ #define TMP102_TLOW_REG 0x02 #define TMP102_THIGH_REG 0x03 +#define TMP102_CONFREG_MASK (TMP102_CONF_SD | TMP102_CONF_TM | \ + TMP102_CONF_POL | TMP102_CONF_F0 | \ + TMP102_CONF_F1 | TMP102_CONF_OS | \ + TMP102_CONF_EM | TMP102_CONF_AL | \ + TMP102_CONF_CR0 | TMP102_CONF_CR1) + +#define TMP102_CONFIG_CLEAR (TMP102_CONF_SD | TMP102_CONF_OS | \ + TMP102_CONF_CR0) +#define TMP102_CONFIG_SET (TMP102_CONF_TM | TMP102_CONF_EM | \ + TMP102_CONF_CR1) + #define CONVERSION_TIME_MS 35 /* in milli-seconds */ struct tmp102 { @@ -167,9 +178,6 @@ static struct attribute *tmp102_attrs[] = { }; ATTRIBUTE_GROUPS(tmp102); -#define TMP102_CONFIG (TMP102_CONF_TM | TMP102_CONF_EM | TMP102_CONF_CR1) -#define TMP102_CONFIG_RD_ONLY (TMP102_CONF_R0 | TMP102_CONF_R1 | TMP102_CONF_AL) - static const struct thermal_zone_of_device_ops tmp102_of_thermal_ops = { .get_temp = tmp102_read_temp, }; @@ -210,26 +218,25 @@ static int tmp102_probe(struct i2c_client *client, dev_err(dev, "error reading config register\n"); return status; } + + if ((status & ~TMP102_CONFREG_MASK) != + (TMP102_CONF_R0 | TMP102_CONF_R1)) { + dev_err(dev, "unexpected config register value\n"); + return -ENODEV; + } + tmp102->config_orig = status; devm_add_action(dev, tmp102_restore_config, tmp102); - status = i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, - TMP102_CONFIG); + status &= ~TMP102_CONFIG_CLEAR; + status |= TMP102_CONFIG_SET; + + status = i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, status); if (status < 0) { dev_err(dev, "error writing config register\n"); return status; } - status = i2c_smbus_read_word_swapped(client, TMP102_CONF_REG); - if (status < 0) { - dev_err(dev, "error reading config register\n"); - return status; - } - status &= ~TMP102_CONFIG_RD_ONLY; - if (status != TMP102_CONFIG) { - dev_err(dev, "config settings did not stick\n"); - return -ENODEV; - } mutex_init(&tmp102->lock); -- cgit v0.10.2 From 28a340db9043f11dfd092c1b8ea4043f59cc0ca1 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 20 Jun 2016 09:55:46 -0700 Subject: hwmon: (tmp102) Convert to use regmap, and drop local cache By converting the driver to regmap, we can use regmap to cache non-volatile registers. Stop caching the temperature register; while potentially reading it more often can result in reading it more often than necessary, this is offset by the gain due to not re-reading the limit registers. A positive side effect of this change is that limit registers can now be read and updated before the first temperature conversion is complete. Acked-by: Nishanth Menon Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index f271353..e72cd3d 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1561,6 +1561,7 @@ config SENSORS_TMP102 tristate "Texas Instruments TMP102" depends on I2C depends on THERMAL || !THERMAL_OF + select REGMAP_I2C help If you say yes here you get support for Texas Instruments TMP102 sensor chips. diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c index 82a8a29..a942a25 100644 --- a/drivers/hwmon/tmp102.c +++ b/drivers/hwmon/tmp102.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -61,13 +62,9 @@ #define CONVERSION_TIME_MS 35 /* in milli-seconds */ struct tmp102 { - struct i2c_client *client; - struct mutex lock; + struct regmap *regmap; u16 config_orig; - unsigned long last_update; unsigned long ready_time; - bool valid; - int temp[3]; }; /* convert left adjusted 13-bit TMP102 register value to milliCelsius */ @@ -82,45 +79,22 @@ static inline u16 tmp102_mC_to_reg(int val) return (val * 128) / 1000; } -static const u8 tmp102_reg[] = { - TMP102_TEMP_REG, - TMP102_TLOW_REG, - TMP102_THIGH_REG, -}; - -static void tmp102_update_device(struct device *dev) -{ - struct tmp102 *tmp102 = dev_get_drvdata(dev); - struct i2c_client *client = tmp102->client; - - mutex_lock(&tmp102->lock); - if (!tmp102->valid || - time_after(jiffies, tmp102->last_update + HZ / 3)) { - int i; - for (i = 0; i < ARRAY_SIZE(tmp102->temp); ++i) { - int status = i2c_smbus_read_word_swapped(client, - tmp102_reg[i]); - if (status > -1) - tmp102->temp[i] = tmp102_reg_to_mC(status); - } - tmp102->last_update = jiffies; - tmp102->valid = true; - } - mutex_unlock(&tmp102->lock); -} - static int tmp102_read_temp(void *dev, int *temp) { struct tmp102 *tmp102 = dev_get_drvdata(dev); + unsigned int reg; + int ret; if (time_before(jiffies, tmp102->ready_time)) { dev_dbg(dev, "%s: Conversion not ready yet..\n", __func__); return -EAGAIN; } - tmp102_update_device(dev); + ret = regmap_read(tmp102->regmap, TMP102_TEMP_REG, ®); + if (ret < 0) + return ret; - *temp = tmp102->temp[0]; + *temp = tmp102_reg_to_mC(reg); return 0; } @@ -131,13 +105,19 @@ static ssize_t tmp102_show_temp(struct device *dev, { struct sensor_device_attribute *sda = to_sensor_dev_attr(attr); struct tmp102 *tmp102 = dev_get_drvdata(dev); + int regaddr = sda->index; + unsigned int reg; + int err; - if (time_before(jiffies, tmp102->ready_time)) + if (regaddr == TMP102_TEMP_REG && + time_before(jiffies, tmp102->ready_time)) return -EAGAIN; - tmp102_update_device(dev); + err = regmap_read(tmp102->regmap, regaddr, ®); + if (err < 0) + return err; - return sprintf(buf, "%d\n", tmp102->temp[sda->index]); + return sprintf(buf, "%d\n", tmp102_reg_to_mC(reg)); } static ssize_t tmp102_set_temp(struct device *dev, @@ -146,29 +126,26 @@ static ssize_t tmp102_set_temp(struct device *dev, { struct sensor_device_attribute *sda = to_sensor_dev_attr(attr); struct tmp102 *tmp102 = dev_get_drvdata(dev); - struct i2c_client *client = tmp102->client; + int reg = sda->index; long val; - int status; + int err; if (kstrtol(buf, 10, &val) < 0) return -EINVAL; val = clamp_val(val, -256000, 255000); - mutex_lock(&tmp102->lock); - tmp102->temp[sda->index] = val; - status = i2c_smbus_write_word_swapped(client, tmp102_reg[sda->index], - tmp102_mC_to_reg(val)); - mutex_unlock(&tmp102->lock); - return status ? : count; + err = regmap_write(tmp102->regmap, reg, tmp102_mC_to_reg(val)); + return err ? : count; } -static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, tmp102_show_temp, NULL , 0); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, tmp102_show_temp, NULL, + TMP102_TEMP_REG); static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, tmp102_show_temp, - tmp102_set_temp, 1); + tmp102_set_temp, TMP102_TLOW_REG); static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, tmp102_show_temp, - tmp102_set_temp, 2); + tmp102_set_temp, TMP102_THIGH_REG); static struct attribute *tmp102_attrs[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, @@ -185,19 +162,39 @@ static const struct thermal_zone_of_device_ops tmp102_of_thermal_ops = { static void tmp102_restore_config(void *data) { struct tmp102 *tmp102 = data; - struct i2c_client *client = tmp102->client; - i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, - tmp102->config_orig); + regmap_write(tmp102->regmap, TMP102_CONF_REG, tmp102->config_orig); +} + +static bool tmp102_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return reg != TMP102_TEMP_REG; } +static bool tmp102_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg == TMP102_TEMP_REG; +} + +static const struct regmap_config tmp102_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = TMP102_THIGH_REG, + .writeable_reg = tmp102_is_writeable_reg, + .volatile_reg = tmp102_is_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_BIG, + .cache_type = REGCACHE_RBTREE, + .use_single_rw = true, +}; + static int tmp102_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; struct device *hwmon_dev; struct tmp102 *tmp102; - int status; + unsigned int regval; + int err; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) { @@ -211,35 +208,36 @@ static int tmp102_probe(struct i2c_client *client, return -ENOMEM; i2c_set_clientdata(client, tmp102); - tmp102->client = client; - status = i2c_smbus_read_word_swapped(client, TMP102_CONF_REG); - if (status < 0) { + tmp102->regmap = devm_regmap_init_i2c(client, &tmp102_regmap_config); + if (IS_ERR(tmp102->regmap)) + return PTR_ERR(tmp102->regmap); + + err = regmap_read(tmp102->regmap, TMP102_CONF_REG, ®val); + if (err < 0) { dev_err(dev, "error reading config register\n"); - return status; + return err; } - if ((status & ~TMP102_CONFREG_MASK) != + if ((regval & ~TMP102_CONFREG_MASK) != (TMP102_CONF_R0 | TMP102_CONF_R1)) { dev_err(dev, "unexpected config register value\n"); return -ENODEV; } - tmp102->config_orig = status; + tmp102->config_orig = regval; devm_add_action(dev, tmp102_restore_config, tmp102); - status &= ~TMP102_CONFIG_CLEAR; - status |= TMP102_CONFIG_SET; + regval &= ~TMP102_CONFIG_CLEAR; + regval |= TMP102_CONFIG_SET; - status = i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, status); - if (status < 0) { + err = regmap_write(tmp102->regmap, TMP102_CONF_REG, regval); + if (err < 0) { dev_err(dev, "error writing config register\n"); - return status; + return err; } - mutex_init(&tmp102->lock); - tmp102->ready_time = jiffies; if (tmp102->config_orig & TMP102_CONF_SD) { /* @@ -268,30 +266,24 @@ static int tmp102_probe(struct i2c_client *client, static int tmp102_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); - int config; - - config = i2c_smbus_read_word_swapped(client, TMP102_CONF_REG); - if (config < 0) - return config; + struct tmp102 *tmp102 = i2c_get_clientdata(client); - config |= TMP102_CONF_SD; - return i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, config); + return regmap_update_bits(tmp102->regmap, TMP102_CONF_REG, + TMP102_CONF_SD, TMP102_CONF_SD); } static int tmp102_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct tmp102 *tmp102 = i2c_get_clientdata(client); - int config; + int err; - config = i2c_smbus_read_word_swapped(client, TMP102_CONF_REG); - if (config < 0) - return config; + err = regmap_update_bits(tmp102->regmap, TMP102_CONF_REG, + TMP102_CONF_SD, 0); tmp102->ready_time = jiffies + msecs_to_jiffies(CONVERSION_TIME_MS); - config &= ~TMP102_CONF_SD; - return i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, config); + return err; } #endif /* CONFIG_PM */ -- cgit v0.10.2 From e066f89d0608236a72d8e9e13cef05005e8d1aea Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 2 Jul 2016 09:02:43 -0700 Subject: dt/bindings: Add bindings for JC-42.4 compatible temperature sensors Provide generic bindings for all Jedec JC-42.4 compatible temperature sensor chips. Acked-by: Rob Herring Signed-off-by: Guenter Roeck diff --git a/Documentation/devicetree/bindings/hwmon/jc42.txt b/Documentation/devicetree/bindings/hwmon/jc42.txt new file mode 100644 index 0000000..07a2504 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/jc42.txt @@ -0,0 +1,42 @@ +Properties for Jedec JC-42.4 compatible temperature sensors + +Required properties: +- compatible: May include a device-specific string consisting of the + manufacturer and the name of the chip. A list of supported + chip names follows. + Must include "jedec,jc-42.4-temp" for any Jedec JC-42.4 + compatible temperature sensor. + + Supported chip names: + adi,adt7408 + atmel,at30ts00 + atmel,at30tse004 + onnn,cat6095 + onnn,cat34ts02 + maxim,max6604 + microchip,mcp9804 + microchip,mcp9805 + microchip,mcp9808 + microchip,mcp98243 + microchip,mcp98244 + microchip,mcp9843 + nxp,se97 + nxp,se98 + st,stts2002 + st,stts2004 + st,stts3000 + st,stts424 + st,stts424e + idt,tse2002 + idt,tse2004 + idt,ts3000 + idt,ts3001 + +- reg: I2C address + +Example: + +temp-sensor@1a { + compatible = "jedec,jc-42.4-temp"; + reg = <0x1a>; +}; -- cgit v0.10.2 From 803deccec2513379ff0873a386d955af6f43d005 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 2 Jul 2016 09:05:48 -0700 Subject: hwmon: (jc42) Add support for generic JC-42.4 devicetree binding With this change, JC-42.4 compatible temperature sensors can be configured in devicetree by providing a generic "jedec,jc-42.4-temp" binding. Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c index d9c4f78..9d5f85f 100644 --- a/drivers/hwmon/jc42.c +++ b/drivers/hwmon/jc42.c @@ -31,6 +31,7 @@ #include #include #include +#include /* Addresses to scan */ static const unsigned short normal_i2c[] = { @@ -541,11 +542,20 @@ static const struct i2c_device_id jc42_id[] = { }; MODULE_DEVICE_TABLE(i2c, jc42_id); +#ifdef CONFIG_OF +static const struct of_device_id jc42_of_ids[] = { + { .compatible = "jedec,jc-42.4-temp", }, + { } +}; +MODULE_DEVICE_TABLE(of, jc42_of_ids); +#endif + static struct i2c_driver jc42_driver = { .class = I2C_CLASS_SPD | I2C_CLASS_HWMON, .driver = { .name = "jc42", .pm = JC42_DEV_PM_OPS, + .of_match_table = of_match_ptr(jc42_of_ids), }, .probe = jc42_probe, .remove = jc42_remove, -- cgit v0.10.2 From 50c22cd154e5f7d13072d98ef3e2baba3c074734 Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Sat, 9 Jul 2016 23:49:18 -0700 Subject: hwmon: (sht3x) add humidity heater element control The enables control of the SHT31 sensors heating element that can turned on to remove excess humidity. Cc: Guenter Roeck Cc: David Frey Signed-off-by: Matt Ranostay Signed-off-by: Guenter Roeck diff --git a/Documentation/hwmon/sht3x b/Documentation/hwmon/sht3x index f5aa633..b0d8818 100644 --- a/Documentation/hwmon/sht3x +++ b/Documentation/hwmon/sht3x @@ -67,6 +67,10 @@ temp1_alarm: alarm flag is set to 1 if the temperature is outside the configured limits. Alarm only works in periodic measure mode humidity1_alarm: alarm flag is set to 1 if the humidity is outside the configured limits. Alarm only works in periodic measure mode +heater_enable: heater enable, heating element removes excess humidity from + sensor + 0: turned off + 1: turned on update_interval: update interval, 0 for single shot, interval in msec for periodic measurement. If the interval is not supported by the sensor, the next faster interval is chosen diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c index 707a3f8..b73a488 100644 --- a/drivers/hwmon/sht3x.c +++ b/drivers/hwmon/sht3x.c @@ -44,6 +44,10 @@ static const unsigned char sht3x_cmd_measure_nonblocking_lpm[] = { 0x24, 0x16 }; static const unsigned char sht3x_cmd_measure_periodic_mode[] = { 0xe0, 0x00 }; static const unsigned char sht3x_cmd_break[] = { 0x30, 0x93 }; +/* commands for heater control */ +static const unsigned char sht3x_cmd_heater_on[] = { 0x30, 0x6d }; +static const unsigned char sht3x_cmd_heater_off[] = { 0x30, 0x66 }; + /* other commands */ static const unsigned char sht3x_cmd_read_status_reg[] = { 0xf3, 0x2d }; static const unsigned char sht3x_cmd_clear_status_reg[] = { 0x30, 0x41 }; @@ -507,6 +511,49 @@ static ssize_t humidity1_alarm_show(struct device *dev, return scnprintf(buf, PAGE_SIZE, "%d\n", !!(buffer[0] & 0x08)); } +static ssize_t heater_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + char buffer[SHT3X_WORD_LEN + SHT3X_CRC8_LEN]; + int ret; + + ret = status_register_read(dev, attr, buffer, + SHT3X_WORD_LEN + SHT3X_CRC8_LEN); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%d\n", !!(buffer[0] & 0x20)); +} + +static ssize_t heater_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret; + bool status; + + ret = kstrtobool(buf, &status); + if (ret) + return ret; + + mutex_lock(&data->i2c_lock); + + if (status) + ret = i2c_master_send(client, (char *)&sht3x_cmd_heater_on, + SHT3X_CMD_LENGTH); + else + ret = i2c_master_send(client, (char *)&sht3x_cmd_heater_off, + SHT3X_CMD_LENGTH); + + mutex_unlock(&data->i2c_lock); + + return ret; +} + static ssize_t update_interval_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -612,6 +659,8 @@ static SENSOR_DEVICE_ATTR(humidity1_min_hyst, S_IRUGO | S_IWUSR, static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, temp1_alarm_show, NULL, 0); static SENSOR_DEVICE_ATTR(humidity1_alarm, S_IRUGO, humidity1_alarm_show, NULL, 0); +static SENSOR_DEVICE_ATTR(heater_enable, S_IRUGO | S_IWUSR, + heater_enable_show, heater_enable_store, 0); static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR, update_interval_show, update_interval_store, 0); @@ -628,6 +677,7 @@ static struct attribute *sht3x_attrs[] = { &sensor_dev_attr_humidity1_min_hyst.dev_attr.attr, &sensor_dev_attr_temp1_alarm.dev_attr.attr, &sensor_dev_attr_humidity1_alarm.dev_attr.attr, + &sensor_dev_attr_heater_enable.dev_attr.attr, &sensor_dev_attr_update_interval.dev_attr.attr, NULL }; -- cgit v0.10.2 From 08426eda58e07af44aac7c9900ec8a6a62e16b2f Mon Sep 17 00:00:00 2001 From: Thilo Cestonaro Date: Mon, 18 Jul 2016 13:51:29 +0200 Subject: hwmon: Add driver for FTS BMC chip "Teutates" This driver implements hardware monitoring and watchdog support for the FTS BMC Chip "Teutates". Signed-off-by: Thilo Cestonaro [groeck: Updated subject and description; fixed dependencies] Signed-off-by: Guenter Roeck diff --git a/Documentation/hwmon/ftsteutates b/Documentation/hwmon/ftsteutates new file mode 100644 index 0000000..2a1bf69 --- /dev/null +++ b/Documentation/hwmon/ftsteutates @@ -0,0 +1,23 @@ +Kernel driver ftsteutates +===================== + +Supported chips: + * FTS Teutates + Prefix: 'ftsteutates' + Addresses scanned: I2C 0x73 (7-Bit) + +Author: Thilo Cestonaro + + +Description +----------- +The BMC Teutates is the Eleventh generation of Superior System +monitoring and thermal management solution. It is builds on the basic +functionality of the BMC Theseus and contains several new features and +enhancements. It can monitor up to 4 voltages, 16 temperatures and +8 fans. It also contains an integrated watchdog which is currently +implemented in this driver. + +Specification of the chip can be found here: +ftp:///pub/Mainboard-OEM-Sales/Services/Software&Tools/Linux_SystemMonitoring&Watchdog&GPIO/BMC-Teutates_Specification_V1.21.pdf +ftp:///pub/Mainboard-OEM-Sales/Services/Software&Tools/Linux_SystemMonitoring&Watchdog&GPIO/Fujitsu_mainboards-1-Sensors_HowTo-en-US.pdf diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index e72cd3d..eaf2f91 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -486,6 +486,18 @@ config SENSORS_FSCHMD This driver can also be built as a module. If so, the module will be called fschmd. +config SENSORS_FTSTEUTATES + tristate "Fujitsu Technology Solutions sensor chip Teutates" + depends on I2C && WATCHDOG + select WATCHDOG_CORE + help + If you say yes here you get support for the Fujitsu Technology + Solutions (FTS) sensor chip "Teutates" including support for + the integrated watchdog. + + This driver can also be built as a module. If so, the module + will be called ftsteutates. + config SENSORS_GL518SM tristate "Genesys Logic GL518SM" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 446a4e7..fe87d28 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o obj-$(CONFIG_SENSORS_F75375S) += f75375s.o obj-$(CONFIG_SENSORS_FAM15H_POWER) += fam15h_power.o obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o +obj-$(CONFIG_SENSORS_FTSTEUTATES) += ftsteutates.o obj-$(CONFIG_SENSORS_G760A) += g760a.o obj-$(CONFIG_SENSORS_G762) += g762.o obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o diff --git a/drivers/hwmon/ftsteutates.c b/drivers/hwmon/ftsteutates.c new file mode 100644 index 0000000..aaef5b5 --- /dev/null +++ b/drivers/hwmon/ftsteutates.c @@ -0,0 +1,820 @@ +/* + * Support for the FTS Systemmonitoring Chip "Teutates" + * + * Copyright (C) 2016 Fujitsu Technology Solutions GmbH, + * Thilo Cestonaro + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#define FTS_DEVICE_ID_REG 0x0000 +#define FTS_DEVICE_REVISION_REG 0x0001 +#define FTS_DEVICE_STATUS_REG 0x0004 +#define FTS_SATELLITE_STATUS_REG 0x0005 +#define FTS_EVENT_STATUS_REG 0x0006 +#define FTS_GLOBAL_CONTROL_REG 0x0007 + +#define FTS_SENSOR_EVENT_REG 0x0010 + +#define FTS_FAN_EVENT_REG 0x0014 +#define FTS_FAN_PRESENT_REG 0x0015 + +#define FTS_POWER_ON_TIME_COUNTER_A 0x007A +#define FTS_POWER_ON_TIME_COUNTER_B 0x007B +#define FTS_POWER_ON_TIME_COUNTER_C 0x007C + +#define FTS_PAGE_SELECT_REG 0x007F + +#define FTS_WATCHDOG_TIME_PRESET 0x000B +#define FTS_WATCHDOG_CONTROL 0x5081 + +#define FTS_NO_FAN_SENSORS 0x08 +#define FTS_NO_TEMP_SENSORS 0x10 +#define FTS_NO_VOLT_SENSORS 0x04 + +static struct i2c_device_id fts_id[] = { + { "ftsteutates", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, fts_id); + +enum WATCHDOG_RESOLUTION { + seconds = 1, + minutes = 60 +}; + +struct fts_data { + struct i2c_client *client; + /* update sensor data lock */ + struct mutex update_lock; + /* read/write register lock */ + struct mutex access_lock; + unsigned long last_updated; /* in jiffies */ + struct watchdog_device wdd; + enum WATCHDOG_RESOLUTION resolution; + bool valid; /* false until following fields are valid */ + + u8 volt[FTS_NO_VOLT_SENSORS]; + + u8 temp_input[FTS_NO_TEMP_SENSORS]; + u8 temp_alarm; + + u8 fan_present; + u8 fan_input[FTS_NO_FAN_SENSORS]; /* in rps */ + u8 fan_source[FTS_NO_FAN_SENSORS]; + u8 fan_alarm; +}; + +#define FTS_REG_FAN_INPUT(idx) ((idx) + 0x20) +#define FTS_REG_FAN_SOURCE(idx) ((idx) + 0x30) +#define FTS_REG_FAN_CONTROL(idx) (((idx) << 16) + 0x4881) + +#define FTS_REG_TEMP_INPUT(idx) ((idx) + 0x40) +#define FTS_REG_TEMP_CONTROL(idx) (((idx) << 16) + 0x0681) + +#define FTS_REG_VOLT(idx) ((idx) + 0x18) + +/*****************************************************************************/ +/* I2C Helper functions */ +/*****************************************************************************/ +static int fts_read_byte(struct i2c_client *client, unsigned short reg) +{ + int ret; + unsigned char page = reg >> 8; + struct fts_data *data = dev_get_drvdata(&client->dev); + + mutex_lock(&data->access_lock); + + dev_dbg(&client->dev, "page select - page: 0x%.02x\n", page); + ret = i2c_smbus_write_byte_data(client, FTS_PAGE_SELECT_REG, page); + if (ret < 0) + goto error; + + reg &= 0xFF; + ret = i2c_smbus_read_byte_data(client, reg); + dev_dbg(&client->dev, "read - reg: 0x%.02x: val: 0x%.02x\n", reg, ret); + +error: + mutex_unlock(&data->access_lock); + return ret; +} + +static int fts_write_byte(struct i2c_client *client, unsigned short reg, + unsigned char value) +{ + int ret; + unsigned char page = reg >> 8; + struct fts_data *data = dev_get_drvdata(&client->dev); + + mutex_lock(&data->access_lock); + + dev_dbg(&client->dev, "page select - page: 0x%.02x\n", page); + ret = i2c_smbus_write_byte_data(client, FTS_PAGE_SELECT_REG, page); + if (ret < 0) + goto error; + + reg &= 0xFF; + dev_dbg(&client->dev, + "write - reg: 0x%.02x: val: 0x%.02x\n", reg, value); + ret = i2c_smbus_write_byte_data(client, reg, value); + +error: + mutex_unlock(&data->access_lock); + return ret; +} + +/*****************************************************************************/ +/* Data Updater Helper function */ +/*****************************************************************************/ +static int fts_update_device(struct fts_data *data) +{ + int i; + int err = 0; + + mutex_lock(&data->update_lock); + if (!time_after(jiffies, data->last_updated + 2 * HZ) && data->valid) + goto exit; + + err = fts_read_byte(data->client, FTS_DEVICE_STATUS_REG); + if (err < 0) + goto exit; + + data->valid = !!(err & 0x02); /* Data not ready yet */ + if (unlikely(!data->valid)) { + err = -EAGAIN; + goto exit; + } + + err = fts_read_byte(data->client, FTS_FAN_PRESENT_REG); + if (err < 0) + goto exit; + data->fan_present = err; + + err = fts_read_byte(data->client, FTS_FAN_EVENT_REG); + if (err < 0) + goto exit; + data->fan_alarm = err; + + for (i = 0; i < FTS_NO_FAN_SENSORS; i++) { + if (data->fan_present & BIT(i)) { + err = fts_read_byte(data->client, FTS_REG_FAN_INPUT(i)); + if (err < 0) + goto exit; + data->fan_input[i] = err; + + err = fts_read_byte(data->client, + FTS_REG_FAN_SOURCE(i)); + if (err < 0) + goto exit; + data->fan_source[i] = err; + } else { + data->fan_input[i] = 0; + data->fan_source[i] = 0; + } + } + + err = fts_read_byte(data->client, FTS_SENSOR_EVENT_REG); + if (err < 0) + goto exit; + data->temp_alarm = err; + + for (i = 0; i < FTS_NO_TEMP_SENSORS; i++) { + err = fts_read_byte(data->client, FTS_REG_TEMP_INPUT(i)); + if (err < 0) + goto exit; + data->temp_input[i] = err; + } + + for (i = 0; i < FTS_NO_VOLT_SENSORS; i++) { + err = fts_read_byte(data->client, FTS_REG_VOLT(i)); + if (err < 0) + goto exit; + data->volt[i] = err; + } + data->last_updated = jiffies; + err = 0; +exit: + mutex_unlock(&data->update_lock); + return err; +} + +/*****************************************************************************/ +/* Watchdog functions */ +/*****************************************************************************/ +static int fts_wd_set_resolution(struct fts_data *data, + enum WATCHDOG_RESOLUTION resolution) +{ + int ret; + + if (data->resolution == resolution) + return 0; + + ret = fts_read_byte(data->client, FTS_WATCHDOG_CONTROL); + if (ret < 0) + return ret; + + if ((resolution == seconds && ret & BIT(1)) || + (resolution == minutes && (ret & BIT(1)) == 0)) { + data->resolution = resolution; + return 0; + } + + if (resolution == seconds) + set_bit(1, (unsigned long *)&ret); + else + ret &= ~BIT(1); + + ret = fts_write_byte(data->client, FTS_WATCHDOG_CONTROL, ret); + if (ret < 0) + return ret; + + data->resolution = resolution; + return ret; +} + +static int fts_wd_set_timeout(struct watchdog_device *wdd, unsigned int timeout) +{ + struct fts_data *data; + enum WATCHDOG_RESOLUTION resolution = seconds; + int ret; + + data = watchdog_get_drvdata(wdd); + /* switch watchdog resolution to minutes if timeout does not fit + * into a byte + */ + if (timeout > 0xFF) { + timeout = DIV_ROUND_UP(timeout, 60) * 60; + resolution = minutes; + } + + ret = fts_wd_set_resolution(data, resolution); + if (ret < 0) + return ret; + + wdd->timeout = timeout; + return 0; +} + +static int fts_wd_start(struct watchdog_device *wdd) +{ + struct fts_data *data = watchdog_get_drvdata(wdd); + + return fts_write_byte(data->client, FTS_WATCHDOG_TIME_PRESET, + wdd->timeout / (u8)data->resolution); +} + +static int fts_wd_stop(struct watchdog_device *wdd) +{ + struct fts_data *data; + + data = watchdog_get_drvdata(wdd); + return fts_write_byte(data->client, FTS_WATCHDOG_TIME_PRESET, 0); +} + +static const struct watchdog_info fts_wd_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "FTS Teutates Hardware Watchdog", +}; + +static const struct watchdog_ops fts_wd_ops = { + .owner = THIS_MODULE, + .start = fts_wd_start, + .stop = fts_wd_stop, + .set_timeout = fts_wd_set_timeout, +}; + +static int fts_watchdog_init(struct fts_data *data) +{ + int timeout, ret; + + watchdog_set_drvdata(&data->wdd, data); + + timeout = fts_read_byte(data->client, FTS_WATCHDOG_TIME_PRESET); + if (timeout < 0) + return timeout; + + /* watchdog not running, set timeout to a default of 60 sec. */ + if (timeout == 0) { + ret = fts_wd_set_resolution(data, seconds); + if (ret < 0) + return ret; + data->wdd.timeout = 60; + } else { + ret = fts_read_byte(data->client, FTS_WATCHDOG_CONTROL); + if (ret < 0) + return ret; + + data->resolution = ret & BIT(1) ? seconds : minutes; + data->wdd.timeout = timeout * (u8)data->resolution; + set_bit(WDOG_HW_RUNNING, &data->wdd.status); + } + + /* Register our watchdog part */ + data->wdd.info = &fts_wd_info; + data->wdd.ops = &fts_wd_ops; + data->wdd.parent = &data->client->dev; + data->wdd.min_timeout = 1; + + /* max timeout 255 minutes. */ + data->wdd.max_hw_heartbeat_ms = 0xFF * 60 * MSEC_PER_SEC; + + return watchdog_register_device(&data->wdd); +} + +/*****************************************************************************/ +/* SysFS handler functions */ +/*****************************************************************************/ +static ssize_t show_in_value(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + int err; + + err = fts_update_device(data); + if (err < 0) + return err; + + return sprintf(buf, "%u\n", data->volt[index]); +} + +static ssize_t show_temp_value(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + int err; + + err = fts_update_device(data); + if (err < 0) + return err; + + return sprintf(buf, "%u\n", data->temp_input[index]); +} + +static ssize_t show_temp_fault(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + int err; + + err = fts_update_device(data); + if (err < 0) + return err; + + /* 00h Temperature = Sensor Error */ + return sprintf(buf, "%d\n", data->temp_input[index] == 0); +} + +static ssize_t show_temp_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + int err; + + err = fts_update_device(data); + if (err < 0) + return err; + + return sprintf(buf, "%u\n", !!(data->temp_alarm & BIT(index))); +} + +static ssize_t +clear_temp_alarm(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + long ret; + + ret = fts_update_device(data); + if (ret < 0) + return ret; + + if (kstrtoul(buf, 10, &ret) || ret != 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + ret = fts_read_byte(data->client, FTS_REG_TEMP_CONTROL(index)); + if (ret < 0) + goto error; + + ret = fts_write_byte(data->client, FTS_REG_TEMP_CONTROL(index), + ret | 0x1); + if (ret < 0) + goto error; + + data->valid = false; +error: + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t show_fan_value(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + int err; + + err = fts_update_device(data); + if (err < 0) + return err; + + return sprintf(buf, "%u\n", data->fan_input[index]); +} + +static ssize_t show_fan_source(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + int err; + + err = fts_update_device(data); + if (err < 0) + return err; + + return sprintf(buf, "%u\n", data->fan_source[index]); +} + +static ssize_t show_fan_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + int err; + + err = fts_update_device(data); + if (err < 0) + return err; + + return sprintf(buf, "%d\n", !!(data->fan_alarm & BIT(index))); +} + +static ssize_t +clear_fan_alarm(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + long ret; + + ret = fts_update_device(data); + if (ret < 0) + return ret; + + if (kstrtoul(buf, 10, &ret) || ret != 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + ret = fts_read_byte(data->client, FTS_REG_FAN_CONTROL(index)); + if (ret < 0) + goto error; + + ret = fts_write_byte(data->client, FTS_REG_FAN_CONTROL(index), + ret | 0x1); + if (ret < 0) + goto error; + + data->valid = false; +error: + mutex_unlock(&data->update_lock); + return ret; +} + +/*****************************************************************************/ +/* SysFS structs */ +/*****************************************************************************/ + +/* Temprature sensors */ +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_value, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_value, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp_value, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp_value, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp_value, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp_value, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_temp_value, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_temp_value, NULL, 7); +static SENSOR_DEVICE_ATTR(temp9_input, S_IRUGO, show_temp_value, NULL, 8); +static SENSOR_DEVICE_ATTR(temp10_input, S_IRUGO, show_temp_value, NULL, 9); +static SENSOR_DEVICE_ATTR(temp11_input, S_IRUGO, show_temp_value, NULL, 10); +static SENSOR_DEVICE_ATTR(temp12_input, S_IRUGO, show_temp_value, NULL, 11); +static SENSOR_DEVICE_ATTR(temp13_input, S_IRUGO, show_temp_value, NULL, 12); +static SENSOR_DEVICE_ATTR(temp14_input, S_IRUGO, show_temp_value, NULL, 13); +static SENSOR_DEVICE_ATTR(temp15_input, S_IRUGO, show_temp_value, NULL, 14); +static SENSOR_DEVICE_ATTR(temp16_input, S_IRUGO, show_temp_value, NULL, 15); + +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_temp_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_temp_fault, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_fault, S_IRUGO, show_temp_fault, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_fault, S_IRUGO, show_temp_fault, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_fault, S_IRUGO, show_temp_fault, NULL, 7); +static SENSOR_DEVICE_ATTR(temp9_fault, S_IRUGO, show_temp_fault, NULL, 8); +static SENSOR_DEVICE_ATTR(temp10_fault, S_IRUGO, show_temp_fault, NULL, 9); +static SENSOR_DEVICE_ATTR(temp11_fault, S_IRUGO, show_temp_fault, NULL, 10); +static SENSOR_DEVICE_ATTR(temp12_fault, S_IRUGO, show_temp_fault, NULL, 11); +static SENSOR_DEVICE_ATTR(temp13_fault, S_IRUGO, show_temp_fault, NULL, 12); +static SENSOR_DEVICE_ATTR(temp14_fault, S_IRUGO, show_temp_fault, NULL, 13); +static SENSOR_DEVICE_ATTR(temp15_fault, S_IRUGO, show_temp_fault, NULL, 14); +static SENSOR_DEVICE_ATTR(temp16_fault, S_IRUGO, show_temp_fault, NULL, 15); + +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 0); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 1); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 2); +static SENSOR_DEVICE_ATTR(temp4_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 3); +static SENSOR_DEVICE_ATTR(temp5_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 4); +static SENSOR_DEVICE_ATTR(temp6_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 5); +static SENSOR_DEVICE_ATTR(temp7_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 6); +static SENSOR_DEVICE_ATTR(temp8_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 7); +static SENSOR_DEVICE_ATTR(temp9_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 8); +static SENSOR_DEVICE_ATTR(temp10_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 9); +static SENSOR_DEVICE_ATTR(temp11_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 10); +static SENSOR_DEVICE_ATTR(temp12_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 11); +static SENSOR_DEVICE_ATTR(temp13_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 12); +static SENSOR_DEVICE_ATTR(temp14_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 13); +static SENSOR_DEVICE_ATTR(temp15_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 14); +static SENSOR_DEVICE_ATTR(temp16_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 15); + +static struct attribute *fts_temp_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp7_input.dev_attr.attr, + &sensor_dev_attr_temp8_input.dev_attr.attr, + &sensor_dev_attr_temp9_input.dev_attr.attr, + &sensor_dev_attr_temp10_input.dev_attr.attr, + &sensor_dev_attr_temp11_input.dev_attr.attr, + &sensor_dev_attr_temp12_input.dev_attr.attr, + &sensor_dev_attr_temp13_input.dev_attr.attr, + &sensor_dev_attr_temp14_input.dev_attr.attr, + &sensor_dev_attr_temp15_input.dev_attr.attr, + &sensor_dev_attr_temp16_input.dev_attr.attr, + + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + &sensor_dev_attr_temp5_fault.dev_attr.attr, + &sensor_dev_attr_temp6_fault.dev_attr.attr, + &sensor_dev_attr_temp7_fault.dev_attr.attr, + &sensor_dev_attr_temp8_fault.dev_attr.attr, + &sensor_dev_attr_temp9_fault.dev_attr.attr, + &sensor_dev_attr_temp10_fault.dev_attr.attr, + &sensor_dev_attr_temp11_fault.dev_attr.attr, + &sensor_dev_attr_temp12_fault.dev_attr.attr, + &sensor_dev_attr_temp13_fault.dev_attr.attr, + &sensor_dev_attr_temp14_fault.dev_attr.attr, + &sensor_dev_attr_temp15_fault.dev_attr.attr, + &sensor_dev_attr_temp16_fault.dev_attr.attr, + + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_alarm.dev_attr.attr, + &sensor_dev_attr_temp7_alarm.dev_attr.attr, + &sensor_dev_attr_temp8_alarm.dev_attr.attr, + &sensor_dev_attr_temp9_alarm.dev_attr.attr, + &sensor_dev_attr_temp10_alarm.dev_attr.attr, + &sensor_dev_attr_temp11_alarm.dev_attr.attr, + &sensor_dev_attr_temp12_alarm.dev_attr.attr, + &sensor_dev_attr_temp13_alarm.dev_attr.attr, + &sensor_dev_attr_temp14_alarm.dev_attr.attr, + &sensor_dev_attr_temp15_alarm.dev_attr.attr, + &sensor_dev_attr_temp16_alarm.dev_attr.attr, + NULL +}; + +/* Fans */ +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_value, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_value, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan_value, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan_value, NULL, 3); +static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, show_fan_value, NULL, 4); +static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, show_fan_value, NULL, 5); +static SENSOR_DEVICE_ATTR(fan7_input, S_IRUGO, show_fan_value, NULL, 6); +static SENSOR_DEVICE_ATTR(fan8_input, S_IRUGO, show_fan_value, NULL, 7); + +static SENSOR_DEVICE_ATTR(fan1_source, S_IRUGO, show_fan_source, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_source, S_IRUGO, show_fan_source, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_source, S_IRUGO, show_fan_source, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_source, S_IRUGO, show_fan_source, NULL, 3); +static SENSOR_DEVICE_ATTR(fan5_source, S_IRUGO, show_fan_source, NULL, 4); +static SENSOR_DEVICE_ATTR(fan6_source, S_IRUGO, show_fan_source, NULL, 5); +static SENSOR_DEVICE_ATTR(fan7_source, S_IRUGO, show_fan_source, NULL, 6); +static SENSOR_DEVICE_ATTR(fan8_source, S_IRUGO, show_fan_source, NULL, 7); + +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 0); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 1); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 2); +static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 3); +static SENSOR_DEVICE_ATTR(fan5_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 4); +static SENSOR_DEVICE_ATTR(fan6_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 5); +static SENSOR_DEVICE_ATTR(fan7_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 6); +static SENSOR_DEVICE_ATTR(fan8_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 7); + +static struct attribute *fts_fan_attrs[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan5_input.dev_attr.attr, + &sensor_dev_attr_fan6_input.dev_attr.attr, + &sensor_dev_attr_fan7_input.dev_attr.attr, + &sensor_dev_attr_fan8_input.dev_attr.attr, + + &sensor_dev_attr_fan1_source.dev_attr.attr, + &sensor_dev_attr_fan2_source.dev_attr.attr, + &sensor_dev_attr_fan3_source.dev_attr.attr, + &sensor_dev_attr_fan4_source.dev_attr.attr, + &sensor_dev_attr_fan5_source.dev_attr.attr, + &sensor_dev_attr_fan6_source.dev_attr.attr, + &sensor_dev_attr_fan7_source.dev_attr.attr, + &sensor_dev_attr_fan8_source.dev_attr.attr, + + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_fan4_alarm.dev_attr.attr, + &sensor_dev_attr_fan5_alarm.dev_attr.attr, + &sensor_dev_attr_fan6_alarm.dev_attr.attr, + &sensor_dev_attr_fan7_alarm.dev_attr.attr, + &sensor_dev_attr_fan8_alarm.dev_attr.attr, + NULL +}; + +/* Voltages */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in_value, NULL, 0); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_in_value, NULL, 1); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_in_value, NULL, 2); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_in_value, NULL, 3); +static struct attribute *fts_voltage_attrs[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group fts_voltage_attr_group = { + .attrs = fts_voltage_attrs +}; + +static const struct attribute_group fts_temp_attr_group = { + .attrs = fts_temp_attrs +}; + +static const struct attribute_group fts_fan_attr_group = { + .attrs = fts_fan_attrs +}; + +static const struct attribute_group *fts_attr_groups[] = { + &fts_voltage_attr_group, + &fts_temp_attr_group, + &fts_fan_attr_group, + NULL +}; + +/*****************************************************************************/ +/* Module initialization / remove functions */ +/*****************************************************************************/ +static int fts_remove(struct i2c_client *client) +{ + struct fts_data *data = dev_get_drvdata(&client->dev); + + watchdog_unregister_device(&data->wdd); + return 0; +} + +static int fts_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + u8 revision; + struct fts_data *data; + int err; + s8 deviceid; + struct device *hwmon_dev; + + if (client->addr != 0x73) + return -ENODEV; + + /* Baseboard Management Controller check */ + deviceid = i2c_smbus_read_byte_data(client, FTS_DEVICE_ID_REG); + if (deviceid > 0 && (deviceid & 0xF0) == 0x10) { + switch (deviceid & 0x0F) { + case 0x01: + break; + default: + dev_dbg(&client->dev, + "No Baseboard Management Controller\n"); + return -ENODEV; + } + } else { + dev_dbg(&client->dev, "No fujitsu board\n"); + return -ENODEV; + } + + data = devm_kzalloc(&client->dev, sizeof(struct fts_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->update_lock); + mutex_init(&data->access_lock); + data->client = client; + dev_set_drvdata(&client->dev, data); + + err = i2c_smbus_read_byte_data(client, FTS_DEVICE_REVISION_REG); + if (err < 0) + return err; + revision = err; + + hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, + "ftsteutates", + data, + fts_attr_groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + err = fts_watchdog_init(data); + if (err) + return err; + + dev_info(&client->dev, "Detected FTS Teutates chip, revision: %d.%d\n", + (revision & 0xF0) >> 4, revision & 0x0F); + return 0; +} + +/*****************************************************************************/ +/* Module Details */ +/*****************************************************************************/ +static struct i2c_driver fts_driver = { + .driver = { + .name = "ftsteutates", + }, + .id_table = fts_id, + .probe = fts_probe, + .remove = fts_remove, +}; + +module_i2c_driver(fts_driver); + +MODULE_AUTHOR("Thilo Cestonaro "); +MODULE_DESCRIPTION("FTS Teutates driver"); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From b53893aae441a034bf4dbbad42fe218561d7d81f Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Tue, 19 Jul 2016 16:43:26 +0200 Subject: hwmon: (adt7411) set bit 3 in CFG1 register According to the datasheet you should only write 1 to this bit. If it is not set, at least AIN3 will return bad values on newer silicon revisions. Fixes: d84ca5b345c2 ("hwmon: Add driver for ADT7411 voltage and temperature sensor") Signed-off-by: Michael Walle Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/adt7411.c b/drivers/hwmon/adt7411.c index 827c037..a7f8869 100644 --- a/drivers/hwmon/adt7411.c +++ b/drivers/hwmon/adt7411.c @@ -30,6 +30,7 @@ #define ADT7411_REG_CFG1 0x18 #define ADT7411_CFG1_START_MONITOR (1 << 0) +#define ADT7411_CFG1_RESERVED_BIT3 (1 << 3) #define ADT7411_REG_CFG2 0x19 #define ADT7411_CFG2_DISABLE_AVG (1 << 5) @@ -296,8 +297,10 @@ static int adt7411_probe(struct i2c_client *client, mutex_init(&data->device_lock); mutex_init(&data->update_lock); + /* According to the datasheet, we must only write 1 to bit 3 */ ret = adt7411_modify_bit(client, ADT7411_REG_CFG1, - ADT7411_CFG1_START_MONITOR, 1); + ADT7411_CFG1_RESERVED_BIT3 + | ADT7411_CFG1_START_MONITOR, 1); if (ret < 0) return ret; -- cgit v0.10.2 From c55374cd862e3b42831c6001cbd9d28b0e182ed4 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Wed, 20 Jul 2016 12:06:16 +0000 Subject: hwmon: (ftsteutates) Remove unused including Remove including that don't need it. Signed-off-by: Wei Yongjun Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/ftsteutates.c b/drivers/hwmon/ftsteutates.c index aaef5b5..2b2ff67 100644 --- a/drivers/hwmon/ftsteutates.c +++ b/drivers/hwmon/ftsteutates.c @@ -27,7 +27,6 @@ #include #include #include -#include #include #define FTS_DEVICE_ID_REG 0x0000 -- cgit v0.10.2 From 1d3dd4ce210f347dd214913544e22fd9a8122901 Mon Sep 17 00:00:00 2001 From: hotran Date: Thu, 21 Jul 2016 13:55:55 -0700 Subject: Documentation: dtb: xgene: Add hwmon dts binding documentation This patch adds the APM X-Gene hwmon device tree node documentation. Signed-off-by: Hoan Tran Acked-by: Rob Herring Signed-off-by: Guenter Roeck diff --git a/Documentation/devicetree/bindings/hwmon/apm-xgene-hwmon.txt b/Documentation/devicetree/bindings/hwmon/apm-xgene-hwmon.txt new file mode 100644 index 0000000..59b3855 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/apm-xgene-hwmon.txt @@ -0,0 +1,14 @@ +APM X-Gene hwmon driver + +APM X-Gene SOC sensors are accessed over the "SLIMpro" mailbox. + +Required properties : + - compatible : should be "apm,xgene-slimpro-hwmon" + - mboxes : use the label reference for the mailbox as the first parameter. + The second parameter is the channel number. + +Example : + hwmonslimpro { + compatible = "apm,xgene-slimpro-hwmon"; + mboxes = <&mailbox 7>; + }; -- cgit v0.10.2