hwmon: Driver for MAX31730
authorGuenter Roeck <linux@roeck-us.net>
Sat, 23 Nov 2019 19:11:26 +0000 (11:11 -0800)
committerGuenter Roeck <linux@roeck-us.net>
Thu, 23 Jan 2020 21:15:09 +0000 (13:15 -0800)
MAX31730 is a 3-Channel Remote Temperature Sensor.

Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Documentation/hwmon/index.rst
Documentation/hwmon/max31730.rst [new file with mode: 0644]
drivers/hwmon/Kconfig
drivers/hwmon/Makefile
drivers/hwmon/max31730.c [new file with mode: 0644]

index 43cc605..b91da3a 100644 (file)
@@ -108,6 +108,7 @@ Hardware Monitoring Kernel Drivers
    max197
    max20751
    max31722
+   max31730
    max31785
    max31790
    max34440
diff --git a/Documentation/hwmon/max31730.rst b/Documentation/hwmon/max31730.rst
new file mode 100644 (file)
index 0000000..def0de1
--- /dev/null
@@ -0,0 +1,44 @@
+Kernel driver max31790
+======================
+
+Supported chips:
+
+  * Maxim MAX31730
+
+    Prefix: 'max31730'
+
+    Addresses scanned: 0x1c, 0x1d, 0x1e, 0x1f, 0x4c, 0x4d, 0x4e, 0x4f
+
+    Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX31730.pdf
+
+Author: Guenter Roeck <linux@roeck-us.net>
+
+
+Description
+-----------
+
+This driver implements support for Maxim MAX31730.
+
+The MAX31730 temperature sensor monitors its own temperature and the
+temperatures of three external diode-connected transistors. The operating
+supply voltage is from 3.0V to 3.6V. Resistance cancellation compensates
+for high series resistance in circuit-board traces and the external thermal
+diode, while beta compensation corrects for temperature-measurement
+errors due to low-beta sensing transistors.
+
+
+Sysfs entries
+-------------
+
+=================== == =======================================================
+temp[1-4]_enable    RW Temperature enable/disable
+                       Set to 0 to enable channel, 0 to disable
+temp[1-4]_input     RO Temperature input
+temp[2-4]_fault     RO Fault indicator for remote channels
+temp[1-4]_max       RW Maximum temperature
+temp[1-4]_max_alarm RW Maximum temperature alarm
+temp[1-4]_min       RW Minimum temperature. Common for all channels.
+                       Only temp1_min is writeable.
+temp[1-4]_min_alarm RO Minimum temperature alarm
+temp[2-4]_offset    RW Temperature offset for remote channels
+=================== == =======================================================
index 23dfe84..54e1b9c 100644 (file)
@@ -889,7 +889,7 @@ config SENSORS_MAX197
          will be called max197.
 
 config SENSORS_MAX31722
-tristate "MAX31722 temperature sensor"
+       tristate "MAX31722 temperature sensor"
        depends on SPI
        help
          Support for the Maxim Integrated MAX31722/MAX31723 digital
@@ -898,6 +898,16 @@ tristate "MAX31722 temperature sensor"
          This driver can also be built as a module. If so, the module
          will be called max31722.
 
+config SENSORS_MAX31730
+       tristate "MAX31730 temperature sensor"
+       depends on I2C
+       help
+         Support for the Maxim Integrated MAX31730 3-Channel Remote
+         Temperature Sensor.
+
+         This driver can also be built as a module. If so, the module
+         will be called max31730.
+
 config SENSORS_MAX6621
        tristate "Maxim MAX6621 sensor chip"
        depends on I2C
index 6db5db9..226a118 100644 (file)
@@ -123,6 +123,7 @@ obj-$(CONFIG_SENSORS_MAX1619)       += max1619.o
 obj-$(CONFIG_SENSORS_MAX1668)  += max1668.o
 obj-$(CONFIG_SENSORS_MAX197)   += max197.o
 obj-$(CONFIG_SENSORS_MAX31722) += max31722.o
+obj-$(CONFIG_SENSORS_MAX31730) += max31730.o
 obj-$(CONFIG_SENSORS_MAX6621)  += max6621.o
 obj-$(CONFIG_SENSORS_MAX6639)  += max6639.o
 obj-$(CONFIG_SENSORS_MAX6642)  += max6642.o
diff --git a/drivers/hwmon/max31730.c b/drivers/hwmon/max31730.c
new file mode 100644 (file)
index 0000000..eb22a34
--- /dev/null
@@ -0,0 +1,440 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for MAX31730 3-Channel Remote Temperature Sensor
+ *
+ * Copyright (c) 2019 Guenter Roeck <linux@roeck-us.net>
+ */
+
+#include <linux/bits.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/hwmon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+
+/* Addresses scanned */
+static const unsigned short normal_i2c[] = { 0x1c, 0x1d, 0x1e, 0x1f, 0x4c,
+                                            0x4d, 0x4e, 0x4f, I2C_CLIENT_END };
+
+/* The MAX31730 registers */
+#define MAX31730_REG_TEMP              0x00
+#define MAX31730_REG_CONF              0x13
+#define  MAX31730_STOP                 BIT(7)
+#define  MAX31730_EXTRANGE             BIT(1)
+#define MAX31730_REG_TEMP_OFFSET       0x16
+#define  MAX31730_TEMP_OFFSET_BASELINE 0x77
+#define MAX31730_REG_OFFSET_ENABLE     0x17
+#define MAX31730_REG_TEMP_MAX          0x20
+#define MAX31730_REG_TEMP_MIN          0x30
+#define MAX31730_REG_STATUS_HIGH       0x32
+#define MAX31730_REG_STATUS_LOW                0x33
+#define MAX31730_REG_CHANNEL_ENABLE    0x35
+#define MAX31730_REG_TEMP_FAULT                0x36
+
+#define MAX31730_REG_MFG_ID            0x50
+#define  MAX31730_MFG_ID               0x4d
+#define MAX31730_REG_MFG_REV           0x51
+#define  MAX31730_MFG_REV              0x01
+
+#define MAX31730_TEMP_MIN              (-128000)
+#define MAX31730_TEMP_MAX              127937
+
+/* Each client has this additional data */
+struct max31730_data {
+       struct i2c_client       *client;
+       u8                      orig_conf;
+       u8                      current_conf;
+       u8                      offset_enable;
+       u8                      channel_enable;
+};
+
+/*-----------------------------------------------------------------------*/
+
+static inline long max31730_reg_to_mc(s16 temp)
+{
+       return DIV_ROUND_CLOSEST((temp >> 4) * 1000, 16);
+}
+
+static int max31730_write_config(struct max31730_data *data, u8 set_mask,
+                                u8 clr_mask)
+{
+       u8 value;
+
+       clr_mask |= MAX31730_EXTRANGE;
+       value = data->current_conf & ~clr_mask;
+       value |= set_mask;
+
+       if (data->current_conf != value) {
+               s32 err;
+
+               err = i2c_smbus_write_byte_data(data->client, MAX31730_REG_CONF,
+                                               value);
+               if (err)
+                       return err;
+               data->current_conf = value;
+       }
+       return 0;
+}
+
+static int max31730_set_enable(struct i2c_client *client, int reg,
+                              u8 *confdata, int channel, bool enable)
+{
+       u8 regval = *confdata;
+       int err;
+
+       if (enable)
+               regval |= BIT(channel);
+       else
+               regval &= ~BIT(channel);
+
+       if (regval != *confdata) {
+               err = i2c_smbus_write_byte_data(client, reg, regval);
+               if (err)
+                       return err;
+               *confdata = regval;
+       }
+       return 0;
+}
+
+static int max31730_set_offset_enable(struct max31730_data *data, int channel,
+                                     bool enable)
+{
+       return max31730_set_enable(data->client, MAX31730_REG_OFFSET_ENABLE,
+                                  &data->offset_enable, channel, enable);
+}
+
+static int max31730_set_channel_enable(struct max31730_data *data, int channel,
+                                      bool enable)
+{
+       return max31730_set_enable(data->client, MAX31730_REG_CHANNEL_ENABLE,
+                                  &data->channel_enable, channel, enable);
+}
+
+static int max31730_read(struct device *dev, enum hwmon_sensor_types type,
+                        u32 attr, int channel, long *val)
+{
+       struct max31730_data *data = dev_get_drvdata(dev);
+       int regval, reg, offset;
+
+       if (type != hwmon_temp)
+               return -EINVAL;
+
+       switch (attr) {
+       case hwmon_temp_input:
+               if (!(data->channel_enable & BIT(channel)))
+                       return -ENODATA;
+               reg = MAX31730_REG_TEMP + (channel * 2);
+               break;
+       case hwmon_temp_max:
+               reg = MAX31730_REG_TEMP_MAX + (channel * 2);
+               break;
+       case hwmon_temp_min:
+               reg = MAX31730_REG_TEMP_MIN;
+               break;
+       case hwmon_temp_enable:
+               *val = !!(data->channel_enable & BIT(channel));
+               return 0;
+       case hwmon_temp_offset:
+               if (!channel)
+                       return -EINVAL;
+               if (!(data->offset_enable & BIT(channel))) {
+                       *val = 0;
+                       return 0;
+               }
+               offset = i2c_smbus_read_byte_data(data->client,
+                                                 MAX31730_REG_TEMP_OFFSET);
+               if (offset < 0)
+                       return offset;
+               *val = (offset - MAX31730_TEMP_OFFSET_BASELINE) * 125;
+               return 0;
+       case hwmon_temp_fault:
+               regval = i2c_smbus_read_byte_data(data->client,
+                                                 MAX31730_REG_TEMP_FAULT);
+               if (regval < 0)
+                       return regval;
+               *val = !!(regval & BIT(channel));
+               return 0;
+       case hwmon_temp_min_alarm:
+               regval = i2c_smbus_read_byte_data(data->client,
+                                                 MAX31730_REG_STATUS_LOW);
+               if (regval < 0)
+                       return regval;
+               *val = !!(regval & BIT(channel));
+               return 0;
+       case hwmon_temp_max_alarm:
+               regval = i2c_smbus_read_byte_data(data->client,
+                                                 MAX31730_REG_STATUS_HIGH);
+               if (regval < 0)
+                       return regval;
+               *val = !!(regval & BIT(channel));
+               return 0;
+       default:
+               return -EINVAL;
+       }
+       regval = i2c_smbus_read_word_swapped(data->client, reg);
+       if (regval < 0)
+               return regval;
+
+       *val = max31730_reg_to_mc(regval);
+
+       return 0;
+}
+
+static int max31730_write(struct device *dev, enum hwmon_sensor_types type,
+                         u32 attr, int channel, long val)
+{
+       struct max31730_data *data = dev_get_drvdata(dev);
+       int reg, err;
+
+       if (type != hwmon_temp)
+               return -EINVAL;
+
+       switch (attr) {
+       case hwmon_temp_max:
+               reg = MAX31730_REG_TEMP_MAX + channel * 2;
+               break;
+       case hwmon_temp_min:
+               reg = MAX31730_REG_TEMP_MIN;
+               break;
+       case hwmon_temp_enable:
+               if (val != 0 && val != 1)
+                       return -EINVAL;
+               return max31730_set_channel_enable(data, channel, val);
+       case hwmon_temp_offset:
+               val = clamp_val(val, -14875, 17000) + 14875;
+               val = DIV_ROUND_CLOSEST(val, 125);
+               err = max31730_set_offset_enable(data, channel,
+                                       val != MAX31730_TEMP_OFFSET_BASELINE);
+               if (err)
+                       return err;
+               return i2c_smbus_write_byte_data(data->client,
+                                                MAX31730_REG_TEMP_OFFSET, val);
+       default:
+               return -EINVAL;
+       }
+
+       val = clamp_val(val, MAX31730_TEMP_MIN, MAX31730_TEMP_MAX);
+       val = DIV_ROUND_CLOSEST(val << 4, 1000) << 4;
+
+       return i2c_smbus_write_word_swapped(data->client, reg, (u16)val);
+}
+
+static umode_t max31730_is_visible(const void *data,
+                                  enum hwmon_sensor_types type,
+                                  u32 attr, int channel)
+{
+       switch (type) {
+       case hwmon_temp:
+               switch (attr) {
+               case hwmon_temp_input:
+               case hwmon_temp_min_alarm:
+               case hwmon_temp_max_alarm:
+               case hwmon_temp_fault:
+                       return 0444;
+               case hwmon_temp_min:
+                       return channel ? 0444 : 0644;
+               case hwmon_temp_offset:
+               case hwmon_temp_enable:
+               case hwmon_temp_max:
+                       return 0644;
+               }
+               break;
+       default:
+               break;
+       }
+       return 0;
+}
+
+static const struct hwmon_channel_info *max31730_info[] = {
+       HWMON_CHANNEL_INFO(chip,
+                          HWMON_C_REGISTER_TZ),
+       HWMON_CHANNEL_INFO(temp,
+                          HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+                          HWMON_T_ENABLE |
+                          HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM,
+                          HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+                          HWMON_T_OFFSET | HWMON_T_ENABLE |
+                          HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
+                          HWMON_T_FAULT,
+                          HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+                          HWMON_T_OFFSET | HWMON_T_ENABLE |
+                          HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
+                          HWMON_T_FAULT,
+                          HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+                          HWMON_T_OFFSET | HWMON_T_ENABLE |
+                          HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
+                          HWMON_T_FAULT
+                          ),
+       NULL
+};
+
+static const struct hwmon_ops max31730_hwmon_ops = {
+       .is_visible = max31730_is_visible,
+       .read = max31730_read,
+       .write = max31730_write,
+};
+
+static const struct hwmon_chip_info max31730_chip_info = {
+       .ops = &max31730_hwmon_ops,
+       .info = max31730_info,
+};
+
+static void max31730_remove(void *data)
+{
+       struct max31730_data *max31730 = data;
+       struct i2c_client *client = max31730->client;
+
+       i2c_smbus_write_byte_data(client, MAX31730_REG_CONF,
+                                 max31730->orig_conf);
+}
+
+static int
+max31730_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+       struct device *dev = &client->dev;
+       struct device *hwmon_dev;
+       struct max31730_data *data;
+       int status, err;
+
+       if (!i2c_check_functionality(client->adapter,
+                       I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
+               return -EIO;
+
+       data = devm_kzalloc(dev, sizeof(struct max31730_data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       data->client = client;
+
+       /* Cache original configuration and enable status */
+       status = i2c_smbus_read_byte_data(client, MAX31730_REG_CHANNEL_ENABLE);
+       if (status < 0)
+               return status;
+       data->channel_enable = status;
+
+       status = i2c_smbus_read_byte_data(client, MAX31730_REG_OFFSET_ENABLE);
+       if (status < 0)
+               return status;
+       data->offset_enable = status;
+
+       status = i2c_smbus_read_byte_data(client, MAX31730_REG_CONF);
+       if (status < 0)
+               return status;
+       data->orig_conf = status;
+       data->current_conf = status;
+
+       err = max31730_write_config(data,
+                                   data->channel_enable ? 0 : MAX31730_STOP,
+                                   data->channel_enable ? MAX31730_STOP : 0);
+       if (err)
+               return err;
+
+       dev_set_drvdata(dev, data);
+
+       err = devm_add_action_or_reset(dev, max31730_remove, data);
+       if (err)
+               return err;
+
+       hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+                                                        data,
+                                                        &max31730_chip_info,
+                                                        NULL);
+       return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct i2c_device_id max31730_ids[] = {
+       { "max31730", 0, },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, max31730_ids);
+
+static const struct of_device_id __maybe_unused max31730_of_match[] = {
+       {
+               .compatible = "maxim,max31730",
+       },
+       { },
+};
+MODULE_DEVICE_TABLE(of, max31730_of_match);
+
+static bool max31730_check_reg_temp(struct i2c_client *client,
+                                   int reg)
+{
+       int regval;
+
+       regval = i2c_smbus_read_byte_data(client, reg + 1);
+       return regval < 0 || (regval & 0x0f);
+}
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int max31730_detect(struct i2c_client *client,
+                          struct i2c_board_info *info)
+{
+       struct i2c_adapter *adapter = client->adapter;
+       int regval;
+       int i;
+
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
+                                    I2C_FUNC_SMBUS_WORD_DATA))
+               return -ENODEV;
+
+       regval = i2c_smbus_read_byte_data(client, MAX31730_REG_MFG_ID);
+       if (regval != MAX31730_MFG_ID)
+               return -ENODEV;
+       regval = i2c_smbus_read_byte_data(client, MAX31730_REG_MFG_REV);
+       if (regval != MAX31730_MFG_REV)
+               return -ENODEV;
+
+       /* lower 4 bit of temperature and limit registers must be 0 */
+       if (max31730_check_reg_temp(client, MAX31730_REG_TEMP_MIN))
+               return -ENODEV;
+
+       for (i = 0; i < 4; i++) {
+               if (max31730_check_reg_temp(client, MAX31730_REG_TEMP + i * 2))
+                       return -ENODEV;
+               if (max31730_check_reg_temp(client,
+                                           MAX31730_REG_TEMP_MAX + i * 2))
+                       return -ENODEV;
+       }
+
+       strlcpy(info->type, "max31730", I2C_NAME_SIZE);
+
+       return 0;
+}
+
+static int __maybe_unused max31730_suspend(struct device *dev)
+{
+       struct max31730_data *data = dev_get_drvdata(dev);
+
+       return max31730_write_config(data, MAX31730_STOP, 0);
+}
+
+static int __maybe_unused max31730_resume(struct device *dev)
+{
+       struct max31730_data *data = dev_get_drvdata(dev);
+
+       return max31730_write_config(data, 0, MAX31730_STOP);
+}
+
+static SIMPLE_DEV_PM_OPS(max31730_pm_ops, max31730_suspend, max31730_resume);
+
+static struct i2c_driver max31730_driver = {
+       .class          = I2C_CLASS_HWMON,
+       .driver = {
+               .name   = "max31730",
+               .of_match_table = of_match_ptr(max31730_of_match),
+               .pm     = &max31730_pm_ops,
+       },
+       .probe          = max31730_probe,
+       .id_table       = max31730_ids,
+       .detect         = max31730_detect,
+       .address_list   = normal_i2c,
+};
+
+module_i2c_driver(max31730_driver);
+
+MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
+MODULE_DESCRIPTION("MAX31730 driver");
+MODULE_LICENSE("GPL");