[PATCH v3] hwmon: Add support for MAX31785 intelligent fan controller

Guenter Roeck linux at roeck-us.net
Tue Jun 6 23:33:17 AEST 2017


On 06/06/2017 12:02 AM, Andrew Jeffery wrote:
> Add a basic driver for the MAX31785, focusing on the fan control
> features but ignoring the temperature and voltage monitoring
> features of the device.
> 
> This driver supports all fan control modes and tachometer / PWM
> readback where applicable.
> 
> Signed-off-by: Timothy Pearson <tpearson at raptorengineering.com>
> Signed-off-by: Andrew Jeffery <andrew at aj.id.au>
> ---
> Hello,
> 
> This is a rework of Timothy Pearson's original patch:
> 
>      https://www.mail-archive.com/linux-hwmon@vger.kernel.org/msg00868.html
> 
> I've labelled it as v3 to differentiate from Timothy's postings.
> 
> The original thread had some discussion about the MAX31785 being a PMBus device
> and that it should thus be a PMBus driver. The implementation still makes use
> of features not available in the pmbus core, so I've taken up the earlier
> suggestion and ported it to the devm_hwmon_device_register_with_info()
> interface. This gave a modest reduction in lines-of-code and at least to me is
> more aesthetically pleasing.
> 
> Over and above the features of the original patch is support for a secondary
> rotor measurement value that is provided by MAX31785 chips with a revised
> firmware. The feature(s) of the firmware are determined at probe time and extra
> attributes exposed accordingly. Specifically, the MFR_REVISION 0x3040 of the
> firmware supports 'slow' and 'fast' rotor reads. The feature is implemented by
> command 0x90 (READ_FAN_SPEED_1) providing a 4-byte response (with the 'fast'
> measurement in the second word) rather than the 2-bytes response in the
> original firmware (MFR_REVISION 0x3030).
> 

Taking the pmbus driver question out, why would this warrant another non-standard
attribute outside the ABI ? I could see the desire to replace the 'slow' access
with the 'fast' one, but provide two attributes ? No, I don't see the point, sorry,
even more so without detailed explanation why the second attribute in addition
to the first one would add any value.

> This feature is not documented in the public datasheet[1].
> 
> [1] https://datasheets.maximintegrated.com/en/ds/MAX31785.pdf
> 
> The need to read a 4-byte value drives the addition of a helper that is a
> cut-down version of i2c_smbus_xfer_emulated(), as 4-byte transactions aren't a
> defined transaction type in the PMBus spec. This seemed more tasteful than
> hacking the PMBus core to support the quirks of a single device.
> 

That is why we have PMBus helper drivers.

Guenter

> Also changed from Timothy's original posting is I've massaged the locking a bit
> and removed what seemed to be a copy/paste bug around max31785_fan_set_pulses()
> setting the fan_command member.
> 
> Tested on an IBM Witherspoon machine.
> 
> Cheers,
> 
> Andrew
> 
>   Documentation/hwmon/max31785 |  44 +++
>   drivers/hwmon/Kconfig        |  10 +
>   drivers/hwmon/Makefile       |   1 +
>   drivers/hwmon/max31785.c     | 824 +++++++++++++++++++++++++++++++++++++++++++
>   4 files changed, 879 insertions(+)
>   create mode 100644 Documentation/hwmon/max31785
>   create mode 100644 drivers/hwmon/max31785.c
> 
> diff --git a/Documentation/hwmon/max31785 b/Documentation/hwmon/max31785
> new file mode 100644
> index 000000000000..dd891c06401e
> --- /dev/null
> +++ b/Documentation/hwmon/max31785
> @@ -0,0 +1,44 @@
> +Kernel driver max31785
> +======================
> +
> +Supported chips:
> +  * Maxim MAX31785
> +    Prefix: 'max31785'
> +    Addresses scanned: 0x52 0x53 0x54 0x55
> +    Datasheet: http://pdfserv.maximintegrated.com/en/ds/MAX31785.pdf
> +
> +Author: Timothy Pearson <tpearson at raptorengineering.com>
> +
> +
> +Description
> +-----------
> +
> +This driver implements support for the Maxim MAX31785 chip.
> +
> +The MAX31785 controls the speeds of up to six fans using six independent
> +PWM outputs. The desired fan speeds (or PWM duty cycles) are written
> +through the I2C interface. The outputs drive "4-wire" fans directly,
> +or can be used to modulate the fan's power terminals using an external
> +pass transistor.
> +
> +Tachometer inputs monitor fan tachometer logic outputs for precise (+/-1%)
> +monitoring and control of fan RPM as well as detection of fan failure.
> +
> +
> +Sysfs entries
> +-------------
> +
> +fan[1-6]_input           RO  fan tachometer speed in RPM
> +fan[1-6]_fault           RO  fan experienced fault
> +fan[1-6]_pulses          RW  tachometer pulses per fan revolution
> +fan[1-6]_target          RW  desired fan speed in RPM
> +pwm[1-6]_enable          RW  pwm mode, 0=disabled, 1=pwm, 2=rpm, 3=automatic
> +pwm[1-6]                 RW  fan target duty cycle (0-255)
> +
> +Dynamic sysfs entries
> +--------------------
> +
> +Whether these entries are present depends on the firmware features detected on
> +the device during probe.
> +
> +fan[1-6]_input_fast      RO  fan tachometer speed in RPM (fast rotor measurement)
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index e80ca81577f4..c75d6072c823 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -886,6 +886,16 @@ config SENSORS_MAX6697
>   	  This driver can also be built as a module.  If so, the module
>   	  will be called max6697.
>   
> +config SENSORS_MAX31785
> +	tristate "Maxim MAX31785 sensor chip"
> +	depends on I2C
> +	help
> +	  If you say yes here you get support for 6-Channel PWM-Output
> +	  Fan RPM Controller.
> +
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called max31785.
> +
>   config SENSORS_MAX31790
>   	tristate "Maxim MAX31790 sensor chip"
>   	depends on I2C
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index f03dd0a15933..dc55722bee88 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -119,6 +119,7 @@ obj-$(CONFIG_SENSORS_MAX6639)	+= max6639.o
>   obj-$(CONFIG_SENSORS_MAX6642)	+= max6642.o
>   obj-$(CONFIG_SENSORS_MAX6650)	+= max6650.o
>   obj-$(CONFIG_SENSORS_MAX6697)	+= max6697.o
> +obj-$(CONFIG_SENSORS_MAX31785)	+= max31785.o
>   obj-$(CONFIG_SENSORS_MAX31790)	+= max31790.o
>   obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
>   obj-$(CONFIG_SENSORS_MCP3021)	+= mcp3021.o
> diff --git a/drivers/hwmon/max31785.c b/drivers/hwmon/max31785.c
> new file mode 100644
> index 000000000000..fc03b7c8e723
> --- /dev/null
> +++ b/drivers/hwmon/max31785.c
> @@ -0,0 +1,824 @@
> +/*
> + * max31785.c - Part of lm_sensors, Linux kernel modules for hardware
> + *	       monitoring.
> + *
> + * (C) 2016 Raptor Engineering, LLC
> + *
> + * 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.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/jiffies.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +
> +/* MAX31785 device IDs */
> +#define MAX31785_MFR_ID				0x4d
> +#define MAX31785_MFR_MODEL			0x53
> +
> +/* MAX31785 registers */
> +#define MAX31785_REG_PAGE			0x00
> +#define MAX31785_PAGE_FAN_CONFIG(ch)		(0x00 + (ch))
> +#define MAX31785_REG_FAN_CONFIG_1_2		0x3a
> +#define MAX31785_REG_FAN_COMMAND_1		0x3b
> +#define MAX31785_REG_STATUS_FANS_1_2		0x81
> +#define MAX31785_REG_FAN_SPEED_1		0x90
> +#define MAX31785_REG_MFR_ID			0x99
> +#define MAX31785_REG_MFR_MODEL			0x9a
> +#define MAX31785_REG_MFR_REVISION		0x9b
> +#define MAX31785_REG_MFR_FAN_CONFIG		0xf1
> +#define MAX31785_REG_READ_FAN_PWM		0xf3
> +
> +/* Fan Config register bits */
> +#define MAX31785_FAN_CFG_PWM_ENABLE		0x80
> +#define MAX31785_FAN_CFG_CONTROL_MODE_RPM	0x40
> +#define MAX31785_FAN_CFG_PULSE_MASK		0x30
> +#define MAX31785_FAN_CFG_PULSE_SHIFT		4
> +#define MAX31785_FAN_CFG_PULSE_OFFSET		1
> +
> +/* Fan Status register bits */
> +#define MAX31785_FAN_STATUS_FAULT_MASK		0x80
> +
> +/* Fan Command constants */
> +#define MAX31785_FAN_COMMAND_PWM_RATIO		40
> +
> +#define NR_CHANNEL				6
> +
> +/* Addresses to scan */
> +static const unsigned short normal_i2c[] = {
> +	0x52, 0x53, 0x54, 0x55,
> +	I2C_CLIENT_END
> +};
> +
> +#define MAX31785_CAP_FAST_ROTOR BIT(0)
> +
> +/*
> + * Client data (each client gets its own)
> + *
> + * @lock:		Protects device access and access to cached values
> + * @valid:		False until fields below it are valid
> + * @last_updated:	Last update time in jiffies
> + */
> +struct max31785 {
> +	struct i2c_client	*client;
> +	struct mutex		lock;
> +	bool			valid;
> +	unsigned long		last_updated;
> +	u32			capabilities;
> +
> +	/* Registers */
> +	u8	fan_config[NR_CHANNEL];
> +	u16	fan_command[NR_CHANNEL];
> +	u8	mfr_fan_config[NR_CHANNEL];
> +	u8	fault_status[NR_CHANNEL];
> +	u16	pwm[NR_CHANNEL];
> +	u16	tach_rpm[NR_CHANNEL];
> +	u16	tach_rpm_fast[NR_CHANNEL];
> +};
> +
> +static int max31785_set_page(struct i2c_client *client,
> +				u8 page)
> +{
> +	return i2c_smbus_write_byte_data(client, MAX31785_REG_PAGE, page);
> +}
> +
> +static int read_fan_data(struct i2c_client *client, u8 fan, u8 reg,
> +				  s32 (*read)(const struct i2c_client *, u8))
> +{
> +	int rv;
> +
> +	rv = max31785_set_page(client, MAX31785_PAGE_FAN_CONFIG(fan));
> +	if (rv < 0)
> +		return rv;
> +
> +	return read(client, reg);
> +}
> +
> +static inline int max31785_read_fan_byte(struct i2c_client *client, u8 fan,
> +					 u8 reg)
> +{
> +	return read_fan_data(client, fan, reg, i2c_smbus_read_byte_data);
> +}
> +
> +static inline int max31785_read_fan_word(struct i2c_client *client, u8 fan,
> +					 u8 reg)
> +{
> +	return read_fan_data(client, fan, reg, i2c_smbus_read_word_data);
> +}
> +
> +static int max31785_write_fan_byte(struct i2c_client *client, u8 fan,
> +					 u8 reg, u8 data)
> +{
> +	int err;
> +
> +	err = max31785_set_page(client, MAX31785_PAGE_FAN_CONFIG(fan));
> +	if (err < 0)
> +		return err;
> +
> +	return i2c_smbus_write_byte_data(client, reg, data);
> +}
> +
> +static int max31785_write_fan_word(struct i2c_client *client, u8 fan,
> +					 u8 reg, u16 data)
> +{
> +	int err;
> +
> +	err = max31785_set_page(client, MAX31785_PAGE_FAN_CONFIG(fan));
> +	if (err < 0)
> +		return err;
> +
> +	return i2c_smbus_write_word_data(client, reg, data);
> +}
> +
> +/* Cut down version of i2c_smbus_xfer_emulated(), reading 4 bytes */
> +static s64 max31785_smbus_read_long_data(struct i2c_client *client, u8 command)
> +{
> +	unsigned char cmdbuf[1];
> +	unsigned char rspbuf[4];
> +	s64 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] = command;
> +
> +	rc = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
> +	if (rc < 0)
> +		return rc;
> +
> +	rc = (rspbuf[0] << (0 * 8)) | (rspbuf[1] << (1 * 8)) |
> +	     (rspbuf[2] << (2 * 8)) | (rspbuf[3] << (3 * 8));
> +
> +	return rc;
> +}
> +
> +static int max31785_update_fan_speed(struct max31785 *data, u8 fan)
> +{
> +	s64 rc;
> +
> +	rc = max31785_set_page(data->client, MAX31785_PAGE_FAN_CONFIG(fan));
> +	if (rc)
> +		return rc;
> +
> +	if (data->capabilities & MAX31785_CAP_FAST_ROTOR) {
> +		rc = max31785_smbus_read_long_data(data->client,
> +				MAX31785_REG_FAN_SPEED_1);
> +		if (rc < 0)
> +			return rc;
> +
> +		data->tach_rpm[fan] = rc & 0xffff;
> +		data->tach_rpm_fast[fan] = (rc >> 16) & 0xffff;
> +
> +		return rc;
> +	}
> +
> +	rc = i2c_smbus_read_word_data(data->client, MAX31785_REG_FAN_SPEED_1);
> +	if (rc < 0)
> +		return rc;
> +
> +	data->tach_rpm[fan] = rc;
> +
> +	return rc;
> +}
> +
> +static inline bool is_automatic_control_mode(struct max31785 *data,
> +			int index)
> +{
> +	return data->fan_command[index] > 0x7fff;
> +}
> +
> +static struct max31785 *max31785_update_device(struct device *dev)
> +{
> +	struct max31785 *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	struct max31785 *ret = data;
> +	int rv;
> +	int i;
> +
> +	mutex_lock(&data->lock);
> +
> +	if (!time_after(jiffies, data->last_updated + HZ) && data->valid) {
> +		mutex_unlock(&data->lock);
> +
> +		return ret;
> +	}
> +
> +	for (i = 0; i < NR_CHANNEL; i++) {
> +		rv = max31785_read_fan_byte(client, i,
> +				MAX31785_REG_STATUS_FANS_1_2);
> +		if (rv < 0)
> +			goto abort;
> +		data->fault_status[i] = rv;
> +
> +		rv = max31785_update_fan_speed(data, i);
> +		if (rv < 0)
> +			goto abort;
> +
> +		if ((data->fan_config[i]
> +					& MAX31785_FAN_CFG_CONTROL_MODE_RPM)
> +				|| is_automatic_control_mode(data, i)) {
> +			rv = max31785_read_fan_word(client, i,
> +					MAX31785_REG_READ_FAN_PWM);
> +			if (rv < 0)
> +				goto abort;
> +			data->pwm[i] = rv;
> +		}
> +
> +		if (!is_automatic_control_mode(data, i)) {
> +			/*
> +			 * Poke watchdog for manual fan control
> +			 *
> +			 * XXX (AJ): This isn't documented in the MAX31785
> +			 * datasheet, or anywhere else it seems.
> +			 */
> +			rv = max31785_write_fan_word(client,
> +					i, MAX31785_REG_FAN_COMMAND_1,
> +					data->fan_command[i]);
> +			if (rv < 0)
> +				goto abort;
> +		}
> +	}
> +
> +	data->last_updated = jiffies;
> +	data->valid = true;
> +
> +	mutex_unlock(&data->lock);
> +
> +	return ret;
> +
> +abort:
> +	data->valid = false;
> +
> +	mutex_unlock(&data->lock);
> +
> +	return ERR_PTR(rv);
> +
> +}
> +
> +static ssize_t max31785_fan_set_target(struct max31785 *data, int channel,
> +		long rpm)
> +{
> +	int rc;
> +
> +	if (rpm > 0x7fff)
> +		return -EINVAL;
> +
> +	mutex_lock(&data->lock);
> +
> +	/* Write new RPM value */
> +	data->fan_command[channel] = rpm;
> +	rc = max31785_write_fan_word(data->client, channel,
> +				MAX31785_REG_FAN_COMMAND_1,
> +				data->fan_command[channel]);
> +
> +	mutex_unlock(&data->lock);
> +
> +	return rc;
> +}
> +
> +static ssize_t max31785_fan_set_pulses(struct max31785 *data, int channel,
> +		long pulses)
> +{
> +	int rc;
> +
> +	if (pulses > 4)
> +		return -EINVAL;
> +
> +	mutex_lock(&data->lock);
> +
> +	/* XXX (AJ): This sequence disables the fan and sets in PWM mode */
> +	data->fan_config[channel] &= MAX31785_FAN_CFG_PULSE_MASK;
> +	data->fan_config[channel] |= ((pulses - MAX31785_FAN_CFG_PULSE_OFFSET)
> +					<< MAX31785_FAN_CFG_PULSE_SHIFT);
> +
> +	/* Write new pulse value */
> +	rc = max31785_write_fan_byte(data->client, channel,
> +				MAX31785_REG_FAN_CONFIG_1_2,
> +				data->fan_config[channel]);
> +
> +	mutex_unlock(&data->lock);
> +
> +	return rc;
> +}
> +
> +static ssize_t max31785_pwm_set(struct max31785 *data, int channel, long pwm)
> +{
> +	int rc;
> +
> +	if (pwm > 255)
> +		return -EINVAL;
> +
> +	mutex_lock(&data->lock);
> +
> +	/* Write new PWM value */
> +	data->fan_command[channel] = pwm * MAX31785_FAN_COMMAND_PWM_RATIO;
> +	rc = max31785_write_fan_word(data->client, channel,
> +				MAX31785_REG_FAN_COMMAND_1,
> +				data->fan_command[channel]);
> +
> +	mutex_unlock(&data->lock);
> +
> +	return rc;
> +}
> +
> +static ssize_t max31785_pwm_enable(struct max31785 *data, int channel,
> +		long mode)
> +{
> +	struct i2c_client *client = data->client;
> +	int rc;
> +
> +	mutex_lock(&data->lock);
> +
> +	switch (mode) {
> +	case 0:
> +		data->fan_config[channel] =
> +			data->fan_config[channel]
> +			& ~MAX31785_FAN_CFG_PWM_ENABLE;
> +		break;
> +	case 1: /* fallthrough */
> +	case 2: /* fallthrough */
> +	case 3:
> +		data->fan_config[channel] =
> +			data->fan_config[channel]
> +			 | MAX31785_FAN_CFG_PWM_ENABLE;
> +		break;
> +	default:
> +		rc = -EINVAL;
> +		goto done;
> +
> +	}
> +
> +	switch (mode) {
> +	case 0:
> +		break;
> +	case 1:
> +		data->fan_config[channel] =
> +			data->fan_config[channel]
> +			& ~MAX31785_FAN_CFG_CONTROL_MODE_RPM;
> +		break;
> +	case 2:
> +		data->fan_config[channel] =
> +			data->fan_config[channel]
> +			| MAX31785_FAN_CFG_CONTROL_MODE_RPM;
> +		break;
> +	case 3:
> +		data->fan_command[channel] = 0xffff;
> +		break;
> +	default:
> +		rc = -EINVAL;
> +		goto done;
> +	}
> +
> +	rc = max31785_write_fan_byte(client, channel,
> +				MAX31785_REG_FAN_CONFIG_1_2,
> +				data->fan_config[channel]);
> +
> +	if (!rc)
> +		rc = max31785_write_fan_word(client, channel,
> +				MAX31785_REG_FAN_COMMAND_1,
> +				data->fan_command[channel]);
> +
> +done:
> +	mutex_unlock(&data->lock);
> +
> +	return rc;
> +}
> +
> +static int max31785_init_fans(struct max31785 *data)
> +{
> +	struct i2c_client *client = data->client;
> +	int i, rv;
> +
> +	for (i = 0; i < NR_CHANNEL; i++) {
> +		rv = max31785_read_fan_byte(client, i,
> +				MAX31785_REG_FAN_CONFIG_1_2);
> +		if (rv < 0)
> +			return rv;
> +		data->fan_config[i] = rv;
> +
> +		rv = max31785_read_fan_word(client, i,
> +				MAX31785_REG_FAN_COMMAND_1);
> +		if (rv < 0)
> +			return rv;
> +		data->fan_command[i] = rv;
> +
> +		rv = max31785_read_fan_byte(client, i,
> +				MAX31785_REG_MFR_FAN_CONFIG);
> +		if (rv < 0)
> +			return rv;
> +		data->mfr_fan_config[i] = rv;
> +
> +		if (!((data->fan_config[i]
> +			& MAX31785_FAN_CFG_CONTROL_MODE_RPM)
> +			|| is_automatic_control_mode(data, i))) {
> +			data->pwm[i] = 0;
> +		}
> +	}
> +
> +	return rv;
> +}
> +
> +/* Return 0 if detection is successful, -ENODEV otherwise */
> +static int max31785_detect(struct i2c_client *client,
> +			  struct i2c_board_info *info)
> +{
> +	struct i2c_adapter *adapter = client->adapter;
> +	int rv;
> +
> +	if (!i2c_check_functionality(adapter,
> +			I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
> +		return -ENODEV;
> +
> +	/* Probe manufacturer / model registers */
> +	rv = i2c_smbus_read_byte_data(client, MAX31785_REG_MFR_ID);
> +	if (rv < 0)
> +		return -ENODEV;
> +	if (rv != MAX31785_MFR_ID)
> +		return -ENODEV;
> +
> +	rv = i2c_smbus_read_byte_data(client, MAX31785_REG_MFR_MODEL);
> +	if (rv < 0)
> +		return -ENODEV;
> +	if (rv != MAX31785_MFR_MODEL)
> +		return -ENODEV;
> +
> +	strlcpy(info->type, "max31785", I2C_NAME_SIZE);
> +
> +	return 0;
> +}
> +
> +static const u32 max31785_fan_config[] = {
> +	HWMON_F_INPUT | HWMON_F_PULSES | HWMON_F_TARGET | HWMON_F_FAULT,
> +	HWMON_F_INPUT | HWMON_F_PULSES | HWMON_F_TARGET | HWMON_F_FAULT,
> +	HWMON_F_INPUT | HWMON_F_PULSES | HWMON_F_TARGET | HWMON_F_FAULT,
> +	HWMON_F_INPUT | HWMON_F_PULSES | HWMON_F_TARGET | HWMON_F_FAULT,
> +	HWMON_F_INPUT | HWMON_F_PULSES | HWMON_F_TARGET | HWMON_F_FAULT,
> +	HWMON_F_INPUT | HWMON_F_PULSES | HWMON_F_TARGET | HWMON_F_FAULT,
> +	0
> +};
> +
> +static const struct hwmon_channel_info max31785_fan = {
> +	.type = hwmon_fan,
> +	.config = max31785_fan_config,
> +};
> +
> +static const u32 max31785_pwm_config[] = {
> +	HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> +	HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> +	HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> +	HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> +	HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> +	HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> +	0,
> +};
> +
> +static const struct hwmon_channel_info max31785_pwm = {
> +	.type = hwmon_pwm,
> +	.config = max31785_pwm_config
> +};
> +
> +static const struct hwmon_channel_info *max31785_info[] = {
> +	&max31785_fan,
> +	&max31785_pwm,
> +	NULL,
> +};
> +
> +static int max31785_read_fan(struct max31785 *data, u32 attr, int channel,
> +		long *val)
> +{
> +	int rc = 0;
> +
> +	switch (attr) {
> +	case hwmon_fan_pulses:
> +	{
> +		long pulses;
> +
> +		pulses = data->fan_config[channel];
> +		pulses &= MAX31785_FAN_CFG_PULSE_MASK;
> +		pulses >>= MAX31785_FAN_CFG_PULSE_SHIFT;
> +		pulses += MAX31785_FAN_CFG_PULSE_OFFSET;
> +
> +		*val = pulses;
> +		break;
> +	}
> +	case hwmon_fan_target:
> +	{
> +		long target;
> +
> +		mutex_lock(&data->lock);
> +
> +		target = data->fan_command[channel];
> +
> +		if (!(data->fan_config[channel] &
> +				MAX31785_FAN_CFG_CONTROL_MODE_RPM))
> +			target /= MAX31785_FAN_COMMAND_PWM_RATIO;
> +
> +		*val = target;
> +
> +		mutex_unlock(&data->lock);
> +
> +		break;
> +	}
> +	case hwmon_fan_input:
> +		*val = data->tach_rpm[channel];
> +		break;
> +	case hwmon_fan_fault:
> +		*val = !!(data->fault_status[channel] &
> +				MAX31785_FAN_STATUS_FAULT_MASK);
> +		break;
> +	default:
> +		rc = -EOPNOTSUPP;
> +		break;
> +	};
> +
> +	return rc;
> +}
> +
> +static int max31785_fan_get_fast(struct device *dev,
> +				struct device_attribute *attr, char *buf)
> +{
> +	struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(attr);
> +	struct max31785 *data = max31785_update_device(dev);
> +
> +	return sprintf(buf, "%d\n", data->tach_rpm_fast[attr2->index]);
> +}
> +
> +static int max31785_read_pwm(struct max31785 *data, u32 attr, int channel,
> +		long *val)
> +{
> +	bool is_auto;
> +	bool is_rpm;
> +	int rc;
> +
> +	mutex_lock(&data->lock);
> +
> +	is_rpm = !!(data->fan_config[channel] &
> +			MAX31785_FAN_CFG_CONTROL_MODE_RPM);
> +	is_auto = is_automatic_control_mode(data, channel);
> +
> +	switch (attr) {
> +	case hwmon_pwm_enable:
> +	{
> +		bool pwm_enabled;
> +
> +		pwm_enabled = (data->fan_config[channel] &
> +				MAX31785_FAN_CFG_PWM_ENABLE);
> +
> +		if (!pwm_enabled)
> +			*val = 0;
> +		else if (is_auto)
> +			*val = 3;
> +		else if (is_rpm)
> +			*val = 2;
> +		else
> +			*val = 1;
> +		break;
> +	}
> +	case hwmon_pwm_input:
> +		if (is_rpm || is_auto)
> +			*val = data->pwm[channel] / 100;
> +		else
> +			*val = data->fan_command[channel]
> +				/ MAX31785_FAN_COMMAND_PWM_RATIO;
> +		break;
> +	default:
> +		rc = -EOPNOTSUPP;
> +	};
> +
> +	mutex_unlock(&data->lock);
> +
> +	return rc;
> +}
> +
> +static int max31785_read(struct device *dev, enum hwmon_sensor_types type,
> +		u32 attr, int channel, long *val)
> +{
> +	struct max31785 *data;
> +	int rc;
> +
> +	data = max31785_update_device(dev);
> +
> +	if (IS_ERR(data))
> +		return PTR_ERR(data);
> +
> +	switch (type) {
> +	case hwmon_fan:
> +		return max31785_read_fan(data, attr, channel, val);
> +	case hwmon_pwm:
> +		return max31785_read_pwm(data, attr, channel, val);
> +	default:
> +		rc = -EOPNOTSUPP;
> +	}
> +
> +	return rc;
> +}
> +
> +static int max31785_write_fan(struct max31785 *data, u32 attr, int channel,
> +		long val)
> +{
> +	int rc;
> +
> +	switch (attr) {
> +		break;
> +	case hwmon_fan_pulses:
> +		return max31785_fan_set_pulses(data, channel, val);
> +	case hwmon_fan_target:
> +		return max31785_fan_set_target(data, channel, val);
> +	default:
> +		rc = -EOPNOTSUPP;
> +	};
> +
> +	return rc;
> +}
> +
> +static int max31785_write_pwm(struct max31785 *data, u32 attr, int channel,
> +		long val)
> +{
> +	int rc;
> +
> +	switch (attr) {
> +	case hwmon_pwm_enable:
> +		return max31785_pwm_enable(data, channel, val);
> +	case hwmon_pwm_input:
> +		return max31785_pwm_set(data, channel, val);
> +	default:
> +		rc = -EOPNOTSUPP;
> +	};
> +
> +	return rc;
> +}
> +
> +static int max31785_write(struct device *dev, enum hwmon_sensor_types type,
> +		u32 attr, int channel, long val)
> +{
> +	struct max31785 *data;
> +	int rc;
> +
> +	data = dev_get_drvdata(dev);
> +
> +	switch (type) {
> +	case hwmon_fan:
> +		return max31785_write_fan(data, attr, channel, val);
> +	case hwmon_pwm:
> +		return max31785_write_pwm(data, attr, channel, val);
> +	default:
> +		rc = -EOPNOTSUPP;
> +	}
> +
> +	return rc;
> +
> +}
> +
> +static umode_t max31785_is_visible(const void *_data,
> +		enum hwmon_sensor_types type, u32 attr, int channel)
> +{
> +	switch (type) {
> +	case hwmon_fan:
> +		switch (attr) {
> +		case hwmon_fan_input:
> +		case hwmon_fan_fault:
> +			return 0444;
> +		case hwmon_fan_pulses:
> +		case hwmon_fan_target:
> +			return 0644;
> +		};
> +	case hwmon_pwm:
> +		return 0644;
> +	default:
> +		return 0;
> +	};
> +}
> +
> +static const struct hwmon_ops max31785_hwmon_ops = {
> +	.is_visible = max31785_is_visible,
> +	.read = max31785_read,
> +	.write = max31785_write,
> +};
> +
> +static const struct hwmon_chip_info max31785_chip_info = {
> +	.ops = &max31785_hwmon_ops,
> +	.info = max31785_info,
> +};
> +
> +static SENSOR_DEVICE_ATTR(fan1_input_fast, 0444, max31785_fan_get_fast,
> +		NULL, 0);
> +static SENSOR_DEVICE_ATTR(fan2_input_fast, 0444, max31785_fan_get_fast,
> +		NULL, 1);
> +static SENSOR_DEVICE_ATTR(fan3_input_fast, 0444, max31785_fan_get_fast,
> +		NULL, 2);
> +static SENSOR_DEVICE_ATTR(fan4_input_fast, 0444, max31785_fan_get_fast,
> +		NULL, 3);
> +static SENSOR_DEVICE_ATTR(fan5_input_fast, 0444, max31785_fan_get_fast,
> +		NULL, 4);
> +static SENSOR_DEVICE_ATTR(fan6_input_fast, 0444, max31785_fan_get_fast,
> +		NULL, 5);
> +
> +static struct attribute *max31785_attrs[] = {
> +	&sensor_dev_attr_fan1_input_fast.dev_attr.attr,
> +	&sensor_dev_attr_fan2_input_fast.dev_attr.attr,
> +	&sensor_dev_attr_fan3_input_fast.dev_attr.attr,
> +	&sensor_dev_attr_fan4_input_fast.dev_attr.attr,
> +	&sensor_dev_attr_fan5_input_fast.dev_attr.attr,
> +	&sensor_dev_attr_fan6_input_fast.dev_attr.attr,
> +	NULL,
> +};
> +ATTRIBUTE_GROUPS(max31785);
> +
> +static int max31785_get_capabilities(struct max31785 *data)
> +{
> +	s32 rc;
> +
> +	rc = i2c_smbus_read_word_data(data->client, MAX31785_REG_MFR_REVISION);
> +	if (rc < 0)
> +		return rc;
> +
> +	if (rc == 0x3040)
> +		data->capabilities |= MAX31785_CAP_FAST_ROTOR;
> +
> +	return 0;
> +}
> +
> +static int max31785_probe(struct i2c_client *client,
> +			  const struct i2c_device_id *id)
> +{
> +	struct i2c_adapter *adapter = client->adapter;
> +	const struct attribute_group **extra_groups;
> +	struct device *dev = &client->dev;
> +	struct device *hwmon_dev;
> +	struct max31785 *data;
> +	int rc;
> +
> +	if (!i2c_check_functionality(adapter,
> +			I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
> +		return -ENODEV;
> +
> +	data = devm_kzalloc(dev, sizeof(struct max31785), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	data->client = client;
> +	mutex_init(&data->lock);
> +
> +	rc = max31785_init_fans(data);
> +	if (rc)
> +		return rc;
> +
> +	rc = max31785_get_capabilities(data);
> +	if (rc < 0)
> +		return rc;
> +
> +	if (data->capabilities & MAX31785_CAP_FAST_ROTOR)
> +		extra_groups = max31785_groups;
> +
> +	hwmon_dev = devm_hwmon_device_register_with_info(dev,
> +			client->name, data, &max31785_chip_info, extra_groups);
> +
> +	return PTR_ERR_OR_ZERO(hwmon_dev);
> +}
> +
> +static const struct i2c_device_id max31785_id[] = {
> +	{ "max31785", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, max31785_id);
> +
> +static struct i2c_driver max31785_driver = {
> +	.class		= I2C_CLASS_HWMON,
> +	.probe		= max31785_probe,
> +	.driver = {
> +		.name	= "max31785",
> +	},
> +	.id_table	= max31785_id,
> +	.detect		= max31785_detect,
> +	.address_list	= normal_i2c,
> +};
> +
> +module_i2c_driver(max31785_driver);
> +
> +MODULE_AUTHOR("Timothy Pearson <tpearson at raptorengineering.com>");
> +MODULE_DESCRIPTION("MAX31785 sensor driver");
> +MODULE_LICENSE("GPL");
> 



More information about the openbmc mailing list