hwmon: (pmbus/max31785) Add dual tachometer support
The dual tachometer feature is implemented in hardware with a TACHSEL input to indicate the rotor under measurement, and exposed on the device by extending the READ_FAN_SPEED_1 word with two extra bytes*. The need to read the non-standard four-byte response leads to a cut-down implementation of i2c_smbus_xfer_emulated() included in the driver. Further, to expose the second rotor tachometer value to userspace the values are exposed through virtual pages. We re-route accesses to FAN_CONFIG_1_2 and READ_FAN_SPEED_1 on pages 23-28 (not defined by the hardware) to the same registers on pages 0-5, and with the latter command we extract the value from the second word of the four-byte response. * The documentation recommends the slower rotor be associated with TACHSEL=0, which corresponds to the first word of the response. The TACHSEL=0 measurement is used by the controller's closed-loop fan management to judge target fan rate. Signed-off-by: Andrew Jeffery <andrew@aj.id.au> Signed-off-by: Guenter Roeck <linux@roeck-us.net>
This commit is contained in:
Родитель
464df6fa37
Коммит
cf583b4275
|
@ -17,8 +17,9 @@ management with temperature and remote voltage sensing. Various fan control
|
||||||
features are provided, including PWM frequency control, temperature hysteresis,
|
features are provided, including PWM frequency control, temperature hysteresis,
|
||||||
dual tachometer measurements, and fan health monitoring.
|
dual tachometer measurements, and fan health monitoring.
|
||||||
|
|
||||||
For dual rotor fan configuration, the MAX31785 exposes the slowest rotor of the
|
For dual-rotor configurations the MAX31785A exposes the second rotor tachometer
|
||||||
two in the fan[1-4]_input attributes.
|
readings in attributes fan[5-8]_input. By contrast the MAX31785 only exposes
|
||||||
|
the slowest rotor measurement, and does so in the fan[1-4]_input attributes.
|
||||||
|
|
||||||
Usage Notes
|
Usage Notes
|
||||||
-----------
|
-----------
|
||||||
|
@ -31,7 +32,8 @@ Sysfs attributes
|
||||||
|
|
||||||
fan[1-4]_alarm Fan alarm.
|
fan[1-4]_alarm Fan alarm.
|
||||||
fan[1-4]_fault Fan fault.
|
fan[1-4]_fault Fan fault.
|
||||||
fan[1-4]_input Fan RPM.
|
fan[1-8]_input Fan RPM. On the MAX31785A, inputs 5-8 correspond to the
|
||||||
|
second rotor of fans 1-4
|
||||||
fan[1-4]_target Fan input target
|
fan[1-4]_target Fan input target
|
||||||
|
|
||||||
in[1-6]_crit Critical maximum output voltage
|
in[1-6]_crit Critical maximum output voltage
|
||||||
|
|
|
@ -16,9 +16,79 @@
|
||||||
|
|
||||||
enum max31785_regs {
|
enum max31785_regs {
|
||||||
MFR_REVISION = 0x9b,
|
MFR_REVISION = 0x9b,
|
||||||
|
MFR_FAN_CONFIG = 0xf1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define MAX31785 0x3030
|
||||||
|
#define MAX31785A 0x3040
|
||||||
|
|
||||||
|
#define MFR_FAN_CONFIG_DUAL_TACH BIT(12)
|
||||||
|
|
||||||
#define MAX31785_NR_PAGES 23
|
#define MAX31785_NR_PAGES 23
|
||||||
|
#define MAX31785_NR_FAN_PAGES 6
|
||||||
|
|
||||||
|
static int max31785_read_byte_data(struct i2c_client *client, int page,
|
||||||
|
int reg)
|
||||||
|
{
|
||||||
|
if (page < MAX31785_NR_PAGES)
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
switch (reg) {
|
||||||
|
case PMBUS_VOUT_MODE:
|
||||||
|
return -ENOTSUPP;
|
||||||
|
case PMBUS_FAN_CONFIG_12:
|
||||||
|
return pmbus_read_byte_data(client, page - MAX31785_NR_PAGES,
|
||||||
|
reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -ENODATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int max31785_write_byte(struct i2c_client *client, int page, u8 value)
|
||||||
|
{
|
||||||
|
if (page < MAX31785_NR_PAGES)
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
return -ENOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int max31785_read_long_data(struct i2c_client *client, int page,
|
||||||
|
int reg, u32 *data)
|
||||||
|
{
|
||||||
|
unsigned char cmdbuf[1];
|
||||||
|
unsigned char rspbuf[4];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
struct i2c_msg msg[2] = {
|
||||||
|
{
|
||||||
|
.addr = client->addr,
|
||||||
|
.flags = 0,
|
||||||
|
.len = sizeof(cmdbuf),
|
||||||
|
.buf = cmdbuf,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.addr = client->addr,
|
||||||
|
.flags = I2C_M_RD,
|
||||||
|
.len = sizeof(rspbuf),
|
||||||
|
.buf = rspbuf,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
cmdbuf[0] = reg;
|
||||||
|
|
||||||
|
rc = pmbus_set_page(client, page);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
rc = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
*data = (rspbuf[0] << (0 * 8)) | (rspbuf[1] << (1 * 8)) |
|
||||||
|
(rspbuf[2] << (2 * 8)) | (rspbuf[3] << (3 * 8));
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
static int max31785_get_pwm(struct i2c_client *client, int page)
|
static int max31785_get_pwm(struct i2c_client *client, int page)
|
||||||
{
|
{
|
||||||
|
@ -62,9 +132,30 @@ static int max31785_get_pwm_mode(struct i2c_client *client, int page)
|
||||||
static int max31785_read_word_data(struct i2c_client *client, int page,
|
static int max31785_read_word_data(struct i2c_client *client, int page,
|
||||||
int reg)
|
int reg)
|
||||||
{
|
{
|
||||||
|
u32 val;
|
||||||
int rv;
|
int rv;
|
||||||
|
|
||||||
switch (reg) {
|
switch (reg) {
|
||||||
|
case PMBUS_READ_FAN_SPEED_1:
|
||||||
|
if (page < MAX31785_NR_PAGES)
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
rv = max31785_read_long_data(client, page - MAX31785_NR_PAGES,
|
||||||
|
reg, &val);
|
||||||
|
if (rv < 0)
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
rv = (val >> 16) & 0xffff;
|
||||||
|
break;
|
||||||
|
case PMBUS_FAN_COMMAND_1:
|
||||||
|
/*
|
||||||
|
* PMBUS_FAN_COMMAND_x is probed to judge whether or not to
|
||||||
|
* expose fan control registers.
|
||||||
|
*
|
||||||
|
* Don't expose fan_target attribute for virtual pages.
|
||||||
|
*/
|
||||||
|
rv = (page >= MAX31785_NR_PAGES) ? -ENOTSUPP : -ENODATA;
|
||||||
|
break;
|
||||||
case PMBUS_VIRT_PWM_1:
|
case PMBUS_VIRT_PWM_1:
|
||||||
rv = max31785_get_pwm(client, page);
|
rv = max31785_get_pwm(client, page);
|
||||||
break;
|
break;
|
||||||
|
@ -157,11 +248,15 @@ static int max31785_write_word_data(struct i2c_client *client, int page,
|
||||||
#define MAX31785_VOUT_FUNCS \
|
#define MAX31785_VOUT_FUNCS \
|
||||||
(PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT)
|
(PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT)
|
||||||
|
|
||||||
|
#define MAX37185_NUM_FAN_PAGES 6
|
||||||
|
|
||||||
static const struct pmbus_driver_info max31785_info = {
|
static const struct pmbus_driver_info max31785_info = {
|
||||||
.pages = MAX31785_NR_PAGES,
|
.pages = MAX31785_NR_PAGES,
|
||||||
|
|
||||||
.write_word_data = max31785_write_word_data,
|
.write_word_data = max31785_write_word_data,
|
||||||
|
.read_byte_data = max31785_read_byte_data,
|
||||||
.read_word_data = max31785_read_word_data,
|
.read_word_data = max31785_read_word_data,
|
||||||
|
.write_byte = max31785_write_byte,
|
||||||
|
|
||||||
/* RPM */
|
/* RPM */
|
||||||
.format[PSC_FAN] = direct,
|
.format[PSC_FAN] = direct,
|
||||||
|
@ -208,13 +303,46 @@ static const struct pmbus_driver_info max31785_info = {
|
||||||
.func[22] = MAX31785_VOUT_FUNCS,
|
.func[22] = MAX31785_VOUT_FUNCS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int max31785_configure_dual_tach(struct i2c_client *client,
|
||||||
|
struct pmbus_driver_info *info)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < MAX31785_NR_FAN_PAGES; i++) {
|
||||||
|
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = i2c_smbus_read_word_data(client, MFR_FAN_CONFIG);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (ret & MFR_FAN_CONFIG_DUAL_TACH) {
|
||||||
|
int virtual = MAX31785_NR_PAGES + i;
|
||||||
|
|
||||||
|
info->pages = virtual + 1;
|
||||||
|
info->func[virtual] |= PMBUS_HAVE_FAN12;
|
||||||
|
info->func[virtual] |= PMBUS_PAGE_VIRTUAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int max31785_probe(struct i2c_client *client,
|
static int max31785_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
struct pmbus_driver_info *info;
|
struct pmbus_driver_info *info;
|
||||||
|
bool dual_tach = false;
|
||||||
s64 ret;
|
s64 ret;
|
||||||
|
|
||||||
|
if (!i2c_check_functionality(client->adapter,
|
||||||
|
I2C_FUNC_SMBUS_BYTE_DATA |
|
||||||
|
I2C_FUNC_SMBUS_WORD_DATA))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL);
|
info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL);
|
||||||
if (!info)
|
if (!info)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
@ -225,6 +353,25 @@ static int max31785_probe(struct i2c_client *client,
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
ret = i2c_smbus_read_word_data(client, MFR_REVISION);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (ret == MAX31785A) {
|
||||||
|
dual_tach = true;
|
||||||
|
} else if (ret == MAX31785) {
|
||||||
|
if (!strcmp("max31785a", id->name))
|
||||||
|
dev_warn(dev, "Expected max3175a, found max31785: cannot provide secondary tachometer readings\n");
|
||||||
|
} else {
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dual_tach) {
|
||||||
|
ret = max31785_configure_dual_tach(client, info);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
return pmbus_do_probe(client, id, info);
|
return pmbus_do_probe(client, id, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче