All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785
@ 2017-06-02  6:22 Andrew Jeffery
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 1/7] hwmon: Add support for MAX31785 intelligent fan controller Andrew Jeffery
                   ` (7 more replies)
  0 siblings, 8 replies; 10+ messages in thread
From: Andrew Jeffery @ 2017-06-02  6:22 UTC (permalink / raw)
  To: joel; +Cc: tpearson, Andrew Jeffery, openbmc, cbostic, msbarth

Hello,

I've dodgied up this series with a patch to the i2c core to make the fast rotor
read documented in [1] work. Thus, it's an RFC series. Further, the fast rotor
values are zero on the machine I was testing against. Maybe I need to broaden
my horizons and test against more machines, but I figured I'd send the patches
to get some "internal" feedback.

Timothy's original patch is reworked against the new hwmon kernel APIs. The
rework made it easier to support both the 0x3030 and 0x3040 firmwares for the
approach I took, though maybe there is a better way with using the hwmon
attribute visibility modes. Further, it's still not a pmbus driver, so it's
probably not yet acceptible upstream despite the fact that upstream still
doesn't support fan control in the pmbus core.

Please review.

Cheers,

Andrew

[1] https://github.com/openbmc/openbmc/issues/652

Andrew Jeffery (5):
  arm: aspeed: Add MAX31785 to aspeed_g5_defconfig
  hwmon: max31785: Document implementation oddities
  hwmon: max31785: Adapt to new hwmon APIs
  i2c: Add bogus I2C_SMBUS_DWORD_DATA bus protocol/transaction type
  hwmon: max31785: Enable fast rotor measurement feature

Christopher Bostic (1):
  hwmon: max31785: Provide config of fault pin behavior

Timothy Pearson (1):
  hwmon: Add support for MAX31785 intelligent fan controller

 Documentation/hwmon/max31785         |  44 ++
 arch/arm/configs/aspeed_g5_defconfig |   1 +
 drivers/hwmon/Kconfig                |  10 +
 drivers/hwmon/Makefile               |   1 +
 drivers/hwmon/max31785.c             | 852 +++++++++++++++++++++++++++++++++++
 drivers/i2c/i2c-core.c               |  30 ++
 include/linux/i2c.h                  |   2 +
 include/uapi/linux/i2c.h             |   2 +
 8 files changed, 942 insertions(+)
 create mode 100644 Documentation/hwmon/max31785
 create mode 100644 drivers/hwmon/max31785.c

-- 
2.11.0

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH linux dev-4.10 1/7] hwmon: Add support for MAX31785 intelligent fan controller
  2017-06-02  6:22 [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785 Andrew Jeffery
@ 2017-06-02  6:22 ` Andrew Jeffery
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 2/7] arm: aspeed: Add MAX31785 to aspeed_g5_defconfig Andrew Jeffery
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Andrew Jeffery @ 2017-06-02  6:22 UTC (permalink / raw)
  To: joel; +Cc: tpearson, openbmc, cbostic, msbarth

From: Timothy Pearson <tpearson@raptorengineering.com>

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@raptorengineering.com>
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
 Documentation/hwmon/max31785 |  36 +++
 drivers/hwmon/Kconfig        |  10 +
 drivers/hwmon/Makefile       |   1 +
 drivers/hwmon/max31785.c     | 714 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 761 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..0911d204547e
--- /dev/null
+++ b/Documentation/hwmon/max31785
@@ -0,0 +1,36 @@
+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@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)
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..fb7b3f010bd8
--- /dev/null
+++ b/drivers/hwmon/max31785.c
@@ -0,0 +1,714 @@
+/*
+ * 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_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 };
+
+/*
+ * Client data (each client gets its own)
+ */
+struct max31785_data {
+	struct i2c_client *client;
+	struct mutex device_lock;
+	bool valid; /* zero until following fields are valid */
+	unsigned long last_updated; /* in jiffies */
+
+	/* register values */
+	u8 fan_config[NR_CHANNEL];
+	u16 fan_command[NR_CHANNEL];
+	u8 mfr_fan_config[NR_CHANNEL];
+	u8 fault_status[NR_CHANNEL];
+	u16 tach_rpm[NR_CHANNEL];
+	u16 pwm[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 max31785_read_fan_data(struct i2c_client *client,
+				u8 fan, u8 reg, u8 byte_mode)
+{
+	int rv;
+
+	rv = max31785_set_page(client, MAX31785_PAGE_FAN_CONFIG(fan));
+	if (rv < 0)
+		return rv;
+
+	if (byte_mode)
+		rv = i2c_smbus_read_byte_data(client, reg);
+	else
+		rv = i2c_smbus_read_word_data(client, reg);
+
+	return rv;
+}
+
+static int max31785_write_fan_data(struct i2c_client *client,
+				u8 fan, u8 reg, u16 data,
+				u8 byte_mode)
+{
+	int err;
+
+	err = max31785_set_page(client, MAX31785_PAGE_FAN_CONFIG(fan));
+	if (err < 0)
+		return err;
+
+	if (byte_mode)
+		err = i2c_smbus_write_byte_data(client, reg, data);
+	else
+		err = i2c_smbus_write_word_data(client, reg, data);
+
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static bool is_automatic_control_mode(struct max31785_data *data,
+			int index)
+{
+	if (data->fan_command[index] > 0x7fff)
+		return true;
+	else
+		return false;
+}
+
+static struct max31785_data *max31785_update_device(struct device *dev)
+{
+	struct max31785_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	struct max31785_data *ret = data;
+	int i;
+	int rv;
+
+	mutex_lock(&data->device_lock);
+
+	if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
+		for (i = 0; i < NR_CHANNEL; i++) {
+			rv = max31785_read_fan_data(client, i,
+					MAX31785_REG_STATUS_FANS_1_2, 1);
+			if (rv < 0)
+				goto abort;
+			data->fault_status[i] = rv;
+
+			rv = max31785_read_fan_data(client, i,
+					MAX31785_REG_FAN_SPEED_1, 0);
+			if (rv < 0)
+				goto abort;
+			data->tach_rpm[i] = rv;
+
+			if ((data->fan_config[i]
+				& MAX31785_FAN_CFG_CONTROL_MODE_RPM)
+				|| is_automatic_control_mode(data, i)) {
+				rv = max31785_read_fan_data(client, i,
+						MAX31785_REG_READ_FAN_PWM, 0);
+				if (rv < 0)
+					goto abort;
+				data->pwm[i] = rv;
+			}
+
+			if (!is_automatic_control_mode(data, i)) {
+				/* Poke watchdog for manual fan control */
+				rv = max31785_write_fan_data(client,
+					i, MAX31785_REG_FAN_COMMAND_1,
+					data->fan_command[i], 0);
+				if (rv < 0)
+					goto abort;
+			}
+		}
+
+		data->last_updated = jiffies;
+		data->valid = true;
+	}
+	goto done;
+
+abort:
+	data->valid = false;
+	ret = ERR_PTR(rv);
+
+done:
+	mutex_unlock(&data->device_lock);
+
+	return ret;
+}
+
+static ssize_t get_fan(struct device *dev,
+		       struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct max31785_data *data = max31785_update_device(dev);
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	return sprintf(buf, "%d\n", data->tach_rpm[attr->index]);
+}
+
+static ssize_t get_fan_target(struct device *dev,
+			      struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct max31785_data *data = max31785_update_device(dev);
+	int rpm;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	if (data->fan_config[attr->index]
+		& MAX31785_FAN_CFG_CONTROL_MODE_RPM)
+		rpm = data->fan_command[attr->index];
+	else
+		rpm = data->fan_command[attr->index]
+					/ MAX31785_FAN_COMMAND_PWM_RATIO;
+
+	return sprintf(buf, "%d\n", rpm);
+}
+
+static ssize_t set_fan_target(struct device *dev,
+			      struct device_attribute *devattr,
+			      const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct max31785_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	unsigned long rpm;
+	int err;
+
+	err = kstrtoul(buf, 10, &rpm);
+	if (err)
+		return err;
+
+	if (rpm > 0x7fff)
+		return -EINVAL;
+
+	mutex_lock(&data->device_lock);
+
+	/* Write new RPM value */
+	data->fan_command[attr->index] = rpm;
+	err = max31785_write_fan_data(client, attr->index,
+				MAX31785_REG_FAN_COMMAND_1,
+				data->fan_command[attr->index], 0);
+
+	mutex_unlock(&data->device_lock);
+
+	if (err < 0)
+		return err;
+
+	return count;
+}
+
+static ssize_t get_fan_pulses(struct device *dev,
+			      struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct max31785_data *data = max31785_update_device(dev);
+	int pulses;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	pulses = ((data->fan_config[attr->index] & MAX31785_FAN_CFG_PULSE_MASK)
+			>> MAX31785_FAN_CFG_PULSE_SHIFT)
+			+ MAX31785_FAN_CFG_PULSE_OFFSET;
+
+	return sprintf(buf, "%d\n", pulses);
+}
+
+static ssize_t set_fan_pulses(struct device *dev,
+			      struct device_attribute *devattr,
+			      const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct max31785_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	unsigned long pulses;
+	int err;
+
+	err = kstrtoul(buf, 10, &pulses);
+	if (err)
+		return err;
+
+	if (pulses > 4)
+		return -EINVAL;
+
+	data->fan_config[attr->index] &= MAX31785_FAN_CFG_PULSE_MASK;
+	data->fan_config[attr->index] |=
+				((pulses - MAX31785_FAN_CFG_PULSE_OFFSET)
+				<< MAX31785_FAN_CFG_PULSE_SHIFT);
+
+	mutex_lock(&data->device_lock);
+
+	/* Write new pulse value */
+	data->fan_command[attr->index] = pulses;
+	err = max31785_write_fan_data(client, attr->index,
+				MAX31785_REG_FAN_CONFIG_1_2,
+				data->fan_config[attr->index], 1);
+
+	mutex_unlock(&data->device_lock);
+
+	if (err < 0)
+		return err;
+
+	return count;
+}
+
+static ssize_t get_pwm(struct device *dev,
+		       struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct max31785_data *data = max31785_update_device(dev);
+	int pwm;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	if ((data->fan_config[attr->index]
+		& MAX31785_FAN_CFG_CONTROL_MODE_RPM)
+		|| is_automatic_control_mode(data, attr->index))
+		pwm = data->pwm[attr->index] / 100;
+	else
+		pwm = data->fan_command[attr->index]
+					/ MAX31785_FAN_COMMAND_PWM_RATIO;
+
+	return sprintf(buf, "%d\n", pwm);
+}
+
+static ssize_t set_pwm(struct device *dev,
+		       struct device_attribute *devattr,
+		       const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct max31785_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	unsigned long pwm;
+	int err;
+
+	err = kstrtoul(buf, 10, &pwm);
+	if (err)
+		return err;
+
+	if (pwm > 255)
+		return -EINVAL;
+
+	mutex_lock(&data->device_lock);
+
+	/* Write new PWM value */
+	data->fan_command[attr->index] = pwm * MAX31785_FAN_COMMAND_PWM_RATIO;
+	err = max31785_write_fan_data(client, attr->index,
+				MAX31785_REG_FAN_COMMAND_1,
+				data->fan_command[attr->index], 0);
+
+	mutex_unlock(&data->device_lock);
+
+	if (err < 0)
+		return err;
+
+	return count;
+}
+
+static ssize_t get_pwm_enable(struct device *dev,
+			      struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct max31785_data *data = max31785_update_device(dev);
+	int mode;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	if (!(data->fan_config[attr->index] & MAX31785_FAN_CFG_PWM_ENABLE))
+		mode = 0;
+	else if (is_automatic_control_mode(data, attr->index))
+		mode = 3;
+	else if (data->fan_config[attr->index]
+		& MAX31785_FAN_CFG_CONTROL_MODE_RPM)
+		mode = 2;
+	else
+		mode = 1;
+
+	return sprintf(buf, "%d\n", mode);
+}
+
+static ssize_t set_pwm_enable(struct device *dev,
+			      struct device_attribute *devattr,
+			      const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct max31785_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	unsigned long mode;
+	int err;
+
+	err = kstrtoul(buf, 10, &mode);
+	if (err)
+		return err;
+
+	switch (mode) {
+	case 0:
+		data->fan_config[attr->index] =
+			data->fan_config[attr->index]
+			& ~MAX31785_FAN_CFG_PWM_ENABLE;
+		break;
+	case 1:
+	case 2:
+	case 3:
+		data->fan_config[attr->index] =
+			data->fan_config[attr->index]
+			 | MAX31785_FAN_CFG_PWM_ENABLE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (mode) {
+	case 0:
+		break;
+	case 1:
+		data->fan_config[attr->index] =
+			data->fan_config[attr->index]
+			& ~MAX31785_FAN_CFG_CONTROL_MODE_RPM;
+		break;
+	case 2:
+		data->fan_config[attr->index] =
+			data->fan_config[attr->index]
+			| MAX31785_FAN_CFG_CONTROL_MODE_RPM;
+		break;
+	case 3:
+		data->fan_command[attr->index] = 0xffff;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mutex_lock(&data->device_lock);
+
+	err = max31785_write_fan_data(client, attr->index,
+				MAX31785_REG_FAN_CONFIG_1_2,
+				data->fan_config[attr->index], 1);
+
+	if (err < 0)
+		goto abort;
+
+	err = max31785_write_fan_data(client, attr->index,
+				MAX31785_REG_FAN_COMMAND_1,
+				data->fan_command[attr->index], 0);
+
+abort:
+	mutex_unlock(&data->device_lock);
+
+	if (err < 0)
+		return err;
+
+	return count;
+}
+
+static ssize_t get_fan_fault(struct device *dev,
+			     struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct max31785_data *data = max31785_update_device(dev);
+	int fault;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	fault = !!(data->fault_status[attr->index]
+			& MAX31785_FAN_STATUS_FAULT_MASK);
+
+	return sprintf(buf, "%d\n", fault);
+}
+
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, get_fan, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, get_fan, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, get_fan, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, get_fan, NULL, 4);
+static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, get_fan, NULL, 5);
+
+static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, get_fan_fault, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan3_fault, S_IRUGO, get_fan_fault, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan4_fault, S_IRUGO, get_fan_fault, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan5_fault, S_IRUGO, get_fan_fault, NULL, 4);
+static SENSOR_DEVICE_ATTR(fan6_fault, S_IRUGO, get_fan_fault, NULL, 5);
+
+static SENSOR_DEVICE_ATTR(fan1_target, S_IWUSR | S_IRUGO,
+		get_fan_target, set_fan_target, 0);
+static SENSOR_DEVICE_ATTR(fan2_target, S_IWUSR | S_IRUGO,
+		get_fan_target, set_fan_target, 1);
+static SENSOR_DEVICE_ATTR(fan3_target, S_IWUSR | S_IRUGO,
+		get_fan_target, set_fan_target, 2);
+static SENSOR_DEVICE_ATTR(fan4_target, S_IWUSR | S_IRUGO,
+		get_fan_target, set_fan_target, 3);
+static SENSOR_DEVICE_ATTR(fan5_target, S_IWUSR | S_IRUGO,
+		get_fan_target, set_fan_target, 4);
+static SENSOR_DEVICE_ATTR(fan6_target, S_IWUSR | S_IRUGO,
+		get_fan_target, set_fan_target, 5);
+
+static SENSOR_DEVICE_ATTR(fan1_pulses, S_IWUSR | S_IRUGO,
+		get_fan_pulses, set_fan_pulses, 0);
+static SENSOR_DEVICE_ATTR(fan2_pulses, S_IWUSR | S_IRUGO,
+		get_fan_pulses, set_fan_pulses, 1);
+static SENSOR_DEVICE_ATTR(fan3_pulses, S_IWUSR | S_IRUGO,
+		get_fan_pulses, set_fan_pulses, 2);
+static SENSOR_DEVICE_ATTR(fan4_pulses, S_IWUSR | S_IRUGO,
+		get_fan_pulses, set_fan_pulses, 3);
+static SENSOR_DEVICE_ATTR(fan5_pulses, S_IWUSR | S_IRUGO,
+		get_fan_pulses, set_fan_pulses, 4);
+static SENSOR_DEVICE_ATTR(fan6_pulses, S_IWUSR | S_IRUGO,
+		get_fan_pulses, set_fan_pulses, 5);
+
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 0);
+static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 2);
+static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 3);
+static SENSOR_DEVICE_ATTR(pwm5, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 4);
+static SENSOR_DEVICE_ATTR(pwm6, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 5);
+
+static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
+		get_pwm_enable, set_pwm_enable, 0);
+static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO,
+		get_pwm_enable, set_pwm_enable, 1);
+static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO,
+		get_pwm_enable, set_pwm_enable, 2);
+static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO,
+		get_pwm_enable, set_pwm_enable, 3);
+static SENSOR_DEVICE_ATTR(pwm5_enable, S_IWUSR | S_IRUGO,
+		get_pwm_enable, set_pwm_enable, 4);
+static SENSOR_DEVICE_ATTR(pwm6_enable, S_IWUSR | S_IRUGO,
+		get_pwm_enable, set_pwm_enable, 5);
+
+static struct attribute *max31785_attrs[] = {
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan2_input.dev_attr.attr,
+	&sensor_dev_attr_fan3_input.dev_attr.attr,
+	&sensor_dev_attr_fan4_input.dev_attr.attr,
+	&sensor_dev_attr_fan5_input.dev_attr.attr,
+	&sensor_dev_attr_fan6_input.dev_attr.attr,
+
+	&sensor_dev_attr_fan1_fault.dev_attr.attr,
+	&sensor_dev_attr_fan2_fault.dev_attr.attr,
+	&sensor_dev_attr_fan3_fault.dev_attr.attr,
+	&sensor_dev_attr_fan4_fault.dev_attr.attr,
+	&sensor_dev_attr_fan5_fault.dev_attr.attr,
+	&sensor_dev_attr_fan6_fault.dev_attr.attr,
+
+	&sensor_dev_attr_fan1_target.dev_attr.attr,
+	&sensor_dev_attr_fan2_target.dev_attr.attr,
+	&sensor_dev_attr_fan3_target.dev_attr.attr,
+	&sensor_dev_attr_fan4_target.dev_attr.attr,
+	&sensor_dev_attr_fan5_target.dev_attr.attr,
+	&sensor_dev_attr_fan6_target.dev_attr.attr,
+
+	&sensor_dev_attr_fan1_pulses.dev_attr.attr,
+	&sensor_dev_attr_fan2_pulses.dev_attr.attr,
+	&sensor_dev_attr_fan3_pulses.dev_attr.attr,
+	&sensor_dev_attr_fan4_pulses.dev_attr.attr,
+	&sensor_dev_attr_fan5_pulses.dev_attr.attr,
+	&sensor_dev_attr_fan6_pulses.dev_attr.attr,
+
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm2.dev_attr.attr,
+	&sensor_dev_attr_pwm3.dev_attr.attr,
+	&sensor_dev_attr_pwm4.dev_attr.attr,
+	&sensor_dev_attr_pwm5.dev_attr.attr,
+	&sensor_dev_attr_pwm6.dev_attr.attr,
+
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm2_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm3_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm4_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm5_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm6_enable.dev_attr.attr,
+	NULL
+};
+
+static umode_t max31785_attrs_visible(struct kobject *kobj,
+				     struct attribute *a, int n)
+{
+	return a->mode;
+}
+
+static const struct attribute_group max31785_group = {
+	.attrs = max31785_attrs,
+	.is_visible = max31785_attrs_visible,
+};
+__ATTRIBUTE_GROUPS(max31785);
+
+static int max31785_init_client(struct i2c_client *client,
+				struct max31785_data *data)
+{
+	int i, rv;
+
+	for (i = 0; i < NR_CHANNEL; i++) {
+		rv = max31785_read_fan_data(client, i,
+				MAX31785_REG_FAN_CONFIG_1_2, 1);
+		if (rv < 0)
+			return rv;
+		data->fan_config[i] = rv;
+
+		rv = max31785_read_fan_data(client, i,
+				MAX31785_REG_FAN_COMMAND_1, 0);
+		if (rv < 0)
+			return rv;
+		data->fan_command[i] = rv;
+
+		rv = max31785_read_fan_data(client, i,
+				MAX31785_REG_MFR_FAN_CONFIG, 1);
+		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 int max31785_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	struct device *dev = &client->dev;
+	struct max31785_data *data;
+	struct device *hwmon_dev;
+	int err;
+
+	if (!i2c_check_functionality(adapter,
+			I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
+		return -ENODEV;
+
+	data = devm_kzalloc(dev, sizeof(struct max31785_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->client = client;
+	mutex_init(&data->device_lock);
+
+	/*
+	 * Initialize the max31785 chip
+	 */
+	err = max31785_init_client(client, data);
+	if (err)
+		return err;
+
+	hwmon_dev = devm_hwmon_device_register_with_groups(dev,
+			client->name, data, max31785_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@raptorengineering.com>");
+MODULE_DESCRIPTION("MAX31785 sensor driver");
+MODULE_LICENSE("GPL");
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH linux dev-4.10 2/7] arm: aspeed: Add MAX31785 to aspeed_g5_defconfig
  2017-06-02  6:22 [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785 Andrew Jeffery
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 1/7] hwmon: Add support for MAX31785 intelligent fan controller Andrew Jeffery
@ 2017-06-02  6:22 ` Andrew Jeffery
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 3/7] hwmon: max31785: Document implementation oddities Andrew Jeffery
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Andrew Jeffery @ 2017-06-02  6:22 UTC (permalink / raw)
  To: joel; +Cc: tpearson, Andrew Jeffery, openbmc, cbostic, msbarth

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
 arch/arm/configs/aspeed_g5_defconfig | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm/configs/aspeed_g5_defconfig b/arch/arm/configs/aspeed_g5_defconfig
index 3fc3eb0e8867..1ff53ca48a31 100644
--- a/arch/arm/configs/aspeed_g5_defconfig
+++ b/arch/arm/configs/aspeed_g5_defconfig
@@ -134,6 +134,7 @@ CONFIG_W1=y
 CONFIG_W1_MASTER_GPIO=y
 CONFIG_W1_SLAVE_THERM=y
 CONFIG_SENSORS_IIO_HWMON=y
+CONFIG_SENSORS_MAX31785=y
 CONFIG_SENSORS_LM75=y
 CONFIG_SENSORS_NCT7904=y
 CONFIG_PMBUS=y
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH linux dev-4.10 3/7] hwmon: max31785: Document implementation oddities
  2017-06-02  6:22 [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785 Andrew Jeffery
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 1/7] hwmon: Add support for MAX31785 intelligent fan controller Andrew Jeffery
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 2/7] arm: aspeed: Add MAX31785 to aspeed_g5_defconfig Andrew Jeffery
@ 2017-06-02  6:22 ` Andrew Jeffery
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 4/7] hwmon: max31785: Adapt to new hwmon APIs Andrew Jeffery
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Andrew Jeffery @ 2017-06-02  6:22 UTC (permalink / raw)
  To: joel; +Cc: tpearson, Andrew Jeffery, openbmc, cbostic, msbarth

Some possible bugs are discussed, and the implementation semantics of
fall-through cases in switch statements are documented.

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
 drivers/hwmon/max31785.c | 29 +++++++++++++++++++++++++----
 1 file changed, 25 insertions(+), 4 deletions(-)

diff --git a/drivers/hwmon/max31785.c b/drivers/hwmon/max31785.c
index fb7b3f010bd8..ad86082ffc7d 100644
--- a/drivers/hwmon/max31785.c
+++ b/drivers/hwmon/max31785.c
@@ -167,7 +167,12 @@ static struct max31785_data *max31785_update_device(struct device *dev)
 			}
 
 			if (!is_automatic_control_mode(data, i)) {
-				/* Poke watchdog for manual fan control */
+				/*
+				 * Poke watchdog for manual fan control
+				 *
+				 * XXX: This isn't documented in the MAX31785
+				 * datasheet, or anywhere else.
+				 */
 				rv = max31785_write_fan_data(client,
 					i, MAX31785_REG_FAN_COMMAND_1,
 					data->fan_command[i], 0);
@@ -290,6 +295,7 @@ static ssize_t set_fan_pulses(struct device *dev,
 	if (pulses > 4)
 		return -EINVAL;
 
+	/* XXX: This sequence disables the fan and sets in PWM mode */
 	data->fan_config[attr->index] &= MAX31785_FAN_CFG_PULSE_MASK;
 	data->fan_config[attr->index] |=
 				((pulses - MAX31785_FAN_CFG_PULSE_OFFSET)
@@ -297,8 +303,23 @@ static ssize_t set_fan_pulses(struct device *dev,
 
 	mutex_lock(&data->device_lock);
 
-	/* Write new pulse value */
+	/*
+	 * XXX: This seems suspect. Pulses must be in [1, 4], however
+	 * fan_command expects the following ranges:
+	 *
+	 * PWM:
+	 * [0x0000, 0x2710]: [0, 100]% PWM duty cycle
+	 * [0x2711, 0x7fff]: 100% PWM duty cycle
+	 *
+	 * RPM:
+	 * [0x0000, 0x7fff]: [0, 32767]RPM
+	 * [0x8000, 0xffff]: Automatic fan control
+	 *
+	 * In all cases [1-4] is quite low
+	 */
 	data->fan_command[attr->index] = pulses;
+
+	/* Write new pulse value */
 	err = max31785_write_fan_data(client, attr->index,
 				MAX31785_REG_FAN_CONFIG_1_2,
 				data->fan_config[attr->index], 1);
@@ -408,8 +429,8 @@ static ssize_t set_pwm_enable(struct device *dev,
 			data->fan_config[attr->index]
 			& ~MAX31785_FAN_CFG_PWM_ENABLE;
 		break;
-	case 1:
-	case 2:
+	case 1: /* fallthrough */
+	case 2: /* fallthrough */
 	case 3:
 		data->fan_config[attr->index] =
 			data->fan_config[attr->index]
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH linux dev-4.10 4/7] hwmon: max31785: Adapt to new hwmon APIs
  2017-06-02  6:22 [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785 Andrew Jeffery
                   ` (2 preceding siblings ...)
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 3/7] hwmon: max31785: Document implementation oddities Andrew Jeffery
@ 2017-06-02  6:22 ` Andrew Jeffery
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 5/7] i2c: Add bogus I2C_SMBUS_DWORD_DATA bus protocol/transaction type Andrew Jeffery
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Andrew Jeffery @ 2017-06-02  6:22 UTC (permalink / raw)
  To: joel; +Cc: tpearson, Andrew Jeffery, openbmc, cbostic, msbarth

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
 drivers/hwmon/max31785.c | 617 ++++++++++++++++++++++-------------------------
 1 file changed, 285 insertions(+), 332 deletions(-)

diff --git a/drivers/hwmon/max31785.c b/drivers/hwmon/max31785.c
index ad86082ffc7d..d1feb73d08f1 100644
--- a/drivers/hwmon/max31785.c
+++ b/drivers/hwmon/max31785.c
@@ -1,6 +1,6 @@
 /*
  * max31785.c - Part of lm_sensors, Linux kernel modules for hardware
- *             monitoring.
+ *	       monitoring.
  *
  * (C) 2016 Raptor Engineering, LLC
  *
@@ -62,7 +62,7 @@ static const unsigned short normal_i2c[] = { 0x52, 0x53, 0x54, 0x55,
 /*
  * Client data (each client gets its own)
  */
-struct max31785_data {
+struct max31785 {
 	struct i2c_client *client;
 	struct mutex device_lock;
 	bool valid; /* zero until following fields are valid */
@@ -123,20 +123,17 @@ static int max31785_write_fan_data(struct i2c_client *client,
 	return 0;
 }
 
-static bool is_automatic_control_mode(struct max31785_data *data,
+static bool is_automatic_control_mode(struct max31785 *data,
 			int index)
 {
-	if (data->fan_command[index] > 0x7fff)
-		return true;
-	else
-		return false;
+	return data->fan_command[index] > 0x7fff;
 }
 
-static struct max31785_data *max31785_update_device(struct device *dev)
+static struct max31785 *max31785_update_device(struct device *dev)
 {
-	struct max31785_data *data = dev_get_drvdata(dev);
+	struct max31785 *data = dev_get_drvdata(dev);
 	struct i2c_client *client = data->client;
-	struct max31785_data *ret = data;
+	struct max31785 *ret = data;
 	int i;
 	int rv;
 
@@ -196,51 +193,10 @@ static struct max31785_data *max31785_update_device(struct device *dev)
 	return ret;
 }
 
-static ssize_t get_fan(struct device *dev,
-		       struct device_attribute *devattr, char *buf)
+static ssize_t max31785_fan_set_target(struct max31785 *data, int channel,
+		long rpm)
 {
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct max31785_data *data = max31785_update_device(dev);
-
-	if (IS_ERR(data))
-		return PTR_ERR(data);
-
-	return sprintf(buf, "%d\n", data->tach_rpm[attr->index]);
-}
-
-static ssize_t get_fan_target(struct device *dev,
-			      struct device_attribute *devattr, char *buf)
-{
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct max31785_data *data = max31785_update_device(dev);
-	int rpm;
-
-	if (IS_ERR(data))
-		return PTR_ERR(data);
-
-	if (data->fan_config[attr->index]
-		& MAX31785_FAN_CFG_CONTROL_MODE_RPM)
-		rpm = data->fan_command[attr->index];
-	else
-		rpm = data->fan_command[attr->index]
-					/ MAX31785_FAN_COMMAND_PWM_RATIO;
-
-	return sprintf(buf, "%d\n", rpm);
-}
-
-static ssize_t set_fan_target(struct device *dev,
-			      struct device_attribute *devattr,
-			      const char *buf, size_t count)
-{
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct max31785_data *data = dev_get_drvdata(dev);
-	struct i2c_client *client = data->client;
-	unsigned long rpm;
-	int err;
-
-	err = kstrtoul(buf, 10, &rpm);
-	if (err)
-		return err;
+	int rc;
 
 	if (rpm > 0x7fff)
 		return -EINVAL;
@@ -248,56 +204,27 @@ static ssize_t set_fan_target(struct device *dev,
 	mutex_lock(&data->device_lock);
 
 	/* Write new RPM value */
-	data->fan_command[attr->index] = rpm;
-	err = max31785_write_fan_data(client, attr->index,
+	data->fan_command[channel] = rpm;
+	rc = max31785_write_fan_data(data->client, channel,
 				MAX31785_REG_FAN_COMMAND_1,
-				data->fan_command[attr->index], 0);
+				data->fan_command[channel], 0);
 
 	mutex_unlock(&data->device_lock);
 
-	if (err < 0)
-		return err;
-
-	return count;
-}
-
-static ssize_t get_fan_pulses(struct device *dev,
-			      struct device_attribute *devattr, char *buf)
-{
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct max31785_data *data = max31785_update_device(dev);
-	int pulses;
-
-	if (IS_ERR(data))
-		return PTR_ERR(data);
-
-	pulses = ((data->fan_config[attr->index] & MAX31785_FAN_CFG_PULSE_MASK)
-			>> MAX31785_FAN_CFG_PULSE_SHIFT)
-			+ MAX31785_FAN_CFG_PULSE_OFFSET;
-
-	return sprintf(buf, "%d\n", pulses);
+	return rc;
 }
 
-static ssize_t set_fan_pulses(struct device *dev,
-			      struct device_attribute *devattr,
-			      const char *buf, size_t count)
+static ssize_t max31785_fan_set_pulses(struct max31785 *data, int channel,
+		long pulses)
 {
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct max31785_data *data = dev_get_drvdata(dev);
-	struct i2c_client *client = data->client;
-	unsigned long pulses;
-	int err;
-
-	err = kstrtoul(buf, 10, &pulses);
-	if (err)
-		return err;
+	int rc;
 
 	if (pulses > 4)
 		return -EINVAL;
 
 	/* XXX: This sequence disables the fan and sets in PWM mode */
-	data->fan_config[attr->index] &= MAX31785_FAN_CFG_PULSE_MASK;
-	data->fan_config[attr->index] |=
+	data->fan_config[channel] &= MAX31785_FAN_CFG_PULSE_MASK;
+	data->fan_config[channel] |=
 				((pulses - MAX31785_FAN_CFG_PULSE_OFFSET)
 				<< MAX31785_FAN_CFG_PULSE_SHIFT);
 
@@ -317,55 +244,21 @@ static ssize_t set_fan_pulses(struct device *dev,
 	 *
 	 * In all cases [1-4] is quite low
 	 */
-	data->fan_command[attr->index] = pulses;
+	data->fan_command[channel] = pulses;
 
 	/* Write new pulse value */
-	err = max31785_write_fan_data(client, attr->index,
+	rc = max31785_write_fan_data(data->client, channel,
 				MAX31785_REG_FAN_CONFIG_1_2,
-				data->fan_config[attr->index], 1);
+				data->fan_config[channel], 1);
 
 	mutex_unlock(&data->device_lock);
 
-	if (err < 0)
-		return err;
-
-	return count;
-}
-
-static ssize_t get_pwm(struct device *dev,
-		       struct device_attribute *devattr, char *buf)
-{
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct max31785_data *data = max31785_update_device(dev);
-	int pwm;
-
-	if (IS_ERR(data))
-		return PTR_ERR(data);
-
-	if ((data->fan_config[attr->index]
-		& MAX31785_FAN_CFG_CONTROL_MODE_RPM)
-		|| is_automatic_control_mode(data, attr->index))
-		pwm = data->pwm[attr->index] / 100;
-	else
-		pwm = data->fan_command[attr->index]
-					/ MAX31785_FAN_COMMAND_PWM_RATIO;
-
-	return sprintf(buf, "%d\n", pwm);
+	return rc;
 }
 
-static ssize_t set_pwm(struct device *dev,
-		       struct device_attribute *devattr,
-		       const char *buf, size_t count)
+static ssize_t max31785_pwm_set(struct max31785 *data, int channel, long pwm)
 {
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct max31785_data *data = dev_get_drvdata(dev);
-	struct i2c_client *client = data->client;
-	unsigned long pwm;
-	int err;
-
-	err = kstrtoul(buf, 10, &pwm);
-	if (err)
-		return err;
+	int rc;
 
 	if (pwm > 255)
 		return -EINVAL;
@@ -373,67 +266,33 @@ static ssize_t set_pwm(struct device *dev,
 	mutex_lock(&data->device_lock);
 
 	/* Write new PWM value */
-	data->fan_command[attr->index] = pwm * MAX31785_FAN_COMMAND_PWM_RATIO;
-	err = max31785_write_fan_data(client, attr->index,
+	data->fan_command[channel] = pwm * MAX31785_FAN_COMMAND_PWM_RATIO;
+	rc = max31785_write_fan_data(data->client, channel,
 				MAX31785_REG_FAN_COMMAND_1,
-				data->fan_command[attr->index], 0);
+				data->fan_command[channel], 0);
 
 	mutex_unlock(&data->device_lock);
 
-	if (err < 0)
-		return err;
-
-	return count;
-}
-
-static ssize_t get_pwm_enable(struct device *dev,
-			      struct device_attribute *devattr, char *buf)
-{
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct max31785_data *data = max31785_update_device(dev);
-	int mode;
-
-	if (IS_ERR(data))
-		return PTR_ERR(data);
-
-	if (!(data->fan_config[attr->index] & MAX31785_FAN_CFG_PWM_ENABLE))
-		mode = 0;
-	else if (is_automatic_control_mode(data, attr->index))
-		mode = 3;
-	else if (data->fan_config[attr->index]
-		& MAX31785_FAN_CFG_CONTROL_MODE_RPM)
-		mode = 2;
-	else
-		mode = 1;
-
-	return sprintf(buf, "%d\n", mode);
+	return rc;
 }
 
-static ssize_t set_pwm_enable(struct device *dev,
-			      struct device_attribute *devattr,
-			      const char *buf, size_t count)
+static ssize_t max31785_pwm_enable(struct max31785 *data, int channel,
+		long mode)
 {
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct max31785_data *data = dev_get_drvdata(dev);
 	struct i2c_client *client = data->client;
-	unsigned long mode;
-	int err;
-
-	err = kstrtoul(buf, 10, &mode);
-	if (err)
-		return err;
+	int rc;
 
 	switch (mode) {
 	case 0:
-		data->fan_config[attr->index] =
-			data->fan_config[attr->index]
+		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[attr->index] =
-			data->fan_config[attr->index]
+		data->fan_config[channel] =
+			data->fan_config[channel]
 			 | MAX31785_FAN_CFG_PWM_ENABLE;
 		break;
 	default:
@@ -444,17 +303,17 @@ static ssize_t set_pwm_enable(struct device *dev,
 	case 0:
 		break;
 	case 1:
-		data->fan_config[attr->index] =
-			data->fan_config[attr->index]
+		data->fan_config[channel] =
+			data->fan_config[channel]
 			& ~MAX31785_FAN_CFG_CONTROL_MODE_RPM;
 		break;
 	case 2:
-		data->fan_config[attr->index] =
-			data->fan_config[attr->index]
+		data->fan_config[channel] =
+			data->fan_config[channel]
 			| MAX31785_FAN_CFG_CONTROL_MODE_RPM;
 		break;
 	case 3:
-		data->fan_command[attr->index] = 0xffff;
+		data->fan_command[channel] = 0xffff;
 		break;
 	default:
 		return -EINVAL;
@@ -462,161 +321,22 @@ static ssize_t set_pwm_enable(struct device *dev,
 
 	mutex_lock(&data->device_lock);
 
-	err = max31785_write_fan_data(client, attr->index,
+	rc = max31785_write_fan_data(client, channel,
 				MAX31785_REG_FAN_CONFIG_1_2,
-				data->fan_config[attr->index], 1);
+				data->fan_config[channel], 1);
 
-	if (err < 0)
-		goto abort;
-
-	err = max31785_write_fan_data(client, attr->index,
+	if (!rc)
+		rc = max31785_write_fan_data(client, channel,
 				MAX31785_REG_FAN_COMMAND_1,
-				data->fan_command[attr->index], 0);
+				data->fan_command[channel], 0);
 
-abort:
 	mutex_unlock(&data->device_lock);
 
-	if (err < 0)
-		return err;
-
-	return count;
+	return rc;
 }
 
-static ssize_t get_fan_fault(struct device *dev,
-			     struct device_attribute *devattr, char *buf)
-{
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct max31785_data *data = max31785_update_device(dev);
-	int fault;
-
-	if (IS_ERR(data))
-		return PTR_ERR(data);
-
-	fault = !!(data->fault_status[attr->index]
-			& MAX31785_FAN_STATUS_FAULT_MASK);
-
-	return sprintf(buf, "%d\n", fault);
-}
-
-static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan, NULL, 0);
-static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, get_fan, NULL, 1);
-static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, get_fan, NULL, 2);
-static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, get_fan, NULL, 3);
-static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, get_fan, NULL, 4);
-static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, get_fan, NULL, 5);
-
-static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan_fault, NULL, 0);
-static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, get_fan_fault, NULL, 1);
-static SENSOR_DEVICE_ATTR(fan3_fault, S_IRUGO, get_fan_fault, NULL, 2);
-static SENSOR_DEVICE_ATTR(fan4_fault, S_IRUGO, get_fan_fault, NULL, 3);
-static SENSOR_DEVICE_ATTR(fan5_fault, S_IRUGO, get_fan_fault, NULL, 4);
-static SENSOR_DEVICE_ATTR(fan6_fault, S_IRUGO, get_fan_fault, NULL, 5);
-
-static SENSOR_DEVICE_ATTR(fan1_target, S_IWUSR | S_IRUGO,
-		get_fan_target, set_fan_target, 0);
-static SENSOR_DEVICE_ATTR(fan2_target, S_IWUSR | S_IRUGO,
-		get_fan_target, set_fan_target, 1);
-static SENSOR_DEVICE_ATTR(fan3_target, S_IWUSR | S_IRUGO,
-		get_fan_target, set_fan_target, 2);
-static SENSOR_DEVICE_ATTR(fan4_target, S_IWUSR | S_IRUGO,
-		get_fan_target, set_fan_target, 3);
-static SENSOR_DEVICE_ATTR(fan5_target, S_IWUSR | S_IRUGO,
-		get_fan_target, set_fan_target, 4);
-static SENSOR_DEVICE_ATTR(fan6_target, S_IWUSR | S_IRUGO,
-		get_fan_target, set_fan_target, 5);
-
-static SENSOR_DEVICE_ATTR(fan1_pulses, S_IWUSR | S_IRUGO,
-		get_fan_pulses, set_fan_pulses, 0);
-static SENSOR_DEVICE_ATTR(fan2_pulses, S_IWUSR | S_IRUGO,
-		get_fan_pulses, set_fan_pulses, 1);
-static SENSOR_DEVICE_ATTR(fan3_pulses, S_IWUSR | S_IRUGO,
-		get_fan_pulses, set_fan_pulses, 2);
-static SENSOR_DEVICE_ATTR(fan4_pulses, S_IWUSR | S_IRUGO,
-		get_fan_pulses, set_fan_pulses, 3);
-static SENSOR_DEVICE_ATTR(fan5_pulses, S_IWUSR | S_IRUGO,
-		get_fan_pulses, set_fan_pulses, 4);
-static SENSOR_DEVICE_ATTR(fan6_pulses, S_IWUSR | S_IRUGO,
-		get_fan_pulses, set_fan_pulses, 5);
-
-static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 0);
-static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 1);
-static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 2);
-static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 3);
-static SENSOR_DEVICE_ATTR(pwm5, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 4);
-static SENSOR_DEVICE_ATTR(pwm6, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 5);
-
-static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
-		get_pwm_enable, set_pwm_enable, 0);
-static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO,
-		get_pwm_enable, set_pwm_enable, 1);
-static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO,
-		get_pwm_enable, set_pwm_enable, 2);
-static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO,
-		get_pwm_enable, set_pwm_enable, 3);
-static SENSOR_DEVICE_ATTR(pwm5_enable, S_IWUSR | S_IRUGO,
-		get_pwm_enable, set_pwm_enable, 4);
-static SENSOR_DEVICE_ATTR(pwm6_enable, S_IWUSR | S_IRUGO,
-		get_pwm_enable, set_pwm_enable, 5);
-
-static struct attribute *max31785_attrs[] = {
-	&sensor_dev_attr_fan1_input.dev_attr.attr,
-	&sensor_dev_attr_fan2_input.dev_attr.attr,
-	&sensor_dev_attr_fan3_input.dev_attr.attr,
-	&sensor_dev_attr_fan4_input.dev_attr.attr,
-	&sensor_dev_attr_fan5_input.dev_attr.attr,
-	&sensor_dev_attr_fan6_input.dev_attr.attr,
-
-	&sensor_dev_attr_fan1_fault.dev_attr.attr,
-	&sensor_dev_attr_fan2_fault.dev_attr.attr,
-	&sensor_dev_attr_fan3_fault.dev_attr.attr,
-	&sensor_dev_attr_fan4_fault.dev_attr.attr,
-	&sensor_dev_attr_fan5_fault.dev_attr.attr,
-	&sensor_dev_attr_fan6_fault.dev_attr.attr,
-
-	&sensor_dev_attr_fan1_target.dev_attr.attr,
-	&sensor_dev_attr_fan2_target.dev_attr.attr,
-	&sensor_dev_attr_fan3_target.dev_attr.attr,
-	&sensor_dev_attr_fan4_target.dev_attr.attr,
-	&sensor_dev_attr_fan5_target.dev_attr.attr,
-	&sensor_dev_attr_fan6_target.dev_attr.attr,
-
-	&sensor_dev_attr_fan1_pulses.dev_attr.attr,
-	&sensor_dev_attr_fan2_pulses.dev_attr.attr,
-	&sensor_dev_attr_fan3_pulses.dev_attr.attr,
-	&sensor_dev_attr_fan4_pulses.dev_attr.attr,
-	&sensor_dev_attr_fan5_pulses.dev_attr.attr,
-	&sensor_dev_attr_fan6_pulses.dev_attr.attr,
-
-	&sensor_dev_attr_pwm1.dev_attr.attr,
-	&sensor_dev_attr_pwm2.dev_attr.attr,
-	&sensor_dev_attr_pwm3.dev_attr.attr,
-	&sensor_dev_attr_pwm4.dev_attr.attr,
-	&sensor_dev_attr_pwm5.dev_attr.attr,
-	&sensor_dev_attr_pwm6.dev_attr.attr,
-
-	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
-	&sensor_dev_attr_pwm2_enable.dev_attr.attr,
-	&sensor_dev_attr_pwm3_enable.dev_attr.attr,
-	&sensor_dev_attr_pwm4_enable.dev_attr.attr,
-	&sensor_dev_attr_pwm5_enable.dev_attr.attr,
-	&sensor_dev_attr_pwm6_enable.dev_attr.attr,
-	NULL
-};
-
-static umode_t max31785_attrs_visible(struct kobject *kobj,
-				     struct attribute *a, int n)
-{
-	return a->mode;
-}
-
-static const struct attribute_group max31785_group = {
-	.attrs = max31785_attrs,
-	.is_visible = max31785_attrs_visible,
-};
-__ATTRIBUTE_GROUPS(max31785);
-
 static int max31785_init_client(struct i2c_client *client,
-				struct max31785_data *data)
+				struct max31785 *data)
 {
 	int i, rv;
 
@@ -678,12 +398,245 @@ static int max31785_detect(struct i2c_client *client,
 	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 = data->fan_command[channel];
+
+		if (!(data->fan_config[channel] &
+				MAX31785_FAN_CFG_CONTROL_MODE_RPM))
+			target /= MAX31785_FAN_COMMAND_PWM_RATIO;
+
+		*val = target;
+		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_read_pwm(struct max31785 *data, u32 attr, int channel,
+		long *val)
+{
+	bool is_auto;
+	bool is_rpm;
+	int rc;
+
+	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;
+	};
+
+	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 int max31785_probe(struct i2c_client *client,
 			  const struct i2c_device_id *id)
 {
 	struct i2c_adapter *adapter = client->adapter;
 	struct device *dev = &client->dev;
-	struct max31785_data *data;
+	struct max31785 *data;
 	struct device *hwmon_dev;
 	int err;
 
@@ -691,7 +644,7 @@ static int max31785_probe(struct i2c_client *client,
 			I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
 		return -ENODEV;
 
-	data = devm_kzalloc(dev, sizeof(struct max31785_data), GFP_KERNEL);
+	data = devm_kzalloc(dev, sizeof(struct max31785), GFP_KERNEL);
 	if (!data)
 		return -ENOMEM;
 
@@ -705,8 +658,8 @@ static int max31785_probe(struct i2c_client *client,
 	if (err)
 		return err;
 
-	hwmon_dev = devm_hwmon_device_register_with_groups(dev,
-			client->name, data, max31785_groups);
+	hwmon_dev = devm_hwmon_device_register_with_info(dev,
+			client->name, data, &max31785_chip_info, NULL);
 
 	return PTR_ERR_OR_ZERO(hwmon_dev);
 }
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH linux dev-4.10 5/7] i2c: Add bogus I2C_SMBUS_DWORD_DATA bus protocol/transaction type
  2017-06-02  6:22 [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785 Andrew Jeffery
                   ` (3 preceding siblings ...)
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 4/7] hwmon: max31785: Adapt to new hwmon APIs Andrew Jeffery
@ 2017-06-02  6:22 ` Andrew Jeffery
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 6/7] hwmon: max31785: Enable fast rotor measurement feature Andrew Jeffery
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Andrew Jeffery @ 2017-06-02  6:22 UTC (permalink / raw)
  To: joel; +Cc: tpearson, Andrew Jeffery, openbmc, cbostic, msbarth

Added so we can issue 4-byte read commands without invoking a block
transfer (which requires the length to be sent by the slave in the first
byte). The 0x3040 revision of the MAX31785 firmware can return a 'fast'
rotor read, and does so in this 4-byte format.

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
 drivers/i2c/i2c-core.c   | 30 ++++++++++++++++++++++++++++++
 include/linux/i2c.h      |  2 ++
 include/uapi/linux/i2c.h |  2 ++
 3 files changed, 34 insertions(+)

diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c
index 583e95042a21..4c8fe52df8ad 100644
--- a/drivers/i2c/i2c-core.c
+++ b/drivers/i2c/i2c-core.c
@@ -3210,6 +3210,18 @@ s32 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command)
 }
 EXPORT_SYMBOL(i2c_smbus_read_word_data);
 
+s64 i2c_smbus_read_dword_data(const struct i2c_client *client, u8 command)
+{
+	union i2c_smbus_data data;
+	int status;
+
+	status = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
+				I2C_SMBUS_READ, command,
+				I2C_SMBUS_DWORD_DATA, &data);
+	return (status < 0) ? status : data.dword;
+}
+EXPORT_SYMBOL(i2c_smbus_read_word_data);
+
 /**
  * i2c_smbus_write_word_data - SMBus "write word" protocol
  * @client: Handle to slave device
@@ -3443,6 +3455,17 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
 				msgbuf0[i] = data->block[i];
 		}
 		break;
+	case I2C_SMBUS_DWORD_DATA:
+		if (read_write == I2C_SMBUS_READ)
+			msg[1].len = 4;
+		else {
+			msg[0].len = 5;
+			msgbuf0[1] = data->dword & 0xff;
+			msgbuf0[2] = (data->dword >> 8) & 0xff;
+			msgbuf0[3] = (data->dword >> 16) & 0xff;
+			msgbuf0[4] = (data->dword >> 24) & 0xff;
+		}
+		break;
 	default:
 		dev_err(&adapter->dev, "Unsupported transaction %d\n", size);
 		return -EOPNOTSUPP;
@@ -3486,6 +3509,13 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
 		case I2C_SMBUS_PROC_CALL:
 			data->word = msgbuf1[0] | (msgbuf1[1] << 8);
 			break;
+		case I2C_SMBUS_DWORD_DATA:
+			data->dword =
+				(msgbuf1[0] << (0 * 8)) |
+				(msgbuf1[1] << (1 * 8)) |
+				(msgbuf1[2] << (2 * 8)) |
+				(msgbuf1[3] << (3 * 8));
+			break;
 		case I2C_SMBUS_I2C_BLOCK_DATA:
 			for (i = 0; i < data->block[0]; i++)
 				data->block[i+1] = msgbuf1[i];
diff --git a/include/linux/i2c.h b/include/linux/i2c.h
index 4b45ec46161f..ff15b25bdeda 100644
--- a/include/linux/i2c.h
+++ b/include/linux/i2c.h
@@ -95,6 +95,8 @@ extern s32 i2c_smbus_read_word_data(const struct i2c_client *client,
 				    u8 command);
 extern s32 i2c_smbus_write_word_data(const struct i2c_client *client,
 				     u8 command, u16 value);
+extern s64 i2c_smbus_read_dword_data(const struct i2c_client *client,
+				     u8 command);
 
 static inline s32
 i2c_smbus_read_word_swapped(const struct i2c_client *client, u8 command)
diff --git a/include/uapi/linux/i2c.h b/include/uapi/linux/i2c.h
index 009e27bb9abe..63ab01e3faa2 100644
--- a/include/uapi/linux/i2c.h
+++ b/include/uapi/linux/i2c.h
@@ -131,6 +131,7 @@ struct i2c_msg {
 union i2c_smbus_data {
 	__u8 byte;
 	__u16 word;
+	__u32 dword;
 	__u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
 			       /* and one more for user-space compatibility */
 };
@@ -150,5 +151,6 @@ union i2c_smbus_data {
 #define I2C_SMBUS_I2C_BLOCK_BROKEN  6
 #define I2C_SMBUS_BLOCK_PROC_CALL   7		/* SMBus 2.0 */
 #define I2C_SMBUS_I2C_BLOCK_DATA    8
+#define I2C_SMBUS_DWORD_DATA	    9
 
 #endif /* _UAPI_LINUX_I2C_H */
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH linux dev-4.10 6/7] hwmon: max31785: Enable fast rotor measurement feature
  2017-06-02  6:22 [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785 Andrew Jeffery
                   ` (4 preceding siblings ...)
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 5/7] i2c: Add bogus I2C_SMBUS_DWORD_DATA bus protocol/transaction type Andrew Jeffery
@ 2017-06-02  6:22 ` Andrew Jeffery
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 7/7] hwmon: max31785: Provide config of fault pin behavior Andrew Jeffery
  2017-06-05  6:01 ` [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785 Joel Stanley
  7 siblings, 0 replies; 10+ messages in thread
From: Andrew Jeffery @ 2017-06-02  6:22 UTC (permalink / raw)
  To: joel; +Cc: tpearson, Andrew Jeffery, openbmc, cbostic, msbarth

MFR_REVISION 0x3040 of the max31785 chip provides two rotor readings
for READ_FAN_SPEED_1, with an extra 16 bits after the original
READ_FAN_SPEED_1 response. For this firmware revision, the first result
represents the 'slow' rotor reading whilst the second represents the
'fast' rotor reading.

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
 Documentation/hwmon/max31785 |   8 +++
 drivers/hwmon/max31785.c     | 115 ++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 112 insertions(+), 11 deletions(-)

diff --git a/Documentation/hwmon/max31785 b/Documentation/hwmon/max31785
index 0911d204547e..f0d0e54d9a9f 100644
--- a/Documentation/hwmon/max31785
+++ b/Documentation/hwmon/max31785
@@ -34,3 +34,11 @@ 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 syfs 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/max31785.c b/drivers/hwmon/max31785.c
index d1feb73d08f1..fb971277165c 100644
--- a/drivers/hwmon/max31785.c
+++ b/drivers/hwmon/max31785.c
@@ -37,6 +37,7 @@
 #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
 
@@ -59,6 +60,8 @@
 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)
  */
@@ -67,14 +70,17 @@ struct max31785 {
 	struct mutex device_lock;
 	bool valid; /* zero until following fields are valid */
 	unsigned long last_updated; /* in jiffies */
+	u32 capabilities;
 
 	/* register values */
 	u8 fan_config[NR_CHANNEL];
 	u16 fan_command[NR_CHANNEL];
 	u8 mfr_fan_config[NR_CHANNEL];
 	u8 fault_status[NR_CHANNEL];
-	u16 tach_rpm[NR_CHANNEL];
 	u16 pwm[NR_CHANNEL];
+	u16 tach_rpm[NR_CHANNEL];
+	/* Fast rotor measurement */
+	u16 tach_rpm_fast[NR_CHANNEL];
 };
 
 static int max31785_set_page(struct i2c_client *client,
@@ -123,6 +129,35 @@ static int max31785_write_fan_data(struct i2c_client *client,
 	return 0;
 }
 
+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 = i2c_smbus_read_dword_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_dword_data(data->client, MAX31785_REG_FAN_SPEED_1);
+	if (rc < 0)
+		return rc;
+
+	data->tach_rpm[fan] = rc;
+
+	return rc;
+}
+
 static bool is_automatic_control_mode(struct max31785 *data,
 			int index)
 {
@@ -134,8 +169,8 @@ 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;
-	int rv;
 
 	mutex_lock(&data->device_lock);
 
@@ -147,11 +182,9 @@ static struct max31785 *max31785_update_device(struct device *dev)
 				goto abort;
 			data->fault_status[i] = rv;
 
-			rv = max31785_read_fan_data(client, i,
-					MAX31785_REG_FAN_SPEED_1, 0);
+			rv = max31785_update_fan_speed(data, i);
 			if (rv < 0)
 				goto abort;
-			data->tach_rpm[i] = rv;
 
 			if ((data->fan_config[i]
 				& MAX31785_FAN_CFG_CONTROL_MODE_RPM)
@@ -478,6 +511,20 @@ static int max31785_read_fan(struct max31785 *data, u32 attr, int channel,
 	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 = dev_get_drvdata(dev);
+
+	int ret = i2c_smbus_read_byte_data(data->client, attr2->index);
+
+	if (ret < 0)
+		return ret;
+
+	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)
 {
@@ -631,14 +678,53 @@ static const struct hwmon_chip_info max31785_chip_info = {
 	.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 max31785 *data;
 	struct device *hwmon_dev;
-	int err;
+	struct max31785 *data;
+	int rc;
 
 	if (!i2c_check_functionality(adapter,
 			I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
@@ -654,12 +740,19 @@ static int max31785_probe(struct i2c_client *client,
 	/*
 	 * Initialize the max31785 chip
 	 */
-	err = max31785_init_client(client, data);
-	if (err)
-		return err;
+	rc = max31785_init_client(client, 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, NULL);
+			client->name, data, &max31785_chip_info, extra_groups);
 
 	return PTR_ERR_OR_ZERO(hwmon_dev);
 }
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH linux dev-4.10 7/7] hwmon: max31785: Provide config of fault pin behavior
  2017-06-02  6:22 [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785 Andrew Jeffery
                   ` (5 preceding siblings ...)
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 6/7] hwmon: max31785: Enable fast rotor measurement feature Andrew Jeffery
@ 2017-06-02  6:22 ` Andrew Jeffery
  2017-06-05  6:01 ` [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785 Joel Stanley
  7 siblings, 0 replies; 10+ messages in thread
From: Andrew Jeffery @ 2017-06-02  6:22 UTC (permalink / raw)
  To: joel; +Cc: tpearson, Christopher Bostic, openbmc, msbarth, Andrew Jeffery

From: Christopher Bostic <cbostic@linux.vnet.ibm.com>

Provide interface to set behavior when FAULT pin is asserted.
When enabled force fan to 100% PWM duty cycle.

Signed-off-by: Christopher Bostic <cbostic@linux.vnet.ibm.com>
[Andrew Jeffery: Make it apply to reworked driver]
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
 drivers/hwmon/max31785.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 71 insertions(+)

diff --git a/drivers/hwmon/max31785.c b/drivers/hwmon/max31785.c
index fb971277165c..06e1708608d7 100644
--- a/drivers/hwmon/max31785.c
+++ b/drivers/hwmon/max31785.c
@@ -38,6 +38,7 @@
 #define MAX31785_REG_MFR_ID			0x99
 #define MAX31785_REG_MFR_MODEL			0x9a
 #define MAX31785_REG_MFR_REVISION		0x9b
+#define MAX31785_REG_MFR_FAULT_RESP		0xd9
 #define MAX31785_REG_MFR_FAN_CONFIG		0xf1
 #define MAX31785_REG_READ_FAN_PWM		0xf3
 
@@ -51,6 +52,9 @@
 /* Fan Status register bits */
 #define MAX31785_FAN_STATUS_FAULT_MASK		0x80
 
+/* Fault response register bits */
+#define MAX31785_FAULT_RESP_PIN_MONITOR		0x01
+
 /* Fan Command constants */
 #define MAX31785_FAN_COMMAND_PWM_RATIO		40
 
@@ -678,6 +682,69 @@ static const struct hwmon_chip_info max31785_chip_info = {
 	.info = max31785_info,
 };
 
+static ssize_t max31785_fault_resp_store(struct device *dev,
+				struct device_attribute *attr, const char *buf,
+				size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int rv;
+
+	rv = max31785_set_page(client, 0);
+	if (rv < 0)
+		return rv;
+
+	rv = i2c_smbus_read_byte_data(client, MAX31785_REG_MFR_FAULT_RESP);
+	if (rv < 0)
+		return rv;
+
+	if (!strncmp(buf, "on", strlen("on"))) {
+		if (!(rv & MAX31785_FAULT_RESP_PIN_MONITOR)) {
+			rv |= MAX31785_FAULT_RESP_PIN_MONITOR;
+			rv = i2c_smbus_write_byte_data(client,
+					MAX31785_REG_MFR_FAULT_RESP, rv);
+			if (rv < 0)
+				return rv;
+		}
+	} else if (!strncmp(buf, "off", strlen("off"))) {
+		if (rv & MAX31785_FAULT_RESP_PIN_MONITOR) {
+			rv &= ~MAX31785_FAULT_RESP_PIN_MONITOR;
+			rv = i2c_smbus_write_byte_data(client,
+					MAX31785_REG_MFR_FAULT_RESP, rv);
+			if (rv < 0)
+				return rv;
+		}
+	} else {
+		dev_warn(dev, "Unknown fault response type: [%s]\n", buf);
+		return -EINVAL;
+	}
+	return count;
+}
+
+static ssize_t max31785_fault_resp_show(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int rv;
+
+	rv = max31785_set_page(client, 0);
+	if (rv < 0)
+		return rv;
+
+	rv = i2c_smbus_read_byte_data(client, MAX31785_REG_MFR_FAULT_RESP);
+	if (rv < 0)
+		return rv;
+
+	if (rv & MAX31785_FAULT_RESP_PIN_MONITOR)
+		rv = sprintf(buf, "on\n");
+	else
+		rv = sprintf(buf, "off\n");
+
+	return rv;
+}
+
+static DEVICE_ATTR(fault_resp, 0644, max31785_fault_resp_show,
+		max31785_fault_resp_store);
+
 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,
@@ -751,6 +818,10 @@ static int max31785_probe(struct i2c_client *client,
 	if (data->capabilities & MAX31785_CAP_FAST_ROTOR)
 		extra_groups = max31785_groups;
 
+	rc = device_create_file(dev, &dev_attr_fault_resp);
+	if (rc)
+		return rc;
+
 	hwmon_dev = devm_hwmon_device_register_with_info(dev,
 			client->name, data, &max31785_chip_info, extra_groups);
 
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785
  2017-06-02  6:22 [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785 Andrew Jeffery
                   ` (6 preceding siblings ...)
  2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 7/7] hwmon: max31785: Provide config of fault pin behavior Andrew Jeffery
@ 2017-06-05  6:01 ` Joel Stanley
  2017-06-05  6:05   ` Andrew Jeffery
  7 siblings, 1 reply; 10+ messages in thread
From: Joel Stanley @ 2017-06-05  6:01 UTC (permalink / raw)
  To: Andrew Jeffery
  Cc: Timothy Pearson, OpenBMC Maillist, Christopher Bostic, msbarth

On Fri, Jun 2, 2017 at 3:52 PM, Andrew Jeffery <andrew@aj.id.au> wrote:
> Hello,
>
> I've dodgied up this series with a patch to the i2c core to make the fast rotor
> read documented in [1] work. Thus, it's an RFC series. Further, the fast rotor
> values are zero on the machine I was testing against. Maybe I need to broaden
> my horizons and test against more machines, but I figured I'd send the patches
> to get some "internal" feedback.
>
> Timothy's original patch is reworked against the new hwmon kernel APIs. The
> rework made it easier to support both the 0x3030 and 0x3040 firmwares for the
> approach I took, though maybe there is a better way with using the hwmon
> attribute visibility modes. Further, it's still not a pmbus driver, so it's
> probably not yet acceptible upstream despite the fact that upstream still
> doesn't support fan control in the pmbus core.

Looks good.

I suggest we send it upstream with patches 1, 3, 4, 6 squashed into
one. Make sure we retain Timothy's copyright in the header.

Instead of using your i2c DWORD hack, use i2c_master_recv to do your
four byte transfer. Call out in a comment or your commit message that
the smbus spec doesn't define a 4 byte transfer, but the device needs
it.

Cheers,

Joel

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785
  2017-06-05  6:01 ` [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785 Joel Stanley
@ 2017-06-05  6:05   ` Andrew Jeffery
  0 siblings, 0 replies; 10+ messages in thread
From: Andrew Jeffery @ 2017-06-05  6:05 UTC (permalink / raw)
  To: Joel Stanley
  Cc: Timothy Pearson, OpenBMC Maillist, Christopher Bostic, msbarth

[-- Attachment #1: Type: text/plain, Size: 1677 bytes --]

On Mon, 2017-06-05 at 15:31 +0930, Joel Stanley wrote:
> > On Fri, Jun 2, 2017 at 3:52 PM, Andrew Jeffery <andrew@aj.id.au> wrote:
> > Hello,
> > 
> > I've dodgied up this series with a patch to the i2c core to make the fast rotor
> > read documented in [1] work. Thus, it's an RFC series. Further, the fast rotor
> > values are zero on the machine I was testing against. Maybe I need to broaden
> > my horizons and test against more machines, but I figured I'd send the patches
> > to get some "internal" feedback.
> > 
> > Timothy's original patch is reworked against the new hwmon kernel APIs. The
> > rework made it easier to support both the 0x3030 and 0x3040 firmwares for the
> > approach I took, though maybe there is a better way with using the hwmon
> > attribute visibility modes. Further, it's still not a pmbus driver, so it's
> > probably not yet acceptible upstream despite the fact that upstream still
> > doesn't support fan control in the pmbus core.
> 
> Looks good.
> 
> I suggest we send it upstream with patches 1, 3, 4, 6 squashed into
> one. Make sure we retain Timothy's copyright in the header.

Yep, it doesn't make sense to upstream the exploded series. I'll squash
things down and rework as below.

> 
> Instead of using your i2c DWORD hack, use i2c_master_recv to do your
> four byte transfer.

Yes, I'll give this a go. Quirky (I2C, not SMBus) code for quirky
behaviour seems reasonable, and less hacky than inventing non-standard
transfer types.

>  Call out in a comment or your commit message that
> the smbus spec doesn't define a 4 byte transfer, but the device needs
> it.

Will do.

Cheers,

Andrew

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2017-06-05  6:06 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-06-02  6:22 [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785 Andrew Jeffery
2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 1/7] hwmon: Add support for MAX31785 intelligent fan controller Andrew Jeffery
2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 2/7] arm: aspeed: Add MAX31785 to aspeed_g5_defconfig Andrew Jeffery
2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 3/7] hwmon: max31785: Document implementation oddities Andrew Jeffery
2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 4/7] hwmon: max31785: Adapt to new hwmon APIs Andrew Jeffery
2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 5/7] i2c: Add bogus I2C_SMBUS_DWORD_DATA bus protocol/transaction type Andrew Jeffery
2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 6/7] hwmon: max31785: Enable fast rotor measurement feature Andrew Jeffery
2017-06-02  6:22 ` [RFC PATCH linux dev-4.10 7/7] hwmon: max31785: Provide config of fault pin behavior Andrew Jeffery
2017-06-05  6:01 ` [RFC PATCH linux dev-4.10 0/7] Add fast rotor reads to MAX31785 Joel Stanley
2017-06-05  6:05   ` Andrew Jeffery

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.