linux/drivers/hwmon/max6621.c

566 lines
14 KiB
C
Raw Normal View History

treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 157 Based on 3 normalized pattern(s): 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 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 [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] 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 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 [author] [graeme] [gregory] [gg]@[slimlogic] [co] [uk] [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] [based] [on] [twl6030]_[usb] [c] [author] [hema] [hk] [hemahk]@[ti] [com] 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 1105 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070033.202006027@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-05-27 06:55:06 +00:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Hardware monitoring driver for Maxim MAX6621
*
* Copyright (c) 2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2017 Vadim Pasternak <vadimp@mellanox.com>
*/
#include <linux/bitops.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
#define MAX6621_DRV_NAME "max6621"
#define MAX6621_TEMP_INPUT_REG_NUM 9
#define MAX6621_TEMP_INPUT_MIN -127000
#define MAX6621_TEMP_INPUT_MAX 128000
#define MAX6621_TEMP_ALERT_CHAN_SHIFT 1
#define MAX6621_TEMP_S0D0_REG 0x00
#define MAX6621_TEMP_S0D1_REG 0x01
#define MAX6621_TEMP_S1D0_REG 0x02
#define MAX6621_TEMP_S1D1_REG 0x03
#define MAX6621_TEMP_S2D0_REG 0x04
#define MAX6621_TEMP_S2D1_REG 0x05
#define MAX6621_TEMP_S3D0_REG 0x06
#define MAX6621_TEMP_S3D1_REG 0x07
#define MAX6621_TEMP_MAX_REG 0x08
#define MAX6621_TEMP_MAX_ADDR_REG 0x0a
#define MAX6621_TEMP_ALERT_CAUSE_REG 0x0b
#define MAX6621_CONFIG0_REG 0x0c
#define MAX6621_CONFIG1_REG 0x0d
#define MAX6621_CONFIG2_REG 0x0e
#define MAX6621_CONFIG3_REG 0x0f
#define MAX6621_TEMP_S0_ALERT_REG 0x10
#define MAX6621_TEMP_S1_ALERT_REG 0x11
#define MAX6621_TEMP_S2_ALERT_REG 0x12
#define MAX6621_TEMP_S3_ALERT_REG 0x13
#define MAX6621_CLEAR_ALERT_REG 0x15
#define MAX6621_REG_MAX (MAX6621_CLEAR_ALERT_REG + 1)
#define MAX6621_REG_TEMP_SHIFT 0x06
#define MAX6621_ENABLE_TEMP_ALERTS_BIT 4
#define MAX6621_ENABLE_I2C_CRC_BIT 5
#define MAX6621_ENABLE_ALTERNATE_DATA 6
#define MAX6621_ENABLE_LOCKUP_TO 7
#define MAX6621_ENABLE_S0D0_BIT 8
#define MAX6621_ENABLE_S3D1_BIT 15
#define MAX6621_ENABLE_TEMP_ALL GENMASK(MAX6621_ENABLE_S3D1_BIT, \
MAX6621_ENABLE_S0D0_BIT)
#define MAX6621_POLL_DELAY_MASK 0x5
#define MAX6621_CONFIG0_INIT (MAX6621_ENABLE_TEMP_ALL | \
BIT(MAX6621_ENABLE_LOCKUP_TO) | \
BIT(MAX6621_ENABLE_I2C_CRC_BIT) | \
MAX6621_POLL_DELAY_MASK)
#define MAX6621_PECI_BIT_TIME 0x2
#define MAX6621_PECI_RETRY_NUM 0x3
#define MAX6621_CONFIG1_INIT ((MAX6621_PECI_BIT_TIME << 8) | \
MAX6621_PECI_RETRY_NUM)
/* Error codes */
#define MAX6621_TRAN_FAILED 0x8100 /*
* PECI transaction failed for more
* than the configured number of
* consecutive retries.
*/
#define MAX6621_POOL_DIS 0x8101 /*
* Polling disabled for requested
* socket/domain.
*/
#define MAX6621_POOL_UNCOMPLETE 0x8102 /*
* First poll not yet completed for
* requested socket/domain (on
* startup).
*/
#define MAX6621_SD_DIS 0x8103 /*
* Read maximum temperature requested,
* but no sockets/domains enabled or
* all enabled sockets/domains have
* errors; or read maximum temperature
* address requested, but read maximum
* temperature was not called.
*/
#define MAX6621_ALERT_DIS 0x8104 /*
* Get alert socket/domain requested,
* but no alert active.
*/
#define MAX6621_PECI_ERR_MIN 0x8000 /* Intel spec PECI error min value. */
#define MAX6621_PECI_ERR_MAX 0x80ff /* Intel spec PECI error max value. */
static const u32 max6621_temp_regs[] = {
MAX6621_TEMP_MAX_REG, MAX6621_TEMP_S0D0_REG, MAX6621_TEMP_S1D0_REG,
MAX6621_TEMP_S2D0_REG, MAX6621_TEMP_S3D0_REG, MAX6621_TEMP_S0D1_REG,
MAX6621_TEMP_S1D1_REG, MAX6621_TEMP_S2D1_REG, MAX6621_TEMP_S3D1_REG,
};
static const char *const max6621_temp_labels[] = {
"maximum",
"socket0_0",
"socket1_0",
"socket2_0",
"socket3_0",
"socket0_1",
"socket1_1",
"socket2_1",
"socket3_1",
};
static const int max6621_temp_alert_chan2reg[] = {
MAX6621_TEMP_S0_ALERT_REG,
MAX6621_TEMP_S1_ALERT_REG,
MAX6621_TEMP_S2_ALERT_REG,
MAX6621_TEMP_S3_ALERT_REG,
};
/**
* struct max6621_data - private data:
*
* @client: I2C client;
* @regmap: register map handle;
* @input_chan2reg: mapping from channel to register;
*/
struct max6621_data {
struct i2c_client *client;
struct regmap *regmap;
int input_chan2reg[MAX6621_TEMP_INPUT_REG_NUM + 1];
};
static long max6621_temp_mc2reg(long val)
{
return (val / 1000L) << MAX6621_REG_TEMP_SHIFT;
}
static umode_t
max6621_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
int channel)
{
/* Skip channels which are not physically conncted. */
if (((struct max6621_data *)data)->input_chan2reg[channel] < 0)
return 0;
switch (type) {
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
case hwmon_temp_label:
case hwmon_temp_crit_alarm:
return 0444;
case hwmon_temp_offset:
case hwmon_temp_crit:
return 0644;
default:
break;
}
break;
default:
break;
}
return 0;
}
static int max6621_verify_reg_data(struct device *dev, int regval)
{
if (regval >= MAX6621_PECI_ERR_MIN &&
regval <= MAX6621_PECI_ERR_MAX) {
dev_dbg(dev, "PECI error code - err 0x%04x.\n",
regval);
return -EIO;
}
switch (regval) {
case MAX6621_TRAN_FAILED:
dev_dbg(dev, "PECI transaction failed - err 0x%04x.\n",
regval);
return -EIO;
case MAX6621_POOL_DIS:
dev_dbg(dev, "Polling disabled - err 0x%04x.\n", regval);
return -EOPNOTSUPP;
case MAX6621_POOL_UNCOMPLETE:
dev_dbg(dev, "First poll not completed on startup - err 0x%04x.\n",
regval);
return -EIO;
case MAX6621_SD_DIS:
dev_dbg(dev, "Resource is disabled - err 0x%04x.\n", regval);
return -EOPNOTSUPP;
case MAX6621_ALERT_DIS:
dev_dbg(dev, "No alert active - err 0x%04x.\n", regval);
return -EOPNOTSUPP;
default:
return 0;
}
}
static int
max6621_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, long *val)
{
struct max6621_data *data = dev_get_drvdata(dev);
u32 regval;
int reg;
s8 temp;
int ret;
switch (type) {
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
reg = data->input_chan2reg[channel];
ret = regmap_read(data->regmap, reg, &regval);
if (ret)
return ret;
ret = max6621_verify_reg_data(dev, regval);
if (ret)
return ret;
/*
* Bit MAX6621_REG_TEMP_SHIFT represents 1 degree step.
* The temperature is given in two's complement and 8
* bits is used for the register conversion.
*/
temp = (regval >> MAX6621_REG_TEMP_SHIFT);
*val = temp * 1000L;
break;
case hwmon_temp_offset:
ret = regmap_read(data->regmap, MAX6621_CONFIG2_REG,
&regval);
if (ret)
return ret;
ret = max6621_verify_reg_data(dev, regval);
if (ret)
return ret;
*val = (regval >> MAX6621_REG_TEMP_SHIFT) *
1000L;
break;
case hwmon_temp_crit:
channel -= MAX6621_TEMP_ALERT_CHAN_SHIFT;
reg = max6621_temp_alert_chan2reg[channel];
ret = regmap_read(data->regmap, reg, &regval);
if (ret)
return ret;
ret = max6621_verify_reg_data(dev, regval);
if (ret)
return ret;
*val = regval * 1000L;
break;
case hwmon_temp_crit_alarm:
/*
* Set val to zero to recover the case, when reading
* MAX6621_TEMP_ALERT_CAUSE_REG results in for example
* MAX6621_ALERT_DIS. Reading will return with error,
* but in such case alarm should be returned as 0.
*/
*val = 0;
ret = regmap_read(data->regmap,
MAX6621_TEMP_ALERT_CAUSE_REG,
&regval);
if (ret)
return ret;
ret = max6621_verify_reg_data(dev, regval);
if (ret) {
/* Do not report error if alert is disabled. */
if (regval == MAX6621_ALERT_DIS)
return 0;
else
return ret;
}
/*
* Clear the alert automatically, using send-byte
* smbus protocol for clearing alert.
*/
if (regval) {
ret = i2c_smbus_write_byte(data->client,
MAX6621_CLEAR_ALERT_REG);
if (ret)
return ret;
}
*val = !!regval;
break;
default:
return -EOPNOTSUPP;
}
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static int
max6621_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, long val)
{
struct max6621_data *data = dev_get_drvdata(dev);
u32 reg;
switch (type) {
case hwmon_temp:
switch (attr) {
case hwmon_temp_offset:
/* Clamp to allowed range to prevent overflow. */
val = clamp_val(val, MAX6621_TEMP_INPUT_MIN,
MAX6621_TEMP_INPUT_MAX);
val = max6621_temp_mc2reg(val);
return regmap_write(data->regmap,
MAX6621_CONFIG2_REG, val);
case hwmon_temp_crit:
channel -= MAX6621_TEMP_ALERT_CHAN_SHIFT;
reg = max6621_temp_alert_chan2reg[channel];
/* Clamp to allowed range to prevent overflow. */
val = clamp_val(val, MAX6621_TEMP_INPUT_MIN,
MAX6621_TEMP_INPUT_MAX);
val = val / 1000L;
return regmap_write(data->regmap, reg, val);
default:
return -EOPNOTSUPP;
}
break;
default:
return -EOPNOTSUPP;
}
return -EOPNOTSUPP;
}
static int
max6621_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, const char **str)
{
switch (type) {
case hwmon_temp:
switch (attr) {
case hwmon_temp_label:
*str = max6621_temp_labels[channel];
return 0;
default:
return -EOPNOTSUPP;
}
break;
default:
return -EOPNOTSUPP;
}
return -EOPNOTSUPP;
}
static bool max6621_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case MAX6621_CONFIG0_REG:
case MAX6621_CONFIG1_REG:
case MAX6621_CONFIG2_REG:
case MAX6621_CONFIG3_REG:
case MAX6621_TEMP_S0_ALERT_REG:
case MAX6621_TEMP_S1_ALERT_REG:
case MAX6621_TEMP_S2_ALERT_REG:
case MAX6621_TEMP_S3_ALERT_REG:
case MAX6621_TEMP_ALERT_CAUSE_REG:
return true;
}
return false;
}
static bool max6621_readable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case MAX6621_TEMP_S0D0_REG:
case MAX6621_TEMP_S0D1_REG:
case MAX6621_TEMP_S1D0_REG:
case MAX6621_TEMP_S1D1_REG:
case MAX6621_TEMP_S2D0_REG:
case MAX6621_TEMP_S2D1_REG:
case MAX6621_TEMP_S3D0_REG:
case MAX6621_TEMP_S3D1_REG:
case MAX6621_TEMP_MAX_REG:
case MAX6621_TEMP_MAX_ADDR_REG:
case MAX6621_CONFIG0_REG:
case MAX6621_CONFIG1_REG:
case MAX6621_CONFIG2_REG:
case MAX6621_CONFIG3_REG:
case MAX6621_TEMP_S0_ALERT_REG:
case MAX6621_TEMP_S1_ALERT_REG:
case MAX6621_TEMP_S2_ALERT_REG:
case MAX6621_TEMP_S3_ALERT_REG:
return true;
}
return false;
}
static bool max6621_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case MAX6621_TEMP_S0D0_REG:
case MAX6621_TEMP_S0D1_REG:
case MAX6621_TEMP_S1D0_REG:
case MAX6621_TEMP_S1D1_REG:
case MAX6621_TEMP_S2D0_REG:
case MAX6621_TEMP_S2D1_REG:
case MAX6621_TEMP_S3D0_REG:
case MAX6621_TEMP_S3D1_REG:
case MAX6621_TEMP_MAX_REG:
case MAX6621_TEMP_S0_ALERT_REG:
case MAX6621_TEMP_S1_ALERT_REG:
case MAX6621_TEMP_S2_ALERT_REG:
case MAX6621_TEMP_S3_ALERT_REG:
case MAX6621_TEMP_ALERT_CAUSE_REG:
return true;
}
return false;
}
static const struct reg_default max6621_regmap_default[] = {
{ MAX6621_CONFIG0_REG, MAX6621_CONFIG0_INIT },
{ MAX6621_CONFIG1_REG, MAX6621_CONFIG1_INIT },
};
static const struct regmap_config max6621_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
.max_register = MAX6621_REG_MAX,
.val_format_endian = REGMAP_ENDIAN_LITTLE,
.cache_type = REGCACHE_FLAT,
.writeable_reg = max6621_writeable_reg,
.readable_reg = max6621_readable_reg,
.volatile_reg = max6621_volatile_reg,
.reg_defaults = max6621_regmap_default,
.num_reg_defaults = ARRAY_SIZE(max6621_regmap_default),
};
static const struct hwmon_channel_info * const max6621_info[] = {
HWMON_CHANNEL_INFO(chip,
HWMON_C_REGISTER_TZ),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_OFFSET,
HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL),
NULL
};
static const struct hwmon_ops max6621_hwmon_ops = {
.read = max6621_read,
.write = max6621_write,
.read_string = max6621_read_string,
.is_visible = max6621_is_visible,
};
static const struct hwmon_chip_info max6621_chip_info = {
.ops = &max6621_hwmon_ops,
.info = max6621_info,
};
static int max6621_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct max6621_data *data;
struct device *hwmon_dev;
int i;
int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->regmap = devm_regmap_init_i2c(client, &max6621_regmap_config);
if (IS_ERR(data->regmap))
return PTR_ERR(data->regmap);
i2c_set_clientdata(client, data);
data->client = client;
/* Set CONFIG0 register masking temperature alerts and PEC. */
ret = regmap_write(data->regmap, MAX6621_CONFIG0_REG,
MAX6621_CONFIG0_INIT);
if (ret)
return ret;
/* Set CONFIG1 register for PEC access retry number. */
ret = regmap_write(data->regmap, MAX6621_CONFIG1_REG,
MAX6621_CONFIG1_INIT);
if (ret)
return ret;
/* Sync registers with hardware. */
regcache_mark_dirty(data->regmap);
ret = regcache_sync(data->regmap);
if (ret)
return ret;
/* Verify which temperature input registers are enabled. */
for (i = 0; i < MAX6621_TEMP_INPUT_REG_NUM; i++) {
ret = i2c_smbus_read_word_data(client, max6621_temp_regs[i]);
if (ret < 0)
return ret;
ret = max6621_verify_reg_data(dev, ret);
if (ret) {
data->input_chan2reg[i] = -1;
continue;
}
data->input_chan2reg[i] = max6621_temp_regs[i];
}
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
data,
&max6621_chip_info,
NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct i2c_device_id max6621_id[] = {
{ MAX6621_DRV_NAME, 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, max6621_id);
static const struct of_device_id __maybe_unused max6621_of_match[] = {
{ .compatible = "maxim,max6621" },
{ }
};
MODULE_DEVICE_TABLE(of, max6621_of_match);
static struct i2c_driver max6621_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = MAX6621_DRV_NAME,
.of_match_table = of_match_ptr(max6621_of_match),
},
.probe = max6621_probe,
.id_table = max6621_id,
};
module_i2c_driver(max6621_driver);
MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
MODULE_DESCRIPTION("Driver for Maxim MAX6621");
MODULE_LICENSE("GPL");