forked from Minki/linux
74ba9207e1
Based on 1 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 you should have received a copy of the gnu general public license along with this program if not write to the free software foundation inc 675 mass ave cambridge ma 02139 usa extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 441 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Michael Ellerman <mpe@ellerman.id.au> (powerpc) Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190520071858.739733335@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
490 lines
14 KiB
C
490 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* adm1021.c - Part of lm_sensors, Linux kernel modules for hardware
|
|
* monitoring
|
|
* Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and
|
|
* Philip Edelbrock <phil@netroedge.com>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/hwmon.h>
|
|
#include <linux/hwmon-sysfs.h>
|
|
#include <linux/err.h>
|
|
#include <linux/mutex.h>
|
|
|
|
|
|
/* Addresses to scan */
|
|
static const unsigned short normal_i2c[] = {
|
|
0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, 0x4c, 0x4d, 0x4e, I2C_CLIENT_END };
|
|
|
|
enum chips {
|
|
adm1021, adm1023, max1617, max1617a, thmc10, lm84, gl523sm, mc1066 };
|
|
|
|
/* adm1021 constants specified below */
|
|
|
|
/* The adm1021 registers */
|
|
/* Read-only */
|
|
/* For nr in 0-1 */
|
|
#define ADM1021_REG_TEMP(nr) (nr)
|
|
#define ADM1021_REG_STATUS 0x02
|
|
/* 0x41 = AD, 0x49 = TI, 0x4D = Maxim, 0x23 = Genesys , 0x54 = Onsemi */
|
|
#define ADM1021_REG_MAN_ID 0xFE
|
|
/* ADM1021 = 0x0X, ADM1023 = 0x3X */
|
|
#define ADM1021_REG_DEV_ID 0xFF
|
|
/* These use different addresses for reading/writing */
|
|
#define ADM1021_REG_CONFIG_R 0x03
|
|
#define ADM1021_REG_CONFIG_W 0x09
|
|
#define ADM1021_REG_CONV_RATE_R 0x04
|
|
#define ADM1021_REG_CONV_RATE_W 0x0A
|
|
/* These are for the ADM1023's additional precision on the remote temp sensor */
|
|
#define ADM1023_REG_REM_TEMP_PREC 0x10
|
|
#define ADM1023_REG_REM_OFFSET 0x11
|
|
#define ADM1023_REG_REM_OFFSET_PREC 0x12
|
|
#define ADM1023_REG_REM_TOS_PREC 0x13
|
|
#define ADM1023_REG_REM_THYST_PREC 0x14
|
|
/* limits */
|
|
/* For nr in 0-1 */
|
|
#define ADM1021_REG_TOS_R(nr) (0x05 + 2 * (nr))
|
|
#define ADM1021_REG_TOS_W(nr) (0x0B + 2 * (nr))
|
|
#define ADM1021_REG_THYST_R(nr) (0x06 + 2 * (nr))
|
|
#define ADM1021_REG_THYST_W(nr) (0x0C + 2 * (nr))
|
|
/* write-only */
|
|
#define ADM1021_REG_ONESHOT 0x0F
|
|
|
|
/* Initial values */
|
|
|
|
/*
|
|
* Note: Even though I left the low and high limits named os and hyst,
|
|
* they don't quite work like a thermostat the way the LM75 does. I.e.,
|
|
* a lower temp than THYST actually triggers an alarm instead of
|
|
* clearing it. Weird, ey? --Phil
|
|
*/
|
|
|
|
/* Each client has this additional data */
|
|
struct adm1021_data {
|
|
struct i2c_client *client;
|
|
enum chips type;
|
|
|
|
const struct attribute_group *groups[3];
|
|
|
|
struct mutex update_lock;
|
|
char valid; /* !=0 if following fields are valid */
|
|
char low_power; /* !=0 if device in low power mode */
|
|
unsigned long last_updated; /* In jiffies */
|
|
|
|
int temp_max[2]; /* Register values */
|
|
int temp_min[2];
|
|
int temp[2];
|
|
u8 alarms;
|
|
/* Special values for ADM1023 only */
|
|
u8 remote_temp_offset;
|
|
u8 remote_temp_offset_prec;
|
|
};
|
|
|
|
/* (amalysh) read only mode, otherwise any limit's writing confuse BIOS */
|
|
static bool read_only;
|
|
|
|
static struct adm1021_data *adm1021_update_device(struct device *dev)
|
|
{
|
|
struct adm1021_data *data = dev_get_drvdata(dev);
|
|
struct i2c_client *client = data->client;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
|
|
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|
|
|| !data->valid) {
|
|
int i;
|
|
|
|
dev_dbg(dev, "Starting adm1021 update\n");
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
data->temp[i] = 1000 *
|
|
(s8) i2c_smbus_read_byte_data(
|
|
client, ADM1021_REG_TEMP(i));
|
|
data->temp_max[i] = 1000 *
|
|
(s8) i2c_smbus_read_byte_data(
|
|
client, ADM1021_REG_TOS_R(i));
|
|
if (data->type != lm84) {
|
|
data->temp_min[i] = 1000 *
|
|
(s8) i2c_smbus_read_byte_data(client,
|
|
ADM1021_REG_THYST_R(i));
|
|
}
|
|
}
|
|
data->alarms = i2c_smbus_read_byte_data(client,
|
|
ADM1021_REG_STATUS) & 0x7c;
|
|
if (data->type == adm1023) {
|
|
/*
|
|
* The ADM1023 provides 3 extra bits of precision for
|
|
* the remote sensor in extra registers.
|
|
*/
|
|
data->temp[1] += 125 * (i2c_smbus_read_byte_data(
|
|
client, ADM1023_REG_REM_TEMP_PREC) >> 5);
|
|
data->temp_max[1] += 125 * (i2c_smbus_read_byte_data(
|
|
client, ADM1023_REG_REM_TOS_PREC) >> 5);
|
|
data->temp_min[1] += 125 * (i2c_smbus_read_byte_data(
|
|
client, ADM1023_REG_REM_THYST_PREC) >> 5);
|
|
data->remote_temp_offset =
|
|
i2c_smbus_read_byte_data(client,
|
|
ADM1023_REG_REM_OFFSET);
|
|
data->remote_temp_offset_prec =
|
|
i2c_smbus_read_byte_data(client,
|
|
ADM1023_REG_REM_OFFSET_PREC);
|
|
}
|
|
data->last_updated = jiffies;
|
|
data->valid = 1;
|
|
}
|
|
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
return data;
|
|
}
|
|
|
|
static ssize_t temp_show(struct device *dev, struct device_attribute *devattr,
|
|
char *buf)
|
|
{
|
|
int index = to_sensor_dev_attr(devattr)->index;
|
|
struct adm1021_data *data = adm1021_update_device(dev);
|
|
|
|
return sprintf(buf, "%d\n", data->temp[index]);
|
|
}
|
|
|
|
static ssize_t temp_max_show(struct device *dev,
|
|
struct device_attribute *devattr, char *buf)
|
|
{
|
|
int index = to_sensor_dev_attr(devattr)->index;
|
|
struct adm1021_data *data = adm1021_update_device(dev);
|
|
|
|
return sprintf(buf, "%d\n", data->temp_max[index]);
|
|
}
|
|
|
|
static ssize_t temp_min_show(struct device *dev,
|
|
struct device_attribute *devattr, char *buf)
|
|
{
|
|
int index = to_sensor_dev_attr(devattr)->index;
|
|
struct adm1021_data *data = adm1021_update_device(dev);
|
|
|
|
return sprintf(buf, "%d\n", data->temp_min[index]);
|
|
}
|
|
|
|
static ssize_t alarm_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int index = to_sensor_dev_attr(attr)->index;
|
|
struct adm1021_data *data = adm1021_update_device(dev);
|
|
return sprintf(buf, "%u\n", (data->alarms >> index) & 1);
|
|
}
|
|
|
|
static ssize_t alarms_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct adm1021_data *data = adm1021_update_device(dev);
|
|
return sprintf(buf, "%u\n", data->alarms);
|
|
}
|
|
|
|
static ssize_t temp_max_store(struct device *dev,
|
|
struct device_attribute *devattr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int index = to_sensor_dev_attr(devattr)->index;
|
|
struct adm1021_data *data = dev_get_drvdata(dev);
|
|
struct i2c_client *client = data->client;
|
|
long temp;
|
|
int reg_val, err;
|
|
|
|
err = kstrtol(buf, 10, &temp);
|
|
if (err)
|
|
return err;
|
|
temp /= 1000;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
reg_val = clamp_val(temp, -128, 127);
|
|
data->temp_max[index] = reg_val * 1000;
|
|
if (!read_only)
|
|
i2c_smbus_write_byte_data(client, ADM1021_REG_TOS_W(index),
|
|
reg_val);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t temp_min_store(struct device *dev,
|
|
struct device_attribute *devattr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int index = to_sensor_dev_attr(devattr)->index;
|
|
struct adm1021_data *data = dev_get_drvdata(dev);
|
|
struct i2c_client *client = data->client;
|
|
long temp;
|
|
int reg_val, err;
|
|
|
|
err = kstrtol(buf, 10, &temp);
|
|
if (err)
|
|
return err;
|
|
temp /= 1000;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
reg_val = clamp_val(temp, -128, 127);
|
|
data->temp_min[index] = reg_val * 1000;
|
|
if (!read_only)
|
|
i2c_smbus_write_byte_data(client, ADM1021_REG_THYST_W(index),
|
|
reg_val);
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t low_power_show(struct device *dev,
|
|
struct device_attribute *devattr, char *buf)
|
|
{
|
|
struct adm1021_data *data = adm1021_update_device(dev);
|
|
return sprintf(buf, "%d\n", data->low_power);
|
|
}
|
|
|
|
static ssize_t low_power_store(struct device *dev,
|
|
struct device_attribute *devattr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct adm1021_data *data = dev_get_drvdata(dev);
|
|
struct i2c_client *client = data->client;
|
|
char low_power;
|
|
unsigned long val;
|
|
int err;
|
|
|
|
err = kstrtoul(buf, 10, &val);
|
|
if (err)
|
|
return err;
|
|
low_power = val != 0;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
if (low_power != data->low_power) {
|
|
int config = i2c_smbus_read_byte_data(
|
|
client, ADM1021_REG_CONFIG_R);
|
|
data->low_power = low_power;
|
|
i2c_smbus_write_byte_data(client, ADM1021_REG_CONFIG_W,
|
|
(config & 0xBF) | (low_power << 6));
|
|
}
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0);
|
|
static SENSOR_DEVICE_ATTR_RW(temp1_max, temp_max, 0);
|
|
static SENSOR_DEVICE_ATTR_RW(temp1_min, temp_min, 0);
|
|
static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, 1);
|
|
static SENSOR_DEVICE_ATTR_RW(temp2_max, temp_max, 1);
|
|
static SENSOR_DEVICE_ATTR_RW(temp2_min, temp_min, 1);
|
|
static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 6);
|
|
static SENSOR_DEVICE_ATTR_RO(temp1_min_alarm, alarm, 5);
|
|
static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, alarm, 4);
|
|
static SENSOR_DEVICE_ATTR_RO(temp2_min_alarm, alarm, 3);
|
|
static SENSOR_DEVICE_ATTR_RO(temp2_fault, alarm, 2);
|
|
|
|
static DEVICE_ATTR_RO(alarms);
|
|
static DEVICE_ATTR_RW(low_power);
|
|
|
|
static struct attribute *adm1021_attributes[] = {
|
|
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
|
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
|
&sensor_dev_attr_temp2_max.dev_attr.attr,
|
|
&sensor_dev_attr_temp2_input.dev_attr.attr,
|
|
&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
|
|
&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
|
|
&sensor_dev_attr_temp2_fault.dev_attr.attr,
|
|
&dev_attr_alarms.attr,
|
|
&dev_attr_low_power.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group adm1021_group = {
|
|
.attrs = adm1021_attributes,
|
|
};
|
|
|
|
static struct attribute *adm1021_min_attributes[] = {
|
|
&sensor_dev_attr_temp1_min.dev_attr.attr,
|
|
&sensor_dev_attr_temp2_min.dev_attr.attr,
|
|
&sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
|
|
&sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group adm1021_min_group = {
|
|
.attrs = adm1021_min_attributes,
|
|
};
|
|
|
|
/* Return 0 if detection is successful, -ENODEV otherwise */
|
|
static int adm1021_detect(struct i2c_client *client,
|
|
struct i2c_board_info *info)
|
|
{
|
|
struct i2c_adapter *adapter = client->adapter;
|
|
const char *type_name;
|
|
int conv_rate, status, config, man_id, dev_id;
|
|
|
|
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
|
|
pr_debug("detect failed, smbus byte data not supported!\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
status = i2c_smbus_read_byte_data(client, ADM1021_REG_STATUS);
|
|
conv_rate = i2c_smbus_read_byte_data(client,
|
|
ADM1021_REG_CONV_RATE_R);
|
|
config = i2c_smbus_read_byte_data(client, ADM1021_REG_CONFIG_R);
|
|
|
|
/* Check unused bits */
|
|
if ((status & 0x03) || (config & 0x3F) || (conv_rate & 0xF8)) {
|
|
pr_debug("detect failed, chip not detected!\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Determine the chip type. */
|
|
man_id = i2c_smbus_read_byte_data(client, ADM1021_REG_MAN_ID);
|
|
dev_id = i2c_smbus_read_byte_data(client, ADM1021_REG_DEV_ID);
|
|
|
|
if (man_id < 0 || dev_id < 0)
|
|
return -ENODEV;
|
|
|
|
if (man_id == 0x4d && dev_id == 0x01)
|
|
type_name = "max1617a";
|
|
else if (man_id == 0x41) {
|
|
if ((dev_id & 0xF0) == 0x30)
|
|
type_name = "adm1023";
|
|
else if ((dev_id & 0xF0) == 0x00)
|
|
type_name = "adm1021";
|
|
else
|
|
return -ENODEV;
|
|
} else if (man_id == 0x49)
|
|
type_name = "thmc10";
|
|
else if (man_id == 0x23)
|
|
type_name = "gl523sm";
|
|
else if (man_id == 0x54)
|
|
type_name = "mc1066";
|
|
else {
|
|
int lte, rte, lhi, rhi, llo, rlo;
|
|
|
|
/* extra checks for LM84 and MAX1617 to avoid misdetections */
|
|
|
|
llo = i2c_smbus_read_byte_data(client, ADM1021_REG_THYST_R(0));
|
|
rlo = i2c_smbus_read_byte_data(client, ADM1021_REG_THYST_R(1));
|
|
|
|
/* fail if any of the additional register reads failed */
|
|
if (llo < 0 || rlo < 0)
|
|
return -ENODEV;
|
|
|
|
lte = i2c_smbus_read_byte_data(client, ADM1021_REG_TEMP(0));
|
|
rte = i2c_smbus_read_byte_data(client, ADM1021_REG_TEMP(1));
|
|
lhi = i2c_smbus_read_byte_data(client, ADM1021_REG_TOS_R(0));
|
|
rhi = i2c_smbus_read_byte_data(client, ADM1021_REG_TOS_R(1));
|
|
|
|
/*
|
|
* Fail for negative temperatures and negative high limits.
|
|
* This check also catches read errors on the tested registers.
|
|
*/
|
|
if ((s8)lte < 0 || (s8)rte < 0 || (s8)lhi < 0 || (s8)rhi < 0)
|
|
return -ENODEV;
|
|
|
|
/* fail if all registers hold the same value */
|
|
if (lte == rte && lte == lhi && lte == rhi && lte == llo
|
|
&& lte == rlo)
|
|
return -ENODEV;
|
|
|
|
/*
|
|
* LM84 Mfr ID is in a different place,
|
|
* and it has more unused bits.
|
|
*/
|
|
if (conv_rate == 0x00
|
|
&& (config & 0x7F) == 0x00
|
|
&& (status & 0xAB) == 0x00) {
|
|
type_name = "lm84";
|
|
} else {
|
|
/* fail if low limits are larger than high limits */
|
|
if ((s8)llo > lhi || (s8)rlo > rhi)
|
|
return -ENODEV;
|
|
type_name = "max1617";
|
|
}
|
|
}
|
|
|
|
pr_debug("Detected chip %s at adapter %d, address 0x%02x.\n",
|
|
type_name, i2c_adapter_id(adapter), client->addr);
|
|
strlcpy(info->type, type_name, I2C_NAME_SIZE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void adm1021_init_client(struct i2c_client *client)
|
|
{
|
|
/* Enable ADC and disable suspend mode */
|
|
i2c_smbus_write_byte_data(client, ADM1021_REG_CONFIG_W,
|
|
i2c_smbus_read_byte_data(client, ADM1021_REG_CONFIG_R) & 0xBF);
|
|
/* Set Conversion rate to 1/sec (this can be tinkered with) */
|
|
i2c_smbus_write_byte_data(client, ADM1021_REG_CONV_RATE_W, 0x04);
|
|
}
|
|
|
|
static int adm1021_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct device *dev = &client->dev;
|
|
struct adm1021_data *data;
|
|
struct device *hwmon_dev;
|
|
|
|
data = devm_kzalloc(dev, sizeof(struct adm1021_data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
data->client = client;
|
|
data->type = id->driver_data;
|
|
mutex_init(&data->update_lock);
|
|
|
|
/* Initialize the ADM1021 chip */
|
|
if (data->type != lm84 && !read_only)
|
|
adm1021_init_client(client);
|
|
|
|
data->groups[0] = &adm1021_group;
|
|
if (data->type != lm84)
|
|
data->groups[1] = &adm1021_min_group;
|
|
|
|
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
|
data, data->groups);
|
|
|
|
return PTR_ERR_OR_ZERO(hwmon_dev);
|
|
}
|
|
|
|
static const struct i2c_device_id adm1021_id[] = {
|
|
{ "adm1021", adm1021 },
|
|
{ "adm1023", adm1023 },
|
|
{ "max1617", max1617 },
|
|
{ "max1617a", max1617a },
|
|
{ "thmc10", thmc10 },
|
|
{ "lm84", lm84 },
|
|
{ "gl523sm", gl523sm },
|
|
{ "mc1066", mc1066 },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, adm1021_id);
|
|
|
|
static struct i2c_driver adm1021_driver = {
|
|
.class = I2C_CLASS_HWMON,
|
|
.driver = {
|
|
.name = "adm1021",
|
|
},
|
|
.probe = adm1021_probe,
|
|
.id_table = adm1021_id,
|
|
.detect = adm1021_detect,
|
|
.address_list = normal_i2c,
|
|
};
|
|
|
|
module_i2c_driver(adm1021_driver);
|
|
|
|
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl> and "
|
|
"Philip Edelbrock <phil@netroedge.com>");
|
|
MODULE_DESCRIPTION("adm1021 driver");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
module_param(read_only, bool, 0);
|
|
MODULE_PARM_DESC(read_only, "Don't set any values, read only mode");
|