| // 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.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 * const 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) |
| { |
| 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; |
| } |
| |
| strscpy(info->type, "max31730", I2C_NAME_SIZE); |
| |
| return 0; |
| } |
| |
| static int max31730_suspend(struct device *dev) |
| { |
| struct max31730_data *data = dev_get_drvdata(dev); |
| |
| return max31730_write_config(data, MAX31730_STOP, 0); |
| } |
| |
| static int max31730_resume(struct device *dev) |
| { |
| struct max31730_data *data = dev_get_drvdata(dev); |
| |
| return max31730_write_config(data, 0, MAX31730_STOP); |
| } |
| |
| static DEFINE_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 = pm_sleep_ptr(&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"); |