mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-24 01:41:39 +00:00
![Rob Herring](/assets/img/avatar_default.png)
The DT of_device.h and of_platform.h date back to the separate of_platform_bus_type before it as merged into the regular platform bus. As part of that merge prepping Arm DT support 13 years ago, they "temporarily" include each other. They also include platform_device.h and of.h. As a result, there's a pretty much random mix of those include files used throughout the tree. In order to detangle these headers and replace the implicit includes with struct declarations, users need to explicitly include the correct includes. Signed-off-by: Rob Herring <robh@kernel.org> Link: https://lore.kernel.org/r/20230714174607.4057185-1-robh@kernel.org Signed-off-by: Guenter Roeck <linux@roeck-us.net>
439 lines
11 KiB
C
439 lines
11 KiB
C
// 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");
|