// 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) { 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_new = 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");