mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
514 lines
11 KiB
514 lines
11 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* Hardware monitoring driver for Maxim MAX6620 |
|
* |
|
* Originally from L. Grunenberg. |
|
* (C) 2012 by L. Grunenberg <[email protected]> |
|
* |
|
* Copyright (c) 2021 Dell Inc. or its subsidiaries. All Rights Reserved. |
|
* |
|
* based on code written by : |
|
* 2007 by Hans J. Koch <[email protected]> |
|
* John Morris <[email protected]> |
|
* Copyright (c) 2003 Spirent Communications |
|
* and Claus Gindhart <[email protected]> |
|
* |
|
* This module has only been tested with the MAX6620 chip. |
|
* |
|
* The datasheet was last seen at: |
|
* |
|
* http://pdfserv.maxim-ic.com/en/ds/MAX6620.pdf |
|
* |
|
*/ |
|
|
|
#include <linux/bits.h> |
|
#include <linux/err.h> |
|
#include <linux/hwmon.h> |
|
#include <linux/i2c.h> |
|
#include <linux/init.h> |
|
#include <linux/jiffies.h> |
|
#include <linux/module.h> |
|
#include <linux/slab.h> |
|
|
|
/* |
|
* MAX 6620 registers |
|
*/ |
|
|
|
#define MAX6620_REG_CONFIG 0x00 |
|
#define MAX6620_REG_FAULT 0x01 |
|
#define MAX6620_REG_CONF_FAN0 0x02 |
|
#define MAX6620_REG_CONF_FAN1 0x03 |
|
#define MAX6620_REG_CONF_FAN2 0x04 |
|
#define MAX6620_REG_CONF_FAN3 0x05 |
|
#define MAX6620_REG_DYN_FAN0 0x06 |
|
#define MAX6620_REG_DYN_FAN1 0x07 |
|
#define MAX6620_REG_DYN_FAN2 0x08 |
|
#define MAX6620_REG_DYN_FAN3 0x09 |
|
#define MAX6620_REG_TACH0 0x10 |
|
#define MAX6620_REG_TACH1 0x12 |
|
#define MAX6620_REG_TACH2 0x14 |
|
#define MAX6620_REG_TACH3 0x16 |
|
#define MAX6620_REG_VOLT0 0x18 |
|
#define MAX6620_REG_VOLT1 0x1A |
|
#define MAX6620_REG_VOLT2 0x1C |
|
#define MAX6620_REG_VOLT3 0x1E |
|
#define MAX6620_REG_TAR0 0x20 |
|
#define MAX6620_REG_TAR1 0x22 |
|
#define MAX6620_REG_TAR2 0x24 |
|
#define MAX6620_REG_TAR3 0x26 |
|
#define MAX6620_REG_DAC0 0x28 |
|
#define MAX6620_REG_DAC1 0x2A |
|
#define MAX6620_REG_DAC2 0x2C |
|
#define MAX6620_REG_DAC3 0x2E |
|
|
|
/* |
|
* Config register bits |
|
*/ |
|
|
|
#define MAX6620_CFG_RUN BIT(7) |
|
#define MAX6620_CFG_POR BIT(6) |
|
#define MAX6620_CFG_TIMEOUT BIT(5) |
|
#define MAX6620_CFG_FULLFAN BIT(4) |
|
#define MAX6620_CFG_OSC BIT(3) |
|
#define MAX6620_CFG_WD_MASK (BIT(2) | BIT(1)) |
|
#define MAX6620_CFG_WD_2 BIT(1) |
|
#define MAX6620_CFG_WD_6 BIT(2) |
|
#define MAX6620_CFG_WD10 (BIT(2) | BIT(1)) |
|
#define MAX6620_CFG_WD BIT(0) |
|
|
|
/* |
|
* Failure status register bits |
|
*/ |
|
|
|
#define MAX6620_FAIL_TACH0 BIT(4) |
|
#define MAX6620_FAIL_TACH1 BIT(5) |
|
#define MAX6620_FAIL_TACH2 BIT(6) |
|
#define MAX6620_FAIL_TACH3 BIT(7) |
|
#define MAX6620_FAIL_MASK0 BIT(0) |
|
#define MAX6620_FAIL_MASK1 BIT(1) |
|
#define MAX6620_FAIL_MASK2 BIT(2) |
|
#define MAX6620_FAIL_MASK3 BIT(3) |
|
|
|
#define MAX6620_CLOCK_FREQ 8192 /* Clock frequency in Hz */ |
|
#define MAX6620_PULSE_PER_REV 2 /* Tachometer pulses per revolution */ |
|
|
|
/* Minimum and maximum values of the FAN-RPM */ |
|
#define FAN_RPM_MIN 240 |
|
#define FAN_RPM_MAX 30000 |
|
|
|
static const u8 config_reg[] = { |
|
MAX6620_REG_CONF_FAN0, |
|
MAX6620_REG_CONF_FAN1, |
|
MAX6620_REG_CONF_FAN2, |
|
MAX6620_REG_CONF_FAN3, |
|
}; |
|
|
|
static const u8 dyn_reg[] = { |
|
MAX6620_REG_DYN_FAN0, |
|
MAX6620_REG_DYN_FAN1, |
|
MAX6620_REG_DYN_FAN2, |
|
MAX6620_REG_DYN_FAN3, |
|
}; |
|
|
|
static const u8 tach_reg[] = { |
|
MAX6620_REG_TACH0, |
|
MAX6620_REG_TACH1, |
|
MAX6620_REG_TACH2, |
|
MAX6620_REG_TACH3, |
|
}; |
|
|
|
static const u8 target_reg[] = { |
|
MAX6620_REG_TAR0, |
|
MAX6620_REG_TAR1, |
|
MAX6620_REG_TAR2, |
|
MAX6620_REG_TAR3, |
|
}; |
|
|
|
/* |
|
* Client data (each client gets its own) |
|
*/ |
|
|
|
struct max6620_data { |
|
struct i2c_client *client; |
|
struct mutex update_lock; |
|
bool valid; /* false until following fields are valid */ |
|
unsigned long last_updated; /* in jiffies */ |
|
|
|
/* register values */ |
|
u8 fancfg[4]; |
|
u8 fandyn[4]; |
|
u8 fault; |
|
u16 tach[4]; |
|
u16 target[4]; |
|
}; |
|
|
|
static u8 max6620_fan_div_from_reg(u8 val) |
|
{ |
|
return BIT((val & 0xE0) >> 5); |
|
} |
|
|
|
static u16 max6620_fan_rpm_to_tach(u8 div, int rpm) |
|
{ |
|
return (60 * div * MAX6620_CLOCK_FREQ) / (rpm * MAX6620_PULSE_PER_REV); |
|
} |
|
|
|
static int max6620_fan_tach_to_rpm(u8 div, u16 tach) |
|
{ |
|
return (60 * div * MAX6620_CLOCK_FREQ) / (tach * MAX6620_PULSE_PER_REV); |
|
} |
|
|
|
static int max6620_update_device(struct device *dev) |
|
{ |
|
struct max6620_data *data = dev_get_drvdata(dev); |
|
struct i2c_client *client = data->client; |
|
int i; |
|
int ret = 0; |
|
|
|
mutex_lock(&data->update_lock); |
|
|
|
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { |
|
for (i = 0; i < 4; i++) { |
|
ret = i2c_smbus_read_byte_data(client, config_reg[i]); |
|
if (ret < 0) |
|
goto error; |
|
data->fancfg[i] = ret; |
|
|
|
ret = i2c_smbus_read_byte_data(client, dyn_reg[i]); |
|
if (ret < 0) |
|
goto error; |
|
data->fandyn[i] = ret; |
|
|
|
ret = i2c_smbus_read_byte_data(client, tach_reg[i]); |
|
if (ret < 0) |
|
goto error; |
|
data->tach[i] = (ret << 3) & 0x7f8; |
|
ret = i2c_smbus_read_byte_data(client, tach_reg[i] + 1); |
|
if (ret < 0) |
|
goto error; |
|
data->tach[i] |= (ret >> 5) & 0x7; |
|
|
|
ret = i2c_smbus_read_byte_data(client, target_reg[i]); |
|
if (ret < 0) |
|
goto error; |
|
data->target[i] = (ret << 3) & 0x7f8; |
|
ret = i2c_smbus_read_byte_data(client, target_reg[i] + 1); |
|
if (ret < 0) |
|
goto error; |
|
data->target[i] |= (ret >> 5) & 0x7; |
|
} |
|
|
|
/* |
|
* Alarms are cleared on read in case the condition that |
|
* caused the alarm is removed. Keep the value latched here |
|
* for providing the register through different alarm files. |
|
*/ |
|
ret = i2c_smbus_read_byte_data(client, MAX6620_REG_FAULT); |
|
if (ret < 0) |
|
goto error; |
|
data->fault |= (ret >> 4) & (ret & 0x0F); |
|
|
|
data->last_updated = jiffies; |
|
data->valid = true; |
|
} |
|
|
|
error: |
|
mutex_unlock(&data->update_lock); |
|
return ret; |
|
} |
|
|
|
static umode_t |
|
max6620_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, |
|
int channel) |
|
{ |
|
switch (type) { |
|
case hwmon_fan: |
|
switch (attr) { |
|
case hwmon_fan_alarm: |
|
case hwmon_fan_input: |
|
return 0444; |
|
case hwmon_fan_div: |
|
case hwmon_fan_target: |
|
return 0644; |
|
default: |
|
break; |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int |
|
max6620_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, |
|
int channel, long *val) |
|
{ |
|
struct max6620_data *data; |
|
struct i2c_client *client; |
|
int ret; |
|
u8 div; |
|
u8 val1; |
|
u8 val2; |
|
|
|
ret = max6620_update_device(dev); |
|
if (ret < 0) |
|
return ret; |
|
data = dev_get_drvdata(dev); |
|
client = data->client; |
|
|
|
switch (type) { |
|
case hwmon_fan: |
|
switch (attr) { |
|
case hwmon_fan_alarm: |
|
mutex_lock(&data->update_lock); |
|
*val = !!(data->fault & BIT(channel)); |
|
|
|
/* Setting TACH count to re-enable fan fault detection */ |
|
if (*val == 1) { |
|
val1 = (data->target[channel] >> 3) & 0xff; |
|
val2 = (data->target[channel] << 5) & 0xe0; |
|
ret = i2c_smbus_write_byte_data(client, |
|
target_reg[channel], val1); |
|
if (ret < 0) { |
|
mutex_unlock(&data->update_lock); |
|
return ret; |
|
} |
|
ret = i2c_smbus_write_byte_data(client, |
|
target_reg[channel] + 1, val2); |
|
if (ret < 0) { |
|
mutex_unlock(&data->update_lock); |
|
return ret; |
|
} |
|
|
|
data->fault &= ~BIT(channel); |
|
} |
|
mutex_unlock(&data->update_lock); |
|
|
|
break; |
|
case hwmon_fan_div: |
|
*val = max6620_fan_div_from_reg(data->fandyn[channel]); |
|
break; |
|
case hwmon_fan_input: |
|
if (data->tach[channel] == 0) { |
|
*val = 0; |
|
} else { |
|
div = max6620_fan_div_from_reg(data->fandyn[channel]); |
|
*val = max6620_fan_tach_to_rpm(div, data->tach[channel]); |
|
} |
|
break; |
|
case hwmon_fan_target: |
|
if (data->target[channel] == 0) { |
|
*val = 0; |
|
} else { |
|
div = max6620_fan_div_from_reg(data->fandyn[channel]); |
|
*val = max6620_fan_tach_to_rpm(div, data->target[channel]); |
|
} |
|
break; |
|
default: |
|
return -EOPNOTSUPP; |
|
} |
|
break; |
|
|
|
default: |
|
return -EOPNOTSUPP; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int |
|
max6620_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, |
|
int channel, long val) |
|
{ |
|
struct max6620_data *data; |
|
struct i2c_client *client; |
|
int ret; |
|
u8 div; |
|
u16 tach; |
|
u8 val1; |
|
u8 val2; |
|
|
|
ret = max6620_update_device(dev); |
|
if (ret < 0) |
|
return ret; |
|
data = dev_get_drvdata(dev); |
|
client = data->client; |
|
mutex_lock(&data->update_lock); |
|
|
|
switch (type) { |
|
case hwmon_fan: |
|
switch (attr) { |
|
case hwmon_fan_div: |
|
switch (val) { |
|
case 1: |
|
div = 0; |
|
break; |
|
case 2: |
|
div = 1; |
|
break; |
|
case 4: |
|
div = 2; |
|
break; |
|
case 8: |
|
div = 3; |
|
break; |
|
case 16: |
|
div = 4; |
|
break; |
|
case 32: |
|
div = 5; |
|
break; |
|
default: |
|
ret = -EINVAL; |
|
goto error; |
|
} |
|
data->fandyn[channel] &= 0x1F; |
|
data->fandyn[channel] |= div << 5; |
|
ret = i2c_smbus_write_byte_data(client, dyn_reg[channel], |
|
data->fandyn[channel]); |
|
break; |
|
case hwmon_fan_target: |
|
val = clamp_val(val, FAN_RPM_MIN, FAN_RPM_MAX); |
|
div = max6620_fan_div_from_reg(data->fandyn[channel]); |
|
tach = max6620_fan_rpm_to_tach(div, val); |
|
val1 = (tach >> 3) & 0xff; |
|
val2 = (tach << 5) & 0xe0; |
|
ret = i2c_smbus_write_byte_data(client, target_reg[channel], val1); |
|
if (ret < 0) |
|
break; |
|
ret = i2c_smbus_write_byte_data(client, target_reg[channel] + 1, val2); |
|
if (ret < 0) |
|
break; |
|
|
|
/* Setting TACH count re-enables fan fault detection */ |
|
data->fault &= ~BIT(channel); |
|
|
|
break; |
|
default: |
|
ret = -EOPNOTSUPP; |
|
break; |
|
} |
|
break; |
|
|
|
default: |
|
ret = -EOPNOTSUPP; |
|
break; |
|
} |
|
|
|
error: |
|
mutex_unlock(&data->update_lock); |
|
return ret; |
|
} |
|
|
|
static const struct hwmon_channel_info *max6620_info[] = { |
|
HWMON_CHANNEL_INFO(fan, |
|
HWMON_F_INPUT | HWMON_F_DIV | HWMON_F_TARGET | HWMON_F_ALARM, |
|
HWMON_F_INPUT | HWMON_F_DIV | HWMON_F_TARGET | HWMON_F_ALARM, |
|
HWMON_F_INPUT | HWMON_F_DIV | HWMON_F_TARGET | HWMON_F_ALARM, |
|
HWMON_F_INPUT | HWMON_F_DIV | HWMON_F_TARGET | HWMON_F_ALARM), |
|
NULL |
|
}; |
|
|
|
static const struct hwmon_ops max6620_hwmon_ops = { |
|
.read = max6620_read, |
|
.write = max6620_write, |
|
.is_visible = max6620_is_visible, |
|
}; |
|
|
|
static const struct hwmon_chip_info max6620_chip_info = { |
|
.ops = &max6620_hwmon_ops, |
|
.info = max6620_info, |
|
}; |
|
|
|
static int max6620_init_client(struct max6620_data *data) |
|
{ |
|
struct i2c_client *client = data->client; |
|
int config; |
|
int err; |
|
int i; |
|
int reg; |
|
|
|
config = i2c_smbus_read_byte_data(client, MAX6620_REG_CONFIG); |
|
if (config < 0) { |
|
dev_err(&client->dev, "Error reading config, aborting.\n"); |
|
return config; |
|
} |
|
|
|
/* |
|
* Set bit 4, disable other fans from going full speed on a fail |
|
* failure. |
|
*/ |
|
err = i2c_smbus_write_byte_data(client, MAX6620_REG_CONFIG, config | 0x10); |
|
if (err < 0) { |
|
dev_err(&client->dev, "Config write error, aborting.\n"); |
|
return err; |
|
} |
|
|
|
for (i = 0; i < 4; i++) { |
|
reg = i2c_smbus_read_byte_data(client, config_reg[i]); |
|
if (reg < 0) |
|
return reg; |
|
data->fancfg[i] = reg; |
|
|
|
/* Enable RPM mode */ |
|
data->fancfg[i] |= 0xa8; |
|
err = i2c_smbus_write_byte_data(client, config_reg[i], data->fancfg[i]); |
|
if (err < 0) |
|
return err; |
|
|
|
/* 2 counts (001) and Rate change 100 (0.125 secs) */ |
|
data->fandyn[i] = 0x30; |
|
err = i2c_smbus_write_byte_data(client, dyn_reg[i], data->fandyn[i]); |
|
if (err < 0) |
|
return err; |
|
} |
|
return 0; |
|
} |
|
|
|
static int max6620_probe(struct i2c_client *client) |
|
{ |
|
struct device *dev = &client->dev; |
|
struct max6620_data *data; |
|
struct device *hwmon_dev; |
|
int err; |
|
|
|
data = devm_kzalloc(dev, sizeof(struct max6620_data), GFP_KERNEL); |
|
if (!data) |
|
return -ENOMEM; |
|
|
|
data->client = client; |
|
mutex_init(&data->update_lock); |
|
|
|
err = max6620_init_client(data); |
|
if (err) |
|
return err; |
|
|
|
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, |
|
data, |
|
&max6620_chip_info, |
|
NULL); |
|
|
|
return PTR_ERR_OR_ZERO(hwmon_dev); |
|
} |
|
|
|
static const struct i2c_device_id max6620_id[] = { |
|
{ "max6620", 0 }, |
|
{ } |
|
}; |
|
MODULE_DEVICE_TABLE(i2c, max6620_id); |
|
|
|
static struct i2c_driver max6620_driver = { |
|
.class = I2C_CLASS_HWMON, |
|
.driver = { |
|
.name = "max6620", |
|
}, |
|
.probe_new = max6620_probe, |
|
.id_table = max6620_id, |
|
}; |
|
|
|
module_i2c_driver(max6620_driver); |
|
|
|
MODULE_AUTHOR("Lucas Grunenberg"); |
|
MODULE_DESCRIPTION("MAX6620 sensor driver"); |
|
MODULE_LICENSE("GPL");
|
|
|